/* * Copyright (c) [2016] [ <ether.camp> ] * This file is part of the ethereumJ library. * * The ethereumJ library is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The ethereumJ library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the ethereumJ library. If not, see <http://www.gnu.org/licenses/>. */ package org.ethereum.core; import org.ethereum.util.RLP; import org.ethereum.util.RLPElement; import org.ethereum.util.RLPList; import org.ethereum.vm.DataWord; import org.ethereum.vm.LogInfo; import org.ethereum.vm.program.InternalTransaction; import org.springframework.util.Assert; import java.math.BigInteger; import java.util.*; import static java.util.Collections.*; import static org.apache.commons.lang3.ArrayUtils.isEmpty; import static org.apache.commons.lang3.ArrayUtils.isNotEmpty; import static org.ethereum.util.BIUtil.toBI; public class TransactionExecutionSummary { private Transaction tx; private BigInteger value = BigInteger.ZERO; private BigInteger gasPrice = BigInteger.ZERO; private BigInteger gasLimit = BigInteger.ZERO; private BigInteger gasUsed = BigInteger.ZERO; private BigInteger gasLeftover = BigInteger.ZERO; private BigInteger gasRefund = BigInteger.ZERO; private List<DataWord> deletedAccounts = emptyList(); private List<InternalTransaction> internalTransactions = emptyList(); private Map<DataWord, DataWord> storageDiff = emptyMap(); private TransactionTouchedStorage touchedStorage = new TransactionTouchedStorage(); private byte[] result; private List<LogInfo> logs; private boolean failed; private byte[] rlpEncoded; private boolean parsed; public TransactionExecutionSummary(Transaction transaction) { this.tx = transaction; this.gasLimit = toBI(transaction.getGasLimit()); this.gasPrice = toBI(transaction.getGasPrice()); this.value = toBI(transaction.getValue()); } public TransactionExecutionSummary(byte[] rlpEncoded) { this.rlpEncoded = rlpEncoded; this.parsed = false; } public void rlpParse() { if (parsed) return; RLPList decodedTxList = RLP.decode2(rlpEncoded); RLPList summary = (RLPList) decodedTxList.get(0); this.tx = new Transaction(summary.get(0).getRLPData()); this.value = decodeBigInteger(summary.get(1).getRLPData()); this.gasPrice = decodeBigInteger(summary.get(2).getRLPData()); this.gasLimit = decodeBigInteger(summary.get(3).getRLPData()); this.gasUsed = decodeBigInteger(summary.get(4).getRLPData()); this.gasLeftover = decodeBigInteger(summary.get(5).getRLPData()); this.gasRefund = decodeBigInteger(summary.get(6).getRLPData()); this.deletedAccounts = decodeDeletedAccounts((RLPList) summary.get(7)); this.internalTransactions = decodeInternalTransactions((RLPList) summary.get(8)); this.touchedStorage = decodeTouchedStorage(summary.get(9)); this.result = summary.get(10).getRLPData(); this.logs = decodeLogs((RLPList) summary.get(11)); byte[] failed = summary.get(12).getRLPData(); this.failed = isNotEmpty(failed) && RLP.decodeInt(failed, 0) == 1; } private static BigInteger decodeBigInteger(byte[] encoded) { return isEmpty(encoded) ? BigInteger.ZERO : new BigInteger(1, encoded); } public byte[] getEncoded() { if (rlpEncoded != null) return rlpEncoded; this.rlpEncoded = RLP.encodeList( this.tx.getEncoded(), RLP.encodeBigInteger(this.value), RLP.encodeBigInteger(this.gasPrice), RLP.encodeBigInteger(this.gasLimit), RLP.encodeBigInteger(this.gasUsed), RLP.encodeBigInteger(this.gasLeftover), RLP.encodeBigInteger(this.gasRefund), encodeDeletedAccounts(this.deletedAccounts), encodeInternalTransactions(this.internalTransactions), encodeTouchedStorage(this.touchedStorage), RLP.encodeElement(this.result), encodeLogs(this.logs), RLP.encodeInt(this.failed ? 1 : 0) ); return rlpEncoded; } public static byte[] encodeTouchedStorage(TransactionTouchedStorage touchedStorage) { Collection<TransactionTouchedStorage.Entry> entries = touchedStorage.getEntries(); byte[][] result = new byte[entries.size()][]; int i = 0; for (TransactionTouchedStorage.Entry entry : entries) { byte[] key = RLP.encodeElement(entry.getKey().getData()); byte[] value = RLP.encodeElement(entry.getValue().getData()); byte[] changed = RLP.encodeInt(entry.isChanged() ? 1 : 0); result[i++] = RLP.encodeList(key, value, changed); } return RLP.encodeList(result); } public static TransactionTouchedStorage decodeTouchedStorage(RLPElement encoded) { TransactionTouchedStorage result = new TransactionTouchedStorage(); for (RLPElement entry : (RLPList) encoded) { RLPList asList = (RLPList) entry; DataWord key = new DataWord(asList.get(0).getRLPData()); DataWord value = new DataWord(asList.get(1).getRLPData()); byte[] changedBytes = asList.get(2).getRLPData(); boolean changed = isNotEmpty(changedBytes) && RLP.decodeInt(changedBytes, 0) == 1; result.add(new TransactionTouchedStorage.Entry(key, value, changed)); } return result; } private static List<LogInfo> decodeLogs(RLPList logs) { ArrayList<LogInfo> result = new ArrayList<>(); for (RLPElement log : logs) { result.add(new LogInfo(log.getRLPData())); } return result; } private static byte[] encodeLogs(List<LogInfo> logs) { byte[][] result = new byte[logs.size()][]; for (int i = 0; i < logs.size(); i++) { LogInfo log = logs.get(i); result[i] = log.getEncoded(); } return RLP.encodeList(result); } private static byte[] encodeStorageDiff(Map<DataWord, DataWord> storageDiff) { byte[][] result = new byte[storageDiff.size()][]; int i = 0; for (Map.Entry<DataWord, DataWord> entry : storageDiff.entrySet()) { byte[] key = RLP.encodeElement(entry.getKey().getData()); byte[] value = RLP.encodeElement(entry.getValue().getData()); result[i++] = RLP.encodeList(key, value); } return RLP.encodeList(result); } private static Map<DataWord, DataWord> decodeStorageDiff(RLPList storageDiff) { Map<DataWord, DataWord> result = new HashMap<>(); for (RLPElement entry : storageDiff) { DataWord key = new DataWord(((RLPList) entry).get(0).getRLPData()); DataWord value = new DataWord(((RLPList) entry).get(1).getRLPData()); result.put(key, value); } return result; } private static byte[] encodeInternalTransactions(List<InternalTransaction> internalTransactions) { byte[][] result = new byte[internalTransactions.size()][]; for (int i = 0; i < internalTransactions.size(); i++) { InternalTransaction transaction = internalTransactions.get(i); result[i] = transaction.getEncoded(); } return RLP.encodeList(result); } private static List<InternalTransaction> decodeInternalTransactions(RLPList internalTransactions) { List<InternalTransaction> result = new ArrayList<>(); for (RLPElement internalTransaction : internalTransactions) { result.add(new InternalTransaction(internalTransaction.getRLPData())); } return result; } private static byte[] encodeDeletedAccounts(List<DataWord> deletedAccounts) { byte[][] result = new byte[deletedAccounts.size()][]; for (int i = 0; i < deletedAccounts.size(); i++) { DataWord deletedAccount = deletedAccounts.get(i); result[i] = RLP.encodeElement(deletedAccount.getData()); } return RLP.encodeList(result); } private static List<DataWord> decodeDeletedAccounts(RLPList deletedAccounts) { List<DataWord> result = new ArrayList<>(); for (RLPElement deletedAccount : deletedAccounts) { result.add(new DataWord(deletedAccount.getRLPData())); } return result; } public Transaction getTransaction() { if (!parsed) rlpParse(); return tx; } public byte[] getTransactionHash() { return getTransaction().getHash(); } private BigInteger calcCost(BigInteger gas) { return gasPrice.multiply(gas); } public BigInteger getFee() { if (!parsed) rlpParse(); return calcCost(gasLimit.subtract(gasLeftover.add(gasRefund))); } public BigInteger getRefund() { if (!parsed) rlpParse(); return calcCost(gasRefund); } public BigInteger getLeftover() { if (!parsed) rlpParse(); return calcCost(gasLeftover); } public BigInteger getGasPrice() { if (!parsed) rlpParse(); return gasPrice; } public BigInteger getGasLimit() { if (!parsed) rlpParse(); return gasLimit; } public BigInteger getGasUsed() { if (!parsed) rlpParse(); return gasUsed; } public BigInteger getGasLeftover() { if (!parsed) rlpParse(); return gasLeftover; } public BigInteger getValue() { if (!parsed) rlpParse(); return value; } public List<DataWord> getDeletedAccounts() { if (!parsed) rlpParse(); return deletedAccounts; } public List<InternalTransaction> getInternalTransactions() { if (!parsed) rlpParse(); return internalTransactions; } @Deprecated /* Use getTouchedStorage().getAll() instead */ public Map<DataWord, DataWord> getStorageDiff() { if (!parsed) rlpParse(); return storageDiff; } public BigInteger getGasRefund() { if (!parsed) rlpParse(); return gasRefund; } public boolean isFailed() { if (!parsed) rlpParse(); return failed; } public byte[] getResult() { if (!parsed) rlpParse(); return result; } public List<LogInfo> getLogs() { if (!parsed) rlpParse(); return logs; } public TransactionTouchedStorage getTouchedStorage() { return touchedStorage; } public static Builder builderFor(Transaction transaction) { return new Builder(transaction); } public static class Builder { private final TransactionExecutionSummary summary; Builder(Transaction transaction) { Assert.notNull(transaction, "Cannot build TransactionExecutionSummary for null transaction."); summary = new TransactionExecutionSummary(transaction); } public Builder gasUsed(BigInteger gasUsed) { summary.gasUsed = gasUsed; return this; } public Builder gasLeftover(BigInteger gasLeftover) { summary.gasLeftover = gasLeftover; return this; } public Builder gasRefund(BigInteger gasRefund) { summary.gasRefund = gasRefund; return this; } public Builder internalTransactions(List<InternalTransaction> internalTransactions) { summary.internalTransactions = unmodifiableList(internalTransactions); return this; } public Builder deletedAccounts(Set<DataWord> deletedAccounts) { summary.deletedAccounts = new ArrayList<>(); for (DataWord account : deletedAccounts) { summary.deletedAccounts.add(account); } return this; } public Builder storageDiff(Map<DataWord, DataWord> storageDiff) { summary.storageDiff = unmodifiableMap(storageDiff); return this; } public Builder touchedStorage(Map<DataWord, DataWord> touched, Map<DataWord, DataWord> changed) { summary.touchedStorage.addReading(touched); summary.touchedStorage.addWriting(changed); return this; } public Builder markAsFailed() { summary.failed = true; return this; } public Builder logs(List<LogInfo> logs) { summary.logs = logs; return this; } public Builder result(byte[] result) { summary.result = result; return this; } public TransactionExecutionSummary build() { summary.parsed = true; if (summary.failed) { for (InternalTransaction transaction : summary.internalTransactions) { transaction.reject(); } } return summary; } } }