diff --git a/ethereumj-core/src/main/java/org/ethereum/core/BlockchainImpl.java b/ethereumj-core/src/main/java/org/ethereum/core/BlockchainImpl.java index 9357cd25..ebc60e0d 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/BlockchainImpl.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/BlockchainImpl.java @@ -114,6 +114,12 @@ public class BlockchainImpl implements Blockchain { return blockStore.getBlockByNumber(blockNr); } + @Override + public TransactionReceipt getTransactionReceiptByHash(byte[] hash){ + + return blockStore.getTransactionReceiptByHash(hash); + } + @Override public Block getBlockByHash(byte[] hash){ return blockStore.getBlockByHash(hash); @@ -210,13 +216,14 @@ public class BlockchainImpl implements Blockchain { } this.processBlock(block); - track.commit(); - repository.flush(); // saving to the disc - stateLogger.info("applied reward for block: [{}] \n state: [{}]", block.getNumber(), Hex.toHexString(repository.getRoot())); + track.commit(); + repository.flush(); // saving to the disc + + // Remove all wallet transactions as they already approved by the net worldManager.getWallet().removeTransactions(block.getTransactionsList()); @@ -303,46 +310,53 @@ public class BlockchainImpl implements Blockchain { } - private void processBlock(Block block) { + private void processBlock(Block block) { + + List receipts = new ArrayList<>(); if(isValid(block)) { if (!block.isGenesis()) { if (!CONFIG.blockChainOnly()) { Wallet wallet = worldManager.getWallet(); wallet.addTransactions(block.getTransactionsList()); - this.applyBlock(block); + receipts = this.applyBlock(block); wallet.processBlock(block); } } - this.storeBlock(block); + this.storeBlock(block, receipts); } else { logger.warn("Invalid block with nr: {}", block.getNumber()); } } - private void applyBlock(Block block) { + private List applyBlock(Block block) { - int i = 0; + int i = 1; long totalGasUsed = 0; + List reciepts = new ArrayList<>(); + for (Transaction tx : block.getTransactionsList()) { stateLogger.info("apply block: [{}] tx: [{}] ", block.getNumber(), i); TransactionReceipt receipt = applyTransaction(block, tx); totalGasUsed += receipt.getCumulativeGasLong(); - receipt.setCumulativeGas(totalGasUsed); track.commit(); - + receipt.setCumulativeGas(totalGasUsed); receipt.setPostTxState(repository.getRoot()); + receipt.setTransaction(tx); + stateLogger.info("block: [{}] executed tx: [{}] \n state: [{}]", block.getNumber(), i, Hex.toHexString(repository.getRoot())); + stateLogger.info("[{}] ", receipt.toString()); + + if (stateLogger.isInfoEnabled()) + stateLogger.info("tx[{}].receipt: [{}] ",i, Hex.toHexString(receipt.getEncoded())); + if(block.getNumber() >= CONFIG.traceStartBlock()) repository.dumpState(block, totalGasUsed, i++, tx.getHash()); - // todo: (!!!). save the receipt - // todo: cache all the receipts and - // todo: save them to the disc together - // todo: with the block afterwards + reciepts.add(receipt); } this.addReward(block); @@ -352,10 +366,12 @@ public class BlockchainImpl implements Blockchain { if(block.getNumber() >= CONFIG.traceStartBlock()) repository.dumpState(block, totalGasUsed, 0, null); + + return reciepts; } /** - * Add reward to block- and every uncle coinbase + * Add reward to block- and every uncle coinbase * assuming the entire block is valid. * * @param block object containing the header and uncles @@ -377,7 +393,7 @@ public class BlockchainImpl implements Blockchain { } @Override - public void storeBlock(Block block) { + public void storeBlock(Block block, List receipts) { /* Debug check to see if the state is still as expected */ if(logger.isWarnEnabled()) { @@ -394,7 +410,7 @@ public class BlockchainImpl implements Blockchain { } } - blockStore.saveBlock(block); + blockStore.saveBlock(block, receipts); this.setBestBlock(block); if (logger.isDebugEnabled()) @@ -538,18 +554,18 @@ public class BlockchainImpl implements Blockchain { if (CONFIG.playVM()) vm.play(program); - // todo: recepit save logs - // todo: receipt calc and save blooms - program.saveProgramTraceToFile(Hex.toHexString(tx.getHash())); ProgramResult result = program.getResult(); applyProgramResult(result, gasDebit, gasPrice, trackTx, senderAddress, receiverAddress, coinbase, isContractCreation); gasUsed = result.getGasUsed(); + List logs = result.getLogInfoList(); + receipt.setLogInfoList(logs); + + } catch (RuntimeException e) { trackTx.rollback(); - receipt.setCumulativeGas(tx.getGasLimit()); return receipt; } diff --git a/ethereumj-core/src/main/java/org/ethereum/core/Bloom.java b/ethereumj-core/src/main/java/org/ethereum/core/Bloom.java index 0e10896d..e1f3dd37 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/Bloom.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/Bloom.java @@ -18,6 +18,8 @@ public class Bloom { byte[] data = new byte[64]; + public Bloom() { + } public Bloom(byte[] data){ this.data = data; diff --git a/ethereumj-core/src/main/java/org/ethereum/core/TransactionReceipt.java b/ethereumj-core/src/main/java/org/ethereum/core/TransactionReceipt.java index 3bb005d3..7ae3648a 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/TransactionReceipt.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/TransactionReceipt.java @@ -1,14 +1,17 @@ package org.ethereum.core; -import org.ethereum.util.RLP; +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.util.ArrayList; import java.util.Iterator; import java.util.List; +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, @@ -19,10 +22,12 @@ import java.util.List; */ public class TransactionReceipt { - private byte[] postTxState; - private byte[] cumulativeGas; - private Bloom bloomFilter; - private List logInfoList; + private Transaction transaction; + + private byte[] postTxState = EMPTY_BYTE_ARRAY; + private byte[] cumulativeGas = EMPTY_BYTE_ARRAY; + private Bloom bloomFilter = new Bloom(); + private List logInfoList = new ArrayList(); /* Tx Receipt in encoded form */ private byte[] rlpEncoded; @@ -30,6 +35,30 @@ public class TransactionReceipt { 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); + + postTxState = postTxStateRLP.getRLPData(); + cumulativeGas = cumulativeGasRLP.getRLPData(); + bloomFilter = new Bloom(bloomRLP.getRLPData()); + + for (int i = 0; i < logs.size(); i++) { + RLPElement logRLP = logs.get(i); + LogInfo logInfo = new LogInfo(logRLP.getRLPData()); + logInfoList.add(logInfo); + } + + rlpEncoded = rlp; + } + + public TransactionReceipt(byte[] postTxState, byte[] cumulativeGas, Bloom bloomFilter, List logInfoList) { this.postTxState = postTxState; @@ -69,14 +98,19 @@ public class TransactionReceipt { byte[] cumulativeGasRLP = RLP.encodeElement(this.cumulativeGas); byte[] bloomRLP = RLP.encodeElement(this.bloomFilter.data); - byte[][] logInfoListE = new byte[logInfoList.size()][]; + byte[] logInfoListRLP = null; + if (logInfoList != null) { + byte[][] logInfoListE = new byte[logInfoList.size()][]; - int i = 0; - for (LogInfo logInfo : logInfoList){ - logInfoListE[i] = logInfo.getEncoded(); - ++i; + int i = 0; + for (LogInfo logInfo : logInfoList) { + logInfoListE[i] = logInfo.getEncoded() ; + ++i; + } + logInfoListRLP = RLP.encodeList(logInfoListE); + } else{ + logInfoListRLP = RLP.encodeList(); } - byte[] logInfoListRLP = RLP.encodeList(logInfoListE); rlpEncoded = RLP.encodeList(postTxStateRLP, cumulativeGasRLP, bloomRLP, logInfoListRLP); @@ -95,12 +129,23 @@ public class TransactionReceipt { this.cumulativeGas = cumulativeGas; } - public void setBloomFilter(Bloom bloomFilter) { - this.bloomFilter = bloomFilter; - } public void setLogInfoList(List logInfoList) { + if (logInfoList == null) return; + this.rlpEncoded = null; this.logInfoList = logInfoList; + + for (LogInfo loginfo : logInfoList){ + bloomFilter.or(loginfo.getBloom()); + } + } + + public void setTransaction(Transaction transaction){ + this.transaction = transaction; + } + + public Transaction getTransaction(){ + return transaction; } @Override diff --git a/ethereumj-core/src/main/java/org/ethereum/db/BlockStore.java b/ethereumj-core/src/main/java/org/ethereum/db/BlockStore.java index 20fba9a1..a8d06f35 100644 --- a/ethereumj-core/src/main/java/org/ethereum/db/BlockStore.java +++ b/ethereumj-core/src/main/java/org/ethereum/db/BlockStore.java @@ -1,6 +1,7 @@ package org.ethereum.db; import org.ethereum.core.Block; +import org.ethereum.core.TransactionReceipt; import org.hibernate.SessionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; @@ -79,11 +80,20 @@ public class BlockStore { @Transactional - public void saveBlock(Block block) { + public void saveBlock(Block block, List receipts) { BlockVO blockVO = new BlockVO(block.getNumber(), block.getHash(), block.getEncoded(), block.getCumulativeDifficulty()); + for (TransactionReceipt receipt : receipts){ + + byte[] hash = receipt.getTransaction().getHash(); + byte[] rlp = receipt.getEncoded(); + + TransactionReceiptVO transactionReceiptVO = new TransactionReceiptVO(hash, rlp); + sessionFactory.getCurrentSession().persist(transactionReceiptVO); + } + sessionFactory.getCurrentSession().persist(blockVO); } @@ -132,4 +142,17 @@ public class BlockStore { sessionFactory.getCurrentSession(). createQuery("delete from BlockVO").executeUpdate(); } + + public TransactionReceipt getTransactionReceiptByHash(byte[] hash) { + + List result = sessionFactory.getCurrentSession(). + createQuery("from TransactionReceiptVO where hash = :hash"). + setParameter("hash", hash).list(); + + if (result.size() == 0) return null; + TransactionReceiptVO vo = (TransactionReceiptVO)result.get(0); + + return new TransactionReceipt(vo.rlp); + + } } diff --git a/ethereumj-core/src/main/java/org/ethereum/db/TransactionReceiptVO.java b/ethereumj-core/src/main/java/org/ethereum/db/TransactionReceiptVO.java new file mode 100644 index 00000000..d2a65521 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/db/TransactionReceiptVO.java @@ -0,0 +1,49 @@ +package org.ethereum.db; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Lob; +import javax.persistence.Table; +import java.math.BigInteger; + +/** + * www.etherj.com + * + * @author: Roman Mandeleil + * Created on: 14/11/2014 09:59 + */ +@Entity +@Table(name = "transaction_receipt") +public class TransactionReceiptVO { + + @Id + byte[] hash; + + @Lob + byte[] rlp; + + public TransactionReceiptVO() { + } + + public TransactionReceiptVO(byte[] hash, byte[] rlp) { + this.hash = hash; + this.rlp = rlp; + } + + public byte[] getHash() { + return hash; + } + + public void setHash(byte[] hash) { + this.hash = hash; + } + + public byte[] getRlp() { + return rlp; + } + + public void setRlp(byte[] rlp) { + this.rlp = rlp; + } + +} diff --git a/ethereumj-core/src/main/java/org/ethereum/facade/Blockchain.java b/ethereumj-core/src/main/java/org/ethereum/facade/Blockchain.java index fbf444fd..e0d02b2d 100644 --- a/ethereumj-core/src/main/java/org/ethereum/facade/Blockchain.java +++ b/ethereumj-core/src/main/java/org/ethereum/facade/Blockchain.java @@ -6,6 +6,7 @@ import java.util.Map; import org.ethereum.core.Block; import org.ethereum.core.Chain; +import org.ethereum.core.TransactionReceipt; import org.ethereum.db.ByteArrayWrapper; import org.ethereum.net.BlockQueue; import org.ethereum.core.Genesis; @@ -17,7 +18,7 @@ public interface Blockchain { public long getSize(); public void add(Block block); public void tryToConnect(Block block); - public void storeBlock(Block block); + public void storeBlock(Block block, List receipts); public Block getBlockByNumber(long blockNr); public void setBestBlock(Block block); public Block getBestBlock(); @@ -30,6 +31,9 @@ public interface Blockchain { public void setTotalDifficulty(BigInteger totalDifficulty); public byte[] getBestBlockHash(); public List getListOfHashesStartFrom(byte[] hash, int qty); + + TransactionReceipt getTransactionReceiptByHash(byte[] hash); + public Block getBlockByHash(byte[] hash); public List getAltChains(); public List getGarbage(); diff --git a/ethereumj-core/src/main/java/org/ethereum/facade/EthereumFactory.java b/ethereumj-core/src/main/java/org/ethereum/facade/EthereumFactory.java index 8b98a2f1..c72b6c98 100644 --- a/ethereumj-core/src/main/java/org/ethereum/facade/EthereumFactory.java +++ b/ethereumj-core/src/main/java/org/ethereum/facade/EthereumFactory.java @@ -1,6 +1,8 @@ package org.ethereum.facade; import org.ethereum.config.SystemProperties; +import org.ethereum.net.eth.EthHandler; +import org.ethereum.net.shh.ShhHandler; import org.hibernate.SessionFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -99,6 +101,9 @@ public class EthereumFactory { public static Ethereum createEthereum(){ + logger.info("capability eth version: [{}]", EthHandler.VERSION); + logger.info("capability shh version: [{}]", ShhHandler.VERSION); + if (context == null){ context = new AnnotationConfigApplicationContext(EthereumFactory.class); factory = context.getBean(EthereumFactory.class); diff --git a/ethereumj-core/src/main/java/org/ethereum/manager/WorldManager.java b/ethereumj-core/src/main/java/org/ethereum/manager/WorldManager.java index fcb05262..ea481f3e 100644 --- a/ethereumj-core/src/main/java/org/ethereum/manager/WorldManager.java +++ b/ethereumj-core/src/main/java/org/ethereum/manager/WorldManager.java @@ -19,10 +19,7 @@ import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.math.BigInteger; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; import static org.ethereum.config.SystemProperties.CONFIG; @@ -158,7 +155,7 @@ public class WorldManager { repository.addBalance(Hex.decode(address), Genesis.PREMINE_AMOUNT); } - blockStore.saveBlock(Genesis.getInstance()); + blockStore.saveBlock(Genesis.getInstance(), new ArrayList()); blockchain.setBestBlock(Genesis.getInstance()); blockchain.setTotalDifficulty(BigInteger.ZERO); diff --git a/ethereumj-core/src/main/java/org/ethereum/net/eth/EthHandler.java b/ethereumj-core/src/main/java/org/ethereum/net/eth/EthHandler.java index 5e17c726..9ee1b2f9 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/eth/EthHandler.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/eth/EthHandler.java @@ -42,7 +42,7 @@ import static org.ethereum.net.message.StaticMessages.GET_TRANSACTIONS_MESSAGE; @Scope("prototype") public class EthHandler extends SimpleChannelInboundHandler { - public final static byte VERSION = 47; + public final static byte VERSION = 48; public final static byte NETWORK_ID = 0x0; private final static Logger logger = LoggerFactory.getLogger("net"); diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/LogInfo.java b/ethereumj-core/src/main/java/org/ethereum/vm/LogInfo.java index b10dec10..ae5f8cda 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/LogInfo.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/LogInfo.java @@ -4,6 +4,9 @@ import org.ethereum.core.BlockHeader; import org.ethereum.core.Bloom; import org.ethereum.crypto.HashUtil; import org.ethereum.util.RLP; +import org.ethereum.util.RLPElement; +import org.ethereum.util.RLPItem; +import org.ethereum.util.RLPList; import org.spongycastle.util.encoders.Hex; import java.nio.ByteBuffer; @@ -20,17 +23,38 @@ import java.util.List; public class LogInfo { - byte[] address; + byte[] address = new byte[]{}; List topics = new ArrayList(); - byte[] data; + byte[] data = new byte[]{}; /* Log info in encoded form */ private byte[] rlpEncoded; + public LogInfo(byte[] rlp){ + + RLPList params = RLP.decode2(rlp); + RLPList logInfo = (RLPList) params.get(0); + + RLPItem address = (RLPItem)logInfo.get(0); + RLPList topics = (RLPList)logInfo.get(1); + RLPItem data = (RLPItem)logInfo.get(2); + + this.address = address.getRLPData() != null ? address.getRLPData() : new byte[]{}; + this.data = data.getRLPData() != null ? data.getRLPData() : new byte[]{}; + + for (int i = 0; i < topics.size(); ++i){ + + byte[] topic = topics.get(i).getRLPData(); + this.topics.add(new DataWord(topic)); + } + + rlpEncoded = rlp; + } + public LogInfo(byte[] address, List topics, byte[] data) { - this.address = address; - this.topics = (topics == null) ? new ArrayList() : topics; - this.data = data; + this.address = (address != null) ? address : new byte[]{} ; + this.topics = (topics != null) ? topics : new ArrayList(); + this.data = (data != null) ? data : new byte[]{} ; } public byte[] getAddress() { @@ -48,7 +72,6 @@ public class LogInfo { /* [address, [topic, topic ...] data] */ public byte[] getEncoded() { - byte[] addressEncoded = RLP.encodeElement(this.address); byte[][] topicsEncoded = null; @@ -57,7 +80,7 @@ public class LogInfo { int i = 0; for( DataWord topic : topics ){ byte[] topicData = topic.getData(); - topicsEncoded[i] = topicData; + topicsEncoded[i] = RLP.encodeElement(topicData); ++i; } } @@ -72,7 +95,6 @@ public class LogInfo { byte[] topicData = topic.getData(); ret.or(Bloom.create(HashUtil.sha3(topicData))); } - return ret; } diff --git a/ethereumj-core/src/test/java/test/ethereum/core/LogInfoTest.java b/ethereumj-core/src/test/java/test/ethereum/core/LogInfoTest.java new file mode 100644 index 00000000..87d9f771 --- /dev/null +++ b/ethereumj-core/src/test/java/test/ethereum/core/LogInfoTest.java @@ -0,0 +1,53 @@ +package test.ethereum.core; + +import org.ethereum.vm.LogInfo; +import org.junit.Assert; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spongycastle.util.encoders.Hex; + +import static org.junit.Assert.*; + +/** + * www.etherj.com + * + * @author: Roman Mandeleil + * Created on: 05/12/2014 16:26 + */ + +public class LogInfoTest { + + private static final Logger logger = LoggerFactory.getLogger("test"); + + @Test // rlp decode + public void test_1(){ + + // LogInfo{address=d5ccd26ba09ce1d85148b5081fa3ed77949417be, topics=[000000000000000000000000459d3a7595df9eba241365f4676803586d7d199c 436f696e73000000000000000000000000000000000000000000000000000000 ], data=} + byte[] rlp = Hex.decode("f85a94d5ccd26ba09ce1d85148b5081fa3ed77949417bef842a0000000000000000000000000459d3a7595df9eba241365f4676803586d7d199ca0436f696e7300000000000000000000000000000000000000000000000000000080"); + LogInfo logInfo = new LogInfo(rlp); + + assertEquals("d5ccd26ba09ce1d85148b5081fa3ed77949417be", + Hex.toHexString(logInfo.getAddress())); + assertEquals("", Hex.toHexString(logInfo.getData())); + + assertEquals("000000000000000000000000459d3a7595df9eba241365f4676803586d7d199c", + logInfo.getTopics().get(0).toString()); + assertEquals("436f696e73000000000000000000000000000000000000000000000000000000", + logInfo.getTopics().get(1).toString()); + + logger.info("{}", logInfo); + } + + @Test // rlp decode + public void test_2(){ + + LogInfo log = new LogInfo(Hex.decode("d5ccd26ba09ce1d85148b5081fa3ed77949417be"), null, null); + assertEquals("d794d5ccd26ba09ce1d85148b5081fa3ed77949417bec080", Hex.toHexString(log.getEncoded())); + + logger.info("{}", log); + } + + + +} diff --git a/ethereumj-core/src/test/java/test/ethereum/core/TransactionReceiptTest.java b/ethereumj-core/src/test/java/test/ethereum/core/TransactionReceiptTest.java new file mode 100644 index 00000000..5ac7c14a --- /dev/null +++ b/ethereumj-core/src/test/java/test/ethereum/core/TransactionReceiptTest.java @@ -0,0 +1,43 @@ +package test.ethereum.core; + +import org.ethereum.core.TransactionReceipt; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spongycastle.util.encoders.Hex; + +import static org.junit.Assert.assertEquals; + +/** + * www.etherj.com + * + * @author: Roman Mandeleil + * Created on: 05/12/2014 16:26 + */ + +public class TransactionReceiptTest { + + private static final Logger logger = LoggerFactory.getLogger("test"); + + + @Test // rlp decode + public void test_1(){ + + byte[] rlp = Hex.decode("f8c4a0966265cc49fa1f10f0445f035258d116563931022a3570a640af5d73a214a8da822b6fb84000000010000000010000000000008000000000000000000000000000000000000000000000000000000000020000000000000014000000000400000000000440f85cf85a94d5ccd26ba09ce1d85148b5081fa3ed77949417bef842a0000000000000000000000000459d3a7595df9eba241365f4676803586d7d199ca0436f696e7300000000000000000000000000000000000000000000000000000080"); + TransactionReceipt txReceipt = new TransactionReceipt(rlp); + + assertEquals(1, txReceipt.getLogInfoList().size()); + + assertEquals("966265cc49fa1f10f0445f035258d116563931022a3570a640af5d73a214a8da", + Hex.toHexString(txReceipt.getPostTxState())); + + assertEquals("2b6f", + Hex.toHexString(txReceipt.getCumulativeGas())); + + assertEquals("00000010000000010000000000008000000000000000000000000000000000000000000000000000000000020000000000000014000000000400000000000440", + Hex.toHexString(txReceipt.getBloomFilter().getData())); + + logger.info("{}", txReceipt); + } + +}