/* * 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.*; import org.ethereum.vm.LogInfo; import org.spongycastle.util.BigIntegers; import org.spongycastle.util.encoders.Hex; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import static org.apache.commons.lang3.ArrayUtils.nullToEmpty; import static org.ethereum.util.ByteUtil.EMPTY_BYTE_ARRAY; /** * The transaction receipt is a tuple of three items * comprising the transaction, together with the post-transaction state, * and the cumulative gas used in the block containing the transaction receipt * as of immediately after the transaction has happened, */ public class TransactionReceipt { private Transaction transaction; private byte[] postTxState = EMPTY_BYTE_ARRAY; private byte[] cumulativeGas = EMPTY_BYTE_ARRAY; private Bloom bloomFilter = new Bloom(); private List<LogInfo> logInfoList = new ArrayList<>(); private byte[] gasUsed = EMPTY_BYTE_ARRAY; private byte[] executionResult = EMPTY_BYTE_ARRAY; private String error = ""; /* Tx Receipt in encoded form */ private byte[] rlpEncoded; public TransactionReceipt() { } public TransactionReceipt(byte[] rlp) { RLPList params = RLP.decode2(rlp); RLPList receipt = (RLPList) params.get(0); RLPItem postTxStateRLP = (RLPItem) receipt.get(0); RLPItem cumulativeGasRLP = (RLPItem) receipt.get(1); RLPItem bloomRLP = (RLPItem) receipt.get(2); RLPList logs = (RLPList) receipt.get(3); RLPItem gasUsedRLP = (RLPItem) receipt.get(4); RLPItem result = (RLPItem) receipt.get(5); postTxState = nullToEmpty(postTxStateRLP.getRLPData()); cumulativeGas = cumulativeGasRLP.getRLPData(); bloomFilter = new Bloom(bloomRLP.getRLPData()); gasUsed = gasUsedRLP.getRLPData(); executionResult = (executionResult = result.getRLPData()) == null ? EMPTY_BYTE_ARRAY : executionResult; if (receipt.size() > 6) { byte[] errBytes = receipt.get(6).getRLPData(); error = errBytes != null ? new String(errBytes, StandardCharsets.UTF_8) : ""; } for (RLPElement log : logs) { LogInfo logInfo = new LogInfo(log.getRLPData()); logInfoList.add(logInfo); } rlpEncoded = rlp; } public TransactionReceipt(byte[] postTxState, byte[] cumulativeGas, Bloom bloomFilter, List<LogInfo> logInfoList) { this.postTxState = postTxState; this.cumulativeGas = cumulativeGas; this.bloomFilter = bloomFilter; this.logInfoList = logInfoList; } public TransactionReceipt(final RLPList rlpList) { if (rlpList == null || rlpList.size() != 4) throw new RuntimeException("Should provide RLPList with postTxState, cumulativeGas, bloomFilter, logInfoList"); this.postTxState = rlpList.get(0).getRLPData(); this.cumulativeGas = rlpList.get(1).getRLPData(); this.bloomFilter = new Bloom(rlpList.get(2).getRLPData()); List<LogInfo> logInfos = new ArrayList<>(); for (RLPElement logInfoEl: (RLPList) rlpList.get(3)) { LogInfo logInfo = new LogInfo(logInfoEl.getRLPData()); logInfos.add(logInfo); } this.logInfoList = logInfos; } public byte[] getPostTxState() { return postTxState; } public byte[] getCumulativeGas() { return cumulativeGas; } public byte[] getGasUsed() { return gasUsed; } public byte[] getExecutionResult() { return executionResult; } public long getCumulativeGasLong() { return new BigInteger(1, cumulativeGas).longValue(); } public Bloom getBloomFilter() { return bloomFilter; } public List<LogInfo> getLogInfoList() { return logInfoList; } public boolean isValid() { return ByteUtil.byteArrayToLong(gasUsed) > 0; } public boolean isSuccessful() { return error.isEmpty(); } public String getError() { return error; } /** * Used for Receipt trie hash calculation. Should contain only the following items encoded: * [postTxState, cumulativeGas, bloomFilter, logInfoList] */ public byte[] getReceiptTrieEncoded() { return getEncoded(true); } /** * Used for serialization, contains all the receipt data encoded */ public byte[] getEncoded() { if (rlpEncoded == null) { rlpEncoded = getEncoded(false); } return rlpEncoded; } public byte[] getEncoded(boolean receiptTrie) { byte[] postTxStateRLP = RLP.encodeElement(this.postTxState); byte[] cumulativeGasRLP = RLP.encodeElement(this.cumulativeGas); byte[] bloomRLP = RLP.encodeElement(this.bloomFilter.data); final byte[] logInfoListRLP; if (logInfoList != null) { byte[][] logInfoListE = new byte[logInfoList.size()][]; int i = 0; for (LogInfo logInfo : logInfoList) { logInfoListE[i] = logInfo.getEncoded(); ++i; } logInfoListRLP = RLP.encodeList(logInfoListE); } else { logInfoListRLP = RLP.encodeList(); } return receiptTrie ? RLP.encodeList(postTxStateRLP, cumulativeGasRLP, bloomRLP, logInfoListRLP): RLP.encodeList(postTxStateRLP, cumulativeGasRLP, bloomRLP, logInfoListRLP, RLP.encodeElement(gasUsed), RLP.encodeElement(executionResult), RLP.encodeElement(error.getBytes(StandardCharsets.UTF_8))); } public void setPostTxState(byte[] postTxState) { this.postTxState = postTxState; rlpEncoded = null; } public void setCumulativeGas(long cumulativeGas) { this.cumulativeGas = BigIntegers.asUnsignedByteArray(BigInteger.valueOf(cumulativeGas)); rlpEncoded = null; } public void setCumulativeGas(byte[] cumulativeGas) { this.cumulativeGas = cumulativeGas; rlpEncoded = null; } public void setGasUsed(byte[] gasUsed) { this.gasUsed = gasUsed; rlpEncoded = null; } public void setGasUsed(long gasUsed) { this.gasUsed = BigIntegers.asUnsignedByteArray(BigInteger.valueOf(gasUsed)); rlpEncoded = null; } public void setExecutionResult(byte[] executionResult) { this.executionResult = executionResult; rlpEncoded = null; } public void setError(String error) { this.error = error == null ? "" : error; } public void setLogInfoList(List<LogInfo> logInfoList) { if (logInfoList == null) return; this.logInfoList = logInfoList; for (LogInfo loginfo : logInfoList) { bloomFilter.or(loginfo.getBloom()); } rlpEncoded = null; } public void setTransaction(Transaction transaction) { this.transaction = transaction; } public Transaction getTransaction() { if (transaction == null) throw new NullPointerException("Transaction is not initialized. Use TransactionInfo and BlockStore to setup Transaction instance"); return transaction; } @Override public String toString() { // todo: fix that return "TransactionReceipt[" + "\n , postTxState=" + Hex.toHexString(postTxState) + "\n , cumulativeGas=" + Hex.toHexString(cumulativeGas) + "\n , gasUsed=" + Hex.toHexString(gasUsed) + "\n , error=" + error + "\n , executionResult=" + Hex.toHexString(executionResult) + "\n , bloom=" + bloomFilter.toString() + "\n , logs=" + logInfoList + ']'; } }