diff --git a/.gitignore b/.gitignore index 725e440052..4cb5c675d9 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ logs test_db* testnetSampleDb sampleDB* +*sample*db* # Mac .DS_Store diff --git a/ethereumj-core/build.gradle b/ethereumj-core/build.gradle index 93c4123933..ca9eeb2f5d 100644 --- a/ethereumj-core/build.gradle +++ b/ethereumj-core/build.gradle @@ -130,7 +130,7 @@ dependencies { compile "org.ethereum:rocksdbjni:5.9.2" // RocksDB Java API - compile "org.ethereum:solcJ-all:0.4.19" // Solidity Compiler win/mac/linux binaries + compile "org.ethereum:solcJ-all:0.4.25" // Solidity Compiler win/mac/linux binaries compile "com.google.guava:guava:24.1-jre" diff --git a/ethereumj-core/src/main/java/org/ethereum/Start.java b/ethereumj-core/src/main/java/org/ethereum/Start.java index 312b5e3656..591eda174d 100644 --- a/ethereumj-core/src/main/java/org/ethereum/Start.java +++ b/ethereumj-core/src/main/java/org/ethereum/Start.java @@ -19,9 +19,6 @@ import org.ethereum.cli.CLIInterface; import org.ethereum.config.SystemProperties; -import org.ethereum.facade.Ethereum; -import org.ethereum.facade.EthereumFactory; -import org.ethereum.manager.BlockLoader; import org.ethereum.mine.Ethash; import java.io.IOException; diff --git a/ethereumj-core/src/main/java/org/ethereum/config/CommonConfig.java b/ethereumj-core/src/main/java/org/ethereum/config/CommonConfig.java index 5b2ac2cf0b..d4a00adcff 100644 --- a/ethereumj-core/src/main/java/org/ethereum/config/CommonConfig.java +++ b/ethereumj-core/src/main/java/org/ethereum/config/CommonConfig.java @@ -17,16 +17,23 @@ */ package org.ethereum.config; +import org.ethereum.core.EventDispatchThread; import org.ethereum.core.Repository; import org.ethereum.crypto.HashUtil; import org.ethereum.datasource.*; import org.ethereum.datasource.inmem.HashMapDB; import org.ethereum.datasource.leveldb.LevelDbDataSource; import org.ethereum.datasource.rocksdb.RocksDbDataSource; -import org.ethereum.db.*; +import org.ethereum.db.DbFlushManager; +import org.ethereum.db.HeaderStore; +import org.ethereum.db.PeerSource; +import org.ethereum.db.RepositoryRoot; +import org.ethereum.db.RepositoryWrapper; +import org.ethereum.db.StateSource; import org.ethereum.listener.CompositeEthereumListener; import org.ethereum.listener.EthereumListener; import org.ethereum.net.eth.handler.Eth63; +import org.ethereum.publish.Publisher; import org.ethereum.sync.FastSyncManager; import org.ethereum.validator.*; import org.ethereum.vm.DataWord; @@ -34,7 +41,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.context.annotation.*; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.Scope; import java.util.ArrayList; import java.util.HashSet; @@ -76,7 +88,8 @@ BeanPostProcessor initializer() { } - @Bean @Primary + @Bean + @Primary public Repository repository() { return new RepositoryWrapper(); } @@ -86,7 +99,8 @@ public Repository defaultRepository() { return new RepositoryRoot(stateSource(), null); } - @Bean @Scope("prototype") + @Bean + @Scope("prototype") public Repository repository(byte[] stateRoot) { return new RepositoryRoot(stateSource(), stateRoot); } @@ -94,7 +108,7 @@ public Repository repository(byte[] stateRoot) { /** * A source of nodes for state trie and all contract storage tries.
* This source provides contract code too.

- * + *

* Picks node by 16-bytes prefix of its key.
* Within {@link NodeKeyCompositor} this source is a part of ref counting workaround

* @@ -126,7 +140,7 @@ public StateSource stateSource() { @Bean @Scope("prototype") public Source cachedDbSource(String name) { - AbstractCachedSource writeCache = new AsyncWriteCache(blockchainSource(name)) { + AbstractCachedSource writeCache = new AsyncWriteCache(blockchainSource(name)) { @Override protected WriteCache createCache(Source source) { WriteCache.BytesKey ret = new WriteCache.BytesKey<>(source, WriteCache.CacheType.SIMPLE); @@ -166,7 +180,7 @@ public DbSource keyValueDataSource(String name, DbSettings settings) { DbSource dbSource; if ("inmem".equals(dataSource)) { dbSource = new HashMapDB<>(); - } else if ("leveldb".equals(dataSource)){ + } else if ("leveldb".equals(dataSource)) { dbSource = levelDbDataSource(); } else { dataSource = "rocksdb"; @@ -221,9 +235,14 @@ private void resetDataSource(Source source) { } } - @Bean(name = "EthereumListener") - public CompositeEthereumListener ethereumListener() { - return new CompositeEthereumListener(); + @Bean + public Publisher publisher(EventDispatchThread eventDispatchThread) { + return new Publisher(eventDispatchThread); + } + + @Bean + public CompositeEthereumListener compositeEthereumListener(EventDispatchThread eventDispatchThread) { + return new CompositeEthereumListener(eventDispatchThread); } @Bean @@ -261,16 +280,18 @@ public byte[] serialize(byte[] object) { DataWord addResult = ret.add(DataWord.ONE); return addResult.getLast20Bytes(); } + public byte[] deserialize(byte[] stream) { throw new RuntimeException("Shouldn't be called"); } }, new Serializer() { - public byte[] serialize(ProgramPrecompile object) { - return object == null ? null : object.serialize(); - } - public ProgramPrecompile deserialize(byte[] stream) { - return stream == null ? null : ProgramPrecompile.deserialize(stream); - } + public byte[] serialize(ProgramPrecompile object) { + return object == null ? null : object.serialize(); + } + + public ProgramPrecompile deserialize(byte[] stream) { + return stream == null ? null : ProgramPrecompile.deserialize(stream); + } }); } @@ -289,14 +310,14 @@ public DbFlushManager dbFlushManager() { } @Bean - public BlockHeaderValidator headerValidator() { + public BlockHeaderValidator headerValidator(SystemProperties systemProperties, Publisher publisher) { List rules = new ArrayList<>(asList( new GasValueRule(), - new ExtraDataRule(systemProperties()), - EthashRule.createRegular(systemProperties(), ethereumListener()), - new GasLimitRule(systemProperties()), - new BlockHashRule(systemProperties()) + new ExtraDataRule(systemProperties), + EthashRule.createRegular(systemProperties, publisher), + new GasLimitRule(systemProperties), + new BlockHashRule(systemProperties) )); return new BlockHeaderValidator(rules); 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 c5a043924c..1350cda546 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/BlockchainImpl.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/BlockchainImpl.java @@ -23,14 +23,23 @@ import org.ethereum.config.SystemProperties; import org.ethereum.crypto.HashUtil; import org.ethereum.datasource.inmem.HashMapDB; -import org.ethereum.db.*; -import org.ethereum.trie.Trie; -import org.ethereum.trie.TrieImpl; +import org.ethereum.db.BlockStore; +import org.ethereum.db.ByteArrayWrapper; +import org.ethereum.db.DbFlushManager; +import org.ethereum.db.HeaderStore; +import org.ethereum.db.IndexedBlockStore; +import org.ethereum.db.PruneManager; +import org.ethereum.db.StateSource; +import org.ethereum.db.TransactionStore; import org.ethereum.listener.EthereumListener; -import org.ethereum.listener.EthereumListenerAdapter; import org.ethereum.manager.AdminInfo; import org.ethereum.sync.SyncManager; -import org.ethereum.util.*; +import org.ethereum.trie.Trie; +import org.ethereum.trie.TrieImpl; +import org.ethereum.util.AdvancedDeviceUtils; +import org.ethereum.util.ByteUtil; +import org.ethereum.util.FastByteComparisons; +import org.ethereum.util.RLP; import org.ethereum.validator.DependentBlockHeaderRule; import org.ethereum.validator.ParentBlockHeaderValidator; import org.ethereum.vm.hook.VMHook; @@ -66,7 +75,11 @@ import static java.math.BigInteger.ZERO; import static java.util.Collections.emptyList; import static org.ethereum.core.Denomination.SZABO; -import static org.ethereum.core.ImportResult.*; +import static org.ethereum.core.ImportResult.EXIST; +import static org.ethereum.core.ImportResult.IMPORTED_BEST; +import static org.ethereum.core.ImportResult.IMPORTED_NOT_BEST; +import static org.ethereum.core.ImportResult.INVALID_BLOCK; +import static org.ethereum.core.ImportResult.NO_PARENT; import static org.ethereum.crypto.HashUtil.sha3; import static org.ethereum.util.ByteUtil.toHexString; @@ -111,7 +124,8 @@ public class BlockchainImpl implements Blockchain, org.ethereum.facade.Blockchai private static final int MAGIC_REWARD_OFFSET = 8; public static final byte[] EMPTY_LIST_HASH = sha3(RLP.encodeList(new byte[0])); - @Autowired @Qualifier("defaultRepository") + @Autowired + @Qualifier("defaultRepository") private Repository repository; @Autowired @@ -192,11 +206,11 @@ public BlockchainImpl(final SystemProperties config) { } //todo: autowire over constructor - public BlockchainImpl(final BlockStore blockStore, final Repository repository) { + public BlockchainImpl(final BlockStore blockStore, final Repository repository, EthereumListener listener) { this.blockStore = blockStore; this.repository = repository; this.adminInfo = new AdminInfo(); - this.listener = new EthereumListenerAdapter(); + this.listener = listener; this.parentHeaderValidator = null; this.transactionStore = new TransactionStore(new HashMapDB()); this.eventDispatchThread = EventDispatchThread.getDefault(); @@ -214,11 +228,6 @@ public BlockchainImpl withAdminInfo(AdminInfo adminInfo) { return this; } - public BlockchainImpl withEthereumListener(EthereumListener listener) { - this.listener = listener; - return this; - } - public BlockchainImpl withSyncManager(SyncManager syncManager) { this.syncManager = syncManager; return this; @@ -503,7 +512,7 @@ public synchronized Block createNewBlock(Block parent, List txs, Li new byte[0], // nonce (to mine) new byte[0], // receiptsRoot - computed after running all transactions calcTxTrie(txs), // TransactionsRoot - computed after running all transactions - new byte[] {0}, // stateRoot - computed after running all transactions + new byte[]{0}, // stateRoot - computed after running all transactions txs, null); // uncle list @@ -835,7 +844,7 @@ public static Set getAncestors(BlockStore blockStore, Block te if (!isParentBlock) { it = blockStore.getBlockByHash(it.getParentHash()); } - while(it != null && it.getNumber() >= limitNum) { + while (it != null && it.getNumber() >= limitNum) { ret.add(new ByteArrayWrapper(it.getHash())); it = blockStore.getBlockByHash(it.getParentHash()); } @@ -849,7 +858,7 @@ public Set getUsedUncles(BlockStore blockStore, Block testedBl if (!isParentBlock) { it = blockStore.getBlockByHash(it.getParentHash()); } - while(it.getNumber() > limitNum) { + while (it.getNumber() > limitNum) { for (BlockHeader uncle : it.getUncleList()) { ret.add(new ByteArrayWrapper(uncle.getHash())); } @@ -964,7 +973,7 @@ private Map addReward(Repository track, Block block, List getIteratorOfHeadersStartFrom(BlockIdentifier ident * Searches block in blockStore, if it's not found there * and headerStore is defined, searches blockHeader in it. * @param number block number - * @return Block header + * @return Block header */ private BlockHeader findHeaderByNumber(long number) { Block block = blockStore.getChainBlockByNumber(number); @@ -1392,7 +1401,7 @@ public byte[] next() { } private class State { -// Repository savedRepo = repository; + // Repository savedRepo = repository; byte[] root = repository.getRoot(); Block savedBest = bestBlock; BigInteger savedTD = totalDifficulty; diff --git a/ethereumj-core/src/main/java/org/ethereum/core/EventDispatchThread.java b/ethereumj-core/src/main/java/org/ethereum/core/EventDispatchThread.java index 8f1fa4a5a9..e1d07a80b5 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/EventDispatchThread.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/EventDispatchThread.java @@ -35,7 +35,7 @@ * Created by Anton Nashatyrev on 29.12.2015. */ @Component -public class EventDispatchThread { +public class EventDispatchThread implements Executor { private static final Logger logger = LoggerFactory.getLogger("blockchain"); private static EventDispatchThread eventDispatchThread; @@ -67,6 +67,11 @@ public void invokeLater(Runnable r) { return eventDispatchThread; } + @Override + public void execute(Runnable command) { + invokeLater(command); + } + public void invokeLater(final Runnable r) { if (executor.isShutdown()) return; if (counter++ % 1000 == 0) logStatus(); diff --git a/ethereumj-core/src/main/java/org/ethereum/core/PendingStateImpl.java b/ethereumj-core/src/main/java/org/ethereum/core/PendingStateImpl.java index 3bf80fd18c..be1401b403 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/PendingStateImpl.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/PendingStateImpl.java @@ -17,18 +17,6 @@ */ package org.ethereum.core; -import static org.ethereum.listener.EthereumListener.PendingTransactionState.DROPPED; -import static org.ethereum.listener.EthereumListener.PendingTransactionState.INCLUDED; -import static org.ethereum.listener.EthereumListener.PendingTransactionState.NEW_PENDING; -import static org.ethereum.listener.EthereumListener.PendingTransactionState.PENDING; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.TreeSet; - import org.apache.commons.collections4.map.LRUMap; import org.ethereum.config.CommonConfig; import org.ethereum.config.SystemProperties; @@ -37,7 +25,6 @@ import org.ethereum.db.TransactionStore; import org.ethereum.listener.EthereumListener; import org.ethereum.listener.EthereumListener.PendingTransactionState; -import org.ethereum.listener.EthereumListenerAdapter; import org.ethereum.util.ByteUtil; import org.ethereum.util.FastByteComparisons; import org.ethereum.vm.program.invoke.ProgramInvokeFactory; @@ -46,6 +33,16 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeSet; + +import static org.ethereum.listener.EthereumListener.PendingTransactionState.DROPPED; +import static org.ethereum.listener.EthereumListener.PendingTransactionState.INCLUDED; +import static org.ethereum.listener.EthereumListener.PendingTransactionState.NEW_PENDING; +import static org.ethereum.listener.EthereumListener.PendingTransactionState.PENDING; import static org.ethereum.util.ByteUtil.toHexString; /** @@ -78,7 +75,6 @@ public TransactionSortedSet() { @Autowired CommonConfig commonConfig = CommonConfig.getDefault(); - @Autowired private EthereumListener listener; @Autowired @@ -174,7 +170,7 @@ public synchronized List addPendingTransactions(List t if (!newPending.isEmpty()) { listener.onPendingTransactionsReceived(newPending); - listener.onPendingStateChanged(PendingStateImpl.this); + listener.onPendingStateChanged(this); } return newPending; @@ -210,7 +206,8 @@ state, toHexString(txReceipt.getTransaction().getSender()).substring(0, 8), /** * Executes pending tx on the latest best block * Fires pending state update - * @param tx Transaction + * + * @param tx Transaction * @return True if transaction gets NEW_PENDING state, False if DROPPED */ private boolean addPendingTransactionImpl(final Transaction tx) { @@ -258,7 +255,7 @@ private String validate(Transaction tx) { } private Block findCommonAncestor(Block b1, Block b2) { - while(!b1.isEqual(b2)) { + while (!b1.isEqual(b2)) { if (b1.getNumber() >= b2.getNumber()) { b1 = blockchain.getBlockByHash(b1.getParentHash()); } @@ -288,7 +285,7 @@ public synchronized void processBest(Block newBlock, List re // first return back the transactions from forked blocks Block rollback = getBestBlock(); - while(!rollback.isEqual(commonAncestor)) { + while (!rollback.isEqual(commonAncestor)) { List blockTxs = new ArrayList<>(); for (Transaction tx : rollback.getTransactionsList()) { logger.trace("Returning transaction back to pending: " + tx); @@ -304,7 +301,7 @@ public synchronized void processBest(Block newBlock, List re // next process blocks from new fork Block main = newBlock; List mainFork = new ArrayList<>(); - while(!main.isEqual(commonAncestor)) { + while (!main.isEqual(commonAncestor)) { mainFork.add(main); main = blockchain.getBlockByHash(main.getParentHash()); } diff --git a/ethereumj-core/src/main/java/org/ethereum/core/PendingTransaction.java b/ethereumj-core/src/main/java/org/ethereum/core/PendingTransaction.java index ab2411790e..a5a2e9f671 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/PendingTransaction.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/PendingTransaction.java @@ -31,6 +31,46 @@ */ public class PendingTransaction { + public enum State { + /** + * Transaction may be dropped due to: + * - Invalid transaction (invalid nonce, low gas price, insufficient account funds, + * invalid signature) + * - Timeout (when pending transaction is not included to any block for + * last [transaction.outdated.threshold] blocks + * This is the final state + */ + DROPPED, + + /** + * The same as PENDING when transaction is just arrived + * Next state can be either PENDING or INCLUDED + */ + NEW_PENDING, + + /** + * State when transaction is not included to any blocks (on the main chain), and + * was executed on the last best block. The repository state is reflected in the PendingState + * Next state can be either INCLUDED, DROPPED (due to timeout) + * or again PENDING when a new block (without this transaction) arrives + */ + PENDING, + + /** + * State when the transaction is included to a block. + * This could be the final state, however next state could also be + * PENDING: when a fork became the main chain but doesn't include this tx + * INCLUDED: when a fork became the main chain and tx is included into another + * block from the new main chain + * DROPPED: If switched to a new (long enough) main chain without this Tx + */ + INCLUDED; + + public boolean isPending() { + return this == NEW_PENDING || this == PENDING; + } + } + /** * transaction */ diff --git a/ethereumj-core/src/main/java/org/ethereum/core/TransactionExecutor.java b/ethereumj-core/src/main/java/org/ethereum/core/TransactionExecutor.java index 3ef9070bbd..e5917d1449 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/TransactionExecutor.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/TransactionExecutor.java @@ -24,7 +24,6 @@ import org.ethereum.db.BlockStore; import org.ethereum.db.ContractDetails; import org.ethereum.listener.EthereumListener; -import org.ethereum.listener.EthereumListenerAdapter; import org.ethereum.util.ByteArraySet; import org.ethereum.vm.*; import org.ethereum.vm.hook.VMHook; @@ -94,7 +93,7 @@ public class TransactionExecutor { public TransactionExecutor(Transaction tx, byte[] coinbase, Repository track, BlockStore blockStore, ProgramInvokeFactory programInvokeFactory, Block currentBlock) { - this(tx, coinbase, track, blockStore, programInvokeFactory, currentBlock, new EthereumListenerAdapter(), 0, VMHook.EMPTY); + this(tx, coinbase, track, blockStore, programInvokeFactory, currentBlock, EthereumListener.EMPTY, 0); } public TransactionExecutor(Transaction tx, byte[] coinbase, Repository track, BlockStore blockStore, diff --git a/ethereumj-core/src/main/java/org/ethereum/db/DbFlushManager.java b/ethereumj-core/src/main/java/org/ethereum/db/DbFlushManager.java index ba3dfc2697..0e811f45e2 100644 --- a/ethereumj-core/src/main/java/org/ethereum/db/DbFlushManager.java +++ b/ethereumj-core/src/main/java/org/ethereum/db/DbFlushManager.java @@ -18,23 +18,31 @@ package org.ethereum.db; import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.JdkFutureAdapters; -import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ThreadFactoryBuilder; -import org.ethereum.config.CommonConfig; import org.ethereum.config.SystemProperties; -import org.ethereum.datasource.*; -import org.ethereum.listener.CompositeEthereumListener; -import org.ethereum.listener.EthereumListenerAdapter; +import org.ethereum.datasource.AbstractCachedSource; +import org.ethereum.datasource.AsyncFlushable; +import org.ethereum.datasource.DbSource; +import org.ethereum.datasource.Source; +import org.ethereum.publish.Publisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.concurrent.*; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import static org.ethereum.publish.Subscription.to; +import static org.ethereum.publish.event.Events.Type.SYNC_DONE; +import static org.ethereum.sync.SyncManager.State.COMPLETE; /** * Created by Anton Nashatyrev on 01.12.2016. @@ -71,17 +79,15 @@ public DbFlushManager(SystemProperties config, Set dbSources, Abstract } @Autowired - public void setEthereumListener(CompositeEthereumListener listener) { + public void setPublisher(Publisher publisher) { if (!flushAfterSyncDone) return; - listener.addListener(new EthereumListenerAdapter() { - @Override - public void onSyncDone(SyncState state) { - if (state == SyncState.COMPLETE) { - logger.info("DbFlushManager: long sync done, flushing each block now"); - syncDone = true; - } + + publisher.subscribe(to(SYNC_DONE, state -> { + if (state == COMPLETE) { + logger.info("DbFlushManager: long sync done, flushing each block now"); + syncDone = true; } - }); + })); } public void setSizeThreshold(long sizeThreshold) { diff --git a/ethereumj-core/src/main/java/org/ethereum/facade/Ethereum.java b/ethereumj-core/src/main/java/org/ethereum/facade/Ethereum.java index 0fcd872280..f7fdab2ec2 100644 --- a/ethereumj-core/src/main/java/org/ethereum/facade/Ethereum.java +++ b/ethereumj-core/src/main/java/org/ethereum/facade/Ethereum.java @@ -17,7 +17,11 @@ */ package org.ethereum.facade; -import org.ethereum.core.*; +import org.ethereum.core.Block; +import org.ethereum.core.BlockSummary; +import org.ethereum.core.CallTransaction; +import org.ethereum.core.Transaction; +import org.ethereum.core.TransactionReceipt; import org.ethereum.crypto.ECKey; import org.ethereum.listener.EthereumListener; import org.ethereum.manager.AdminInfo; @@ -27,6 +31,9 @@ import org.ethereum.net.rlpx.Node; import org.ethereum.net.server.ChannelManager; import org.ethereum.net.shh.Whisper; +import org.ethereum.publish.Publisher; +import org.ethereum.publish.Subscription; +import org.ethereum.publish.event.Event; import org.ethereum.vm.program.ProgramResult; import java.math.BigInteger; @@ -34,6 +41,8 @@ import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; +import java.util.function.BiConsumer; +import java.util.function.Consumer; /** * @author Roman Mandeleil @@ -53,8 +62,74 @@ public interface Ethereum { Blockchain getBlockchain(); + /** + * Uses expensive enough logic for delivery events to client.
+ * Will be removed in future releases. + * + * @param listener + * @deprecated use {@link #subscribe(Subscription)} or other overloaded subscribe(...) methods instead. + */ + @Deprecated void addListener(EthereumListener listener); + /** + * Adds specified subscription to Ethereum pub/sub system.
+ * Use this method to get more control over {@link Subscription}, like adding handle condition and other supported stuff. + *

+ * Could be used in conjunction with {@link #unsubscribe(Subscription)} method to get extra control + * over subscription lifecycle. + *

+ * If you don't need any tricky subscription logic it's better to use more convenient subscription methods: + *

+ * + * @param subscription event subscription instance to add; + * @return publisher instance to support fluent API. + * @see Subscription + * @see Publisher + * @see Event + */ + Ethereum subscribe(Subscription subscription); + + /** + * Removes specified subscription to Ethereum pub/sub system if it was subscribed before.
+ * Usually unsubscribe client from event previously subscribed via {@link #subscribe(Subscription)} method. + * + * @param subscription event subscription instance to remove; + * @return publisher instance to support fluent API. + * @see Subscription + * @see Publisher + * @see Event + */ + Ethereum unsubscribe(Subscription subscription); + + /** + * Subscribes client's handler to specific Ethereum event. + *

+ * Supported events list you can find here {@link org.ethereum.publish.event.Events.Type} + * + * @param type event type to subscribe; + * @param handler event handler; + * @param event payload which will be passed to handler; + * @return {@link Ethereum} instance to support fluent API. + */ + Ethereum subscribe(Class> type, Consumer handler); + + /** + * More advanced version of {@link #subscribe(Class, Consumer)} + * where besides event's payload to client handler passes subscription's {@link org.ethereum.publish.Subscription.LifeCycle}. + *

+ * Supported events list you can find here {@link org.ethereum.publish.event.Events.Type} + * + * @param type event type to subscribe; + * @param handler extended event handler; + * @param event payload which will be passed to handler; + * @return {@link Ethereum} instance to support fluent API. + */ + Ethereum subscribe(Class> type, BiConsumer handler); + PeerClient getDefaultPeer(); boolean isConnected(); @@ -69,24 +144,23 @@ public interface Ethereum { /** * Factory for general transaction * - * - * @param nonce - account nonce, based on number of transaction submited by - * this account - * @param gasPrice - gas price bid by miner , the user ask can be based on - * lastr submited block - * @param gas - the quantity of gas requested for the transaction + * @param nonce - account nonce, based on number of transaction submited by + * this account + * @param gasPrice - gas price bid by miner , the user ask can be based on + * lastr submited block + * @param gas - the quantity of gas requested for the transaction * @param receiveAddress - the target address of the transaction - * @param value - the ether value of the transaction - * @param data - can be init procedure for creational transaction, - * also msg data for invoke transaction for only value - * transactions this one is empty. + * @param value - the ether value of the transaction + * @param data - can be init procedure for creational transaction, + * also msg data for invoke transaction for only value + * transactions this one is empty. * @return newly created transaction */ Transaction createTransaction(BigInteger nonce, - BigInteger gasPrice, - BigInteger gas, - byte[] receiveAddress, - BigInteger value, byte[] data); + BigInteger gasPrice, + BigInteger gas, + byte[] receiveAddress, + BigInteger value, byte[] data); /** @@ -99,11 +173,12 @@ Transaction createTransaction(BigInteger nonce, /** * Executes the transaction based on the specified block but doesn't change the blockchain state * and doesn't send the transaction to the network - * @param tx The transaction to execute. No need to sign the transaction and specify the correct nonce - * @param block Transaction is executed the same way as if it was executed after all transactions existing - * in that block. I.e. the root state is the same as this block's root state and this block - * is assumed to be the current block - * @return receipt of the executed transaction + * + * @param tx The transaction to execute. No need to sign the transaction and specify the correct nonce + * @param block Transaction is executed the same way as if it was executed after all transactions existing + * in that block. I.e. the root state is the same as this block's root state and this block + * is assumed to be the current block + * @return receipt of the executed transaction */ TransactionReceipt callConstant(Transaction tx, Block block); @@ -116,16 +191,17 @@ Transaction createTransaction(BigInteger nonce, * * @param block block to be replayed * @return block summary with receipts and execution summaries - * Note: it doesn't include block rewards info + * Note: it doesn't include block rewards info */ BlockSummary replayBlock(Block block); /** * Call a contract function locally without sending transaction to the network * and without changing contract storage. + * * @param receiveAddress hex encoded contract address - * @param function contract function - * @param funcArgs function arguments + * @param function contract function + * @param funcArgs function arguments * @return function result. The return value can be fetched via {@link ProgramResult#getHReturn()} * and decoded with {@link org.ethereum.core.CallTransaction.Function#decodeResult(byte[])}. */ @@ -136,11 +212,12 @@ ProgramResult callConstantFunction(String receiveAddress, CallTransaction.Functi /** * Call a contract function locally without sending transaction to the network * and without changing contract storage. - * @param receiveAddress hex encoded contract address - * @param senderPrivateKey Normally the constant call doesn't require a sender though - * in some cases it may affect the result (e.g. if function refers to msg.sender) - * @param function contract function - * @param funcArgs function arguments + * + * @param receiveAddress hex encoded contract address + * @param senderPrivateKey Normally the constant call doesn't require a sender though + * in some cases it may affect the result (e.g. if function refers to msg.sender) + * @param function contract function + * @param funcArgs function arguments * @return function result. The return value can be fetched via {@link ProgramResult#getHReturn()} * and decoded with {@link org.ethereum.core.CallTransaction.Function#decodeResult(byte[])}. */ @@ -192,7 +269,7 @@ ProgramResult callConstantFunction(String receiveAddress, ECKey senderPrivateKey Whisper getWhisper(); /** - * Gets the Miner component + * Gets the Miner component */ BlockMiner getBlockMiner(); @@ -202,8 +279,7 @@ ProgramResult callConstantFunction(String receiveAddress, ECKey senderPrivateKey void initSyncing(); /** - * @deprecated - * Calculates a 'reasonable' Gas price based on statistics of the latest transaction's Gas prices + * @deprecated Calculates a 'reasonable' Gas price based on statistics of the latest transaction's Gas prices * Normally the price returned should be sufficient to execute a transaction since ~25% of the latest * transactions were executed at this or lower price. * If the transaction is wanted to be executed promptly with higher chances the returned price might @@ -222,6 +298,7 @@ ProgramResult callConstantFunction(String receiveAddress, ECKey senderPrivateKey /** * Chain id for next block. * Introduced in EIP-155 + * * @return chain id or null */ Integer getChainIdForNextBlock(); @@ -229,6 +306,7 @@ ProgramResult callConstantFunction(String receiveAddress, ECKey senderPrivateKey /** * Manual switch to Short Sync mode * Maybe useful in small private and detached networks when automatic detection fails + * * @return Future, which completes when syncDone is turned to True in {@link org.ethereum.sync.SyncManager} */ CompletableFuture switchToShortSync(); diff --git a/ethereumj-core/src/main/java/org/ethereum/facade/EthereumImpl.java b/ethereumj-core/src/main/java/org/ethereum/facade/EthereumImpl.java index 13f3ecaa66..6fd5779f71 100644 --- a/ethereumj-core/src/main/java/org/ethereum/facade/EthereumImpl.java +++ b/ethereumj-core/src/main/java/org/ethereum/facade/EthereumImpl.java @@ -24,7 +24,6 @@ import org.ethereum.core.PendingState; import org.ethereum.core.Repository; import org.ethereum.crypto.ECKey; -import org.ethereum.listener.CompositeEthereumListener; import org.ethereum.listener.EthereumListener; import org.ethereum.listener.GasPriceTracker; import org.ethereum.manager.AdminInfo; @@ -37,6 +36,8 @@ import org.ethereum.net.shh.Whisper; import org.ethereum.net.submit.TransactionExecutor; import org.ethereum.net.submit.TransactionTask; +import org.ethereum.publish.Subscription; +import org.ethereum.publish.event.Event; import org.ethereum.sync.SyncManager; import org.ethereum.util.ByteUtil; import org.ethereum.vm.hook.VMHook; @@ -52,13 +53,20 @@ import org.springframework.stereotype.Component; import org.springframework.util.concurrent.FutureAdapter; +import javax.annotation.PostConstruct; import java.math.BigInteger; import java.net.InetAddress; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import static org.ethereum.publish.Subscription.to; +import static org.ethereum.publish.event.Events.Type.BLOCK_ADDED; import static org.ethereum.util.ByteUtil.toHexString; /** @@ -103,20 +111,20 @@ public class EthereumImpl implements Ethereum, SmartLifecycle { private SystemProperties config; - private CompositeEthereumListener compositeEthereumListener; - - private GasPriceTracker gasPriceTracker = new GasPriceTracker(); @Autowired - public EthereumImpl(final SystemProperties config, final CompositeEthereumListener compositeEthereumListener) { - this.compositeEthereumListener = compositeEthereumListener; + public EthereumImpl(final SystemProperties config) { this.config = config; System.out.println(); - this.compositeEthereumListener.addListener(gasPriceTracker); gLogger.info("EthereumJ node started: enode://" + toHexString(config.nodeId()) + "@" + config.externalIp() + ":" + config.listenPort()); } + @PostConstruct + public void init() { + worldManager.subscribe(to(BLOCK_ADDED, data -> gasPriceTracker.onBlock(data.getBlockSummary()))); + } + @Override public void startPeerDiscovery() { worldManager.startPeerDiscovery(); @@ -166,6 +174,28 @@ public void addListener(EthereumListener listener) { worldManager.addListener(listener); } + @Override + public Ethereum subscribe(Subscription subscription) { + worldManager.getPublisher().subscribe(subscription); + return this; + } + + @Override + public Ethereum unsubscribe(Subscription subscription) { + worldManager.getPublisher().unsubscribe(subscription); + return this; + } + + @Override + public Ethereum subscribe(Class> type, Consumer handler) { + return subscribe(to(type, handler)); + } + + @Override + public Ethereum subscribe(Class> type, BiConsumer handler) { + return subscribe(to(type, handler)); + } + @Override public void close() { logger.info("### Shutdown initiated ### "); diff --git a/ethereumj-core/src/main/java/org/ethereum/listener/BackwardCompatibilityEthereumListenerProxy.java b/ethereumj-core/src/main/java/org/ethereum/listener/BackwardCompatibilityEthereumListenerProxy.java new file mode 100644 index 0000000000..33a2817ec0 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/listener/BackwardCompatibilityEthereumListenerProxy.java @@ -0,0 +1,187 @@ +/* + * Copyright (c) [2016] [ ] + * 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 . + */ +package org.ethereum.listener; + +import org.ethereum.core.Block; +import org.ethereum.core.BlockSummary; +import org.ethereum.core.EventDispatchThread; +import org.ethereum.core.PendingState; +import org.ethereum.core.PendingTransaction; +import org.ethereum.core.Transaction; +import org.ethereum.core.TransactionExecutionSummary; +import org.ethereum.core.TransactionReceipt; +import org.ethereum.net.eth.message.StatusMessage; +import org.ethereum.net.message.Message; +import org.ethereum.net.p2p.HelloMessage; +import org.ethereum.net.rlpx.Node; +import org.ethereum.net.server.Channel; +import org.ethereum.publish.Publisher; +import org.ethereum.publish.event.Events; +import org.ethereum.sync.SyncManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * Backward compatibility component that holds old and new event publishing implementations.
+ * Proxies all events to both implementations. + * Will be removed after {@link EthereumListener} eviction. + * + * @author Eugene Shevchenko + */ +@Primary +@Component +public class BackwardCompatibilityEthereumListenerProxy implements EthereumListener { + + private final CompositeEthereumListener compositeListener; + private final Publisher publisher; + + @Autowired + public BackwardCompatibilityEthereumListenerProxy(CompositeEthereumListener listener, Publisher publisher) { + this.compositeListener = listener; + this.publisher = publisher; + } + + public CompositeEthereumListener getCompositeListener() { + return compositeListener; + } + + public Publisher getPublisher() { + return publisher; + } + + public void addListener(EthereumListener listener) { + this.compositeListener.addListener(listener); + } + + @Override + public void trace(String output) { + compositeListener.trace(output); + } + + @Override + public void onNodeDiscovered(Node node) { + compositeListener.onNodeDiscovered(node); + publisher.publish(Events.onNodeDiscovered(node)); + } + + @Override + public void onHandShakePeer(Channel channel, HelloMessage message) { + compositeListener.onHandShakePeer(channel, message); + publisher.publish(Events.onPeerHanshaked(channel, message)); + } + + @Override + public void onEthStatusUpdated(Channel channel, StatusMessage message) { + compositeListener.onEthStatusUpdated(channel, message); + publisher.publish(Events.onEthStatusUpdated(channel, message)); + } + + @Override + public void onRecvMessage(Channel channel, Message message) { + compositeListener.onRecvMessage(channel, message); + publisher.publish(Events.onMessageReceived(channel, message)); + } + + @Override + public void onSendMessage(Channel channel, Message message) { + compositeListener.onSendMessage(channel, message); + publisher.publish(Events.onMessageSent(channel, message)); + } + + @Override + public void onBlock(BlockSummary blockSummary) { + compositeListener.onBlock(blockSummary); + publisher.publish(Events.onBlockAdded(blockSummary, false)); + } + + @Override + public void onBlock(BlockSummary blockSummary, boolean best) { + compositeListener.onBlock(blockSummary, best); + compositeListener.onBlock(blockSummary); + publisher.publish(Events.onBlockAdded(blockSummary, best)); + } + + @Override + public void onPeerDisconnect(String host, long port) { + compositeListener.onPeerDisconnect(host, port); + publisher.publish(Events.onPeerDisconnected(host, port)); + } + + @Override + public void onPendingTransactionsReceived(List transactions) { + compositeListener.onPendingTransactionsReceived(transactions); + } + + @Override + public void onPendingStateChanged(PendingState pendingState) { + compositeListener.onPendingStateChanged(pendingState); + publisher.publish(Events.onPendingStateChanged(pendingState)); + } + + @Override + public void onPendingTransactionUpdate(TransactionReceipt txReceipt, PendingTransactionState state, Block block) { + compositeListener.onPendingTransactionUpdate(txReceipt, state, block); + PendingTransaction.State translatedState = translate(state, PendingTransaction.State.class); + publisher.publish(Events.onPendingTransactionUpdated(block, txReceipt, translatedState)); + } + + @Override + public void onSyncDone(SyncState state) { + compositeListener.onSyncDone(state); + SyncManager.State translatedState = translate(state, SyncManager.State.class); + publisher.publish(Events.onSyncDone(translatedState)); + } + + private static , T extends Enum> T translate(S source, Class targetType) { + return Enum.valueOf(targetType, source.name()); + } + + @Override + public void onNoConnections() { + compositeListener.onNoConnections(); + } + + @Override + public void onVMTraceCreated(String transactionHash, String trace) { + compositeListener.onVMTraceCreated(transactionHash, trace); + publisher.publish(Events.onVmTraceCreated(transactionHash, trace)); + } + + @Override + public void onTransactionExecuted(TransactionExecutionSummary summary) { + compositeListener.onTransactionExecuted(summary); + publisher.publish(Events.onTransactionExecuted(summary)); + } + + @Override + public void onPeerAddedToSyncPool(Channel peer) { + compositeListener.onPeerAddedToSyncPool(peer); + publisher.publish(Events.onPeerAddedToSyncPool(peer)); + } + + public static BackwardCompatibilityEthereumListenerProxy createDefault() { + EventDispatchThread eventDispatchThread = EventDispatchThread.getDefault(); + CompositeEthereumListener compositeEthereumListener = new CompositeEthereumListener(eventDispatchThread); + Publisher publisher = new Publisher(eventDispatchThread); + + return new BackwardCompatibilityEthereumListenerProxy(compositeEthereumListener, publisher); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/listener/BlockReplay.java b/ethereumj-core/src/main/java/org/ethereum/listener/BlockReplay.java index fbb25ad1db..e1aa9f2b18 100644 --- a/ethereumj-core/src/main/java/org/ethereum/listener/BlockReplay.java +++ b/ethereumj-core/src/main/java/org/ethereum/listener/BlockReplay.java @@ -37,6 +37,8 @@ import static org.ethereum.sync.BlockDownloader.MAX_IN_REQUEST; /** + * @deprecated This component uses deprecated {@link EthereumListenerAdapter}, use {@link BlockReplayer} instead. + * * Class capable of replaying stored blocks prior to 'going online' and * notifying on newly imported blocks * diff --git a/ethereumj-core/src/main/java/org/ethereum/listener/BlockReplayer.java b/ethereumj-core/src/main/java/org/ethereum/listener/BlockReplayer.java new file mode 100644 index 0000000000..10fd28a541 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/listener/BlockReplayer.java @@ -0,0 +1,207 @@ +/* + * Copyright (c) [2016] [ ] + * 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 . + */ +package org.ethereum.listener; + +import org.apache.commons.collections4.queue.CircularFifoQueue; +import org.ethereum.core.Block; +import org.ethereum.core.BlockSummary; +import org.ethereum.core.TransactionInfo; +import org.ethereum.core.TransactionReceipt; +import org.ethereum.db.BlockStore; +import org.ethereum.db.TransactionStore; +import org.ethereum.facade.Ethereum; +import org.ethereum.publish.event.BlockAdded; +import org.ethereum.sync.BlockDownloader; +import org.ethereum.util.FastByteComparisons; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigInteger; +import java.util.List; +import java.util.function.Consumer; + +import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.toList; +import static org.ethereum.publish.event.Events.Type.BLOCK_ADDED; + +/** + * Class capable of replaying stored blocks prior to 'going online' and + * notifying on newly imported blocks + *

+ * For example of usage, look at {@link org.ethereum.samples.BlockReplaySample} + *

+ * Created by Eugene Shevchenko on 07.10.2018. + */ +public class BlockReplayer { + + private static final Logger logger = LoggerFactory.getLogger("events"); + private static final int HALF_BUFFER = BlockDownloader.MAX_IN_REQUEST; + + private final BlockStore blockStore; + private final TransactionStore transactionStore; + private final long firstBlock; + private final Consumer handler; + + private final CircularFifoQueue blocksCache = new CircularFifoQueue<>(HALF_BUFFER * 2); + private boolean completed = false; + private byte[] lastReplayedBlockHash; + + public BlockReplayer(long firstBlock, BlockStore blockStore, TransactionStore transactionStore, Consumer handler) { + if (firstBlock < 0L) { + throw new IllegalArgumentException("Initial block number should be positive value or zero."); + } + requireNonNull(blockStore, "Blocks store is not defined."); + requireNonNull(transactionStore, "Transactions store is not defined."); + requireNonNull(handler, "BlockAdded event handler is not defined."); + + this.blockStore = blockStore; + this.transactionStore = transactionStore; + this.firstBlock = firstBlock; + this.handler = handler; + } + + private void replayBlock(long num) { + Block block = blockStore.getChainBlockByNumber(num); + List receipts = block.getTransactionsList().stream() + .map(tx -> { + TransactionInfo info = transactionStore.get(tx.getHash(), block.getHash()); + TransactionReceipt receipt = info.getReceipt(); + receipt.setTransaction(tx); + return receipt; + }) + .collect(toList()); + + BlockSummary blockSummary = new BlockSummary(block, null, receipts, null); + blockSummary.setTotalDifficulty(BigInteger.valueOf(num)); + + lastReplayedBlockHash = block.getHash(); + + handler.accept(new BlockAdded.Data(blockSummary, true)); + } + + /** + * Replay blocks synchronously + */ + public void replay() { + long lastBlock = blockStore.getMaxNumber(); + long currentBlock = firstBlock; + int replayedBlocksCount = 0; + + logger.info("Replaying blocks from {}, current best block: {}", firstBlock, lastBlock); + while (!completed) { + for (; currentBlock <= lastBlock; currentBlock++, replayedBlocksCount++) { + replayBlock(currentBlock); + + if (replayedBlocksCount % 1000 == 0) { + logger.info("Replayed " + replayedBlocksCount + " blocks so far. Current block: " + currentBlock); + } + } + + synchronized (this) { + if (blocksCache.size() < blocksCache.maxSize()) { + completed = true; + } else { + // So we'll have half of the buffer for new blocks until not synchronized replay finish + long newLastBlock = blockStore.getMaxNumber() - HALF_BUFFER; + if (lastBlock < newLastBlock) { + lastBlock = newLastBlock; + } else { + completed = true; + } + } + } + } + logger.info("Replay complete."); + } + + private synchronized void onBlock(BlockAdded.Data data) { + if (completed) { + if (!blocksCache.isEmpty()) { + replayCachedBlocks(); + logger.info("Cache replay complete. Switching to online mode."); + } + + handler.accept(data); + } else { + blocksCache.add(data); + } + } + + private void replayCachedBlocks() { + logger.info("Replaying cached {} blocks...", blocksCache.size()); + + boolean lastBlockFound = (lastReplayedBlockHash == null) || (blocksCache.size() < blocksCache.maxSize()); + for (BlockAdded.Data cachedBlock : blocksCache) { + if (lastBlockFound) { + handler.accept(cachedBlock); + } else { + byte[] blockHash = cachedBlock.getBlockSummary().getBlock().getHash(); + lastBlockFound = FastByteComparisons.equal(blockHash, lastReplayedBlockHash); + } + } + + blocksCache.clear(); + } + + public boolean isDone() { + return completed && blocksCache.isEmpty(); + } + + public static Builder startFrom(long blockNumber) { + return new Builder(blockNumber); + } + + public static class Builder { + + private long startBlockNumber; + private BlockStore blockStore; + private TransactionStore transactionStore; + private Consumer handler; + + public Builder(long startBlockNumber) { + this.startBlockNumber = startBlockNumber; + } + + public Builder withStores(BlockStore blockStore, TransactionStore transactionStore) { + this.blockStore = blockStore; + this.transactionStore = transactionStore; + return this; + } + + public Builder withHandler(Consumer handler) { + this.handler = handler; + return this; + } + + public BlockReplayer build() { + return new BlockReplayer(startBlockNumber, blockStore, transactionStore, handler); + } + + public BlockReplayer replayAsyncAt(Ethereum ethereum) { + if (startBlockNumber > ethereum.getBlockchain().getBestBlock().getNumber()) { + logger.info("Nothing to replay: start replay block is greater than blockchain's best block."); + } + + BlockReplayer blockReplay = build(); + ethereum.subscribe(BLOCK_ADDED, blockReplay::onBlock); + new Thread(() -> blockReplay.replay()).start(); + + return blockReplay; + } + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/listener/CompositeEthereumListener.java b/ethereumj-core/src/main/java/org/ethereum/listener/CompositeEthereumListener.java index 5f3d5998b5..ec021f7924 100644 --- a/ethereumj-core/src/main/java/org/ethereum/listener/CompositeEthereumListener.java +++ b/ethereumj-core/src/main/java/org/ethereum/listener/CompositeEthereumListener.java @@ -23,16 +23,17 @@ import org.ethereum.net.p2p.HelloMessage; import org.ethereum.net.rlpx.Node; import org.ethereum.net.server.Channel; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executor; /** + * @deprecated use {@link org.ethereum.publish.Publisher} instead. * @author Roman Mandeleil * @since 12.11.2014 */ +@Deprecated public class CompositeEthereumListener implements EthereumListener { private static abstract class RunnableInfo implements Runnable { @@ -50,10 +51,14 @@ public String toString() { } } - @Autowired - EventDispatchThread eventDispatchThread = EventDispatchThread.getDefault(); - - protected List listeners = new CopyOnWriteArrayList<>(); + private final Executor executor; + private final List listeners; + + + public CompositeEthereumListener(Executor executor) { + this.executor = executor; + this.listeners = new CopyOnWriteArrayList<>(); + } public void addListener(EthereumListener listener) { listeners.add(listener); @@ -65,7 +70,7 @@ public void removeListener(EthereumListener listener) { @Override public void trace(final String output) { for (final EthereumListener listener : listeners) { - eventDispatchThread.invokeLater(new RunnableInfo(listener, "trace") { + executor.execute(new RunnableInfo(listener, "trace") { @Override public void run() { listener.trace(output); @@ -77,7 +82,7 @@ public void run() { @Override public void onBlock(final BlockSummary blockSummary) { for (final EthereumListener listener : listeners) { - eventDispatchThread.invokeLater(new RunnableInfo(listener, "onBlock") { + executor.execute(new RunnableInfo(listener, "onBlock") { @Override public void run() { listener.onBlock(blockSummary); @@ -89,7 +94,7 @@ public void run() { @Override public void onBlock(final BlockSummary blockSummary, final boolean best) { for (final EthereumListener listener : listeners) { - eventDispatchThread.invokeLater(new RunnableInfo(listener, "onBlock") { + executor.execute(new RunnableInfo(listener, "onBlock") { @Override public void run() { listener.onBlock(blockSummary, best); @@ -101,7 +106,7 @@ public void run() { @Override public void onRecvMessage(final Channel channel, final Message message) { for (final EthereumListener listener : listeners) { - eventDispatchThread.invokeLater(new RunnableInfo(listener, "onRecvMessage") { + executor.execute(new RunnableInfo(listener, "onRecvMessage") { @Override public void run() { listener.onRecvMessage(channel, message); @@ -113,7 +118,7 @@ public void run() { @Override public void onSendMessage(final Channel channel, final Message message) { for (final EthereumListener listener : listeners) { - eventDispatchThread.invokeLater(new RunnableInfo(listener, "onSendMessage") { + executor.execute(new RunnableInfo(listener, "onSendMessage") { @Override public void run() { listener.onSendMessage(channel, message); @@ -125,7 +130,7 @@ public void run() { @Override public void onPeerDisconnect(final String host, final long port) { for (final EthereumListener listener : listeners) { - eventDispatchThread.invokeLater(new RunnableInfo(listener, "onPeerDisconnect") { + executor.execute(new RunnableInfo(listener, "onPeerDisconnect") { @Override public void run() { listener.onPeerDisconnect(host, port); @@ -137,7 +142,7 @@ public void run() { @Override public void onPendingTransactionsReceived(final List transactions) { for (final EthereumListener listener : listeners) { - eventDispatchThread.invokeLater(new RunnableInfo(listener, "onPendingTransactionsReceived") { + executor.execute(new RunnableInfo(listener, "onPendingTransactionsReceived") { @Override public void run() { listener.onPendingTransactionsReceived(transactions); @@ -149,7 +154,7 @@ public void run() { @Override public void onPendingStateChanged(final PendingState pendingState) { for (final EthereumListener listener : listeners) { - eventDispatchThread.invokeLater(new RunnableInfo(listener, "onPendingStateChanged") { + executor.execute(new RunnableInfo(listener, "onPendingStateChanged") { @Override public void run() { listener.onPendingStateChanged(pendingState); @@ -161,7 +166,7 @@ public void run() { @Override public void onSyncDone(final SyncState state) { for (final EthereumListener listener : listeners) { - eventDispatchThread.invokeLater(new RunnableInfo(listener, "onSyncDone") { + executor.execute(new RunnableInfo(listener, "onSyncDone") { @Override public void run() { listener.onSyncDone(state); @@ -173,7 +178,7 @@ public void run() { @Override public void onNoConnections() { for (final EthereumListener listener : listeners) { - eventDispatchThread.invokeLater(new RunnableInfo(listener, "onNoConnections") { + executor.execute(new RunnableInfo(listener, "onNoConnections") { @Override public void run() { listener.onNoConnections(); @@ -185,7 +190,7 @@ public void run() { @Override public void onHandShakePeer(final Channel channel, final HelloMessage helloMessage) { for (final EthereumListener listener : listeners) { - eventDispatchThread.invokeLater(new RunnableInfo(listener, "onHandShakePeer") { + executor.execute(new RunnableInfo(listener, "onHandShakePeer") { @Override public void run() { listener.onHandShakePeer(channel, helloMessage); @@ -197,7 +202,7 @@ public void run() { @Override public void onVMTraceCreated(final String transactionHash, final String trace) { for (final EthereumListener listener : listeners) { - eventDispatchThread.invokeLater(new RunnableInfo(listener, "onVMTraceCreated") { + executor.execute(new RunnableInfo(listener, "onVMTraceCreated") { @Override public void run() { listener.onVMTraceCreated(transactionHash, trace); @@ -209,7 +214,7 @@ public void run() { @Override public void onNodeDiscovered(final Node node) { for (final EthereumListener listener : listeners) { - eventDispatchThread.invokeLater(new RunnableInfo(listener, "onNodeDiscovered") { + executor.execute(new RunnableInfo(listener, "onNodeDiscovered") { @Override public void run() { listener.onNodeDiscovered(node); @@ -221,7 +226,7 @@ public void run() { @Override public void onEthStatusUpdated(final Channel channel, final StatusMessage status) { for (final EthereumListener listener : listeners) { - eventDispatchThread.invokeLater(new RunnableInfo(listener, "onEthStatusUpdated") { + executor.execute(new RunnableInfo(listener, "onEthStatusUpdated") { @Override public void run() { listener.onEthStatusUpdated(channel, status); @@ -233,7 +238,7 @@ public void run() { @Override public void onTransactionExecuted(final TransactionExecutionSummary summary) { for (final EthereumListener listener : listeners) { - eventDispatchThread.invokeLater(new RunnableInfo(listener, "onTransactionExecuted") { + executor.execute(new RunnableInfo(listener, "onTransactionExecuted") { @Override public void run() { listener.onTransactionExecuted(summary); @@ -245,7 +250,7 @@ public void run() { @Override public void onPeerAddedToSyncPool(final Channel peer) { for (final EthereumListener listener : listeners) { - eventDispatchThread.invokeLater(new RunnableInfo(listener, "onPeerAddedToSyncPool") { + executor.execute(new RunnableInfo(listener, "onPeerAddedToSyncPool") { @Override public void run() { listener.onPeerAddedToSyncPool(peer); @@ -258,7 +263,7 @@ public void run() { public void onPendingTransactionUpdate(final TransactionReceipt txReceipt, final PendingTransactionState state, final Block block) { for (final EthereumListener listener : listeners) { - eventDispatchThread.invokeLater(new RunnableInfo(listener, "onPendingTransactionUpdate") { + executor.execute(new RunnableInfo(listener, "onPendingTransactionUpdate") { @Override public void run() { listener.onPendingTransactionUpdate(txReceipt, state, block); diff --git a/ethereumj-core/src/main/java/org/ethereum/listener/EthereumListener.java b/ethereumj-core/src/main/java/org/ethereum/listener/EthereumListener.java index 4ece1f0682..166cc80c43 100644 --- a/ethereumj-core/src/main/java/org/ethereum/listener/EthereumListener.java +++ b/ethereumj-core/src/main/java/org/ethereum/listener/EthereumListener.java @@ -27,18 +27,25 @@ import java.util.List; /** + * Uses expensive enough logic for delivery events to client.
+ * Will be removed in future releases. + * * @author Roman Mandeleil * @since 27.07.2014 + * @deprecated use {@link org.ethereum.publish.Publisher} instead. */ public interface EthereumListener { + /** + * Will be extracted to separate enum in future releases after {@link EthereumListener} eviction. + */ enum PendingTransactionState { /** * Transaction may be dropped due to: * - Invalid transaction (invalid nonce, low gas price, insufficient account funds, - * invalid signature) + * invalid signature) * - Timeout (when pending transaction is not included to any block for - * last [transaction.outdated.threshold] blocks + * last [transaction.outdated.threshold] blocks * This is the final state */ DROPPED, @@ -62,7 +69,7 @@ enum PendingTransactionState { * This could be the final state, however next state could also be * PENDING: when a fork became the main chain but doesn't include this tx * INCLUDED: when a fork became the main chain and tx is included into another - * block from the new main chain + * block from the new main chain * DROPPED: If switched to a new (long enough) main chain without this Tx */ INCLUDED; @@ -72,6 +79,9 @@ public boolean isPending() { } } + /** + * Will be extracted to separate enum in future releases after {@link EthereumListener} eviction. + */ enum SyncState { /** * When doing fast sync UNSECURE sync means that the full state is downloaded, @@ -135,9 +145,9 @@ default void onBlock(BlockSummary blockSummary, boolean best) { * Is called when PendingTransaction arrives, executed or dropped and included to a block * * @param txReceipt Receipt of the tx execution on the current PendingState - * @param state Current state of pending tx - * @param block The block which the current pending state is based on (for PENDING tx state) - * or the block which tx was included to (for INCLUDED state) + * @param state Current state of pending tx + * @param block The block which the current pending state is based on (for PENDING tx state) + * or the block which tx was included to (for INCLUDED state) */ void onPendingTransactionUpdate(TransactionReceipt txReceipt, PendingTransactionState state, Block block); @@ -150,4 +160,6 @@ default void onBlock(BlockSummary blockSummary, boolean best) { void onTransactionExecuted(TransactionExecutionSummary summary); void onPeerAddedToSyncPool(Channel peer); + + EthereumListener EMPTY = new EthereumListenerAdapter(); } diff --git a/ethereumj-core/src/main/java/org/ethereum/listener/EthereumListenerAdapter.java b/ethereumj-core/src/main/java/org/ethereum/listener/EthereumListenerAdapter.java index e2f7d29930..67b4969868 100644 --- a/ethereumj-core/src/main/java/org/ethereum/listener/EthereumListenerAdapter.java +++ b/ethereumj-core/src/main/java/org/ethereum/listener/EthereumListenerAdapter.java @@ -27,9 +27,15 @@ import java.util.List; /** + * Default implementation of {@link EthereumListener}.
+ * Uses expensive enough logic for delivery events to client.
+ * Will be removed in future releases. + * * @author Roman Mandeleil * @since 08.08.2014 + * @deprecated use {@link org.ethereum.publish.Publisher} instead. */ +@Deprecated public class EthereumListenerAdapter implements EthereumListener { @Override diff --git a/ethereumj-core/src/main/java/org/ethereum/listener/EventListener.java b/ethereumj-core/src/main/java/org/ethereum/listener/EventListener.java index f00fe5e3b5..b7844ff0a7 100644 --- a/ethereumj-core/src/main/java/org/ethereum/listener/EventListener.java +++ b/ethereumj-core/src/main/java/org/ethereum/listener/EventListener.java @@ -22,14 +22,18 @@ import org.ethereum.core.Bloom; import org.ethereum.core.CallTransaction; import org.ethereum.core.PendingStateImpl; +import org.ethereum.core.PendingTransaction; import org.ethereum.core.TransactionReceipt; import org.ethereum.db.ByteArrayWrapper; import org.ethereum.listener.EthereumListener.PendingTransactionState; +import org.ethereum.publish.event.BlockAdded; +import org.ethereum.publish.event.PendingTransactionUpdated; import org.ethereum.util.ByteArrayMap; import org.ethereum.util.Utils; import org.ethereum.vm.LogInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sun.reflect.generics.reflectiveObjects.NotImplementedException; import java.math.BigInteger; import java.util.ArrayList; @@ -38,6 +42,8 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import static org.ethereum.core.PendingTransaction.State.DROPPED; +import static org.ethereum.core.PendingTransaction.State.INCLUDED; import static org.ethereum.sync.BlockDownloader.MAX_IN_REQUEST; import static org.ethereum.util.ByteUtil.toHexString; @@ -73,7 +79,7 @@ protected class PendingEvent { public Block includedTo; // the latest block from the main branch public Block bestConfirmingBlock; - // if came from a block we take block time, it pending we take current time + // if came from a block we take block time, if pending we take current time public long created; // status of the Ethereum Tx public TxStatus txStatus; @@ -82,13 +88,20 @@ public PendingEvent(long created) { this.created = created; } + /** + * @deprecated use {@link #update(TransactionReceipt, List, PendingTransaction.State, Block)} instead of this method. + */ public void update(TransactionReceipt receipt, List txs, PendingTransactionState state, Block includedTo) { + update(receipt, txs, translate(state), includedTo); + } + + public void update(TransactionReceipt receipt, List txs, PendingTransaction.State state, Block includedTo) { this.receipt = receipt; this.eventData = txs; - this.bestConfirmingBlock = state == PendingTransactionState.INCLUDED ? includedTo : null; - this.includedTo = state == PendingTransactionState.INCLUDED ? includedTo : null; + this.bestConfirmingBlock = state == INCLUDED ? includedTo : null; + this.includedTo = state == INCLUDED ? includedTo : null; txStatus = state.isPending() ? TxStatus.PENDING : - (state == PendingTransactionState.DROPPED ? TxStatus.REJECTED : TxStatus.getConfirmed(1)); + (state == DROPPED ? TxStatus.REJECTED : TxStatus.getConfirmed(1)); } public boolean setBestConfirmingBlock(Block bestConfirmingBlock) { @@ -141,6 +154,14 @@ public EventListener(PendingStateImpl pendingState) { this.pendingState = pendingState; } + public void onBlock(BlockAdded.Data data) { + executor.submit(() -> onBlockImpl(data.getBlockSummary())); + } + + public void onPendingTransactionUpdated(PendingTransactionUpdated.Data data) { + executor.submit(() -> onPendingTransactionUpdateImpl(data.getReceipt(), data.getState(), data.getBlock())); + } + public void onBlockImpl(BlockSummary blockSummary) { if (!initialized) throw new RuntimeException("Event listener should be initialized"); try { @@ -180,9 +201,21 @@ public void onBlockImpl(BlockSummary blockSummary) { } } + private static PendingTransaction.State translate(PendingTransactionState state) { + return PendingTransaction.State.valueOf(state.name()); + } + + private static PendingTransactionState translate(PendingTransaction.State state) { + return PendingTransactionState.valueOf(state.name()); + } + public void onPendingTransactionUpdateImpl(TransactionReceipt txReceipt, PendingTransactionState state, Block block) { + onPendingTransactionUpdateImpl(txReceipt, translate(state), block); + } + + public void onPendingTransactionUpdateImpl(TransactionReceipt txReceipt, PendingTransaction.State state, Block block) { try { - if (state != PendingTransactionState.DROPPED || pendings.containsKey(txReceipt.getTransaction().getHash())) { + if (state != DROPPED || pendings.containsKey(txReceipt.getTransaction().getHash())) { logger.debug("onPendingTransactionUpdate: " + toHexString(txReceipt.getTransaction().getHash()) + ", " + state); } onReceipt(txReceipt, block, state); @@ -213,7 +246,7 @@ public synchronized void initContractTopic(String abi, byte[] topic) { // checks the Tx receipt for the contract LogEvents // initiated on [onPendingTransactionUpdateImpl] callback only - private synchronized void onReceipt(TransactionReceipt receipt, Block block, PendingTransactionState state) { + private synchronized void onReceipt(TransactionReceipt receipt, Block block, PendingTransaction.State state) { if (!initialized) throw new RuntimeException("Event listener should be initialized"); byte[] txHash = receipt.getTransaction().getHash(); if (logFilter.matchBloom(receipt.getBloomFilter())) { @@ -235,7 +268,7 @@ private synchronized void onReceipt(TransactionReceipt receipt, Block block, Pen // cool, we've got some Events from this Tx, let's track further Tx lifecycle onEventData(receipt, block, state, matchedTxs); } - } else if (state == PendingTransactionState.DROPPED && pendings.containsKey(txHash)) { + } else if (state == DROPPED && pendings.containsKey(txHash)) { PendingEvent event = pendings.get(txHash); onEventData(receipt, block, state, event.eventData); } @@ -244,7 +277,7 @@ private synchronized void onReceipt(TransactionReceipt receipt, Block block, Pen // process the list of [EventData] generated by the Tx // initiated on [onPendingTransactionUpdateImpl] callback only private void onEventData(TransactionReceipt receipt, Block block, - PendingTransactionState state, List matchedTxs) { + PendingTransaction.State state, List matchedTxs) { byte[] txHash = receipt.getTransaction().getHash(); PendingEvent event = pendings.get(txHash); boolean newEvent = false; @@ -266,7 +299,20 @@ private void onEventData(TransactionReceipt receipt, Block block, pendingTransactionsUpdated(); } + /** + * @deprecated use {@link #onLogMatch(LogInfo, Block, TransactionReceipt, int, PendingTransaction.State)} instead of this method. + * @param logInfo + * @param block + * @param receipt + * @param txCount + * @param state + * @return + */ protected EventData onLogMatch(LogInfo logInfo, Block block, TransactionReceipt receipt, int txCount, PendingTransactionState state) { + return onLogMatch(logInfo, block,receipt, txCount, translate(state)); + } + + protected EventData onLogMatch(LogInfo logInfo, Block block, TransactionReceipt receipt, int txCount, PendingTransaction.State state) { CallTransaction.Invocation event = contract.parseEvent(logInfo); if (event == null) { @@ -277,6 +323,15 @@ protected EventData onLogMatch(LogInfo logInfo, Block block, TransactionReceipt return onEvent(event, block, receipt, txCount, state); } + /** + * @deprecated override {@link #onEvent(CallTransaction.Invocation, Block, TransactionReceipt, int, PendingTransaction.State)} + * instead of this method. + */ + protected EventData onEvent(CallTransaction.Invocation event, Block block, TransactionReceipt receipt, + int txCount, PendingTransactionState state) { + throw new NotImplementedException(); + } + /** * The implementing subclass should create an EventData instance with the data extracted from * Solidity [event] @@ -289,8 +344,11 @@ protected EventData onLogMatch(LogInfo logInfo, Block block, TransactionReceipt * @param state The state of Transaction (Pending/Rejected/Included) * @return Either null if this [event] is not interesting for implementation class, or [event] representation */ - protected abstract EventData onEvent(CallTransaction.Invocation event, Block block, TransactionReceipt receipt, - int txCount, PendingTransactionState state); + protected EventData onEvent(CallTransaction.Invocation event, Block block, TransactionReceipt receipt, + int txCount, PendingTransaction.State state) { + // proxies invoke to deprecated implementation for backward compatibility. + return onEvent(event, block, receipt, txCount, translate(state)); + } /** * Called after one or more transactions updated diff --git a/ethereumj-core/src/main/java/org/ethereum/listener/GasPriceTracker.java b/ethereumj-core/src/main/java/org/ethereum/listener/GasPriceTracker.java index beac0910f5..ab7c53c17d 100644 --- a/ethereumj-core/src/main/java/org/ethereum/listener/GasPriceTracker.java +++ b/ethereumj-core/src/main/java/org/ethereum/listener/GasPriceTracker.java @@ -19,20 +19,19 @@ import org.ethereum.core.BlockSummary; import org.ethereum.core.Transaction; -import org.ethereum.core.TransactionExecutionSummary; import org.ethereum.util.ByteUtil; import java.util.Arrays; /** * Calculates a 'reasonable' Gas price based on statistics of the latest transaction's Gas prices - * + *

* Normally the price returned should be sufficient to execute a transaction since ~25% of the latest * transactions were executed at this or lower price. - * + *

* Created by Anton Nashatyrev on 22.09.2015. */ -public class GasPriceTracker extends EthereumListenerAdapter { +public class GasPriceTracker { private static final long defaultPrice = 70_000_000_000L; @@ -42,14 +41,13 @@ public class GasPriceTracker extends EthereumListenerAdapter { private long lastVal; - @Override public void onBlock(BlockSummary blockSummary) { for (Transaction tx : blockSummary.getBlock().getTransactionsList()) { onTransaction(tx); } } - public void onTransaction(Transaction tx) { + private void onTransaction(Transaction tx) { if (idx == -1) { idx = window.length - 1; filled = true; 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 988d169bc5..cd50fe55d3 100644 --- a/ethereumj-core/src/main/java/org/ethereum/manager/WorldManager.java +++ b/ethereumj-core/src/main/java/org/ethereum/manager/WorldManager.java @@ -18,34 +18,45 @@ package org.ethereum.manager; import org.ethereum.config.SystemProperties; -import org.ethereum.core.*; +import org.ethereum.core.Block; +import org.ethereum.core.BlockSummary; +import org.ethereum.core.Blockchain; +import org.ethereum.core.BlockchainImpl; +import org.ethereum.core.EventDispatchThread; +import org.ethereum.core.Genesis; +import org.ethereum.core.PendingState; +import org.ethereum.core.Repository; import org.ethereum.db.BlockStore; import org.ethereum.db.DbFlushManager; import org.ethereum.db.HeaderStore; import org.ethereum.db.migrate.MigrateHeaderSourceTotalDiff; -import org.ethereum.listener.CompositeEthereumListener; +import org.ethereum.listener.BackwardCompatibilityEthereumListenerProxy; import org.ethereum.listener.EthereumListener; import org.ethereum.net.client.PeerClient; +import org.ethereum.net.rlpx.discover.NodeManager; import org.ethereum.net.rlpx.discover.UDPListener; +import org.ethereum.net.server.ChannelManager; +import org.ethereum.publish.Publisher; +import org.ethereum.publish.Subscription; import org.ethereum.sync.FastSyncManager; import org.ethereum.sync.SyncManager; -import org.ethereum.net.rlpx.discover.NodeManager; -import org.ethereum.net.server.ChannelManager; import org.ethereum.sync.SyncPool; import org.ethereum.util.Utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.spongycastle.util.encoders.Hex; +import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.math.BigInteger; -import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; import static org.ethereum.crypto.HashUtil.EMPTY_TRIE_HASH; import static org.ethereum.util.ByteUtil.toHexString; @@ -56,7 +67,7 @@ * @since 01.06.2014 */ @Component -public class WorldManager { +public class WorldManager implements ApplicationContextAware { private static final Logger logger = LoggerFactory.getLogger("general"); @@ -66,9 +77,6 @@ public class WorldManager { @Autowired private ChannelManager channelManager; - @Autowired - private AdminInfo adminInfo; - @Autowired private NodeManager nodeManager; @@ -90,28 +98,23 @@ public class WorldManager { @Autowired private DbFlushManager dbFlushManager; - @Autowired private ApplicationContext ctx; - - private SystemProperties config; - - private EthereumListener listener; - - private Blockchain blockchain; - - private Repository repository; - - private BlockStore blockStore; + private final SystemProperties config; + private final EthereumListener listener; + private final Blockchain blockchain; + private final Repository repository; + private final BlockStore blockStore; @Autowired - public WorldManager(final SystemProperties config, final Repository repository, - final EthereumListener listener, final Blockchain blockchain, - final BlockStore blockStore) { + public WorldManager(SystemProperties config, Repository repository, EthereumListener listener, + Blockchain blockchain, BlockStore blockStore) { + this.listener = listener; this.blockchain = blockchain; this.repository = repository; this.blockStore = blockStore; this.config = config; + loadBlockchain(); } @@ -121,9 +124,25 @@ private void init() { syncManager.init(channelManager, pool); } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.ctx = applicationContext; + } + + /** + * @param listener + * @deprecated use #subscription instead. + */ + @Deprecated public void addListener(EthereumListener listener) { logger.info("Ethereum listener added"); - ((CompositeEthereumListener) this.listener).addListener(listener); + ((BackwardCompatibilityEthereumListenerProxy) this.listener).getCompositeListener().addListener(listener); + } + + + public Publisher subscribe(Subscription subscription) { + return getPublisher().subscribe(subscription); } public void startPeerDiscovery() { @@ -143,12 +162,21 @@ public ChannelManager getChannelManager() { return channelManager; } + /** + * @return + * @deprecated use {@link #getPublisher()} instead. + */ + @Deprecated public EthereumListener getListener() { return listener; } + public Publisher getPublisher() { + return ((BackwardCompatibilityEthereumListenerProxy) listener).getPublisher(); + } + public org.ethereum.facade.Repository getRepository() { - return (org.ethereum.facade.Repository)repository; + return repository; } public Blockchain getBlockchain() { @@ -186,7 +214,7 @@ public void loadBlockchain() { blockchain.setBestBlock(Genesis.getInstance(config)); blockchain.setTotalDifficulty(Genesis.getInstance(config).getDifficultyBI()); - listener.onBlock(new BlockSummary(Genesis.getInstance(config), new HashMap(), new ArrayList(), new ArrayList()), true); + listener.onBlock(new BlockSummary(Genesis.getInstance(config), emptyMap(), emptyList(), emptyList()), true); // repository.dumpState(Genesis.getInstance(config), 0, 0, null); logger.info("Genesis block loaded"); diff --git a/ethereumj-core/src/main/java/org/ethereum/mine/BlockMiner.java b/ethereumj-core/src/main/java/org/ethereum/mine/BlockMiner.java index 7d5685f13b..a9cbae91f3 100644 --- a/ethereumj-core/src/main/java/org/ethereum/mine/BlockMiner.java +++ b/ethereumj-core/src/main/java/org/ethereum/mine/BlockMiner.java @@ -27,9 +27,8 @@ import org.ethereum.db.IndexedBlockStore; import org.ethereum.facade.Ethereum; import org.ethereum.facade.EthereumImpl; -import org.ethereum.listener.CompositeEthereumListener; -import org.ethereum.listener.EthereumListenerAdapter; import org.ethereum.mine.MinerIfc.MiningResult; +import org.ethereum.publish.Publisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -37,21 +36,24 @@ import java.math.BigInteger; import java.util.*; -import java.util.concurrent.*; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CopyOnWriteArrayList; import static java.lang.Math.max; +import static org.ethereum.publish.Subscription.to; +import static org.ethereum.publish.event.Events.Type.PENDING_STATE_CHANGED; +import static org.ethereum.publish.event.Events.Type.SYNC_DONE; /** * Manages embedded CPU mining and allows to use external miners. - * + *

* Created by Anton Nashatyrev on 10.12.2015. */ @Component public class BlockMiner { private static final Logger logger = LoggerFactory.getLogger("mine"); - private static ExecutorService executor = Executors.newSingleThreadExecutor(); - private Blockchain blockchain; private BlockStore blockStore; @@ -61,8 +63,6 @@ public class BlockMiner { protected PendingState pendingState; - private CompositeEthereumListener listener; - private SystemProperties config; private List listeners = new CopyOnWriteArrayList<>(); @@ -82,34 +82,29 @@ public class BlockMiner { private int UNCLE_GENERATION_LIMIT; @Autowired - public BlockMiner(final SystemProperties config, final CompositeEthereumListener listener, + public BlockMiner(final SystemProperties config, Publisher publisher, final Blockchain blockchain, final BlockStore blockStore, final PendingState pendingState) { - this.listener = listener; this.config = config; this.blockchain = blockchain; this.blockStore = blockStore; this.pendingState = pendingState; - UNCLE_LIST_LIMIT = config.getBlockchainConfig().getCommonConstants().getUNCLE_LIST_LIMIT(); - UNCLE_GENERATION_LIMIT = config.getBlockchainConfig().getCommonConstants().getUNCLE_GENERATION_LIMIT(); - minGasPrice = config.getMineMinGasPrice(); - minBlockTimeout = config.getMineMinBlockTimeoutMsec(); - cpuThreads = config.getMineCpuThreads(); - fullMining = config.isMineFullDataset(); - listener.addListener(new EthereumListenerAdapter() { - @Override - public void onPendingStateChanged(PendingState pendingState) { - BlockMiner.this.onPendingStateChanged(); - } + this.UNCLE_LIST_LIMIT = config.getBlockchainConfig().getCommonConstants().getUNCLE_LIST_LIMIT(); + this.UNCLE_GENERATION_LIMIT = config.getBlockchainConfig().getCommonConstants().getUNCLE_GENERATION_LIMIT(); + this.minGasPrice = config.getMineMinGasPrice(); + this.minBlockTimeout = config.getMineMinBlockTimeoutMsec(); + this.cpuThreads = config.getMineCpuThreads(); + this.fullMining = config.isMineFullDataset(); + + publisher + .subscribe(PENDING_STATE_CHANGED, ps -> onPendingStateChanged()) + .subscribe(SYNC_DONE, s -> { + if (config.minerStart() && config.isSyncEnabled()) { + logger.info("Sync complete, start mining..."); + startMining(); + } + }); - @Override - public void onSyncDone(SyncState state) { - if (config.minerStart() && config.isSyncEnabled()) { - logger.info("Sync complete, start mining..."); - startMining(); - } - } - }); if (config.minerStart() && !config.isSyncEnabled()) { logger.info("Sync disabled, start mining now..."); @@ -152,7 +147,7 @@ protected List getAllPendingTransactions() { PendingStateImpl.TransactionSortedSet ret = new PendingStateImpl.TransactionSortedSet(); ret.addAll(pendingState.getPendingTransactions()); Iterator it = ret.iterator(); - while(it.hasNext()) { + while (it.hasNext()) { Transaction tx = it.next(); if (!isAcceptableTx(tx)) { logger.debug("Miner excluded the transaction: {}", tx); @@ -211,7 +206,7 @@ protected List getUncles(Block mineBest) { long limitNum = max(0, miningNum - UNCLE_GENERATION_LIMIT); Set ancestors = BlockchainImpl.getAncestors(blockStore, mineBest, UNCLE_GENERATION_LIMIT + 1, true); - Set knownUncles = ((BlockchainImpl)blockchain).getUsedUncles(blockStore, mineBest, true); + Set knownUncles = ((BlockchainImpl) blockchain).getUsedUncles(blockStore, mineBest, true); knownUncles.addAll(ancestors); knownUncles.add(new ByteArrayWrapper(mineBest.getHash())); @@ -254,7 +249,7 @@ protected Block getNewBlockForMining() { protected void restartMining() { Block newMiningBlock = getNewBlockForMining(); - synchronized(this) { + synchronized (this) { cancelCurrentBlock(); miningBlock = newMiningBlock; diff --git a/ethereumj-core/src/main/java/org/ethereum/mine/Ethash.java b/ethereumj-core/src/main/java/org/ethereum/mine/Ethash.java index e2722bdfd6..8b0b686518 100644 --- a/ethereumj-core/src/main/java/org/ethereum/mine/Ethash.java +++ b/ethereumj-core/src/main/java/org/ethereum/mine/Ethash.java @@ -298,12 +298,12 @@ public ListenableFuture mineLight(final Block block, int nThreads) AtomicLong taskStartNonce = new AtomicLong(startNonce >= 0 ? startNonce : new Random().nextLong()); @Override public MiningResult call() throws Exception { - long threadStartNonce = taskStartNonce.getAndAdd(0x100000000L); - final long nonce = getEthashAlgo().mineLight(getFullSize(), getCacheLight(), - sha3(block.getHeader().getEncodedWithoutNonce()), - ByteUtil.byteArrayToLong(block.getHeader().getDifficulty()), threadStartNonce); - final Pair pair = hashimotoLight(block.getHeader(), nonce); - return new MiningResult(nonce, pair.getLeft(), block); + long threadStartNonce = taskStartNonce.getAndAdd(0x100000000L); + final long nonce = getEthashAlgo().mineLight(getFullSize(), getCacheLight(), + sha3(block.getHeader().getEncodedWithoutNonce()), + ByteUtil.byteArrayToLong(block.getHeader().getDifficulty()), threadStartNonce); + final Pair pair = hashimotoLight(block.getHeader(), nonce); + return new MiningResult(nonce, pair.getLeft(), block); } }).submit(); } diff --git a/ethereumj-core/src/main/java/org/ethereum/net/MessageQueue.java b/ethereumj-core/src/main/java/org/ethereum/net/MessageQueue.java index cd1e86b325..4d2296b7d1 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/MessageQueue.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/MessageQueue.java @@ -43,14 +43,14 @@ * This class contains the logic for sending messages in a queue * * Messages open by send and answered by receive of appropriate message - * PING by PONG - * GET_PEERS by PEERS - * GET_TRANSACTIONS by TRANSACTIONS - * GET_BLOCK_HASHES by BLOCK_HASHES - * GET_BLOCKS by BLOCKS + * PING by PONG + * GET_PEERS by PEERS + * GET_TRANSACTIONS by TRANSACTIONS + * GET_BLOCK_HASHES by BLOCK_HASHES + * GET_BLOCKS by BLOCKS * * The following messages will not be answered: - * PONG, PEERS, HELLO, STATUS, TRANSACTIONS, BLOCKS + * PONG, PEERS, HELLO, STATUS, TRANSACTIONS, BLOCKS * * @author Roman Mandeleil */ diff --git a/ethereumj-core/src/main/java/org/ethereum/net/eth/handler/Eth62.java b/ethereumj-core/src/main/java/org/ethereum/net/eth/handler/Eth62.java index d0deb570cb..9144adec77 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/eth/handler/Eth62.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/eth/handler/Eth62.java @@ -24,15 +24,15 @@ import org.ethereum.config.SystemProperties; import org.ethereum.core.*; import org.ethereum.db.BlockStore; -import org.ethereum.listener.CompositeEthereumListener; import org.ethereum.net.eth.EthVersion; import org.ethereum.net.eth.message.*; import org.ethereum.net.message.ReasonCode; import org.ethereum.net.rlpx.discover.NodeManager; import org.ethereum.net.submit.TransactionExecutor; import org.ethereum.net.submit.TransactionTask; -import org.ethereum.sync.SyncManager; +import org.ethereum.publish.Publisher; import org.ethereum.sync.PeerState; +import org.ethereum.sync.SyncManager; import org.ethereum.sync.SyncStatistics; import org.ethereum.util.ByteUtil; import org.ethereum.validator.BlockHeaderRule; @@ -52,10 +52,10 @@ import static org.ethereum.datasource.MemSizeEstimator.ByteArrayEstimator; import static org.ethereum.net.eth.EthVersion.V62; import static org.ethereum.net.message.ReasonCode.USELESS_PEER; +import static org.ethereum.publish.event.Events.onEthStatusUpdated; import static org.ethereum.sync.PeerState.*; -import static org.ethereum.sync.PeerState.BLOCK_RETRIEVING; -import static org.ethereum.util.Utils.longToTimePeriod; import static org.ethereum.util.ByteUtil.toHexString; +import static org.ethereum.util.Utils.longToTimePeriod; /** * Eth 62 @@ -127,14 +127,14 @@ public Eth62() { @Autowired public Eth62(final SystemProperties config, final Blockchain blockchain, - final BlockStore blockStore, final CompositeEthereumListener ethereumListener) { - this(version, config, blockchain, blockStore, ethereumListener); + final BlockStore blockStore, final Publisher publisher) { + this(version, config, blockchain, blockStore, publisher); } Eth62(final EthVersion version, final SystemProperties config, final Blockchain blockchain, final BlockStore blockStore, - final CompositeEthereumListener ethereumListener) { - super(version, config, blockchain, blockStore, ethereumListener); + final Publisher publisher) { + super(version, config, blockchain, blockStore, publisher); } @Override @@ -340,7 +340,7 @@ protected synchronized void processStatus(StatusMessage msg, ChannelHandlerConte // basic checks passed, update statistics channel.getNodeStatistics().ethHandshake(msg); - ethereumListener.onEthStatusUpdated(channel, msg); + publisher.publish(onEthStatusUpdated(channel, msg)); if (peerDiscoveryMode) { loggerNet.trace("Peer discovery mode: STATUS received, disconnecting..."); diff --git a/ethereumj-core/src/main/java/org/ethereum/net/eth/handler/Eth63.java b/ethereumj-core/src/main/java/org/ethereum/net/eth/handler/Eth63.java index acde14d363..0405b160d3 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/eth/handler/Eth63.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/eth/handler/Eth63.java @@ -22,18 +22,21 @@ import io.netty.channel.ChannelHandlerContext; import org.apache.commons.lang3.tuple.Pair; import org.ethereum.config.SystemProperties; -import org.ethereum.core.*; +import org.ethereum.core.Block; +import org.ethereum.core.Blockchain; +import org.ethereum.core.Transaction; +import org.ethereum.core.TransactionInfo; +import org.ethereum.core.TransactionReceipt; import org.ethereum.datasource.Source; import org.ethereum.db.BlockStore; -import org.ethereum.listener.CompositeEthereumListener; import org.ethereum.net.eth.EthVersion; import org.ethereum.net.eth.message.EthMessage; import org.ethereum.net.eth.message.GetNodeDataMessage; import org.ethereum.net.eth.message.GetReceiptsMessage; import org.ethereum.net.eth.message.NodeDataMessage; import org.ethereum.net.eth.message.ReceiptsMessage; - import org.ethereum.net.message.ReasonCode; +import org.ethereum.publish.Publisher; import org.ethereum.sync.PeerState; import org.ethereum.util.ByteArraySet; import org.ethereum.util.Value; @@ -73,8 +76,8 @@ public Eth63() { @Autowired public Eth63(final SystemProperties config, final Blockchain blockchain, BlockStore blockStore, - final CompositeEthereumListener ethereumListener) { - super(version, config, blockchain, blockStore, ethereumListener); + final Publisher publisher) { + super(version, config, blockchain, blockStore, publisher); } @Override diff --git a/ethereumj-core/src/main/java/org/ethereum/net/eth/handler/EthHandler.java b/ethereumj-core/src/main/java/org/ethereum/net/eth/handler/EthHandler.java index a520d3f402..1b15ec0dfc 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/eth/handler/EthHandler.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/eth/handler/EthHandler.java @@ -19,27 +19,27 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; -import org.ethereum.db.BlockStore; -import org.ethereum.listener.EthereumListener; import org.ethereum.config.SystemProperties; -import org.ethereum.core.*; -import org.ethereum.listener.CompositeEthereumListener; -import org.ethereum.listener.EthereumListenerAdapter; +import org.ethereum.core.Block; +import org.ethereum.core.Blockchain; +import org.ethereum.db.BlockStore; import org.ethereum.net.MessageQueue; import org.ethereum.net.eth.EthVersion; -import org.ethereum.net.eth.message.*; +import org.ethereum.net.eth.message.EthMessage; +import org.ethereum.net.eth.message.EthMessageCodes; +import org.ethereum.net.eth.message.StatusMessage; import org.ethereum.net.message.ReasonCode; import org.ethereum.net.server.Channel; +import org.ethereum.publish.Publisher; +import org.ethereum.publish.Subscription; +import org.ethereum.publish.event.BlockAdded; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.List; - /** * Process the messages between peers with 'eth' capability on the network
* Contains common logic to all supported versions * delegating version specific stuff to its descendants - * */ public abstract class EthHandler extends SimpleChannelInboundHandler implements Eth { @@ -49,7 +49,7 @@ public abstract class EthHandler extends SimpleChannelInboundHandler protected SystemProperties config; - protected CompositeEthereumListener ethereumListener; + protected Publisher publisher; protected Channel channel; @@ -60,12 +60,7 @@ public abstract class EthHandler extends SimpleChannelInboundHandler protected boolean peerDiscoveryMode = false; protected Block bestBlock; - protected EthereumListener listener = new EthereumListenerAdapter() { - @Override - public void onBlock(Block block, List receipts) { - bestBlock = block; - } - }; + private Subscription bestBlockSubscription; protected boolean processTransactions = false; @@ -75,27 +70,30 @@ protected EthHandler(EthVersion version) { protected EthHandler(final EthVersion version, final SystemProperties config, final Blockchain blockchain, final BlockStore blockStore, - final CompositeEthereumListener ethereumListener) { + final Publisher publisher) { this.version = version; this.config = config; - this.ethereumListener = ethereumListener; this.blockchain = blockchain; - bestBlock = blockStore.getBestBlock(); - this.ethereumListener.addListener(listener); + this.bestBlock = blockStore.getBestBlock(); + this.publisher = publisher; + this.bestBlockSubscription = Subscription.to(BlockAdded.class, this::setBestBlock); + this.publisher.subscribe(this.bestBlockSubscription); + // when sync enabled we delay transactions processing until sync is complete - processTransactions = !config.isSyncEnabled(); + this.processTransactions = !config.isSyncEnabled(); + } + + private void setBestBlock(BlockAdded.Data data) { + this.bestBlock = data.getBlockSummary().getBlock(); } @Override public void channelRead0(final ChannelHandlerContext ctx, EthMessage msg) throws InterruptedException { - - if (EthMessageCodes.inRange(msg.getCommand().asByte(), version)) + if (EthMessageCodes.inRange(msg.getCommand().asByte(), version)) { logger.trace("EthHandler invoke: [{}]", msg.getCommand()); - - ethereumListener.trace(String.format("EthHandler invoke: [%s]", msg.getCommand())); + } channel.getNodeStatistics().ethInbound.add(); - msgQueue.receivedMessage(msg); } @@ -108,13 +106,12 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws E @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { logger.debug("handlerRemoved: kill timers in EthHandler"); - ethereumListener.removeListener(listener); + publisher.unsubscribe(bestBlockSubscription); onShutdown(); } public void activate() { logger.debug("ETH protocol activated"); - ethereumListener.trace("ETH protocol activated"); sendStatus(); } diff --git a/ethereumj-core/src/main/java/org/ethereum/publish/Publisher.java b/ethereumj-core/src/main/java/org/ethereum/publish/Publisher.java new file mode 100644 index 0000000000..2fc118ddaf --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/publish/Publisher.java @@ -0,0 +1,254 @@ +/* + * Copyright (c) [2016] [ ] + * 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 . + */ +package org.ethereum.publish; + +import org.apache.commons.lang3.text.StrBuilder; +import org.ethereum.core.EventDispatchThread; +import org.ethereum.facade.Ethereum; +import org.ethereum.publish.event.Event; +import org.ethereum.publish.event.OneOffEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executor; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +import static java.util.Collections.emptyList; +import static java.util.Objects.nonNull; +import static org.ethereum.publish.Subscription.to; + +/** + * Event publisher that uses pub/sub model to deliver event messages.
+ * Uses {@link EventDispatchThread} as task executor, and subscribers notifying in parallel thread depends on + * {@link EventDispatchThread} implementation passed via constructor.
+ *

+ * Usage examples: + *

+ * {@code
+ *
+ *     // Publisher creating and subscribing
+ *     EventDispatchThread edt = new EventDispatchThread();
+ *     Publisher publisher = new Publisher(edt)
+ *             .subscribe(to(SingleEvent.class, singleEventPayload -> handleOnce(singleEventPayload)))
+ *             .subscribe(to(SomeEvent.class, someEventPayload -> doSmthWith(someEventPayload)))
+ *             .subscribe(to(SomeEvent.class, someEventPayload -> doSmthWithElse(someEventPayload);}))
+ *             .subscribe(to(AnotherEvent.class, SubscriberClass::handleAnotherEventPayload))
+ *             .subscribe(to(OneMoreEvent.class, subscriberInstance::processOneMoreEventPayload)
+ *                     .conditionally(oneMoreEventPayload -> shouldHandleOrNot(oneMoreEventPayload)));
+ *
+ *     // Publishing events
+ *     publisher
+ *             .publish(new OneMoreEvent())    // will fire processOneMoreEventPayload if shouldHandleOrNot return true
+ *             .publish(new SomeEvent())       // will fire doSmthWith and doSmthWithElse with the same payload argument
+ *             .publish(new UnknownEvent())    // do nothing, because there is no subscription for this event type
+ *             .publish(new SingleEvent())     // will fire handleOnce and unsubscribe all subscribers of this event type
+ *             .publish(new SingleEvent());    // do nothing, because there is no subscription for this event type
+ * }
+ * 

+ * + * @see Subscription + * @see Event + * @see OneOffEvent + * + * @author Eugene Shevchenko + */ +public class Publisher { + + private static final Logger log = LoggerFactory.getLogger("events"); + + private class Command implements Runnable { + private final Subscription subscription; + private final Event event; + + private Command(Subscription subscription, Event event) { + this.subscription = subscription; + this.event = event; + } + + @Override + public void run() { + try { + subscription.handle(event); + } finally { + if (subscription.needUnsubscribeAfter(event)) { + Publisher.this.unsubscribe(subscription); + } + } + } + + @Override + public String toString() { + return event.toString(); + } + } + + private final Executor executor; + private final Map, List> subscriptionsByEvent = new ConcurrentHashMap<>(); + + public Publisher(Executor executor) { + this.executor = executor; + } + + /** + * Publishes specified event for all its subscribers.
+ * Concurrent execution depends on implementation of nested {@link EventDispatchThread}. + * + * @param event event to publish; + * @return current {@link Publisher} instance to support fluent API. + */ + public Publisher publish(Event event) { + List subscriptions = subscriptionsByEvent.getOrDefault(event.getClass(), emptyList()); + subscriptions.stream() + .filter(subscription -> subscription.matches(event)) + .map(subscription -> new Command(subscription, event)) + .forEach(executor::execute); + + subscriptions.stream() + .filter(subscription -> subscription.needUnsubscribeAfter(event)) + .forEach(this::unsubscribe); + + if (event instanceof OneOffEvent) { + subscriptionsByEvent.remove(event.getClass()); + } + + return this; + } + + /** + * Adds specified {@link Subscription} to publisher.
+ * Do nothing if specified subscription already added. + * + * @param subscription + * @param {@link Event} subclass which describes specific event type; + * @param

payload type of specified event type; + * @return current {@link Publisher} instance to support fluent API. + */ + public , P> Publisher subscribe(Subscription subscription) { + List subscriptions = subscriptionsByEvent.computeIfAbsent(subscription.getEventType(), t -> new CopyOnWriteArrayList<>()); + if (subscriptions.contains(subscription)) { + log.warn("Specified subscription already exists {}.", subscription.getEventType().getSimpleName()); + } else { + subscriptions.add(subscription); + log.debug("{} added to publisher.", subscription); + } + return this; + } + + /** + * Subscribes client's handler to specific Ethereum event. Does the same thing as {@link #subscribe(Subscription)}, + * in more convenient way, but you don't have access to created {@link Subscription} instance. + *

+ * Supported events list you can find here {@link org.ethereum.publish.event.Events.Type} + * + * @param type event type to subscribe; + * @param handler event handler; + * @param event payload which will be passed to handler; + * @return {@link Ethereum} instance to support fluent API. + */ + public Publisher subscribe(Class> type, Consumer handler){ + return subscribe(to(type, handler)); + } + + /** + * More advanced version of {@link #subscribe(Class, Consumer)} + * where besides of event's payload to client's handler passes subscription's {@link org.ethereum.publish.Subscription.LifeCycle}. + *

+ * Supported events list you can find here {@link org.ethereum.publish.event.Events.Type} + * + * @param type event type to subscribe; + * @param handler extended event handler; + * @param event payload which will be passed to handler; + * @return {@link Publisher} instance to support fluent API. + */ + public Publisher subscribe(Class> type, BiConsumer handler) { + return subscribe(to(type, handler)); + } + + + /** + * Removes specified {@link Subscription} from publisher. + * + * @param subscription subscription to remove; + * @return current {@link Publisher} instance to support fluent API. + */ + public Publisher unsubscribe(Subscription subscription) { + List subscriptions = subscriptionsByEvent.get(subscription.getEventType()); + if (nonNull(subscriptions)) { + subscriptions.remove(subscription); + log.debug("{} removed from publisher.", subscription); + if (subscriptions.isEmpty()) { + subscriptionsByEvent.remove(subscription.getEventType()); + } + } + + return this; + } + + /** + * Calculates specific event type {@link Subscription}s amount added to current {@link Publisher}. + * + * @param eventType event type to filter {@link Subscription}s; + * @return specified event type {@link Subscription}s count. + */ + public int subscribersCount(Class eventType) { + return subscriptionsByEvent.getOrDefault(eventType, emptyList()).size(); + } + + /** + * Calculates total amount {@link Subscription}s added to current {@link Publisher}. + * + * @return all subscribers total count. + */ + public int subscribersCount() { + return subscriptionsByEvent.values().stream() + .mapToInt(List::size) + .sum(); + } + + /** + * Gets events set subscribed via current publisher. + * + * @return all events which have subscribers. + */ + public Set> events() { + return subscriptionsByEvent.keySet(); + } + + @Override + public String toString() { + StrBuilder builder = new StrBuilder("Publisher info:\n"); + if (subscriptionsByEvent.isEmpty()) { + builder.append("\tempty.\n"); + } else { + subscriptionsByEvent.forEach((type, subscriptions) -> builder + .append("\t- ") + .append(type.getSimpleName()) + .append(": ") + .append(subscriptions.size()) + .append(" subscription(s);\n")); + } + + return builder.toString(); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/publish/Subscription.java b/ethereumj-core/src/main/java/org/ethereum/publish/Subscription.java new file mode 100644 index 0000000000..882fbd2412 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/publish/Subscription.java @@ -0,0 +1,194 @@ +/* + * Copyright (c) [2016] [ ] + * 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 . + */ +package org.ethereum.publish; + +import org.ethereum.publish.event.Event; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +import static java.util.Objects.isNull; +import static java.util.Objects.nonNull; + +/** + * Abstraction that holds together event type, event processor (a.k.a consumer) and optional conditions that resolves + * event processing and auto-unsubscribe. + * + * @author Eugene Shevchenko + */ +public class Subscription, D> { + + private final static Logger log = LoggerFactory.getLogger("event"); + + /** + * Abstraction that helps manage subscription state + */ + public static class LifeCycle { + private final Subscription subscription; + + private LifeCycle(Subscription subscription) { + this.subscription = subscription; + } + + /** + * Unsubscribes owner's subscription from current event flow. + */ + public void unsubscribe() { + subscription.unsubscribeAfter(data -> true); + } + } + + + private final Class eventType; + private final BiConsumer biConsumer; + private final LifeCycle lifeCycle; + + private Function handleCondition; + private Function unsubscribeCondition; + + Subscription(Class eventType, BiConsumer biConsumer) { + this.eventType = eventType; + this.lifeCycle = new LifeCycle(this); + this.biConsumer = biConsumer; + } + + /** + * Gets event type that current {@link Subscription} processes. + * + * @return type of event. + */ + public Class getEventType() { + return eventType; + } + + /** + * Optionally adds conditional clause that indicates should consumes tested event or not. + * + * @param condition function that resolves sequential event consumption. + * @return current {@link Subscription} instance to support fluent API. + */ + public Subscription conditionally(Function condition) { + this.handleCondition = condition; + return this; + } + + /** + * Optionally adds unsubscribe condition after event consuming. + * + * @param condition function that resolves unsubscribing after event consumption. + * @return current {@link Subscription} instance to support fluent API. + */ + public Subscription unsubscribeAfter(Function condition) { + this.unsubscribeCondition = condition; + return this; + } + + /** + * Optionally adds {@link #conditionally(Function)} and {@link #unsubscribeAfter(Function)} clauses with the same + * condition. It helps achieve specific behavior, when subscriber consumes and then unsubscribe from event source. + * + * @param condition that tests to execute and unsubscribe; + * @return current {@link Subscription} instance to support fluent API. + */ + public Subscription oneOff(Function condition) { + return this + .conditionally(condition) + .unsubscribeAfter(condition); + } + + /** + * Make one-off subscription like {@link #oneOff(Function)} but without any conditions. + * Handles first matched event and unsubscribes after that. + * + * @return current {@link Subscription} instance to support fluent API. + */ + public Subscription oneOff() { + return oneOff(data -> true); + } + + /** + * Tests specified event whether it should be consumed. + * + * @param event event to test; + * @return true if event should be consumed, false otherwise. + */ + boolean matches(E event) { + return isNull(handleCondition) || handleCondition.apply(event.getPayload()); + } + + /** + * Safely (catches all exceptions with logging only) consumes specified event. + * + * @param event event to consume. + */ + void handle(E event) { + try { + handlePayload(event.getPayload()); + } catch (Throwable e) { + log.error(eventType.getSimpleName() + " handling error: ", e); + } + } + + protected void handlePayload(D payload) { + biConsumer.accept(payload, lifeCycle); + } + + /** + * Tests whether publisher should remove current {@link Subscription} after specified event handling. + * + * @param event event to test; + * @return true if after event consumption {@link Subscription} should be unsubscribed, false otherwise. + */ + boolean needUnsubscribeAfter(E event) { + return nonNull(unsubscribeCondition) && unsubscribeCondition.apply(event.getPayload()); + } + + /** + * Short static alias for {@link Subscription} constructor. + * + * @param eventType event type to process; + * @param biConsumer callback that consumes event's payload and subscription's {@link LifeCycle} object to make extra manipulations; + * @param event type that should be process; + * @param payload's type of specified event type; + * @return new {@link Subscription} instance. + */ + public static , D> Subscription to(Class eventType, BiConsumer biConsumer) { + return new Subscription<>(eventType, biConsumer); + } + + /** + * Short static alias for {@link Subscription} constructor. + * + * @param eventType event type to process; + * @param consumer callback that consumes event's payload; + * @param event type that should be process; + * @param payload's type of specified event type; + * @return new {@link Subscription} instance. + */ + public static , D> Subscription to(Class eventType, Consumer consumer) { + return new Subscription<>(eventType, (payload, lifeCycle) -> consumer.accept(payload)); + } + + @Override + public String toString() { + return eventType.getSimpleName() + " subscription"; + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/publish/event/BlockAdded.java b/ethereumj-core/src/main/java/org/ethereum/publish/event/BlockAdded.java new file mode 100644 index 0000000000..2a8dd98dbb --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/publish/event/BlockAdded.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) [2016] [ ] + * 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 . + */ +package org.ethereum.publish.event; + +import org.ethereum.core.BlockSummary; + +public class BlockAdded extends Event { + + public static class Data { + private final BlockSummary blockSummary; + private final boolean best; + + public Data(BlockSummary blockSummary, boolean best) { + this.blockSummary = blockSummary; + this.best = best; + } + + public BlockSummary getBlockSummary() { + return blockSummary; + } + + public boolean isBest() { + return best; + } + } + + public BlockAdded(BlockSummary blockSummary, boolean best) { + super(new Data(blockSummary, best)); + } +} \ No newline at end of file diff --git a/ethereumj-core/src/main/java/org/ethereum/publish/event/Event.java b/ethereumj-core/src/main/java/org/ethereum/publish/event/Event.java new file mode 100644 index 0000000000..0b708c9f80 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/publish/event/Event.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) [2016] [ ] + * 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 . + */ +package org.ethereum.publish.event; + +/** + * Base class for all event types which can be used with {@link org.ethereum.publish.Publisher}. + * + * @author Eugene Shevchenko + */ +public abstract class Event { + + private final T payload; + private final long timestamp; + + public Event(T payload) { + this.payload = payload; + this.timestamp = System.currentTimeMillis(); + } + + /** + * @return event's payload object. + */ + public T getPayload() { + return payload; + } + + /** + * @return timestamp of event creation. + */ + public long getTimestamp() { + return timestamp; + } + + @Override + public String toString() { + return getClass().getSimpleName() + " emitted at " + getTimestamp(); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/publish/event/Events.java b/ethereumj-core/src/main/java/org/ethereum/publish/event/Events.java new file mode 100644 index 0000000000..f72b9e82b2 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/publish/event/Events.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) [2016] [ ] + * 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 . + */ +package org.ethereum.publish.event; + +import org.ethereum.core.Block; +import org.ethereum.core.BlockSummary; +import org.ethereum.core.PendingState; +import org.ethereum.core.PendingTransaction; +import org.ethereum.core.TransactionExecutionSummary; +import org.ethereum.core.TransactionReceipt; +import org.ethereum.net.eth.message.StatusMessage; +import org.ethereum.net.message.Message; +import org.ethereum.net.p2p.HelloMessage; +import org.ethereum.net.rlpx.Node; +import org.ethereum.net.server.Channel; +import org.ethereum.publish.event.message.AbstractMessageEvent; +import org.ethereum.publish.event.message.EthStatusUpdated; +import org.ethereum.publish.event.message.MessageReceived; +import org.ethereum.publish.event.message.MessageSent; +import org.ethereum.publish.event.message.PeerHandshaked; +import org.ethereum.sync.SyncManager; + +public final class Events { + + public interface Type { + + Class> ETH_STATUS_UPDATED = EthStatusUpdated.class; + + Class>> MESSAGE_RECEIVED = MessageReceived.class; + + Class>> MESSAGE_SENT = MessageSent.class; + + Class> PEER_HANDSHAKED = PeerHandshaked.class; + + Class> BLOCK_ADDED = BlockAdded.class; + + Class> NODE_DISCOVERED = NodeDiscovered.class; + + Class> PEER_ADDED_TO_SYNC_POOL = PeerAddedToSyncPool.class; + + Class> PEER_DISCONNECTED = PeerDisconnected.class; + + Class> PENDING_STATE_CHANGED = PendingStateChanged.class; + + Class> PENDING_TRANSACTION_UPDATED = PendingTransactionUpdated.class; + + Class> SYNC_DONE = SyncDone.class; + + Class> TRANSACTION_EXECUTED = TransactionExecuted.class; + + Class> VM_TRACE_CREATED = VmTraceCreated.class; + } + + private Events() { + } + + public static Event onEthStatusUpdated(Channel channel, StatusMessage message) { + return new EthStatusUpdated(channel, message); + } + + public static Event onMessageReceived(Channel channel, Message message) { + return new MessageReceived(channel, message); + } + + public static Event onMessageSent(Channel channel, Message message) { + return new MessageSent(channel, message); + } + + public static Event onPeerHanshaked(Channel channel, HelloMessage message) { + return new PeerHandshaked(channel, message); + } + + public static Event onBlockAdded(BlockSummary summary, boolean isBest) { + return new BlockAdded(summary, isBest); + } + + public static Event onNodeDiscovered(Node node) { + return new NodeDiscovered(node); + } + + public static Event onPeerAddedToSyncPool(Channel channel) { + return new PeerAddedToSyncPool(channel); + } + + public static Event onPeerDisconnected(String host, long port) { + return new PeerDisconnected(host, port); + } + + public static Event onPendingStateChanged(PendingState state) { + return new PendingStateChanged(state); + } + + public static Event onPendingTransactionUpdated(Block block, TransactionReceipt receipt, PendingTransaction.State state) { + return new PendingTransactionUpdated(block, receipt, state); + } + + public static Event onSyncDone(SyncManager.State state) { + return new SyncDone(state); + } + + public static Event onTransactionExecuted(TransactionExecutionSummary summary) { + return new TransactionExecuted(summary); + } + + public static Event onVmTraceCreated(String txHash, String trace) { + return new VmTraceCreated(txHash, trace); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/publish/event/NodeDiscovered.java b/ethereumj-core/src/main/java/org/ethereum/publish/event/NodeDiscovered.java new file mode 100644 index 0000000000..df1e32797c --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/publish/event/NodeDiscovered.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) [2016] [ ] + * 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 . + */ +package org.ethereum.publish.event; + +import org.ethereum.net.rlpx.Node; + +public class NodeDiscovered extends Event { + public NodeDiscovered(Node payload) { + super(payload); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/publish/event/OneOffEvent.java b/ethereumj-core/src/main/java/org/ethereum/publish/event/OneOffEvent.java new file mode 100644 index 0000000000..98e04039eb --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/publish/event/OneOffEvent.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) [2016] [ ] + * 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 . + */ +package org.ethereum.publish.event; + +/** + * Marker interface for one time emitted events. + * + * @author Eugene Shevchenko + */ +public interface OneOffEvent { +} diff --git a/ethereumj-core/src/main/java/org/ethereum/publish/event/PeerAddedToSyncPool.java b/ethereumj-core/src/main/java/org/ethereum/publish/event/PeerAddedToSyncPool.java new file mode 100644 index 0000000000..7aef12ce2a --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/publish/event/PeerAddedToSyncPool.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) [2016] [ ] + * 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 . + */ +package org.ethereum.publish.event; + +import org.ethereum.net.server.Channel; + +public class PeerAddedToSyncPool extends Event { + + public PeerAddedToSyncPool(Channel channel) { + super(channel); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/publish/event/PeerDisconnected.java b/ethereumj-core/src/main/java/org/ethereum/publish/event/PeerDisconnected.java new file mode 100644 index 0000000000..cfd4d73ec3 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/publish/event/PeerDisconnected.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) [2016] [ ] + * 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 . + */ +package org.ethereum.publish.event; + +public class PeerDisconnected extends Event { + + public static class Data { + private final String host; + private final long port; + + public Data(String host, long port) { + this.host = host; + this.port = port; + } + + public String getHost() { + return host; + } + + public long getPort() { + return port; + } + } + + public PeerDisconnected(String host, long port) { + super(new Data(host, port)); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/publish/event/PendingStateChanged.java b/ethereumj-core/src/main/java/org/ethereum/publish/event/PendingStateChanged.java new file mode 100644 index 0000000000..d70620b40e --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/publish/event/PendingStateChanged.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) [2016] [ ] + * 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 . + */ +package org.ethereum.publish.event; + +import org.ethereum.core.PendingState; + +/** + * PendingState changes on either new pending transaction or new best block receive + * When a new transaction arrives it is executed on top of the current pending state + * When a new best block arrives the PendingState is adjusted to the new Repository state + * and all transactions which remain pending are executed on top of the new PendingState + * + * @author Eugene Shevchenko + */ +public class PendingStateChanged extends Event { + public PendingStateChanged(PendingState state) { + super(state); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/publish/event/PendingTransactionUpdated.java b/ethereumj-core/src/main/java/org/ethereum/publish/event/PendingTransactionUpdated.java new file mode 100644 index 0000000000..511559c642 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/publish/event/PendingTransactionUpdated.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) [2016] [ ] + * 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 . + */ +package org.ethereum.publish.event; + +import org.ethereum.core.Block; +import org.ethereum.core.PendingTransaction; +import org.ethereum.core.TransactionReceipt; + +/** + * Emits when PendingTransaction arrives, executed or dropped and included to a block. + * + * @author Eugene Shevchenko + */ +public class PendingTransactionUpdated extends Event { + + /** + * Event DTO + */ + public static class Data { + private final TransactionReceipt receipt; + private final PendingTransaction.State state; + private final Block block; + + /** + * @param receipt Receipt of the tx execution on the current PendingState + * @param state Current state of pending tx + * @param block The block which the current pending state is based on (for PENDING tx state) + * or the block which tx was included to (for INCLUDED state) + */ + public Data(Block block, TransactionReceipt receipt, PendingTransaction.State state) { + this.receipt = receipt; + this.state = state; + this.block = block; + } + + public TransactionReceipt getReceipt() { + return receipt; + } + + public PendingTransaction.State getState() { + return state; + } + + public Block getBlock() { + return block; + } + } + + public PendingTransactionUpdated(Block block, TransactionReceipt receipt, PendingTransaction.State state) { + super(new Data(block, receipt, state)); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/publish/event/SyncDone.java b/ethereumj-core/src/main/java/org/ethereum/publish/event/SyncDone.java new file mode 100644 index 0000000000..b99300ffc6 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/publish/event/SyncDone.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) [2016] [ ] + * 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 . + */ +package org.ethereum.publish.event; + +import org.ethereum.sync.SyncManager; + +public class SyncDone extends Event implements OneOffEvent { + + public SyncDone(SyncManager.State payload) { + super(payload); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/publish/event/TransactionExecuted.java b/ethereumj-core/src/main/java/org/ethereum/publish/event/TransactionExecuted.java new file mode 100644 index 0000000000..ee56132907 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/publish/event/TransactionExecuted.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) [2016] [ ] + * 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 . + */ +package org.ethereum.publish.event; + +import org.ethereum.core.TransactionExecutionSummary; + +public class TransactionExecuted extends Event { + public TransactionExecuted(TransactionExecutionSummary executionSummary) { + super(executionSummary); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/publish/event/VmTraceCreated.java b/ethereumj-core/src/main/java/org/ethereum/publish/event/VmTraceCreated.java new file mode 100644 index 0000000000..8eaad440cf --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/publish/event/VmTraceCreated.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) [2016] [ ] + * 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 . + */ +package org.ethereum.publish.event; + +public class VmTraceCreated extends Event { + + public static class Data { + private final String txHash; + private final String trace; + + public Data(String txHash, String trace) { + this.txHash = txHash; + this.trace = trace; + } + + public String getTxHash() { + return txHash; + } + + public String getTrace() { + return trace; + } + } + + public VmTraceCreated(String txHash, String trace) { + super(new Data(txHash, trace)); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/publish/event/message/AbstractMessageEvent.java b/ethereumj-core/src/main/java/org/ethereum/publish/event/message/AbstractMessageEvent.java new file mode 100644 index 0000000000..b05a71ddce --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/publish/event/message/AbstractMessageEvent.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) [2016] [ ] + * 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 . + */ +package org.ethereum.publish.event.message; + +import org.ethereum.net.message.Message; +import org.ethereum.net.server.Channel; +import org.ethereum.publish.event.Event; + +/** + * Base class for any message events (received, sent, etc.) + * + * @author Eugene Shevchenko + */ +public abstract class AbstractMessageEvent

extends Event

{ + + public static class Data { + public final Channel channel; + public final M message; + + public Data(Channel channel, M message) { + this.channel = channel; + this.message = message; + } + + public Channel getChannel() { + return channel; + } + + public M getMessage() { + return message; + } + } + + public AbstractMessageEvent(P payload) { + super(payload); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/publish/event/message/EthStatusUpdated.java b/ethereumj-core/src/main/java/org/ethereum/publish/event/message/EthStatusUpdated.java new file mode 100644 index 0000000000..cec1155d06 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/publish/event/message/EthStatusUpdated.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) [2016] [ ] + * 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 . + */ +package org.ethereum.publish.event.message; + +import org.ethereum.net.eth.message.StatusMessage; +import org.ethereum.net.server.Channel; + +public class EthStatusUpdated extends AbstractMessageEvent { + + public static class Data extends AbstractMessageEvent.Data { + + public Data(Channel channel, StatusMessage message) { + super(channel, message); + } + + } + + public EthStatusUpdated(Channel channel, StatusMessage message) { + super(new Data(channel, message)); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/publish/event/message/MessageReceived.java b/ethereumj-core/src/main/java/org/ethereum/publish/event/message/MessageReceived.java new file mode 100644 index 0000000000..72eb6e56d3 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/publish/event/message/MessageReceived.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) [2016] [ ] + * 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 . + */ +package org.ethereum.publish.event.message; + +import org.ethereum.net.message.Message; +import org.ethereum.net.server.Channel; + +public class MessageReceived extends AbstractMessageEvent> { + + public MessageReceived(Channel channel, Message message) { + super(new Data(channel, message)); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/publish/event/message/MessageSent.java b/ethereumj-core/src/main/java/org/ethereum/publish/event/message/MessageSent.java new file mode 100644 index 0000000000..137c6ba85a --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/publish/event/message/MessageSent.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) [2016] [ ] + * 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 . + */ +package org.ethereum.publish.event.message; + +import org.ethereum.net.message.Message; +import org.ethereum.net.server.Channel; + +public class MessageSent extends AbstractMessageEvent> { + + public MessageSent(Channel channel, Message message) { + super(new Data(channel, message)); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/publish/event/message/PeerHandshaked.java b/ethereumj-core/src/main/java/org/ethereum/publish/event/message/PeerHandshaked.java new file mode 100644 index 0000000000..8f01d49281 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/publish/event/message/PeerHandshaked.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) [2016] [ ] + * 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 . + */ +package org.ethereum.publish.event.message; + +import org.ethereum.net.p2p.HelloMessage; +import org.ethereum.net.server.Channel; + +public class PeerHandshaked extends AbstractMessageEvent { + + public static class Data extends AbstractMessageEvent.Data { + + public Data(Channel channel, HelloMessage message) { + super(channel, message); + } + } + + + public PeerHandshaked(Channel channel, HelloMessage message) { + super(new Data(channel, message)); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/samples/BasicSample.java b/ethereumj-core/src/main/java/org/ethereum/samples/BasicSample.java index e1c8f219ea..5552743f58 100644 --- a/ethereumj-core/src/main/java/org/ethereum/samples/BasicSample.java +++ b/ethereumj-core/src/main/java/org/ethereum/samples/BasicSample.java @@ -24,16 +24,13 @@ import ch.qos.logback.core.filter.Filter; import ch.qos.logback.core.spi.FilterReply; import org.ethereum.config.SystemProperties; -import org.ethereum.core.*; +import org.ethereum.core.Block; +import org.ethereum.core.BlockSummary; +import org.ethereum.core.TransactionReceipt; import org.ethereum.facade.Ethereum; import org.ethereum.facade.EthereumFactory; -import org.ethereum.listener.EthereumListener; -import org.ethereum.listener.EthereumListenerAdapter; import org.ethereum.net.eth.message.StatusMessage; -import org.ethereum.net.message.Message; -import org.ethereum.net.p2p.HelloMessage; import org.ethereum.net.rlpx.Node; -import org.ethereum.net.server.Channel; import org.ethereum.util.ByteUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,18 +40,25 @@ import javax.annotation.PostConstruct; import java.util.*; +import static org.ethereum.publish.Subscription.to; +import static org.ethereum.publish.event.Events.Type.BLOCK_ADDED; +import static org.ethereum.publish.event.Events.Type.ETH_STATUS_UPDATED; +import static org.ethereum.publish.event.Events.Type.NODE_DISCOVERED; +import static org.ethereum.publish.event.Events.Type.PEER_ADDED_TO_SYNC_POOL; +import static org.ethereum.publish.event.Events.Type.SYNC_DONE; + /** - * The base sample class which creates EthereumJ instance, tracks and report all the stages - * of starting up like discovering nodes, connecting, syncing - * - * The class can be started as a standalone sample it should just run until full blockchain - * sync and then just hanging, listening for new blocks and importing them into a DB - * - * This class is a Spring Component which makes it convenient to easily get access (autowire) to - * all components created within EthereumJ. However almost all this could be done without dealing - * with the Spring machinery from within a simple main method - * - * Created by Anton Nashatyrev on 05.02.2016. + * The base sample class which creates EthereumJ instance, tracks and report all the stages + * of starting up like discovering nodes, connecting, syncing + *

+ * The class can be started as a standalone sample it should just run until full blockchain + * sync and then just hanging, listening for new blocks and importing them into a DB + *

+ * This class is a Spring Component which makes it convenient to easily get access (autowire) to + * all components created within EthereumJ. However almost all this could be done without dealing + * with the Spring machinery from within a simple main method + *

+ * Created by Anton Nashatyrev on 05.02.2016. */ public class BasicSample implements Runnable { @@ -131,7 +135,26 @@ private void springInit() { setupLogging(); // adding the main EthereumJ callback to be notified on different kind of events - ethereum.addListener(listener); + this.ethereum + .subscribe(SYNC_DONE, syncState -> synced = true) + .subscribe(ETH_STATUS_UPDATED, data -> ethNodes.put(data.getChannel().getNode(), data.getMessage())) + .subscribe(PEER_ADDED_TO_SYNC_POOL, peer -> syncPeers.add(peer.getNode())) + .subscribe(BLOCK_ADDED, data -> { + BlockSummary blockSummary = data.getBlockSummary(); + Block block = blockSummary.getBlock(); + List receipts = blockSummary.getReceipts(); + + bestBlock = block; + txCount += receipts.size(); + for (TransactionReceipt receipt : receipts) { + gasSpent += ByteUtil.byteArrayToLong(receipt.getGasUsed()); + } + if (syncComplete) { + logger.info("New block: " + block.getShortDescr()); + } + }) + .subscribe(to(NODE_DISCOVERED, node -> nodesDiscovered.add(node)) + .conditionally(node -> nodesDiscovered.size() < 1000)); logger.info("Sample component created. Listening for ethereum events..."); @@ -186,7 +209,7 @@ protected void waitForDiscovery() throws Exception { int bootNodes = config.peerDiscoveryIPList().size(); int cnt = 0; - while(true) { + while (true) { Thread.sleep(cnt < 30 ? 300 : 5000); if (nodesDiscovered.size() > bootNodes) { @@ -215,7 +238,7 @@ protected void waitForDiscovery() throws Exception { protected void waitForAvailablePeers() throws Exception { logger.info("Waiting for available Eth capable nodes..."); int cnt = 0; - while(true) { + while (true) { Thread.sleep(cnt < 30 ? 1000 : 5000); if (ethNodes.size() > 0) { @@ -241,7 +264,7 @@ protected void waitForAvailablePeers() throws Exception { protected void waitForSyncPeers() throws Exception { logger.info("Searching for peers to sync with..."); int cnt = 0; - while(true) { + while (true) { Thread.sleep(cnt < 30 ? 1000 : 5000); if (syncPeers.size() > 0) { @@ -268,7 +291,7 @@ protected void waitForFirstBlock() throws Exception { logger.info("Current BEST block: " + currentBest.getShortDescr()); logger.info("Waiting for blocks start importing (may take a while)..."); int cnt = 0; - while(true) { + while (true) { Thread.sleep(cnt < 300 ? 1000 : 60000); if (bestBlock != null && bestBlock.getNumber() > currentBest.getNumber()) { @@ -293,7 +316,7 @@ protected void waitForFirstBlock() throws Exception { */ private void waitForSync() throws Exception { logger.info("Waiting for the whole blockchain sync (will take up to several hours for the whole chain)..."); - while(true) { + while (true) { Thread.sleep(10000); if (synced) { @@ -309,81 +332,9 @@ private void waitForSync() throws Exception { } } - /** - * The main EthereumJ callback. - */ - EthereumListener listener = new EthereumListenerAdapter() { - @Override - public void onSyncDone(SyncState state) { - synced = true; - } - - @Override - public void onNodeDiscovered(Node node) { - if (nodesDiscovered.size() < 1000) { - nodesDiscovered.add(node); - } - } - - @Override - public void onEthStatusUpdated(Channel channel, StatusMessage statusMessage) { - ethNodes.put(channel.getNode(), statusMessage); - } - - @Override - public void onPeerAddedToSyncPool(Channel peer) { - syncPeers.add(peer.getNode()); - } - - @Override - public void onBlock(Block block, List receipts) { - bestBlock = block; - txCount += receipts.size(); - for (TransactionReceipt receipt : receipts) { - gasSpent += ByteUtil.byteArrayToLong(receipt.getGasUsed()); - } - if (syncComplete) { - logger.info("New block: " + block.getShortDescr()); - } - } - @Override - public void onRecvMessage(Channel channel, Message message) { - } - - @Override - public void onSendMessage(Channel channel, Message message) { - } - - @Override - public void onPeerDisconnect(String host, long port) { - } - - @Override - public void onPendingTransactionsReceived(List transactions) { - } - - @Override - public void onPendingStateChanged(PendingState pendingState) { - } - @Override - public void onHandShakePeer(Channel channel, HelloMessage helloMessage) { - } - - @Override - public void onNoConnections() { - } - - @Override - public void onVMTraceCreated(String transactionHash, String trace) { - } - - @Override - public void onTransactionExecuted(TransactionExecutionSummary summary) { - } - }; - private static class CustomFilter extends Filter { private Set visibleLoggers = new HashSet<>(); + @Override public synchronized FilterReply decide(ILoggingEvent event) { return visibleLoggers.contains(event.getLoggerName()) && event.getLevel().isGreaterOrEqual(Level.INFO) || diff --git a/ethereumj-core/src/main/java/org/ethereum/samples/BlockReplaySample.java b/ethereumj-core/src/main/java/org/ethereum/samples/BlockReplaySample.java new file mode 100644 index 0000000000..0c1e520460 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/samples/BlockReplaySample.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) [2016] [ ] + * 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 . + */ +package org.ethereum.samples; + +import org.ethereum.db.BlockStore; +import org.ethereum.db.TransactionStore; +import org.ethereum.facade.EthereumFactory; +import org.ethereum.listener.BlockReplayer; +import org.ethereum.publish.event.BlockAdded; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; + +import static org.ethereum.publish.Subscription.to; +import static org.ethereum.publish.event.Events.Type.BLOCK_ADDED; + +public class BlockReplaySample extends SingleMinerNetSample { + + @Autowired + private BlockStore blockStore; + @Autowired + private TransactionStore transactionStore; + private BlockReplayer replay; + + @Override + protected void onSampleReady() { + ethereum.subscribe(to(BLOCK_ADDED, this::enableReplay) + .oneOff(data -> data.getBlockSummary().getBlock().getNumber() % 50 == 0)); + } + + private void enableReplay(BlockAdded.Data data) { + long startBlockNumber = data.getBlockSummary().getBlock().getNumber() - 25; + this.replay = BlockReplayer.startFrom(startBlockNumber) + .withStores(blockStore, transactionStore) + .withHandler(this::onBlock) + .replayAsyncAt(ethereum); + } + + private void onBlock(BlockAdded.Data data) { + long blockNumber = data.getBlockSummary().getBlock().getNumber(); + if (replay.isDone()) { + logger.info("Live chain block #{} handled.", blockNumber); + } else { + logger.info("Replayed block #{} handled.", blockNumber); + } + } + + public static void main(String[] args) { + + class Config extends SingleMinerNetSample.Config { + + @Bean + @Override + public SingleMinerNetSample sample() { + return new BlockReplaySample(); + } + + } + + EthereumFactory.createEthereum(Config.class); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/samples/CreateContractSample.java b/ethereumj-core/src/main/java/org/ethereum/samples/CreateContractSample.java index 947461e5fa..c3d28d44b9 100644 --- a/ethereumj-core/src/main/java/org/ethereum/samples/CreateContractSample.java +++ b/ethereumj-core/src/main/java/org/ethereum/samples/CreateContractSample.java @@ -17,14 +17,13 @@ */ package org.ethereum.samples; -import org.ethereum.core.Block; import org.ethereum.core.CallTransaction; import org.ethereum.core.Transaction; import org.ethereum.core.TransactionReceipt; import org.ethereum.crypto.ECKey; import org.ethereum.db.ByteArrayWrapper; import org.ethereum.facade.EthereumFactory; -import org.ethereum.listener.EthereumListenerAdapter; +import org.ethereum.publish.event.BlockAdded; import org.ethereum.solidity.compiler.CompilationResult; import org.ethereum.solidity.compiler.SolidityCompiler; import org.ethereum.util.ByteUtil; @@ -34,8 +33,11 @@ import org.springframework.context.annotation.Bean; import java.math.BigInteger; -import java.util.*; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import static org.ethereum.publish.event.Events.Type.BLOCK_ADDED; import static org.ethereum.util.ByteUtil.toHexString; /** @@ -48,27 +50,21 @@ public class CreateContractSample extends TestNetSample { String contract = "contract Sample {" + - " int i;" + - " function inc(int n) {" + - " i = i + n;" + - " }" + - " function get() returns (int) {" + - " return i;" + - " }" + - "}"; - - private Map txWaiters = - Collections.synchronizedMap(new HashMap()); + " int i;" + + " function inc(int n) {" + + " i = i + n;" + + " }" + + " function get() returns (int) {" + + " return i;" + + " }" + + "}"; + + private Map txWaiters = Collections.synchronizedMap(new HashMap<>()); @Override public void onSyncDone() throws Exception { - ethereum.addListener(new EthereumListenerAdapter() { - // when block arrives look for our included transactions - @Override - public void onBlock(Block block, List receipts) { - CreateContractSample.this.onBlock(block, receipts); - } - }); + // when block arrives look for our included transactions + ethereum.subscribe(BLOCK_ADDED, this::onBlock); logger.info("Compiling contract..."); SolidityCompiler.Result result = compiler.compileSrc(contract.getBytes(), true, true, @@ -130,8 +126,9 @@ protected TransactionReceipt sendTxAndWait(byte[] receiveAddress, byte[] data) t return waitForTx(tx.getHash()); } - private void onBlock(Block block, List receipts) { - for (TransactionReceipt receipt : receipts) { + private void onBlock(BlockAdded.Data data) { + for (TransactionReceipt receipt : data.getBlockSummary().getReceipts()) { + ByteArrayWrapper txHashW = new ByteArrayWrapper(receipt.getTransaction().getHash()); if (txWaiters.containsKey(txHashW)) { txWaiters.put(txHashW, receipt); @@ -146,16 +143,16 @@ protected TransactionReceipt waitForTx(byte[] txHash) throws InterruptedExceptio ByteArrayWrapper txHashW = new ByteArrayWrapper(txHash); txWaiters.put(txHashW, null); long startBlock = ethereum.getBlockchain().getBestBlock().getNumber(); - while(true) { + while (true) { TransactionReceipt receipt = txWaiters.get(txHashW); if (receipt != null) { return receipt; } else { long curBlock = ethereum.getBlockchain().getBestBlock().getNumber(); if (curBlock > startBlock + 16) { - throw new RuntimeException("The transaction was not included during last 16 blocks: " + txHashW.toString().substring(0,8)); + throw new RuntimeException("The transaction was not included during last 16 blocks: " + txHashW.toString().substring(0, 8)); } else { - logger.info("Waiting for block with transaction 0x" + txHashW.toString().substring(0,8) + + logger.info("Waiting for block with transaction 0x" + txHashW.toString().substring(0, 8) + " included (" + (curBlock - startBlock) + " blocks received so far) ..."); } } @@ -168,7 +165,7 @@ protected TransactionReceipt waitForTx(byte[] txHash) throws InterruptedExceptio public static void main(String[] args) throws Exception { sLogger.info("Starting EthereumJ!"); - class Config extends TestNetConfig{ + class Config extends TestNetConfig { @Override @Bean public TestNetSample sampleBean() { diff --git a/ethereumj-core/src/main/java/org/ethereum/samples/EventListenerSample.java b/ethereumj-core/src/main/java/org/ethereum/samples/EventListenerSample.java index 5bef418226..7294bcaf04 100644 --- a/ethereumj-core/src/main/java/org/ethereum/samples/EventListenerSample.java +++ b/ethereumj-core/src/main/java/org/ethereum/samples/EventListenerSample.java @@ -20,97 +20,31 @@ import org.ethereum.core.Block; import org.ethereum.core.CallTransaction; import org.ethereum.core.PendingStateImpl; -import org.ethereum.core.Transaction; +import org.ethereum.core.PendingTransaction; import org.ethereum.core.TransactionReceipt; -import org.ethereum.crypto.ECKey; -import org.ethereum.db.BlockStore; -import org.ethereum.db.ByteArrayWrapper; -import org.ethereum.db.TransactionStore; import org.ethereum.facade.EthereumFactory; -import org.ethereum.listener.BlockReplay; -import org.ethereum.listener.EthereumListener; -import org.ethereum.listener.EthereumListenerAdapter; import org.ethereum.listener.EventListener; import org.ethereum.listener.TxStatus; -import org.ethereum.solidity.compiler.CompilationResult; -import org.ethereum.solidity.compiler.SolidityCompiler; -import org.ethereum.util.ByteUtil; -import org.ethereum.vm.program.ProgramResult; +import org.ethereum.samples.util.Account; +import org.ethereum.samples.util.Contract; import org.spongycastle.util.encoders.Hex; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.math.BigInteger; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Scanner; -import static org.ethereum.crypto.HashUtil.sha3; -import static org.ethereum.util.ByteUtil.toHexString; +import static org.ethereum.publish.event.Events.Type.BLOCK_ADDED; +import static org.ethereum.publish.event.Events.Type.PENDING_TRANSACTION_UPDATED; /** * Sample usage of events listener API. * {@link EventListener} Contract events listener - * {@link BlockReplay} Listener wrapper for pushing old blocks to any listener in addition to live data - * - * - getting free Ether assuming we are running in test network - * - deploying contract with event, which we are going to track - * - calling contract and catching corresponding events - * - alternatively you could provide address of already deployed contract and - * replay any number of blocks in the past to process old events + *

+ * - getting free Ether assuming we are running in test network + * - deploying contract with event, which we are going to track + * - calling contract and catching corresponding events */ -public class EventListenerSample extends TestNetSample { - - @Autowired - SolidityCompiler compiler; - - @Autowired - BlockStore blockStore; - - @Autowired - TransactionStore transactionStore; - - @Autowired - PendingStateImpl pendingState; - - // Change seed phrases - protected final byte[] senderPrivateKey = sha3("cat".getBytes()); - protected final byte[] sender2PrivateKey = sha3("goat".getBytes()); - - // If no contractAddress provided, deploys new contract, otherwise - // replays events from already deployed contract - String contractAddress = null; -// String contractAddress = "cedf27de170a05cf1d1736f21e1f5ffc1cf22eef"; - - String contract = - "contract Sample {\n" + - " int i;\n" + - " event Inc(\n" + - " address _from,\n" + - " int _inc,\n" + - " int _total\n" + - " ); \n" + - " \n" + - " function inc(int n) {\n" + - " i = i + n;\n" + - " Inc(msg.sender, n, i); \n" + - " } \n" + - " \n" + - " function get() returns (int) {\n" + - " return i; \n" + - " }\n" + - "} "; - - private Map txWaiters = - Collections.synchronizedMap(new HashMap()); +public class EventListenerSample extends SingleMinerNetSample { class IncEvent { IncEvent(String address, Long inc, Long total) { @@ -125,11 +59,7 @@ class IncEvent { @Override public String toString() { - return "IncEvent{" + - "address='" + address + '\'' + - ", inc=" + inc + - ", total=" + total + - '}'; + return String.format("IncEvent{address='%s', inc=%d, total=%d}", address, inc, total); } } @@ -140,13 +70,13 @@ class IncEventListener extends EventListener { * After this number of confirmations, event will fire {@link #processConfirmed(PendingEvent, IncEvent)} * on each confirmation */ - protected int blocksToConfirm = 32; + protected int blocksToConfirm = 10; /** * Minimum required Tx block confirmations for this Tx to be purged * from the tracking list * After this number of confirmations, event will not fire {@link #processConfirmed(PendingEvent, IncEvent)} */ - protected int purgeFromPendingsConfirmations = 40; + protected int purgeFromPendingsConfirmations = 12; public IncEventListener(PendingStateImpl pendingState) { super(pendingState); @@ -162,7 +92,7 @@ public IncEventListener(PendingStateImpl pendingState, String contractABI, byte[ } @Override - protected IncEvent onEvent(CallTransaction.Invocation event, Block block, TransactionReceipt receipt, int txCount, EthereumListener.PendingTransactionState state) { + protected IncEvent onEvent(CallTransaction.Invocation event, Block block, TransactionReceipt receipt, int txCount, PendingTransaction.State state) { // Processing raw event data to fill our model IncEvent if ("Inc".equals(event.function.name)) { String address = Hex.toHexString((byte[]) event.args[0]); @@ -200,237 +130,46 @@ protected boolean pendingTransactionUpdated(PendingEvent evt) { } } - /** - * Sample logic starts here when sync is done - */ - @Override - public void onSyncDone() throws Exception { - ethereum.addListener(new EthereumListenerAdapter() { - @Override - public void onPendingTransactionUpdate(TransactionReceipt txReceipt, PendingTransactionState state, Block block) { - ByteArrayWrapper txHashW = new ByteArrayWrapper(txReceipt.getTransaction().getHash()); - // Catching transaction errors - if (txWaiters.containsKey(txHashW) && !txReceipt.isSuccessful()) { - txWaiters.put(txHashW, txReceipt); - } - } - }); - requestFreeEther(ECKey.fromPrivate(senderPrivateKey).getAddress()); - requestFreeEther(ECKey.fromPrivate(sender2PrivateKey).getAddress()); - if (contractAddress == null) { - deployContractAndTest(); - } else { - replayOnly(); - } - } - - public void requestFreeEther(byte[] addressBytes) { - String address = "0x" + toHexString(addressBytes); - logger.info("Checking address {} for available ether.", address); - BigInteger balance = ethereum.getRepository().getBalance(addressBytes); - logger.info("Address {} balance: {} wei", address, balance); - BigInteger requiredBalance = BigInteger.valueOf(3_000_000 * ethereum.getGasPrice()); - if (balance.compareTo(requiredBalance) < 0) { - logger.info("Insufficient funds for address {}, requesting free ether", address); - try { - String result = postQuery("https://ropsten.faucet.b9lab.com/tap", "{\"toWhom\":\"" + address + "\"}"); - logger.info("Answer from free Ether API: {}", result); - waitForEther(addressBytes, requiredBalance); - } catch (Exception ex) { - logger.error("Error during request of free Ether,", ex); - } - } - } - - private String postQuery(String endPoint, String json) throws IOException { - URL url = new URL(endPoint); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setConnectTimeout(5000); - conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); - conn.setDoOutput(true); - conn.setDoInput(true); - conn.setRequestMethod("POST"); - - OutputStream os = conn.getOutputStream(); - os.write(json.getBytes("UTF-8")); - os.close(); - - // read the response - InputStream in = new BufferedInputStream(conn.getInputStream()); - String result = null; - try (Scanner scanner = new Scanner(in, "UTF-8")) { - result = scanner.useDelimiter("\\A").next(); - } - - in.close(); - conn.disconnect(); - - return result; - } - - private void waitForEther(byte[] address, BigInteger requiredBalance) throws InterruptedException { - while(true) { - BigInteger balance = ethereum.getRepository().getBalance(address); - if (balance.compareTo(requiredBalance) > 0) { - logger.info("Address {} successfully funded. Balance: {} wei", "0x" + toHexString(address), balance); - break; - } - synchronized (this) { - wait(20000); - } - } - } - - /** - * - Deploys contract - * - Adds events listener - * - Calls contract from 2 different addresses - */ - private void deployContractAndTest() throws Exception { - ethereum.addListener(new EthereumListenerAdapter() { - // when block arrives look for our included transactions - @Override - public void onBlock(Block block, List receipts) { - EventListenerSample.this.onBlock(block, receipts); - } - }); - - CompilationResult.ContractMetadata metadata = compileContract(); - - logger.info("Sending contract to net and waiting for inclusion"); - TransactionReceipt receipt = sendTxAndWait(new byte[0], Hex.decode(metadata.bin), senderPrivateKey); - - if (!receipt.isSuccessful()) { - logger.error("Some troubles creating a contract: " + receipt.getError()); - return; - } - - byte[] address = receipt.getTransaction().getContractAddress(); - logger.info("Contract created: " + toHexString(address)); - - IncEventListener eventListener = new IncEventListener(pendingState, metadata.abi, address); - ethereum.addListener(eventListener.listener); - - CallTransaction.Contract contract = new CallTransaction.Contract(metadata.abi); - contractIncCall(senderPrivateKey, 777, metadata.abi, address); - contractIncCall(sender2PrivateKey, 555, metadata.abi, address); - - ProgramResult r = ethereum.callConstantFunction(Hex.toHexString(address), - contract.getByName("get")); - Object[] ret = contract.getByName("get").decodeResult(r.getHReturn()); - logger.info("Current contract data member value: " + ret[0]); - } + @Autowired + private PendingStateImpl pendingState; - /** - * Replays contract events for old blocks - * using {@link BlockReplay} with {@link EventListener} - */ - private void replayOnly() throws Exception { - logger.info("Contract already deployed to address 0x{}, using it", contractAddress); - CompilationResult.ContractMetadata metadata = compileContract(); - byte[] address = Hex.decode(contractAddress); - IncEventListener eventListener = new IncEventListener(pendingState, metadata.abi, address); - BlockReplay blockReplay = new BlockReplay(blockStore, transactionStore, eventListener.listener, - blockStore.getMaxNumber() - 5000); - ethereum.addListener(blockReplay); - blockReplay.replayAsync(); - } + @Override + protected void onSampleReady() { + Contract contract = contract("sample"); + IncEventListener eventListener = new IncEventListener(pendingState, contract.getAbi(), contract.getAddress()); - private CompilationResult.ContractMetadata compileContract() throws IOException { - logger.info("Compiling contract..."); - SolidityCompiler.Result result = compiler.compileSrc(contract.getBytes(), true, true, - SolidityCompiler.Options.ABI, SolidityCompiler.Options.BIN); - if (result.isFailed()) { - throw new RuntimeException("Contract compilation failed:\n" + result.errors); - } - CompilationResult res = CompilationResult.parse(result.output); - if (res.getContracts().isEmpty()) { - throw new RuntimeException("Compilation failed, no contracts returned:\n" + result.errors); - } - CompilationResult.ContractMetadata metadata = res.getContracts().iterator().next(); - if (metadata.bin == null || metadata.bin.isEmpty()) { - throw new RuntimeException("Compilation failed, no binary returned:\n" + result.errors); - } + this.ethereum + .subscribe(BLOCK_ADDED, eventListener::onBlock) + .subscribe(PENDING_TRANSACTION_UPDATED, eventListener::onPendingTransactionUpdated); - return metadata; - } + Contract.Caller cow = contractCaller("cow", "sample"); + Contract.Caller cat = contractCaller("cat", "sample"); - private void contractIncCall(byte[] privateKey, int incAmount, - String contractABI, byte[] contractAddress) throws InterruptedException { - logger.info("Calling the contract function 'inc'"); - CallTransaction.Contract contract = new CallTransaction.Contract(contractABI); - CallTransaction.Function inc = contract.getByName("inc"); - byte[] functionCallBytes = inc.encode(incAmount); - TransactionReceipt receipt = sendTxAndWait(contractAddress, functionCallBytes, privateKey); - if (!receipt.isSuccessful()) { - logger.error("Some troubles invoking the contract: " + receipt.getError()); - return; - } - logger.info("Contract modified!"); + cow.call("inc", 777); + cat.call("inc", 555); } - protected TransactionReceipt sendTxAndWait(byte[] receiveAddress, - byte[] data, byte[] privateKey) throws InterruptedException { - BigInteger nonce = ethereum.getRepository().getNonce(ECKey.fromPrivate(privateKey).getAddress()); - Transaction tx = new Transaction( - ByteUtil.bigIntegerToBytes(nonce), - ByteUtil.longToBytesNoLeadZeroes(ethereum.getGasPrice()), - ByteUtil.longToBytesNoLeadZeroes(3_000_000), - receiveAddress, - ByteUtil.longToBytesNoLeadZeroes(0), - data, - ethereum.getChainIdForNextBlock()); - tx.sign(ECKey.fromPrivate(privateKey)); + public static void main(String[] args) { + class Config extends SingleMinerNetSample.Config { - logger.info("<=== Sending transaction: " + tx); - ByteArrayWrapper txHashW = new ByteArrayWrapper(tx.getHash()); - txWaiters.put(txHashW, null); - ethereum.submitTransaction(tx); - return waitForTx(txHashW); - } - - private void onBlock(Block block, List receipts) { - for (TransactionReceipt receipt : receipts) { - ByteArrayWrapper txHashW = new ByteArrayWrapper(receipt.getTransaction().getHash()); - if (txWaiters.containsKey(txHashW)) { - txWaiters.put(txHashW, receipt); - synchronized (this) { - notifyAll(); - } + @Bean + @Override + public SingleMinerNetSample sample() { + return new EventListenerSample(); } - } - } - protected TransactionReceipt waitForTx(ByteArrayWrapper txHashW) throws InterruptedException { - long startBlock = ethereum.getBlockchain().getBestBlock().getNumber(); - while(true) { - TransactionReceipt receipt = txWaiters.get(txHashW); - if (receipt != null) { - return receipt; - } else { - long curBlock = ethereum.getBlockchain().getBestBlock().getNumber(); - if (curBlock > startBlock + 16) { - throw new RuntimeException("The transaction was not included during last 16 blocks: " + txHashW.toString().substring(0,8)); - } else { - logger.info("Waiting for block with transaction 0x" + txHashW.toString().substring(0,8) + - " included (" + (curBlock - startBlock) + " blocks received so far) ..."); - } - } - synchronized (this) { - wait(20000); + @Override + protected void registerAccounts(Account.Register register) { + register + .addSameNameAndPass("cat") + .addSameNameAndPass("cow"); } - } - } - - public static void main(String[] args) throws Exception { - sLogger.info("Starting EthereumJ!"); - class Config extends TestNetConfig{ @Override - @Bean - public TestNetSample sampleBean() { - return new EventListenerSample(); + protected void registerContracts(Contract.Register register) { + register + .add("sample", loadContractSource("sample.sol") ); } } diff --git a/ethereumj-core/src/main/java/org/ethereum/samples/FollowAccount.java b/ethereumj-core/src/main/java/org/ethereum/samples/FollowAccount.java index 08c3ff9925..5fabca0395 100644 --- a/ethereumj-core/src/main/java/org/ethereum/samples/FollowAccount.java +++ b/ethereumj-core/src/main/java/org/ethereum/samples/FollowAccount.java @@ -18,48 +18,36 @@ package org.ethereum.samples; import org.ethereum.core.Block; -import org.ethereum.core.TransactionReceipt; import org.ethereum.facade.Ethereum; import org.ethereum.facade.EthereumFactory; import org.ethereum.facade.Repository; -import org.ethereum.listener.EthereumListenerAdapter; import org.spongycastle.util.encoders.Hex; import java.math.BigInteger; -import java.util.List; -public class FollowAccount extends EthereumListenerAdapter { +import static org.ethereum.publish.event.Events.Type.BLOCK_ADDED; - - Ethereum ethereum = null; - - public FollowAccount(Ethereum ethereum) { - this.ethereum = ethereum; - } +public class FollowAccount { public static void main(String[] args) { - Ethereum ethereum = EthereumFactory.createEthereum(); - ethereum.addListener(new FollowAccount(ethereum)); - } - - @Override - public void onBlock(Block block, List receipts) { - - byte[] cow = Hex.decode("cd2a3d9f938e13cd947ec05abc7fe734df8dd826"); + ethereum.subscribe(BLOCK_ADDED, data -> { + byte[] cow = Hex.decode("cd2a3d9f938e13cd947ec05abc7fe734df8dd826"); - // Get snapshot some time ago - 10% blocks ago - long bestNumber = ethereum.getBlockchain().getBestBlock().getNumber(); - long oldNumber = (long) (bestNumber * 0.9); + // Get snapshot some time ago - 10% blocks ago + long bestNumber = ethereum.getBlockchain().getBestBlock().getNumber(); + long oldNumber = (long) (bestNumber * 0.9); - Block oldBlock = ethereum.getBlockchain().getBlockByNumber(oldNumber); + Block oldBlock = ethereum.getBlockchain().getBlockByNumber(oldNumber); - Repository repository = ethereum.getRepository(); - Repository snapshot = ethereum.getSnapshotTo(oldBlock.getStateRoot()); + Repository repository = ethereum.getRepository(); + Repository snapshot = ethereum.getSnapshotTo(oldBlock.getStateRoot()); - BigInteger nonce_ = snapshot.getNonce(cow); - BigInteger nonce = repository.getNonce(cow); + BigInteger nonce_ = snapshot.getNonce(cow); + BigInteger nonce = repository.getNonce(cow); - System.err.println(" #" + block.getNumber() + " [cd2a3d9] => snapshot_nonce:" + nonce_ + " latest_nonce:" + nonce); + System.err.printf(" #%d [cd2a3d9] => snapshot_nonce:%d latest_nonce:%d\n", + data.getBlockSummary().getBlock().getNumber(), nonce_, nonce); + }); } } diff --git a/ethereumj-core/src/main/java/org/ethereum/samples/PendingStateSample.java b/ethereumj-core/src/main/java/org/ethereum/samples/PendingStateSample.java index 7ff7f6b3b2..ef1a46ae28 100644 --- a/ethereumj-core/src/main/java/org/ethereum/samples/PendingStateSample.java +++ b/ethereumj-core/src/main/java/org/ethereum/samples/PendingStateSample.java @@ -17,14 +17,11 @@ */ package org.ethereum.samples; -import org.ethereum.core.Block; -import org.ethereum.core.PendingState; -import org.ethereum.core.Transaction; -import org.ethereum.core.TransactionReceipt; +import org.ethereum.core.*; import org.ethereum.crypto.ECKey; import org.ethereum.db.ByteArrayWrapper; import org.ethereum.facade.EthereumFactory; -import org.ethereum.listener.EthereumListenerAdapter; +import org.ethereum.publish.event.BlockAdded; import org.ethereum.util.ByteUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; @@ -32,21 +29,25 @@ import java.math.BigInteger; import java.util.*; +import static org.ethereum.core.PendingTransaction.State.NEW_PENDING; +import static org.ethereum.publish.event.Events.Type.BLOCK_ADDED; +import static org.ethereum.publish.event.Events.Type.PENDING_TRANSACTION_UPDATED; +import static org.ethereum.publish.Subscription.to; import static org.ethereum.util.ByteUtil.toHexString; /** * PendingState is the ability to track the changes made by transaction immediately and not wait for * the block containing that transaction. - * + *

* This sample connects to the test network (it has a lot of free Ethers) and starts periodically * transferring funds to a random address. The pending state is monitored and you may see that * while the actual receiver balance remains the same right after transaction sent the pending * state reflects balance change immediately. - * + *

* While new blocks are arrived the sample monitors which of our transactions are included ot those blocks. * After each 5 transactions the sample stops sending transactions and waits for all transactions * are cleared (included to blocks) so the actual and pending receiver balances become equal. - * + *

* Created by Anton Nashatyrev on 05.02.2016. */ public class PendingStateSample extends TestNetSample { @@ -64,21 +65,12 @@ public class PendingStateSample extends TestNetSample { @Override public void onSyncDone() { - ethereum.addListener(new EthereumListenerAdapter() { - // listening here when the PendingState is updated with new transactions - @Override - public void onPendingTransactionsReceived(List transactions) { - for (Transaction tx : transactions) { - PendingStateSample.this.onPendingTransactionReceived(tx); - } - } - - // when block arrives look for our included transactions - @Override - public void onBlock(Block block, List receipts) { - PendingStateSample.this.onBlock(block, receipts); - } - }); + this.ethereum + // listening here when the PendingState is updated with new transactions + .subscribe(to(PENDING_TRANSACTION_UPDATED, data -> onPendingTransactionReceived(data.getReceipt().getTransaction())) + .conditionally(data -> data.getState() == NEW_PENDING)) + // when block arrives look for our included transactions + .subscribe(BLOCK_ADDED, d -> onBlock(d)); new Thread(() -> { try { @@ -100,7 +92,7 @@ void sendTransactions() throws InterruptedException { int weisToSend = 100; int count = 0; - while(true) { + while (true) { if (count < 5) { Transaction tx = new Transaction( ByteUtil.bigIntegerToBytes(nonce), @@ -129,9 +121,9 @@ void sendTransactions() throws InterruptedException { } /** - * The PendingState is updated with a new pending transactions. - * Prints the current receiver balance (based on blocks) and the pending balance - * which should immediately reflect receiver balance change + * The PendingState is updated with a new pending transactions. + * Prints the current receiver balance (based on blocks) and the pending balance + * which should immediately reflect receiver balance change */ void onPendingTransactionReceived(Transaction tx) { logger.info("onPendingTransactionReceived: " + tx); @@ -151,6 +143,11 @@ void onPendingTransactionReceived(Transaction tx) { * For each block we are looking for our transactions and clearing them * The actual receiver balance is confirmed upon block arrival */ + public void onBlock(BlockAdded.Data data) { + BlockSummary blockSummary = data.getBlockSummary(); + onBlock(blockSummary.getBlock(), blockSummary.getReceipts()); + } + public void onBlock(Block block, List receipts) { int cleared = 0; for (Transaction tx : block.getTransactionsList()) { @@ -175,7 +172,7 @@ public void onBlock(Block block, List receipts) { public static void main(String[] args) throws Exception { sLogger.info("Starting EthereumJ!"); - class Config extends TestNetConfig{ + class Config extends TestNetConfig { @Override @Bean public TestNetSample sampleBean() { diff --git a/ethereumj-core/src/main/java/org/ethereum/samples/PrivateMinerSample.java b/ethereumj-core/src/main/java/org/ethereum/samples/PrivateMinerSample.java index 964d581d19..0f18669499 100644 --- a/ethereumj-core/src/main/java/org/ethereum/samples/PrivateMinerSample.java +++ b/ethereumj-core/src/main/java/org/ethereum/samples/PrivateMinerSample.java @@ -63,6 +63,7 @@ private static class MinerConfig { // when more than 1 miner exist on the network extraData helps to identify the block creator "mine.extraDataHex = cccccccccccccccccccc \n" + "mine.cpuMineThreads = 2 \n" + + "mine.mine.fullDataSet = false \n" + "cache.flush.blocks = 1"; @Bean diff --git a/ethereumj-core/src/main/java/org/ethereum/samples/SendTransaction.java b/ethereumj-core/src/main/java/org/ethereum/samples/SendTransaction.java index 0fd3d36258..9b5683cff2 100644 --- a/ethereumj-core/src/main/java/org/ethereum/samples/SendTransaction.java +++ b/ethereumj-core/src/main/java/org/ethereum/samples/SendTransaction.java @@ -17,12 +17,13 @@ */ package org.ethereum.samples; -import org.ethereum.core.*; +import org.ethereum.core.Transaction; +import org.ethereum.core.TransactionReceipt; import org.ethereum.crypto.ECKey; import org.ethereum.crypto.HashUtil; import org.ethereum.db.ByteArrayWrapper; import org.ethereum.facade.EthereumFactory; -import org.ethereum.listener.EthereumListenerAdapter; +import org.ethereum.publish.event.BlockAdded; import org.ethereum.util.ByteUtil; import org.ethereum.util.blockchain.EtherUtil; import org.spongycastle.util.encoders.Hex; @@ -31,13 +32,14 @@ import java.math.BigInteger; import java.util.Collections; import java.util.HashMap; -import java.util.List; import java.util.Map; +import static org.ethereum.publish.event.Events.Type.BLOCK_ADDED; + /** * With this simple example you can send transaction from address to address in live public network * To make it work you just need to set sender's private key and receiver's address - * + *

* Created by Alexander Samtsov on 12.08.16. */ public class SendTransaction extends BasicSample { @@ -47,23 +49,17 @@ public class SendTransaction extends BasicSample { @Override public void onSyncDone() throws Exception { - ethereum.addListener(new EthereumListenerAdapter() { - // when block arrives look for our included transactions - @Override - public void onBlock(Block block, List receipts) { - SendTransaction.this.onBlock(block, receipts); - } - }); - + // when block arrives look for our included transactions + this.ethereum.subscribe(BLOCK_ADDED, this::onBlock); String toAddress = ""; logger.info("Sending transaction to net and waiting for inclusion"); sendTxAndWait(Hex.decode(toAddress), new byte[0]); - logger.info("Transaction included!");} - + logger.info("Transaction included!"); + } - private void onBlock(Block block, List receipts) { - for (TransactionReceipt receipt : receipts) { + private void onBlock(BlockAdded.Data data) { + for (TransactionReceipt receipt : data.getBlockSummary().getReceipts()) { ByteArrayWrapper txHashW = new ByteArrayWrapper(receipt.getTransaction().getHash()); if (txWaiters.containsKey(txHashW)) { txWaiters.put(txHashW, receipt); @@ -74,7 +70,6 @@ private void onBlock(Block block, List receipts) { } } - private TransactionReceipt sendTxAndWait(byte[] receiveAddress, byte[] data) throws InterruptedException { byte[] senderPrivateKey = HashUtil.sha3("cow".getBytes()); @@ -102,16 +97,16 @@ private TransactionReceipt waitForTx(byte[] txHash) throws InterruptedException txWaiters.put(txHashW, null); long startBlock = ethereum.getBlockchain().getBestBlock().getNumber(); - while(true) { + while (true) { TransactionReceipt receipt = txWaiters.get(txHashW); if (receipt != null) { return receipt; } else { long curBlock = ethereum.getBlockchain().getBestBlock().getNumber(); if (curBlock > startBlock + 16) { - throw new RuntimeException("The transaction was not included during last 16 blocks: " + txHashW.toString().substring(0,8)); + throw new RuntimeException("The transaction was not included during last 16 blocks: " + txHashW.toString().substring(0, 8)); } else { - logger.info("Waiting for block with transaction 0x" + txHashW.toString().substring(0,8) + + logger.info("Waiting for block with transaction 0x" + txHashW.toString().substring(0, 8) + " included (" + (curBlock - startBlock) + " blocks received so far) ..."); } diff --git a/ethereumj-core/src/main/java/org/ethereum/samples/SingleMinerNetSample.java b/ethereumj-core/src/main/java/org/ethereum/samples/SingleMinerNetSample.java new file mode 100644 index 0000000000..ddba7afebb --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/samples/SingleMinerNetSample.java @@ -0,0 +1,285 @@ +/* + * Copyright (c) [2016] [ ] + * 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 . + */ +package org.ethereum.samples; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.LoggerContext; +import com.typesafe.config.ConfigFactory; +import org.ethereum.config.SystemProperties; +import org.ethereum.core.TransactionReceipt; +import org.ethereum.crypto.ECKey; +import org.ethereum.facade.Ethereum; +import org.ethereum.facade.EthereumFactory; +import org.ethereum.publish.event.BlockAdded; +import org.ethereum.samples.util.Account; +import org.ethereum.samples.util.TransactionSubmitter; +import org.ethereum.samples.util.Contract; +import org.ethereum.solidity.compiler.SolidityCompiler; +import org.ethereum.util.blockchain.StandaloneBlockchain; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; + +import javax.annotation.PostConstruct; +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import static java.nio.file.Files.readAllBytes; +import static org.ethereum.core.Denomination.ETHER; +import static org.ethereum.publish.Subscription.to; +import static org.ethereum.publish.event.Events.Type.BLOCK_ADDED; + +/** + * Basic class for independent private network samples. + * Within this sample single node starts with miner mode. + * To reduce block's waiting time the mining difficulty setted up to pretty low value (block adding speed is about 1 block per second). + * This class can be used as a base for free transactions testing + * (everyone may use that 'usr1pass' sender which has pretty enough fake coins) + *

+ * Created by Eugene Shevchenko on 05.10.2018. + */ +public class SingleMinerNetSample { + + protected abstract static class Config { + + @Autowired + private ResourceLoader resourceLoader; + + protected Resource loadSampleResource(String path) { + return resourceLoader.getResource("classpath:samples" + path); + } + + protected byte[] loadContractSource(String contractName) { + try { + Resource resource = loadSampleResource("/contracts/" + contractName); + return readAllBytes(resource.getFile().toPath()); + } catch (IOException e) { + throw new RuntimeException(contractName + " contract source loading error: ", e); + } + } + + public abstract SingleMinerNetSample sample(); + + protected Map getExtraConfig() { + return new LinkedHashMap() {{ + put("sync.enabled", false); + put("sync.makeDoneByTimeout", 60); + put("peer.discovery.enabled", false); + put("peer.listen.port", 0); + put("peer.privateKey", "6ef8da380c27cea8fdf7448340ea99e8e2268fc2950d79ed47cbf6f85dc977ec"); + put("peer.networkId", 555); + put("mine.start", true); + put("mine.fullDataSet", false); + put("mine.extraDataHex", "cccccccccccccccccccc"); + put("mine.minBlockTimeoutMsec", 0); + put("mine.cpuMineThreads", 1); + put("genesis", "sample-local-genesis.json"); + put("database.dir", "local-net-sample-db"); + put("cache.flush.blocks", 10); + }}; + } + + @Bean + public final SystemProperties systemProperties() { + SystemProperties props = SystemProperties.getDefault(); + props.setBlockchainConfig(StandaloneBlockchain.getEasyMiningConfig()); + + Map extraConfig = getExtraConfig(); + if (!extraConfig.isEmpty()) { + props.overrideParams(ConfigFactory.parseMap(extraConfig)); + } + + return props; + } + + @Bean + public TransactionSubmitter transactionSubmitter(Ethereum ethereum) { + return new TransactionSubmitter(ethereum); + } + + @Bean + public final Account.Register accountRegister() { + Account.Register register = Account.newRegister().withFaucet("usr1pass"); + registerAccounts(register); + return register; + } + + /** + * Template method for custom accounts installing. + * Register own accounts via {@link org.ethereum.samples.util.Account.Register} to get some test ether. + * @param register + */ + protected void registerAccounts(Account.Register register) { + + } + + @Bean + public final Contract.Register contractRegister(SolidityCompiler compiler) { + Contract.Register register = Contract.newRegister(compiler); + registerContracts(register); + return register; + } + + /** + * Register your contract via {@link org.ethereum.samples.util.Contract.Register} to deploy it at sample prepare phase. + * @param register + */ + protected void registerContracts(Contract.Register register) { + + } + } + + protected static final Logger logger = LoggerFactory.getLogger("sample"); + + @Autowired + protected Ethereum ethereum; + @Autowired + protected SolidityCompiler compiler; + @Autowired + protected TransactionSubmitter txSubmitter; + @Autowired + protected Account.Register accountRegister; + @Autowired + protected Contract.Register contractRegister; + + protected final Account account(String id) { + return accountRegister.get(id); + } + + protected final Contract contract(String id) { + return contractRegister.get(id); + } + + protected final Contract.Caller contractCaller(String accountId, String contractId) { + Account caller = account(accountId); + return contract(contractId).newCaller(caller.getKey(), ethereum, txSubmitter); + } + + private CompletableFuture deployContracts() { + ECKey faucetKey = accountRegister.getFaucet().getKey(); + + CompletableFuture[] futures = contractRegister.contracts().stream() + .filter(contract -> !contract.isDeployed()) + .map(contract -> txSubmitter.deployTransaction(faucetKey, contract.getBinaryCode()).submit() + .thenApply(receipt -> contract.deployedAt(receipt.getTransaction().getContractAddress()))) + .toArray(CompletableFuture[]::new); + + + return CompletableFuture.allOf(futures).whenComplete((smth, err) -> { + if (err == null) { + logger.info("All predefined contracts successfully deployed."); + } else { + logger.info("Contract deployment error: ", err); + } + }); + } + + private CompletableFuture transferFundsToAccounts() { + ECKey faucetKey = accountRegister.getFaucet().getKey(); + + CompletableFuture[] futures = accountRegister.accountsWithoutFaucet().stream() + .map(account -> txSubmitter + .transferTransaction(faucetKey, account.getAddress(), 100, ETHER) + .submit()) + .toArray(CompletableFuture[]::new); + + return CompletableFuture.allOf(futures).whenComplete((smth, err) -> { + if (err == null) { + logger.info("All funds transfers for predefined accounts performed successfully."); + } else { + logger.error("Funds transferring error: ", err); + } + }); + } + + @PostConstruct + public final void initSample() { + this.ethereum + .subscribe(to(BLOCK_ADDED, this::onImportStarted).oneOff()) + .subscribe(to(BLOCK_ADDED, this::printHeartbeat)); + } + + private void printHeartbeat(BlockAdded.Data data) { + if (data.getBlockSummary().getBlock().getNumber() % 15 == 0) { + logger.info("heartbeat: block #{} mined and imported.", data.getBlockSummary().getBlock().getNumber()); + } + } + + private void onImportStarted(BlockAdded.Data data) { + logger.info("Single miner network is up. The first block #{} has been imported.", data.getBlockSummary().getBlock().getNumber()); + + List initActions = new ArrayList<>(); + initActions.add(transferFundsToAccounts()); + initActions.add(deployContracts()); + + CompletableFuture.allOf(initActions.toArray(new CompletableFuture[]{})) + .whenComplete((aVoid, err) -> { + if (err == null) { + logger.info("Sample components successfully deployed."); + onSampleReady(); + } else { + logger.error("Sample setup failed with error: ", err); + onSampleFailed(err); + } + }); + } + + protected void onSampleReady() { + + } + + protected void onSampleFailed(Throwable err) { + System.exit(1); + } + + public static void main(String[] args) { + + class Cfg extends Config { + + @Bean + @Override + public SingleMinerNetSample sample() { + return new SingleMinerNetSample(); + } + } + + EthereumFactory.createEthereum(Cfg.class); + } + + static { + overrideLoggingLevel("mine", Level.WARN); + overrideLoggingLevel("blockchain", Level.WARN); + overrideLoggingLevel("net", Level.WARN); + overrideLoggingLevel("db", Level.WARN); + overrideLoggingLevel("sync", Level.WARN); + } + + private static void overrideLoggingLevel(String loggerName, Level level) { + final LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); + Logger rootLogger = loggerContext.getLogger(loggerName); + ((ch.qos.logback.classic.Logger) rootLogger).setLevel(level); + } + +} diff --git a/ethereumj-core/src/main/java/org/ethereum/samples/TransactionBomb.java b/ethereumj-core/src/main/java/org/ethereum/samples/TransactionBomb.java index 3a464a5ee2..714ead8523 100644 --- a/ethereumj-core/src/main/java/org/ethereum/samples/TransactionBomb.java +++ b/ethereumj-core/src/main/java/org/ethereum/samples/TransactionBomb.java @@ -17,41 +17,39 @@ */ package org.ethereum.samples; -import org.ethereum.core.Block; import org.ethereum.core.Transaction; -import org.ethereum.core.TransactionReceipt; import org.ethereum.facade.Ethereum; import org.ethereum.facade.EthereumFactory; -import org.ethereum.listener.EthereumListenerAdapter; +import org.ethereum.publish.event.BlockAdded; +import org.ethereum.sync.SyncManager; import org.spongycastle.util.encoders.Hex; import java.util.Collections; -import java.util.List; import static org.ethereum.crypto.HashUtil.sha3; +import static org.ethereum.publish.event.Events.Type.BLOCK_ADDED; +import static org.ethereum.publish.event.Events.Type.SYNC_DONE; import static org.ethereum.util.ByteUtil.longToBytesNoLeadZeroes; import static org.ethereum.util.ByteUtil.toHexString; -public class TransactionBomb extends EthereumListenerAdapter { +public class TransactionBomb { Ethereum ethereum = null; boolean startedTxBomb = false; public TransactionBomb(Ethereum ethereum) { - this.ethereum = ethereum; + this.ethereum = ethereum + .subscribe(SYNC_DONE, this::onSyncDone) + .subscribe(BLOCK_ADDED, this::onBlock); } public static void main(String[] args) { - - Ethereum ethereum = EthereumFactory.createEthereum(); - ethereum.addListener(new TransactionBomb(ethereum)); + new TransactionBomb(EthereumFactory.createEthereum()); } - @Override - public void onSyncDone(SyncState state) { - + public void onSyncDone(SyncManager.State state) { // We will send transactions only // after we have the full chain syncs // - in order to prevent old nonce usage @@ -59,12 +57,10 @@ public void onSyncDone(SyncState state) { System.err.println(" ~~~ SYNC DONE ~~~ "); } - @Override - public void onBlock(Block block, List receipts) { - + public void onBlock(BlockAdded.Data data) { if (startedTxBomb){ byte[] sender = Hex.decode("cd2a3d9f938e13cd947ec05abc7fe734df8dd826"); - long nonce = ethereum.getRepository().getNonce(sender).longValue();; + long nonce = ethereum.getRepository().getNonce(sender).longValue(); for (int i=0; i < 20; ++i){ sendTx(nonce); diff --git a/ethereumj-core/src/main/java/org/ethereum/samples/util/Account.java b/ethereumj-core/src/main/java/org/ethereum/samples/util/Account.java new file mode 100644 index 0000000000..f0d2334a77 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/samples/util/Account.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) [2016] [ ] + * 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 . + */ +package org.ethereum.samples.util; + +import org.ethereum.crypto.ECKey; + +import java.math.BigInteger; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import static java.util.Objects.isNull; +import static org.ethereum.crypto.HashUtil.sha3; + +public class Account { + + public static class Register { + + private static final String FAUCET_NAME = "faucet"; + + private final Map map = new HashMap<>(); + + public Register add(String name, String password) { + map.put(name, new Account(password)); + return this; + } + + public Register addSameNameAndPass(String name) { + return add(name, name); + } + + public Account get(String name) { + Account value = map.get(name); + if (value == null) { + throw new RuntimeException("Account with name " + name + " isn't registered."); + } + return value; + } + + public Set accounts(Predicate filter) { + return map.entrySet().stream() + .filter(e -> isNull(filter) || filter.test(e.getKey())) + .map(Map.Entry::getValue) + .collect(Collectors.toSet()); + } + + public Set accounts() { + return accounts(null); + } + + public Register withFaucet(String password) { + return add(FAUCET_NAME, password); + } + + public Account getFaucet() { + return get(FAUCET_NAME); + } + + public Set accountsWithoutFaucet() { + return accounts(name -> !FAUCET_NAME.equals(name)); + } + } + + private final ECKey key; + private BigInteger requiredBalance; + + public Account(String password) { + this.key = ECKey.fromPrivate(sha3(password.getBytes())); + } + + public ECKey getKey() { + return key; + } + + public byte[] getAddress() { + return key.getAddress(); + } + + public static Register newRegister() { + return new Register(); + } +} \ No newline at end of file diff --git a/ethereumj-core/src/main/java/org/ethereum/samples/util/Contract.java b/ethereumj-core/src/main/java/org/ethereum/samples/util/Contract.java new file mode 100644 index 0000000000..32b99a15c5 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/samples/util/Contract.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) [2016] [ ] + * 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 . + */ +package org.ethereum.samples.util; + +import org.ethereum.core.CallTransaction; +import org.ethereum.crypto.ECKey; +import org.ethereum.facade.Ethereum; +import org.ethereum.solidity.compiler.CompilationResult; +import org.ethereum.solidity.compiler.SolidityCompiler; +import org.ethereum.vm.program.ProgramResult; +import org.spongycastle.util.encoders.Hex; + +import java.io.IOException; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Stream; + +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.apache.commons.lang3.StringUtils.isEmpty; +import static org.ethereum.util.ByteUtil.toHexString; + +public class Contract { + + private static final SolidityCompiler.Option[] DEFAULT_COMPILATION_OPTIONS = {SolidityCompiler.Options.ABI, SolidityCompiler.Options.BIN}; + + private byte[] address; + private final CompilationResult.ContractMetadata metadata; + private final CallTransaction.Contract accessor; + + public Contract(byte[] address, CompilationResult.ContractMetadata metadata) { + requireNonNull(metadata, "Contract metadata object couldn't be null."); + + this.address = address; + this.metadata = metadata; + this.accessor = new CallTransaction.Contract(metadata.abi); + } + + public boolean isDeployed() { + return address != null; + } + + public Contract deployedAt(byte[] address) { + this.address = address; + return this; + } + + public byte[] getAddress() { + return address; + } + + public String getAbi() { + return metadata.abi; + } + + public byte[] getBinaryCode() { + return Hex.decode(metadata.bin); + } + + public static Contract compile(byte[] source, SolidityCompiler compiler, SolidityCompiler.Option... compilationOpts) throws IOException { + + SolidityCompiler.Option[] options = Stream.concat(Stream.of(DEFAULT_COMPILATION_OPTIONS), Stream.of(compilationOpts)) + .distinct() + .toArray(SolidityCompiler.Option[]::new); + + SolidityCompiler.Result result = compiler.compileSrc(source, true, true, options); + + if (result.isFailed()) { + throw new RuntimeException("Contract compilation failed:\n" + result.errors); + } + CompilationResult res = CompilationResult.parse(result.output); + if (res.getContracts().isEmpty()) { + throw new RuntimeException("Compilation failed, no contracts returned:\n" + result.errors); + } + + CompilationResult.ContractMetadata metadata = res.getContracts().iterator().next(); + if (isEmpty(metadata.bin)) { + throw new RuntimeException("Compilation failed, no binary returned:\n" + result.errors); + } + + return new Contract(null, metadata); + } + + public Caller newCaller(ECKey callerKey, Ethereum ethereum, TransactionSubmitter submitter) { + return new Caller(callerKey, ethereum, submitter); + } + + public static Register newRegister(SolidityCompiler compiler) { + return new Register(compiler); + } + + public class Caller { + + private final Ethereum ethereum; + private final TransactionSubmitter submitter; + + private final ECKey key; + + public Caller(ECKey callerKey, Ethereum ethereum, TransactionSubmitter submitter) { + + if (!isDeployed()) { + throw new RuntimeException("Couldn't create caller for non deployed contract."); + } + + this.ethereum = ethereum; + this.submitter = submitter; + this.key = callerKey; + } + + public CompletableFuture call(String funcName, Object... args) { + CallTransaction.Function func = accessor.getByName(funcName); + if (func == null) { + throw new RuntimeException(format("There is no function with name '%s'.", funcName)); + } + + if (func.constant) { + ProgramResult result = ethereum.callConstantFunction(toHexString(getAddress()), key, func, args); + return completedFuture(result.getHReturn()); + } + + return submitter.invokeTransaction(key, getAddress(), func.encode(args)) + .submit() + .thenApply(receipt -> receipt.getExecutionResult()); + } + } + + public static class Register { + + private final Map contractById = new ConcurrentHashMap<>(); + private final SolidityCompiler compiler; + + public Register(SolidityCompiler compiler) { + this.compiler = compiler; + } + + public Register add(String id, Contract contract) { + contractById.put(id, contract); + return this; + } + + public Register addDeployed(String id, byte[] address, byte[] source) { + try { + return add(id, Contract.compile(source, compiler).deployedAt(address)); + } catch (Exception e) { + throw new RuntimeException("Contract registration error: ", e); + } + } + + public Register add(String id, byte[] source) { + return addDeployed(id, null, source); + } + + public Contract get(String id) { + Contract contract = contractById.get(id); + if (contract == null) { + throw new RuntimeException(format("There is no contract with id '%s' in the register.", id)); + } + return contract; + } + + public Collection contracts() { + return contractById.values(); + } + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/samples/util/TransactionSubmitter.java b/ethereumj-core/src/main/java/org/ethereum/samples/util/TransactionSubmitter.java new file mode 100644 index 0000000000..d67ca35b05 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/samples/util/TransactionSubmitter.java @@ -0,0 +1,267 @@ +/* + * Copyright (c) [2016] [ ] + * 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 . + */ +package org.ethereum.samples.util; + +import org.ethereum.core.Block; +import org.ethereum.core.Denomination; +import org.ethereum.core.Transaction; +import org.ethereum.core.TransactionReceipt; +import org.ethereum.crypto.ECKey; +import org.ethereum.db.ByteArrayWrapper; +import org.ethereum.facade.Ethereum; +import org.ethereum.publish.event.BlockAdded; +import org.ethereum.publish.event.PendingTransactionUpdated; +import org.ethereum.util.ByteUtil; +import org.ethereum.util.FastByteComparisons; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; + +import static java.lang.String.format; +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toMap; +import static org.ethereum.publish.event.Events.Type.BLOCK_ADDED; +import static org.ethereum.publish.event.Events.Type.PENDING_TRANSACTION_UPDATED; +import static org.ethereum.util.BIUtil.toBI; + +public class TransactionSubmitter { + + private final Ethereum ethereum; + private final Map activeByTxHash = new ConcurrentHashMap<>(); + private final Map> txQueueBySenderAddr = new ConcurrentHashMap<>(); + + public TransactionSubmitter(Ethereum ethereum) { + this.ethereum = ethereum + .subscribe(PENDING_TRANSACTION_UPDATED, this::onPendingTransactionUpdated) + .subscribe(BLOCK_ADDED, this::onBlockAdded); + } + + public TransactionBuilder newTransaction(ECKey senderKey, byte[] receiverAddress) { + return new TransactionBuilder(senderKey, receiverAddress); + } + + public TransactionBuilder transferTransaction(ECKey senderKey, byte[] receiverAddress, long value, Denomination denomination) { + return newTransaction(senderKey, receiverAddress) + .value(toBI(value), denomination); + } + + public TransactionBuilder deployTransaction(ECKey senderKey, byte[] binaryCode) { + return newTransaction(senderKey, ByteUtil.EMPTY_BYTE_ARRAY) + .data(binaryCode); + } + + public TransactionBuilder invokeTransaction(ECKey senderKey, byte[] contractAddress, byte[] encodedInvocationData) { + return newTransaction(senderKey, contractAddress) + .data(encodedInvocationData); + } + + private void onPendingTransactionUpdated(PendingTransactionUpdated.Data data) { + TransactionReceipt receipt = data.getReceipt(); + if (receipt.isSuccessful()) return; + + byte[] txHash = receipt.getTransaction().getHash(); + TransactionBuilder tb = activeByTxHash.get(wrap(txHash)); + if (tb != null && tb.isApplicable(receipt)) { + tb.completeExceptionally(receipt.getError()); + } + } + + private void onBlockAdded(BlockAdded.Data data) { + Block block = data.getBlockSummary().getBlock(); + List receipts = data.getBlockSummary().getReceipts(); + + Map receiptByTxHash = receipts.stream() + .filter(receipt -> activeByTxHash.containsKey(wrap(receipt.getTransaction().getHash()))) + .collect(toMap(receipt -> new ByteArrayWrapper(receipt.getTransaction().getHash()), identity())); + + + activeByTxHash.forEach((txHash, tb) -> { + TransactionReceipt receipt = receiptByTxHash.get(txHash); + if (receipt != null) { + if (receipt.isSuccessful()) { + tb.complete(receipt); + } else { + tb.completeExceptionally(receipt.getError()); + } + } else if (tb.isExpired(block)) { + tb.completeExceptionally("The transaction was not included during last " + tb.waitBlocksCount + " blocks."); + } + }); + } + + + private void activate(TransactionBuilder txBuilder) { + txBuilder.buildAndSubmit(); + activeByTxHash.put(wrap(txBuilder.txHash), txBuilder); + } + + private void addToSubmitQueue(TransactionBuilder txBuilder) { + ByteArrayWrapper address = wrap(txBuilder.senderKey.getAddress()); + Queue queue = txQueueBySenderAddr.computeIfAbsent(address, addr -> new LinkedList<>()); + synchronized (queue) { + if (queue.isEmpty()) { + activate(txBuilder); + } + queue.add(txBuilder); + } + } + + + private void removeFromSubmitQueue(TransactionBuilder txBuilder) { + ByteArrayWrapper address = new ByteArrayWrapper(txBuilder.senderKey.getAddress()); + Queue queue = txQueueBySenderAddr.get(address); + synchronized (queue) { + queue.poll(); + activeByTxHash.remove(wrap(txBuilder.txHash)); + if (queue.isEmpty()) { + txQueueBySenderAddr.remove(address); + } else { + activate(queue.peek()); + } + } + } + + public class TransactionBuilder { + + private final ECKey senderKey; + private final byte[] receiverAddress; + // changeable during building transaction's data + private byte[] value = ByteUtil.longToBytesNoLeadZeroes(0); + private byte[] data = ByteUtil.EMPTY_BYTE_ARRAY; + private byte[] gasPrice = ByteUtil.longToBytesNoLeadZeroes(ethereum.getGasPrice()); + private byte[] gasLimit = ByteUtil.longToBytesNoLeadZeroes(3_000_000); + + private byte[] txHash; + private CompletableFuture futureReceipt; + + private long submitBlockNumber; + private long waitBlocksCount; + + public TransactionBuilder(ECKey senderKey, byte[] receiverAddress) { + this.senderKey = senderKey; + this.receiverAddress = receiverAddress; + } + + public TransactionBuilder data(byte[] data) { + this.data = data; + return this; + } + + public TransactionBuilder gasPrice(byte[] gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public TransactionBuilder gasPrice(BigInteger gasPrice) { + return gasPrice(gasPrice.toByteArray()); + } + + public TransactionBuilder gasLimit(byte[] gasLimit) { + this.gasLimit = gasLimit; + return this; + } + + public TransactionBuilder gasLimit(BigInteger gasLimit) { + return gasLimit(gasLimit.toByteArray()); + } + + public TransactionBuilder value(byte[] value) { + this.value = value; + return this; + } + + public TransactionBuilder value(BigInteger value, Denomination denomination) { + return value(value.multiply(denomination.value()).toByteArray()); + } + + public TransactionBuilder value(BigInteger value) { + return value(value, Denomination.WEI); + } + + private void buildAndSubmit() { + byte[] nonce = ByteUtil.bigIntegerToBytes(ethereum.getRepository().getNonce(senderKey.getAddress())); + Integer chainId = ethereum.getChainIdForNextBlock(); + + Transaction tx = new Transaction(nonce, gasPrice, gasLimit, receiverAddress, value, data, chainId); + tx.sign(senderKey); + + ethereum.submitTransaction(tx); + + this.txHash = tx.getHash(); + this.submitBlockNumber = ethereum.getBlockchain().getBestBlock().getNumber(); + } + + private boolean isSubmitted() { + return submitBlockNumber > 0; + } + + private boolean isApplicable(TransactionReceipt receipt) { + return isSubmitted() && FastByteComparisons.equal(receipt.getTransaction().getHash(), txHash); + } + + private void complete(TransactionReceipt receipt) { + if (!isSubmitted()) { + throw new IllegalStateException("Cannot complete non submitted transaction."); + } + futureReceipt.complete(receipt); + } + + private void completeExceptionally(String error, Object... args) { + if (!isSubmitted()) { + throw new IllegalStateException("Cannot complete non submitted transaction."); + } + String message = format("Transaction %s execution error: %s", toHexString(txHash, 4), format(error, args)); + futureReceipt.completeExceptionally(new RuntimeException(message)); + } + + public boolean isExpired(Block block) { + return isSubmitted() && (block.getNumber() - submitBlockNumber) > waitBlocksCount; + } + + public CompletableFuture submit(int waitBlocksCount) { + if (futureReceipt != null) { + return futureReceipt; + } + + this.futureReceipt = new CompletableFuture<>(); + this.waitBlocksCount = waitBlocksCount; + + addToSubmitQueue(this); + + return futureReceipt.whenComplete((receipt, err) -> removeFromSubmitQueue(this)); + } + + public CompletableFuture submit() { + return submit(15); + } + } + + private static String toHexString(byte[] bytes, int count) { + return ByteUtil.toHexString(Arrays.copyOf(bytes, count)); + } + + private static ByteArrayWrapper wrap(byte[] bytes) { + return new ByteArrayWrapper(bytes); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/solidity/Abi.java b/ethereumj-core/src/main/java/org/ethereum/solidity/Abi.java index d795602fe4..120edda378 100644 --- a/ethereumj-core/src/main/java/org/ethereum/solidity/Abi.java +++ b/ethereumj-core/src/main/java/org/ethereum/solidity/Abi.java @@ -172,6 +172,7 @@ public static Entry create(@JsonProperty("anonymous") boolean anonymous, result = new Constructor(inputs, outputs); break; case function: + case fallback: result = new Function(constant, name, inputs, outputs, payable); break; case event: diff --git a/ethereumj-core/src/main/java/org/ethereum/solidity/SolidityType.java b/ethereumj-core/src/main/java/org/ethereum/solidity/SolidityType.java index 5393aa80a8..b814415af9 100644 --- a/ethereumj-core/src/main/java/org/ethereum/solidity/SolidityType.java +++ b/ethereumj-core/src/main/java/org/ethereum/solidity/SolidityType.java @@ -58,7 +58,8 @@ public String getCanonicalName() { public static SolidityType getType(String typeName) { if (typeName.contains("[")) return ArrayType.getType(typeName); if ("bool".equals(typeName)) return new BoolType(); - if (typeName.startsWith("int") || typeName.startsWith("uint")) return new IntType(typeName); + if (typeName.startsWith("int")) return new IntType(typeName); + if (typeName.startsWith("uint")) return new UnsignedIntType(typeName); if ("address".equals(typeName)) return new AddressType(); if ("string".equals(typeName)) return new StringType(); if ("bytes".equals(typeName)) return new BytesType(); @@ -356,23 +357,14 @@ public Object decode(byte[] encoded, int offset) { return ByteUtil.bigIntegerToBytes(bi, 20); } } - - public static class IntType extends SolidityType { - public IntType(String name) { + + public static abstract class NumericType extends SolidityType { + public NumericType(String name) { super(name); } - @Override - public String getCanonicalName() { - if (getName().equals("int")) return "int256"; - if (getName().equals("uint")) return "uint256"; - return super.getCanonicalName(); - } - - @Override - public byte[] encode(Object value) { + BigInteger encodeInternal(Object value) { BigInteger bigInt; - if (value instanceof String) { String s = ((String)value).toLowerCase().trim(); int radix = 10; @@ -393,14 +385,20 @@ public byte[] encode(Object value) { } else { throw new RuntimeException("Invalid value for type '" + this + "': " + value + " (" + value.getClass() + ")"); } - return encodeInt(bigInt); + return bigInt; + } + } + + public static class IntType extends NumericType { + public IntType(String name) { + super(name); } @Override - public Object decode(byte[] encoded, int offset) { - return decodeInt(encoded, offset); + public String getCanonicalName() { + if (getName().equals("int")) return "int256"; + return super.getCanonicalName(); } - public static BigInteger decodeInt(byte[] encoded, int offset) { return new BigInteger(Arrays.copyOfRange(encoded, offset, offset + 32)); } @@ -410,6 +408,48 @@ public static byte[] encodeInt(int i) { public static byte[] encodeInt(BigInteger bigInt) { return ByteUtil.bigIntegerToBytesSigned(bigInt, 32); } + @Override + public Object decode(byte[] encoded, int offset) { + return decodeInt(encoded, offset); + } + @Override + public byte[] encode(Object value) { + BigInteger bigInt = encodeInternal(value); + return encodeInt(bigInt); + } + } + + public static class UnsignedIntType extends NumericType { + public UnsignedIntType(String name) { + super(name); + } + + @Override + public String getCanonicalName() { + if (getName().equals("uint")) return "uint256"; + return super.getCanonicalName(); + } + public static BigInteger decodeInt(byte[] encoded, int offset) { + return new BigInteger(1, Arrays.copyOfRange(encoded, offset, offset + 32)); + } + public static byte[] encodeInt(int i) { + return encodeInt(new BigInteger("" + i)); + } + public static byte[] encodeInt(BigInteger bigInt) { + if (bigInt.signum() == -1) { + throw new RuntimeException("Wrong value for uint type: " + bigInt); + } + return ByteUtil.bigIntegerToBytes(bigInt, 32); + } + @Override + public byte[] encode(Object value) { + BigInteger bigInt = encodeInternal(value); + return encodeInt(bigInt); + } + @Override + public Object decode(byte[] encoded, int offset) { + return decodeInt(encoded, offset); + } } public static class BoolType extends IntType { diff --git a/ethereumj-core/src/main/java/org/ethereum/sync/FastSyncManager.java b/ethereumj-core/src/main/java/org/ethereum/sync/FastSyncManager.java index c26bd53847..27b32d7e43 100644 --- a/ethereumj-core/src/main/java/org/ethereum/sync/FastSyncManager.java +++ b/ethereumj-core/src/main/java/org/ethereum/sync/FastSyncManager.java @@ -22,7 +22,11 @@ import com.google.common.util.concurrent.ListenableFuture; import org.apache.commons.lang3.tuple.Pair; import org.ethereum.config.SystemProperties; -import org.ethereum.core.*; +import org.ethereum.core.AccountState; +import org.ethereum.core.Block; +import org.ethereum.core.BlockHeader; +import org.ethereum.core.BlockIdentifier; +import org.ethereum.core.BlockchainImpl; import org.ethereum.crypto.HashUtil; import org.ethereum.datasource.DbSource; import org.ethereum.datasource.NodeKeyCompositor; @@ -32,15 +36,18 @@ import org.ethereum.db.IndexedBlockStore; import org.ethereum.db.StateSource; import org.ethereum.facade.SyncStatus; -import org.ethereum.listener.CompositeEthereumListener; import org.ethereum.listener.EthereumListener; -import org.ethereum.listener.EthereumListenerAdapter; import org.ethereum.net.client.Capability; import org.ethereum.net.eth.handler.Eth63; import org.ethereum.net.message.ReasonCode; import org.ethereum.net.server.Channel; +import org.ethereum.publish.Publisher; import org.ethereum.trie.TrieKey; -import org.ethereum.util.*; +import org.ethereum.util.ByteArrayMap; +import org.ethereum.util.ByteArraySet; +import org.ethereum.util.FastByteComparisons; +import org.ethereum.util.FileUtil; +import org.ethereum.util.Value; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -52,16 +59,30 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.*; -import java.util.concurrent.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.stream.Collectors; import static org.ethereum.listener.EthereumListener.SyncState.COMPLETE; import static org.ethereum.listener.EthereumListener.SyncState.SECURE; import static org.ethereum.listener.EthereumListener.SyncState.UNSECURE; +import static org.ethereum.publish.event.Events.Type.SYNC_DONE; import static org.ethereum.trie.TrieKey.fromPacked; -import static org.ethereum.util.CompactEncoder.hasTerminator; import static org.ethereum.util.ByteUtil.toHexString; +import static org.ethereum.util.CompactEncoder.hasTerminator; /** * Created by Anton Nashatyrev on 24.10.2016. @@ -109,7 +130,10 @@ public class FastSyncManager { DbFlushManager dbFlushManager; @Autowired - CompositeEthereumListener listener; + private EthereumListener listener; + @Autowired + private Publisher publisher; + @Autowired ApplicationContext applicationContext; @@ -194,7 +218,7 @@ public SyncStatus getSyncState() { case SECURE: if (headersDownloader != null) { return new SyncStatus(SyncStatus.SyncStage.Headers, headersDownloader.getHeadersLoaded(), - pivot.getNumber()); + pivot.getNumber()); } else { return new SyncStatus(SyncStatus.SyncStage.Headers, pivot.getNumber(), pivot.getNumber()); } @@ -236,9 +260,15 @@ private class TrieNodeRequest { this.nodeHash = nodeHash; switch (type) { - case STATE: stateNodesCnt++; break; - case CODE: codeNodesCnt++; break; - case STORAGE: storageNodesCnt++; break; + case STATE: + stateNodesCnt++; + break; + case CODE: + codeNodesCnt++; + break; + case STORAGE: + storageNodesCnt++; + break; } } @@ -540,7 +570,7 @@ private void syncUnsecure(BlockHeader pivot) { retrieveLoop(); - logger.info("FastSync: state trie download complete! (Nodes count: state: " + stateNodesCnt + ", storage: " +storageNodesCnt + ", code: " +codeNodesCnt + ")"); + logger.info("FastSync: state trie download complete! (Nodes count: state: " + stateNodesCnt + ", storage: " + storageNodesCnt + ", code: " + codeNodesCnt + ")"); last = 0; logStat(); @@ -556,12 +586,7 @@ private void syncUnsecure(BlockHeader pivot) { logger.info("FastSync: proceeding to regular sync..."); final CountDownLatch syncDoneLatch = new CountDownLatch(1); - listener.addListener(new EthereumListenerAdapter() { - @Override - public void onSyncDone(SyncState state) { - syncDoneLatch.countDown(); - } - }); + publisher.subscribe(SYNC_DONE, syncState -> syncDoneLatch.countDown()); syncManager.initRegularSync(UNSECURE); logger.info("FastSync: waiting for regular sync to reach the blockchain head..."); @@ -880,10 +905,11 @@ private BlockHeader getPivotHeaderByHash(byte[] pivotBlockHash) throws Exception * 1. Get pivotBlockNumber blocks from all peers * 2. Ensure that pivot block available from 50% + 1 peer * 3. Otherwise proposes new pivotBlockNumber (stepped back) - * @param pivotBlockNumber Pivot block number - * @return null - if no peers available - * null, newPivotBlockNumber - if it's better to try other pivot block number - * BlockHeader, null - if pivot successfully fetched and verified by majority of peers + * + * @param pivotBlockNumber Pivot block number + * @return null - if no peers available + * null, newPivotBlockNumber - if it's better to try other pivot block number + * BlockHeader, null - if pivot successfully fetched and verified by majority of peers */ private Pair getPivotHeaderByNumber(long pivotBlockNumber) throws Exception { List allIdle = pool.getAllIdle(); diff --git a/ethereumj-core/src/main/java/org/ethereum/sync/SyncManager.java b/ethereumj-core/src/main/java/org/ethereum/sync/SyncManager.java index d5d19ab49d..8262c0c666 100644 --- a/ethereumj-core/src/main/java/org/ethereum/sync/SyncManager.java +++ b/ethereumj-core/src/main/java/org/ethereum/sync/SyncManager.java @@ -19,9 +19,7 @@ import org.ethereum.config.SystemProperties; import org.ethereum.core.*; -import org.ethereum.core.Blockchain; import org.ethereum.facade.SyncStatus; -import org.ethereum.listener.CompositeEthereumListener; import org.ethereum.listener.EthereumListener; import org.ethereum.net.server.Channel; import org.ethereum.net.server.ChannelManager; @@ -47,8 +45,8 @@ import static java.lang.Math.max; import static java.util.Collections.singletonList; import static org.ethereum.core.ImportResult.*; -import static org.ethereum.util.Utils.longToTimePeriod; import static org.ethereum.util.ByteUtil.toHexString; +import static org.ethereum.util.Utils.longToTimePeriod; /** * @author Mikhail Kalinin @@ -59,9 +57,34 @@ public class SyncManager extends BlockDownloader { private final static Logger logger = LoggerFactory.getLogger("sync"); + public enum State { + /** + * When doing fast sync UNSECURE sync means that the full state is downloaded, + * chain is on the latest block, and blockchain operations may be executed + * (such as state querying, transaction submission) + * but the state isn't yet confirmed with the whole block chain and can't be + * trusted. + * At this stage historical blocks and receipts are unavailable yet + */ + UNSECURE, + /** + * When doing fast sync SECURE sync means that the full state is downloaded, + * chain is on the latest block, and blockchain operations may be executed + * (such as state querying, transaction submission) + * The state is now confirmed by the full chain (all block headers are + * downloaded and verified) and can be trusted + * At this stage historical blocks and receipts are unavailable yet + */ + SECURE, + /** + * Sync is fully complete. All blocks and receipts are downloaded. + */ + COMPLETE + } + // Transaction.getSender() is quite heavy operation so we are prefetching this value on several threads // to unload the main block importing cycle - private ExecutorPipeline exec1 = new ExecutorPipeline<> + private ExecutorPipeline exec1 = new ExecutorPipeline<> (4, 1000, true, blockWrapper -> { for (Transaction tx : blockWrapper.getBlock().getTransactionsList()) { tx.getSender(); @@ -86,7 +109,7 @@ public void accept(BlockWrapper blockWrapper) { private Blockchain blockchain; @Autowired - private CompositeEthereumListener compositeEthereumListener; + private EthereumListener listener; @Autowired private FastSyncManager fastSyncManager; @@ -132,7 +155,7 @@ public void init(final ChannelManager channelManager, final SyncPool pool) { try { logger.info("Sync state: " + getSyncStatus() + (isSyncDone() || importStart == 0 ? "" : "; Import idle time " + - longToTimePeriod(importIdleTime.get()) + " of total " + longToTimePeriod(System.currentTimeMillis() - importStart))); + longToTimePeriod(importIdleTime.get()) + " of total " + longToTimePeriod(System.currentTimeMillis() - importStart))); } catch (Exception e) { logger.error("Unexpected", e); } @@ -166,7 +189,7 @@ void initRegularSync(EthereumListener.SyncState syncDoneType) { Runnable queueProducer = this::produceQueue; - syncQueueThread = new Thread (queueProducer, "SyncQueueThread"); + syncQueueThread = new Thread(queueProducer, "SyncQueueThread"); syncQueueThread.start(); if (config.makeDoneByTimeout() >= 0) { @@ -225,7 +248,8 @@ protected void pushBlocks(List blockWrappers) { } @Override - protected void pushHeaders(List headers) {} + protected void pushHeaders(List headers) { + } @Override protected int getBlockQueueFreeSize() { @@ -310,7 +334,8 @@ private void produceQueue() { wrapper.getBlock().getTransactionsList().size(), ts); if (syncDone && (importResult == IMPORTED_BEST || importResult == IMPORTED_NOT_BEST)) { - if (logger.isDebugEnabled()) logger.debug("Block dump: " + toHexString(wrapper.getBlock().getEncoded())); + if (logger.isDebugEnabled()) + logger.debug("Block dump: " + toHexString(wrapper.getBlock().getEncoded())); // Propagate block to the net after successful import asynchronously if (wrapper.isNewBlock()) channelManager.onNewForeignBlock(wrapper); } @@ -339,14 +364,14 @@ private synchronized void makeSyncDone() { if (syncDone) return; syncDone = true; channelManager.onSyncDone(true); - compositeEthereumListener.onSyncDone(syncDoneType); + listener.onSyncDone(syncDoneType); } public CompletableFuture switchToShortSync() { final CompletableFuture syncDoneF = new CompletableFuture<>(); - if(!syncDone && config.isSyncEnabled()) { + if (!syncDone && config.isSyncEnabled()) { new Thread(() -> { - while(!blockQueue.isEmpty() && !syncDone) { + while (!blockQueue.isEmpty() && !syncDone) { try { Thread.sleep(100); } catch (InterruptedException e) { @@ -366,11 +391,10 @@ public CompletableFuture switchToShortSync() { /** * Adds NEW block to the queue * - * @param block new block + * @param block new block * @param nodeId nodeId of the remote peer which this block is received from - * * @return true if block passed validations and was added to the queue, - * otherwise it returns false + * otherwise it returns false */ public boolean validateAndAddNewBlock(Block block, byte[] nodeId) { diff --git a/ethereumj-core/src/main/java/org/ethereum/util/blockchain/StandaloneBlockchain.java b/ethereumj-core/src/main/java/org/ethereum/util/blockchain/StandaloneBlockchain.java index 46db1698bf..5af496240f 100644 --- a/ethereumj-core/src/main/java/org/ethereum/util/blockchain/StandaloneBlockchain.java +++ b/ethereumj-core/src/main/java/org/ethereum/util/blockchain/StandaloneBlockchain.java @@ -19,20 +19,24 @@ import org.ethereum.config.BlockchainNetConfig; import org.ethereum.config.SystemProperties; -import org.ethereum.config.blockchain.FrontierConfig; +import org.ethereum.config.blockchain.ByzantiumConfig; +import org.ethereum.config.blockchain.DaoNoHFConfig; +import org.ethereum.config.blockchain.HomesteadConfig; import org.ethereum.core.*; import org.ethereum.core.genesis.GenesisLoader; import org.ethereum.crypto.ECKey; -import org.ethereum.datasource.*; +import org.ethereum.datasource.JournalSource; +import org.ethereum.datasource.Source; import org.ethereum.datasource.inmem.HashMapDB; -import org.ethereum.db.PruneManager; -import org.ethereum.db.RepositoryRoot; import org.ethereum.db.ByteArrayWrapper; import org.ethereum.db.IndexedBlockStore; -import org.ethereum.listener.CompositeEthereumListener; +import org.ethereum.db.PruneManager; +import org.ethereum.db.RepositoryRoot; import org.ethereum.listener.EthereumListener; -import org.ethereum.listener.EthereumListenerAdapter; import org.ethereum.mine.Ethash; +import org.ethereum.listener.BackwardCompatibilityEthereumListenerProxy; +import org.ethereum.publish.Subscription; +import org.ethereum.publish.event.Event; import org.ethereum.solidity.compiler.CompilationResult; import org.ethereum.solidity.compiler.CompilationResult.ContractMetadata; import org.ethereum.solidity.compiler.SolidityCompiler; @@ -53,6 +57,8 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutionException; +import static org.ethereum.publish.Subscription.to; +import static org.ethereum.publish.event.Events.Type.BLOCK_ADDED; import static org.ethereum.util.ByteUtil.wrap; /** @@ -60,29 +66,29 @@ */ public class StandaloneBlockchain implements LocalBlockchain { - Genesis genesis; - byte[] coinbase; - BlockchainImpl blockchain; - PendingStateImpl pendingState; - CompositeEthereumListener listener; - ECKey txSender; - long gasPrice; - long gasLimit; - boolean autoBlock; - long dbDelay = 0; - long totalDbHits = 0; - BlockchainNetConfig netConfig; + private Genesis genesis; + private byte[] coinbase; + private BlockchainImpl blockchain; + private PendingStateImpl pendingState; + private ECKey txSender; + private long gasPrice; + private long gasLimit; + private boolean autoBlock; + private long dbDelay = 0; + private long totalDbHits = 0; + private BlockchainNetConfig netConfig; - int blockGasIncreasePercent = 0; + private int blockGasIncreasePercent = 0; - long time = 0; - long timeIncrement = 13; + private long time = 0; + private long timeIncrement = 13; private HashMapDB stateDS; - JournalSource pruningStateDS; - PruneManager pruneManager; + private JournalSource pruningStateDS; + private PruneManager pruneManager; private BlockSummary lastSummary; + private final BackwardCompatibilityEthereumListenerProxy listenerProxy; private VMHook vmHook = VMHook.EMPTY; class PendingTx { @@ -133,6 +139,8 @@ public StandaloneBlockchain() { withMinerCoinbase(Hex.decode("ffffffffffffffffffffffffffffffffffffffff")); setSender(ECKey.fromPrivate(Hex.decode("3ec771c31cac8c0dba77a69e503765701d3c2bb62435888d4ffa38fed60c445c"))); // withAccountBalance(txSender.getAddress(), new BigInteger("100000000000000000000000000")); + + listenerProxy = BackwardCompatibilityEthereumListenerProxy.createDefault(); } public StandaloneBlockchain withGenesis(Genesis genesis) { @@ -273,7 +281,7 @@ public Block createForkBlock(Block parent) { submittedTxes.clear(); return b; - } catch (InterruptedException|ExecutionException e) { + } catch (InterruptedException | ExecutionException e) { throw new RuntimeException(e); } } @@ -291,6 +299,7 @@ private TransactionExecutionSummary getTxSummary(BlockSummary bs, int idx) { public Transaction createTransaction(long nonce, byte[] toAddress, long value, byte[] data) { return createTransaction(getSender(), nonce, toAddress, BigInteger.valueOf(value), data); } + public Transaction createTransaction(ECKey sender, long nonce, byte[] toAddress, BigInteger value, byte[] data) { Transaction transaction = new Transaction(ByteUtil.longToBytesNoLeadZeroes(nonce), ByteUtil.longToBytesNoLeadZeroes(gasPrice), @@ -348,71 +357,71 @@ public SolidityContract submitNewContractFromJson(String json, Object... constru @Override public SolidityContract submitNewContractFromJson(String json, String contractName, Object... constructorArgs) { - SolidityContractImpl contract; - try { - contract = createContractFromJson(contractName, json); - return submitNewContract(contract, constructorArgs); - } catch (IOException e) { - throw new RuntimeException(e); - } + SolidityContractImpl contract; + try { + contract = createContractFromJson(contractName, json); + return submitNewContract(contract, constructorArgs); + } catch (IOException e) { + throw new RuntimeException(e); + } } @Override - public SolidityContract submitNewContract(ContractMetadata contractMetaData, Object... constructorArgs) { - SolidityContractImpl contract = new SolidityContractImpl(contractMetaData); - return submitNewContract(contract, constructorArgs); - } - - private SolidityContract submitNewContract(SolidityContractImpl contract, Object... constructorArgs) { - CallTransaction.Function constructor = contract.contract.getConstructor(); - if (constructor == null && constructorArgs.length > 0) { - throw new RuntimeException("No constructor with params found"); - } - byte[] argsEncoded = constructor == null ? new byte[0] : constructor.encodeArguments(constructorArgs); - submitNewTx(new PendingTx(new byte[0], BigInteger.ZERO, - ByteUtil.merge(Hex.decode(contract.getBinary()), argsEncoded), contract, null, - new TransactionResult())); - return contract; - } + public SolidityContract submitNewContract(ContractMetadata contractMetaData, Object... constructorArgs) { + SolidityContractImpl contract = new SolidityContractImpl(contractMetaData); + return submitNewContract(contract, constructorArgs); + } + + private SolidityContract submitNewContract(SolidityContractImpl contract, Object... constructorArgs) { + CallTransaction.Function constructor = contract.contract.getConstructor(); + if (constructor == null && constructorArgs.length > 0) { + throw new RuntimeException("No constructor with params found"); + } + byte[] argsEncoded = constructor == null ? new byte[0] : constructor.encodeArguments(constructorArgs); + submitNewTx(new PendingTx(new byte[0], BigInteger.ZERO, + ByteUtil.merge(Hex.decode(contract.getBinary()), argsEncoded), contract, null, + new TransactionResult())); + return contract; + } private SolidityContractImpl createContract(String soliditySrc, String contractName) { try { SolidityCompiler.Result compileRes = SolidityCompiler.compile(soliditySrc.getBytes(), true, SolidityCompiler.Options.ABI, SolidityCompiler.Options.BIN); if (compileRes.isFailed()) throw new RuntimeException("Compile result: " + compileRes.errors); - return createContractFromJson(contractName, compileRes.output); + return createContractFromJson(contractName, compileRes.output); } catch (IOException e) { throw new RuntimeException(e); } } - private SolidityContractImpl createContractFromJson(String contractName, String json) throws IOException { - CompilationResult result = CompilationResult.parse(json); - if (contractName == null) { + private SolidityContractImpl createContractFromJson(String contractName, String json) throws IOException { + CompilationResult result = CompilationResult.parse(json); + if (contractName == null) { contractName = result.getContractName(); - } - - return createContract(contractName, result); - } - - /** - * @param contractName - * @param result - * @return - */ - private SolidityContractImpl createContract(String contractName, CompilationResult result) { - ContractMetadata cMetaData = result.getContract(contractName); - SolidityContractImpl contract = createContract(cMetaData); - - for (CompilationResult.ContractMetadata metadata : result.getContracts()) { - contract.addRelatedContract(metadata.abi); - } - return contract; - } - - private SolidityContractImpl createContract(ContractMetadata contractData) { - SolidityContractImpl contract = new SolidityContractImpl(contractData); - return contract; - } + } + + return createContract(contractName, result); + } + + /** + * @param contractName + * @param result + * @return + */ + private SolidityContractImpl createContract(String contractName, CompilationResult result) { + ContractMetadata cMetaData = result.getContract(contractName); + SolidityContractImpl contract = createContract(cMetaData); + + for (CompilationResult.ContractMetadata metadata : result.getContracts()) { + contract.addRelatedContract(metadata.abi); + } + return contract; + } + + private SolidityContractImpl createContract(ContractMetadata contractData) { + SolidityContractImpl contract = new SolidityContractImpl(contractData); + return contract; + } @Override public SolidityContract createExistingContractFromSrc(String soliditySrc, String contractName, byte[] contractAddress) { @@ -438,19 +447,24 @@ public BlockchainImpl getBlockchain() { if (blockchain == null) { blockchain = createBlockchain(genesis); blockchain.setMinerCoinbase(coinbase); - addEthereumListener(new EthereumListenerAdapter() { - @Override - public void onBlock(BlockSummary blockSummary) { - lastSummary = blockSummary; - } - }); + subscribe(to(BLOCK_ADDED, data -> lastSummary = data.getBlockSummary())); } return blockchain; } + /** + * @param listener + * @deprecated use {@link #subscribe(Subscription)} instead. + */ + @Deprecated public void addEthereumListener(EthereumListener listener) { getBlockchain(); - this.listener.addListener(listener); + listenerProxy.addListener(listener); + } + + public , P> StandaloneBlockchain subscribe(Subscription subscription) { + listenerProxy.getPublisher().subscribe(subscription); + return this; } private void submitNewTx(PendingTx tx) { @@ -487,10 +501,8 @@ private BlockchainImpl createBlockchain(Genesis genesis) { final RepositoryRoot repository = new RepositoryRoot(pruningStateDS); ProgramInvokeFactoryImpl programInvokeFactory = new ProgramInvokeFactoryImpl(); - listener = new CompositeEthereumListener(); - BlockchainImpl blockchain = new BlockchainImpl(blockStore, repository) - .withEthereumListener(listener) + BlockchainImpl blockchain = new BlockchainImpl(blockStore, repository, listenerProxy) .withSyncManager(new SyncManager()) .withVmHook(vmHook); blockchain.setParentHeaderValidator(new DependentBlockHeaderRuleAdapter()); @@ -499,7 +511,7 @@ private BlockchainImpl createBlockchain(Genesis genesis) { blockchain.byTest = true; - pendingState = new PendingStateImpl(listener); + pendingState = new PendingStateImpl(listenerProxy); pendingState.setBlockchain(blockchain); blockchain.setPendingState(pendingState); @@ -547,6 +559,7 @@ public class SolidityContractImpl implements SolidityContract { public SolidityContractImpl(String abi) { contract = new CallTransaction.Contract(abi); } + public SolidityContractImpl(CompilationResult.ContractMetadata result) { this(result.abi); compiled = result; @@ -719,7 +732,7 @@ public byte[] getStorageSlot(byte[] slot) { } } - class SlowHashMapDB extends HashMapDB { + class SlowHashMapDB extends HashMapDB { private void sleep(int cnt) { totalDbHits += cnt; if (dbDelay == 0) return; @@ -756,12 +769,12 @@ public synchronized void updateBatch(Map rows) { } // Override blockchain net config for fast mining - public static FrontierConfig getEasyMiningConfig() { - return new FrontierConfig(new FrontierConfig.FrontierConstants() { + public static ByzantiumConfig getEasyMiningConfig() { + return new ByzantiumConfig(new DaoNoHFConfig(new HomesteadConfig(new HomesteadConfig.HomesteadConstants() { @Override public BigInteger getMINIMUM_DIFFICULTY() { return BigInteger.ONE; } - }); + }), 0)); } } diff --git a/ethereumj-core/src/main/java/org/ethereum/validator/EthashRule.java b/ethereumj-core/src/main/java/org/ethereum/validator/EthashRule.java index 467abc2e8d..38895ba742 100644 --- a/ethereumj-core/src/main/java/org/ethereum/validator/EthashRule.java +++ b/ethereumj-core/src/main/java/org/ethereum/validator/EthashRule.java @@ -20,16 +20,16 @@ import org.apache.commons.lang3.tuple.Pair; import org.ethereum.config.SystemProperties; import org.ethereum.core.BlockHeader; -import org.ethereum.core.BlockSummary; -import org.ethereum.listener.CompositeEthereumListener; -import org.ethereum.listener.EthereumListenerAdapter; import org.ethereum.mine.EthashValidationHelper; +import org.ethereum.publish.Publisher; import org.ethereum.util.FastByteComparisons; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Random; +import static org.ethereum.publish.event.Events.Type.BLOCK_ADDED; +import static org.ethereum.publish.event.Events.Type.SYNC_DONE; import static org.ethereum.validator.EthashRule.ChainType.main; import static org.ethereum.validator.EthashRule.ChainType.reverse; import static org.ethereum.validator.EthashRule.Mode.fake; @@ -39,14 +39,13 @@ * Runs block header validation against Ethash dataset. * *

- * Configurable to work in several modes: - *

    - *
  • fake - partial checks without verification against Ethash dataset - *
  • strict - full check for each block - *
  • mixed - run full check for each block if main import flow during short sync, - * run full check in random fashion (1/{@link #MIX_DENOMINATOR} blocks are checked) - * during long sync, fast sync headers and blocks downloading - * + * Configurable to work in several modes: + *
      + *
    • fake - partial checks without verification against Ethash dataset + *
    • strict - full check for each block + *
    • mixed - run full check for each block if main import flow during short sync, + * run full check in random fashion (1/{@link #MIX_DENOMINATOR} blocks are checked) + * during long sync, fast sync headers and blocks downloading * * @author Mikhail Kalinin * @since 19.06.2018 @@ -89,15 +88,15 @@ public boolean isSide() { private Random rnd = new Random(); // two most common settings - public static EthashRule createRegular(SystemProperties systemProperties, CompositeEthereumListener listener) { - return new EthashRule(Mode.parse(systemProperties.getEthashMode(), mixed), main, listener); + public static EthashRule createRegular(SystemProperties systemProperties, Publisher publisher) { + return new EthashRule(Mode.parse(systemProperties.getEthashMode(), mixed), main, publisher); } public static EthashRule createReverse(SystemProperties systemProperties) { return new EthashRule(Mode.parse(systemProperties.getEthashMode(), mixed), reverse, null); } - public EthashRule(Mode mode, ChainType chain, CompositeEthereumListener listener) { + public EthashRule(Mode mode, ChainType chain, Publisher publisher) { this.mode = mode; this.chain = chain; @@ -105,18 +104,14 @@ public EthashRule(Mode mode, ChainType chain, CompositeEthereumListener listener this.ethashHelper = new EthashValidationHelper( chain == reverse ? EthashValidationHelper.CacheOrder.reverse : EthashValidationHelper.CacheOrder.direct); - if (this.chain == main && listener != null) { - listener.addListener(new EthereumListenerAdapter() { - @Override - public void onSyncDone(SyncState state) { - EthashRule.this.syncDone = true; - } - - @Override - public void onBlock(BlockSummary blockSummary, boolean best) { - if (best) ethashHelper.preCache(blockSummary.getBlock().getNumber()); - } - }); + if (this.chain == main && publisher != null) { + publisher + .subscribe(SYNC_DONE, ss -> EthashRule.this.syncDone = true) + .subscribe(BLOCK_ADDED, data -> { + if (data.isBest()) { + ethashHelper.preCache(data.getBlockSummary().getBlock().getNumber()); + } + }); } } } diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/PrecompiledContracts.java b/ethereumj-core/src/main/java/org/ethereum/vm/PrecompiledContracts.java index 562b75e42a..4feb782cb2 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/PrecompiledContracts.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/PrecompiledContracts.java @@ -30,6 +30,7 @@ import static org.ethereum.util.BIUtil.isLessThan; import static org.ethereum.util.BIUtil.isZero; import static org.ethereum.util.ByteUtil.*; +import static org.ethereum.vm.VMUtils.getSizeInWords; /** * @author Roman Mandeleil @@ -102,7 +103,7 @@ public long getGasForData(byte[] data) { // gas charge for the execution: // minimum 1 and additional 1 for each 32 bytes word (round up) if (data == null) return 15; - return 15 + (data.length + 31) / 32 * 3; + return 15 + getSizeInWords(data.length) * 3; } @Override @@ -120,7 +121,7 @@ public long getGasForData(byte[] data) { // gas charge for the execution: // minimum 50 and additional 50 for each 32 bytes word (round up) if (data == null) return 60; - return 60 + (data.length + 31) / 32 * 12; + return 60 + getSizeInWords(data.length) * 12; } @Override @@ -142,7 +143,7 @@ public long getGasForData(byte[] data) { // gas charge for the execution: // minimum 50 and additional 50 for each 32 bytes word (round up) if (data == null) return 600; - return 600 + (data.length + 31) / 32 * 120; + return 600 + getSizeInWords(data.length) * 120; } @Override diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/VM.java b/ethereumj-core/src/main/java/org/ethereum/vm/VM.java index 04619a6181..37101de763 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/VM.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/VM.java @@ -41,6 +41,7 @@ import static org.ethereum.util.ByteUtil.EMPTY_BYTE_ARRAY; import static org.ethereum.util.ByteUtil.toHexString; import static org.ethereum.vm.OpCode.*; +import static org.ethereum.vm.VMUtils.getSizeInWords; /** * The Ethereum Virtual Machine (EVM) is responsible for initialization @@ -320,7 +321,7 @@ else if (!currentValue.isZero() && newValue.isZero()) { case SHA3: gasCost = gasCosts.getSHA3() + calcMemGas(gasCosts, oldMemSize, memNeeded(stack.peek(), stack.get(stack.size() - 2)), 0); DataWord size = stack.get(stack.size() - 2); - long chunkUsed = (size.longValueSafe() + 31) / 32; + long chunkUsed = getSizeInWords(size.longValueSafe()); gasCost += chunkUsed * gasCosts.getSHA3_WORD(); break; case CALLDATACOPY: @@ -394,8 +395,10 @@ else if (!currentValue.isZero() && newValue.isZero()) { memNeeded(stack.get(stack.size() - 2), stack.get(stack.size() - 3)), 0); break; case CREATE2: - gasCost = gasCosts.getCREATE() + calcMemGas(gasCosts, oldMemSize, - memNeeded(stack.get(stack.size() - 2), stack.get(stack.size() - 3)), 0); + DataWord codeSize = stack.get(stack.size() - 3); + gasCost = gasCosts.getCREATE() + + calcMemGas(gasCosts, oldMemSize, memNeeded(stack.get(stack.size() - 2), codeSize), 0) + + getSizeInWords(codeSize.longValueSafe()) * gasCosts.getSHA3_WORD(); break; case LOG0: case LOG1: diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/VMUtils.java b/ethereumj-core/src/main/java/org/ethereum/vm/VMUtils.java index 252ebcf225..8fe6809aae 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/VMUtils.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/VMUtils.java @@ -153,4 +153,11 @@ public static String unzipAndDecode(String content) { return content; } } + + /** + * Returns number of VM words required to hold data of size {@code size} + */ + public static long getSizeInWords(long size) { + return size == 0 ? 0 : (size - 1) / 32 + 1; + } } diff --git a/ethereumj-core/src/main/resources/genesis/sample-local-genesis.json b/ethereumj-core/src/main/resources/genesis/sample-local-genesis.json new file mode 100644 index 0000000000..603e7c40f0 --- /dev/null +++ b/ethereumj-core/src/main/resources/genesis/sample-local-genesis.json @@ -0,0 +1,27 @@ +{ + "alloc": { + "33dc1c95f5a0a25bb82ed171f263bf776f210005": { + "balance": "1606938044258990275541962092341162602522202993782792835301376" + }, + "86549a9def66a77a351fb4321e1674fc4bcb7aad": { + "balance": "1606938044258990275541962092341162602522202993782792835301376" + }, + "07e45810d7d442cc2e2863688ab3eb0effdb1e62": { + "balance": "1606938044258990275541962092341162602522202993782792835301376" + }, + "21840989a52616816c87fb956191eea09bc779ed": { + "balance": "1606938044258990275541962092341162602522202993782792835301376" + }, + "68293c7fbde6f439f65ea0d1afc3af99f70a82f5": { + "balance": "1606938044258990275541962092341162602522202993782792835301376" + } + }, + "nonce": "0x0000000000000000", + "difficulty": "0xff", + "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "timestamp": "0x00", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extraData": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa", + "gasLimit": "0x1000000000" +} \ No newline at end of file diff --git a/ethereumj-core/src/main/resources/samples/contracts/sample.sol b/ethereumj-core/src/main/resources/samples/contracts/sample.sol new file mode 100644 index 0000000000..3819e30a16 --- /dev/null +++ b/ethereumj-core/src/main/resources/samples/contracts/sample.sol @@ -0,0 +1,19 @@ +pragma solidity ^0.4.11; + +contract Sample { + int i; + event Inc( + address _from, + int _inc, + int _total + ); + + function inc(int n) { + i = i + n; + Inc(msg.sender, n, i); + } + + function get() returns (int) { + return i; + } +} \ No newline at end of file diff --git a/ethereumj-core/src/test/java/org/ethereum/config/DaoLightMiningTest.java b/ethereumj-core/src/test/java/org/ethereum/config/DaoLightMiningTest.java index 0d9db20b31..90c4a81a58 100644 --- a/ethereumj-core/src/test/java/org/ethereum/config/DaoLightMiningTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/config/DaoLightMiningTest.java @@ -74,7 +74,7 @@ private String getData(StandaloneBlockchain sb, long blockNumber) { private StandaloneBlockchain createBlockchain(boolean proFork) { final BaseNetConfig netConfig = new BaseNetConfig(); - final FrontierConfig c1 = StandaloneBlockchain.getEasyMiningConfig(); + final BlockchainConfig c1 = StandaloneBlockchain.getEasyMiningConfig(); netConfig.add(0, StandaloneBlockchain.getEasyMiningConfig()); netConfig.add(FORK_BLOCK, proFork ? new DaoHFConfig(c1, FORK_BLOCK) : new DaoNoHFConfig(c1, FORK_BLOCK)); diff --git a/ethereumj-core/src/test/java/org/ethereum/core/CloseTest.java b/ethereumj-core/src/test/java/org/ethereum/core/CloseTest.java index 911be149aa..fccbb93f7d 100644 --- a/ethereumj-core/src/test/java/org/ethereum/core/CloseTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/core/CloseTest.java @@ -19,13 +19,14 @@ import org.ethereum.facade.Ethereum; import org.ethereum.facade.EthereumFactory; -import org.ethereum.listener.EthereumListenerAdapter; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; -import java.util.List; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.ethereum.publish.event.Events.Type.BLOCK_ADDED; /** * Created by Anton Nashatyrev on 24.06.2016. @@ -41,14 +42,13 @@ public void relaunchTest() throws InterruptedException { Block bestBlock = ethereum.getBlockchain().getBestBlock(); Assert.assertNotNull(bestBlock); final CountDownLatch latch = new CountDownLatch(1); - ethereum.addListener(new EthereumListenerAdapter() { - int counter = 0; - @Override - public void onBlock(Block block, List receipts) { - counter++; - if (counter > 1100) latch.countDown(); + AtomicInteger counter = new AtomicInteger(); + ethereum.subscribe(BLOCK_ADDED, data -> { + if (counter.addAndGet(1) > 1100) { + latch.countDown(); } }); + System.out.println("### Waiting for some blocks to be imported..."); latch.await(); System.out.println("### Closing Ethereum instance"); diff --git a/ethereumj-core/src/test/java/org/ethereum/core/ImportLightTest.java b/ethereumj-core/src/test/java/org/ethereum/core/ImportLightTest.java index e805bb2211..d891fca0f4 100644 --- a/ethereumj-core/src/test/java/org/ethereum/core/ImportLightTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/core/ImportLightTest.java @@ -22,12 +22,12 @@ import org.ethereum.core.genesis.GenesisLoader; import org.ethereum.crypto.ECKey; import org.ethereum.crypto.HashUtil; -import org.ethereum.datasource.inmem.HashMapDB; import org.ethereum.datasource.NoDeleteSource; +import org.ethereum.datasource.inmem.HashMapDB; import org.ethereum.db.IndexedBlockStore; import org.ethereum.db.RepositoryRoot; -import org.ethereum.listener.EthereumListenerAdapter; import org.ethereum.mine.Ethash; +import org.ethereum.listener.BackwardCompatibilityEthereumListenerProxy; import org.ethereum.util.ByteUtil; import org.ethereum.util.blockchain.SolidityContract; import org.ethereum.util.blockchain.StandaloneBlockchain; @@ -44,6 +44,9 @@ import java.math.BigInteger; import java.util.*; +import static org.ethereum.publish.Subscription.to; +import static org.ethereum.publish.event.Events.Type.TRANSACTION_EXECUTED; + /** * Created by Anton Nashatyrev on 29.12.2015. */ @@ -351,24 +354,24 @@ public void createContractFork() throws Exception { String contractSrc = "contract Child {" + - " int a;" + - " int b;" + - " int public c;" + - " function Child(int i) {" + - " a = 333 + i;" + - " b = 444 + i;" + - " }" + - " function sum() {" + - " c = a + b;" + - " }" + - "}" + - "contract Parent {" + - " address public child;" + - " function createChild(int a) returns (address) {" + - " child = new Child(a);" + - " return child;" + - " }" + - "}"; + " int a;" + + " int b;" + + " int public c;" + + " function Child(int i) {" + + " a = 333 + i;" + + " b = 444 + i;" + + " }" + + " function sum() {" + + " c = a + b;" + + " }" + + "}" + + "contract Parent {" + + " address public child;" + + " function createChild(int a) returns (address) {" + + " child = new Child(a);" + + " return child;" + + " }" + + "}"; StandaloneBlockchain bc = new StandaloneBlockchain(); SolidityContract parent = bc.submitNewContract(contractSrc, "Parent"); @@ -390,17 +393,17 @@ public void createContractFork1() throws Exception { // Test creation of the contract on forked branch with different storage String contractSrc = "contract A {" + - " int public a;" + - " function A() {" + - " a = 333;" + - " }" + - "}" + - "contract B {" + - " int public a;" + - " function B() {" + - " a = 111;" + - " }" + - "}"; + " int public a;" + + " function A() {" + + " a = 333;" + + " }" + + "}" + + "contract B {" + + " int public a;" + + " function B() {" + + " a = 111;" + + " }" + + "}"; { StandaloneBlockchain bc = new StandaloneBlockchain(); @@ -425,20 +428,20 @@ public void createValueTest() throws IOException, InterruptedException { // checks that correct msg.value is passed when contract internally created with value String contract = "pragma solidity ^0.4.3;\n" + - "contract B {\n" + - " uint public valReceived;\n" + - " \n" + - " function B() payable {\n" + - " valReceived = msg.value;\n" + - " }\n" + - "}\n" + - "contract A {\n" + - " function () payable { }\n" + - " address public child;\n" + - " function create() payable {\n" + - " child = (new B).value(20)();\n" + - " }\n" + - "}"; + "contract B {\n" + + " uint public valReceived;\n" + + " \n" + + " function B() payable {\n" + + " valReceived = msg.value;\n" + + " }\n" + + "}\n" + + "contract A {\n" + + " function () payable { }\n" + + " address public child;\n" + + " function create() payable {\n" + + " child = (new B).value(20)();\n" + + " }\n" + + "}"; StandaloneBlockchain bc = new StandaloneBlockchain().withAutoblock(true); SolidityContract a = bc.submitNewContract(contract, "A"); bc.sendEther(a.getAddress(), BigInteger.valueOf(10_000)); @@ -453,17 +456,17 @@ public void createValueTest() throws IOException, InterruptedException { public void contractCodeForkTest() throws IOException, InterruptedException { String contractA = "contract A {" + - " function call() returns (uint) {" + - " return 111;" + - " }" + - "}"; + " function call() returns (uint) {" + + " return 111;" + + " }" + + "}"; String contractB = "contract B {" + - " function call() returns (uint) {" + - " return 222222;" + - " }" + - "}"; + " function call() returns (uint) {" + + " return 222222;" + + " }" + + "}"; StandaloneBlockchain bc = new StandaloneBlockchain(); Block b1 = bc.createBlock(); @@ -485,18 +488,18 @@ public void operateNotExistingContractTest() throws IOException, InterruptedExce byte[] addr = Hex.decode("0101010101010101010101010101010101010101"); String contractA = "pragma solidity ^0.4.3;" + - "contract B { function dummy() {}}" + - "contract A {" + - " function callBalance() returns (uint) {" + - " address addr = 0x" + Hex.toHexString(addr) + ";" + - " uint bal = addr.balance;" + - " }" + - " function callMethod() returns (uint) {" + - " address addr = 0x" + Hex.toHexString(addr) + ";" + - " B b = B(addr);" + - " b.dummy();" + - " }" + - "}"; + "contract B { function dummy() {}}" + + "contract A {" + + " function callBalance() returns (uint) {" + + " address addr = 0x" + Hex.toHexString(addr) + ";" + + " uint bal = addr.balance;" + + " }" + + " function callMethod() returns (uint) {" + + " address addr = 0x" + Hex.toHexString(addr) + ";" + + " B b = B(addr);" + + " b.dummy();" + + " }" + + "}"; StandaloneBlockchain bc = new StandaloneBlockchain() .withGasPrice(1) @@ -513,7 +516,7 @@ public void operateNotExistingContractTest() throws IOException, InterruptedExce // checking balance of not existed address should take // less that gas limit - Assert.assertEquals(21532, spent); + Assert.assertTrue(spent < 100_000); } { @@ -526,7 +529,10 @@ public void operateNotExistingContractTest() throws IOException, InterruptedExce // invalid jump error occurred // all gas wasted // (for history: it is worked fine in ^0.3.1) - Assert.assertEquals(5_000_000L, spent); + // Assert.assertEquals(5_000_000L, spent); + + // FIX for 0.4.25 and apparently some earlier versions + Assert.assertTrue(spent < 100_000); } } @@ -552,10 +558,10 @@ public void spendGasSimpleTest() throws IOException, InterruptedException { public void deepRecursionTest() throws Exception { String contractA = "contract A {" + - " function recursive(){" + - " this.recursive();" + - " }" + - "}"; + " function recursive(){" + + " this.recursive();" + + " }" + + "}"; StandaloneBlockchain bc = new StandaloneBlockchain().withGasLimit(5_000_000); SolidityContract a = bc.submitNewContract(contractA, "A"); @@ -570,10 +576,10 @@ public void deepRecursionTest() throws Exception { public void prevBlockHashOnFork() throws Exception { String contractA = "contract A {" + - " bytes32 public blockHash;" + - " function a(){" + - " blockHash = block.blockhash(block.number - 1);" + - " }" + + " bytes32 public blockHash;" + + " function a(){" + + " blockHash = block.blockhash(block.number - 1);" + + " }" + "}"; StandaloneBlockchain bc = new StandaloneBlockchain(); @@ -598,19 +604,19 @@ public void prevBlockHashOnFork() throws Exception { public void rollbackInternalTx() throws Exception { String contractA = "contract A {" + - " uint public a;" + - " uint public b;" + - " function f() {" + - " b = 1;" + - " this.call(bytes4(sha3('exception()')));" + - " a = 2;" + - " }" + + " uint public a;" + + " uint public b;" + + " function f() {" + + " b = 1;" + + " this.call.gas(10000)(bytes4(sha3('exception()')));" + + " a = 2;" + + " }" + - " function exception() {" + - " b = 2;" + - " throw;" + - " }" + - "}"; + " function exception() {" + + " b = 2;" + + " throw;" + + " }" + + "}"; StandaloneBlockchain bc = new StandaloneBlockchain(); SolidityContract a = bc.submitNewContract(contractA); @@ -638,7 +644,7 @@ public void selfdestructAttack() throws Exception { " function f() {" + " B b = new B();" + " for (uint i = 0; i < 3500; i++) {" + - " b.suicide(address(i));" + + " b.suicide.gas(10000)(address(i));" + " }" + " a = 2;" + " }" + @@ -663,15 +669,15 @@ public void selfdestructAttack() throws Exception { public void threadRacePendingTest() throws Exception { String contractA = "contract A {" + - " uint[32] public somedata1;" + - " uint[32] public somedata2;" + - " function set1(uint idx, uint val){" + - " somedata1[idx] = val;" + - " }" + - " function set2(uint idx, uint val){" + - " somedata2[idx] = val;" + - " }" + - "}"; + " uint[32] public somedata1;" + + " uint[32] public somedata2;" + + " function set1(uint idx, uint val){" + + " somedata1[idx] = val;" + + " }" + + " function set2(uint idx, uint val){" + + " somedata2[idx] = val;" + + " }" + + "}"; final StandaloneBlockchain bc = new StandaloneBlockchain(); final StandaloneBlockchain.SolidityContractImpl a = (StandaloneBlockchain.SolidityContractImpl) bc.submitNewContract(contractA); @@ -699,7 +705,7 @@ public void threadRacePendingTest() throws Exception { }).start(); Block b_1 = null; - while(cnt++ > 0) { + while (cnt++ > 0) { long s = System.nanoTime(); a.callFunction("set1", cnt % 32, cnt); @@ -746,14 +752,12 @@ public void threadRacePendingTest() throws Exception { } - - @Test public void suicideInFailedCall() throws Exception { // check that if a contract is suicide in call which is failed (thus suicide is reverted) // the refund for this suicide is not added String contractA = - "contract B {" + + "contract B {" + " function f(){" + " suicide(msg.sender);" + " }" + @@ -773,12 +777,7 @@ public void suicideInFailedCall() throws Exception { SolidityContract a = bc.submitNewContract(contractA, "A"); bc.createBlock(); final BigInteger[] refund = new BigInteger[1]; - bc.addEthereumListener(new EthereumListenerAdapter() { - @Override - public void onTransactionExecuted(TransactionExecutionSummary summary) { - refund[0] = summary.getGasRefund(); - } - }); + bc.subscribe(to(TRANSACTION_EXECUTED, tes -> refund[0] = tes.getGasRefund())); a.callFunction("f"); bc.createBlock(); @@ -792,7 +791,7 @@ public void logInFailedCall() throws Exception { // check that if a contract is suicide in call which is failed (thus suicide is reverted) // the refund for this suicide is not added String contractA = - "contract A {" + + "contract A {" + " function f(){" + " this.call(bytes4(sha3('bad()')));" + " }" + @@ -806,12 +805,7 @@ public void logInFailedCall() throws Exception { SolidityContract a = bc.submitNewContract(contractA, "A"); bc.createBlock(); final List logs = new ArrayList<>(); - bc.addEthereumListener(new EthereumListenerAdapter() { - @Override - public void onTransactionExecuted(TransactionExecutionSummary summary) { - logs.addAll(summary.getLogs()); - } - }); + bc.subscribe(to(TRANSACTION_EXECUTED, tes -> logs.addAll(tes.getLogs()))); a.callFunction("f"); bc.createBlock(); @@ -853,7 +847,7 @@ public void ecRecoverTest() throws Exception { Assert.assertArrayEquals(key.getAddress(), (byte[]) ret[0]); ret = a.callConstFunction("f", hash, - ByteUtil.merge(new byte[] {1}, new byte[30], new byte[]{signature.v}), + ByteUtil.merge(new byte[]{1}, new byte[30], new byte[]{signature.v}), ByteUtil.bigIntegerToBytes(signature.r, 32), ByteUtil.bigIntegerToBytes(signature.s, 32)); @@ -866,12 +860,12 @@ public void functionTypeTest() throws IOException, InterruptedException { "contract A {" + " int public res;" + " function calc(int b, function (int a) external returns (int) f) external returns (int) {" + - " return f(b);" + + " return f.gas(10000)(b);" + " }" + " function fInc(int a) external returns (int) { return a + 1;}" + " function fDec(int a) external returns (int) { return a - 1;}" + " function test() {" + - " res = this.calc(111, this.fInc);" + + " res = this.calc.gas(100000)(111, this.fInc);" + " }" + "}"; @@ -880,7 +874,7 @@ public void functionTypeTest() throws IOException, InterruptedException { bc.createBlock(); a.callFunction("test"); bc.createBlock(); - Assert.assertEquals(a.callConstFunction("res")[0], BigInteger.valueOf(112)); + Assert.assertEquals(BigInteger.valueOf(112), a.callConstFunction("res")[0]); BigInteger r1 = (BigInteger) a.callConstFunction("calc", 222, a.getFunction("fInc"))[0]; Assert.assertEquals(223, r1.intValue()); @@ -895,16 +889,16 @@ public static BlockchainImpl createBlockchain(Genesis genesis) { RepositoryRoot repository = new RepositoryRoot(new NoDeleteSource<>(new HashMapDB())); ProgramInvokeFactoryImpl programInvokeFactory = new ProgramInvokeFactoryImpl(); - EthereumListenerAdapter listener = new EthereumListenerAdapter(); - BlockchainImpl blockchain = new BlockchainImpl(blockStore, repository) + BackwardCompatibilityEthereumListenerProxy listenerProxy = BackwardCompatibilityEthereumListenerProxy.createDefault(); + BlockchainImpl blockchain = new BlockchainImpl(blockStore, repository, listenerProxy) .withParentBlockHeaderValidator(new CommonConfig().parentHeaderValidator()); blockchain.setParentHeaderValidator(new DependentBlockHeaderRuleAdapter()); blockchain.setProgramInvokeFactory(programInvokeFactory); blockchain.byTest = true; - PendingStateImpl pendingState = new PendingStateImpl(listener); + PendingStateImpl pendingState = new PendingStateImpl(listenerProxy); pendingState.setBlockchain(blockchain); blockchain.setPendingState(pendingState); diff --git a/ethereumj-core/src/test/java/org/ethereum/core/PendingStateLongRunTest.java b/ethereumj-core/src/test/java/org/ethereum/core/PendingStateLongRunTest.java index 398dbec5c5..72e0620a32 100644 --- a/ethereumj-core/src/test/java/org/ethereum/core/PendingStateLongRunTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/core/PendingStateLongRunTest.java @@ -19,10 +19,9 @@ import org.ethereum.config.CommonConfig; import org.ethereum.datasource.inmem.HashMapDB; -import org.ethereum.db.RepositoryRoot; -import org.ethereum.db.ByteArrayWrapper; import org.ethereum.db.IndexedBlockStore; -import org.ethereum.listener.EthereumListenerAdapter; +import org.ethereum.db.RepositoryRoot; +import org.ethereum.listener.BackwardCompatibilityEthereumListenerProxy; import org.ethereum.validator.DependentBlockHeaderRuleAdapter; import org.ethereum.vm.program.invoke.ProgramInvokeFactoryImpl; import org.junit.Before; @@ -40,7 +39,7 @@ import java.util.List; import static org.ethereum.util.BIUtil.toBI; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; /** * @author Mikhail Kalinin @@ -125,14 +124,15 @@ private Blockchain createBlockchain(Genesis genesis) { ProgramInvokeFactoryImpl programInvokeFactory = new ProgramInvokeFactoryImpl(); - BlockchainImpl blockchain = new BlockchainImpl(blockStore, repository) + BackwardCompatibilityEthereumListenerProxy listenerProxy = BackwardCompatibilityEthereumListenerProxy.createDefault(); + BlockchainImpl blockchain = new BlockchainImpl(blockStore, repository, listenerProxy) .withParentBlockHeaderValidator(new CommonConfig().parentHeaderValidator()); blockchain.setParentHeaderValidator(new DependentBlockHeaderRuleAdapter()); blockchain.setProgramInvokeFactory(programInvokeFactory); blockchain.byTest = true; - PendingStateImpl pendingState = new PendingStateImpl(new EthereumListenerAdapter()); + PendingStateImpl pendingState = new PendingStateImpl(listenerProxy); pendingState.setBlockchain(blockchain); blockchain.setPendingState(pendingState); diff --git a/ethereumj-core/src/test/java/org/ethereum/core/PendingStateTest.java b/ethereumj-core/src/test/java/org/ethereum/core/PendingStateTest.java index 858a7eac5c..30467f7693 100644 --- a/ethereumj-core/src/test/java/org/ethereum/core/PendingStateTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/core/PendingStateTest.java @@ -20,15 +20,16 @@ import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Triple; import org.ethereum.config.SystemProperties; -import org.ethereum.config.blockchain.FrontierConfig; -import org.ethereum.config.net.MainNetConfig; import org.ethereum.crypto.ECKey; import org.ethereum.db.ByteArrayWrapper; -import org.ethereum.listener.EthereumListener; -import org.ethereum.listener.EthereumListenerAdapter; +import org.ethereum.publish.event.BlockAdded; +import org.ethereum.publish.event.PendingTransactionUpdated; import org.ethereum.util.blockchain.SolidityContract; import org.ethereum.util.blockchain.StandaloneBlockchain; -import org.junit.*; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; import java.math.BigInteger; import java.util.HashMap; @@ -41,7 +42,14 @@ import java.util.concurrent.TimeUnit; import static java.util.concurrent.TimeUnit.SECONDS; -import static org.ethereum.listener.EthereumListener.PendingTransactionState.*; +import static org.ethereum.core.PendingTransaction.State.DROPPED; +import static org.ethereum.core.PendingTransaction.State.INCLUDED; +import static org.ethereum.core.PendingTransaction.State.NEW_PENDING; +import static org.ethereum.core.PendingTransaction.State.PENDING; +import static org.ethereum.publish.event.Events.Type.BLOCK_ADDED; +import static org.ethereum.publish.event.Events.Type.PENDING_STATE_CHANGED; +import static org.ethereum.publish.event.Events.Type.PENDING_TRANSACTION_UPDATED; +import static org.ethereum.publish.Subscription.to; import static org.ethereum.util.blockchain.EtherUtil.Unit.ETHER; import static org.ethereum.util.blockchain.EtherUtil.convert; @@ -61,35 +69,39 @@ public static void cleanup() { SystemProperties.resetToDefault(); } - static class PendingListener extends EthereumListenerAdapter { + static class PendingListener { public BlockingQueue>> onBlock = new LinkedBlockingQueue<>(); public BlockingQueue onPendingStateChanged = new LinkedBlockingQueue<>(); -// public BlockingQueue> onPendingTransactionUpdate = new LinkedBlockingQueue<>(); +// public BlockingQueue> onPendingTransactionUpdate = new LinkedBlockingQueue<>(); - Map>> + Map>> onPendingTransactionUpdate = new HashMap<>(); - @Override - public void onBlock(Block block, List receipts) { + public void onBlock(BlockAdded.Data data) { + BlockSummary blockSummary = data.getBlockSummary(); + Block block = blockSummary.getBlock(); System.out.println("PendingStateTest.onBlock:" + "block = [" + block.getShortDescr() + "]"); - onBlock.add(Pair.of(block, receipts)); + onBlock.add(Pair.of(block, blockSummary.getReceipts())); } - @Override public void onPendingStateChanged(PendingState pendingState) { System.out.println("PendingStateTest.onPendingStateChanged."); onPendingStateChanged.add(new Object()); } - @Override - public void onPendingTransactionUpdate(TransactionReceipt txReceipt, PendingTransactionState state, Block block) { + public void onPendingTransactionUpdate(PendingTransactionUpdated.Data data) { + onPendingTransactionUpdate(data.getReceipt(), data.getState(), data.getBlock()); + } + + + public void onPendingTransactionUpdate(TransactionReceipt txReceipt, PendingTransaction.State state, Block block) { System.out.println("PendingStateTest.onPendingTransactionUpdate:" + "txReceipt.err = [" + txReceipt.getError() + "], state = [" + state + "], block: " + block.getShortDescr()); getQueueFor(txReceipt.getTransaction()).add(Triple.of(txReceipt, state, block)); } - public synchronized BlockingQueue> getQueueFor(Transaction tx) { + public synchronized BlockingQueue> getQueueFor(Transaction tx) { ByteArrayWrapper hashW = new ByteArrayWrapper(tx.getHash()); - BlockingQueue> queue = onPendingTransactionUpdate.get(hashW); + BlockingQueue> queue = onPendingTransactionUpdate.get(hashW); if (queue == null) { queue = new LinkedBlockingQueue<>(); onPendingTransactionUpdate.put(hashW, queue); @@ -97,20 +109,24 @@ public synchronized BlockingQueue pollTxUpdate(Transaction tx) throws InterruptedException { + + public Triple pollTxUpdate(Transaction tx) throws InterruptedException { return getQueueFor(tx).poll(5, SECONDS); } } @Test public void testSimple() throws InterruptedException { - StandaloneBlockchain bc = new StandaloneBlockchain(); PendingListener l = new PendingListener(); - bc.addEthereumListener(l); - Triple txUpd; + StandaloneBlockchain bc = new StandaloneBlockchain() + .subscribe(to(BLOCK_ADDED, l::onBlock)) + .subscribe(to(PENDING_STATE_CHANGED, l::onPendingStateChanged)) + .subscribe(to(PENDING_TRANSACTION_UPDATED, l::onPendingTransactionUpdate)); + + Triple txUpd; PendingStateImpl pendingState = (PendingStateImpl) bc.getBlockchain().getPendingState(); ECKey alice = new ECKey(); @@ -179,10 +195,13 @@ public void testSimple() throws InterruptedException { @Test public void testRebranch1() throws InterruptedException { - StandaloneBlockchain bc = new StandaloneBlockchain(); PendingListener l = new PendingListener(); - bc.addEthereumListener(l); - Triple txUpd = null; + StandaloneBlockchain bc = new StandaloneBlockchain() + .subscribe(to(BLOCK_ADDED, l::onBlock)) + .subscribe(to(PENDING_STATE_CHANGED, l::onPendingStateChanged)) + .subscribe(to(PENDING_TRANSACTION_UPDATED, l::onPendingTransactionUpdate)); + + Triple txUpd = null; PendingStateImpl pendingState = (PendingStateImpl) bc.getBlockchain().getPendingState(); ECKey alice = new ECKey(); @@ -196,7 +215,8 @@ public void testRebranch1() throws InterruptedException { Transaction tx1 = bc.createTransaction(bob, 0, alice.getAddress(), BigInteger.valueOf(1000000), new byte[0]); pendingState.addPendingTransaction(tx1); - Transaction tx2 = bc.createTransaction(charlie, 0, alice.getAddress(), BigInteger.valueOf(1000000), new byte[0]);; + Transaction tx2 = bc.createTransaction(charlie, 0, alice.getAddress(), BigInteger.valueOf(1000000), new byte[0]); + ; pendingState.addPendingTransaction(tx2); Assert.assertEquals(l.pollTxUpdateState(tx1), NEW_PENDING); @@ -244,10 +264,13 @@ public void testRebranch1() throws InterruptedException { @Test public void testRebranch2() throws InterruptedException { - StandaloneBlockchain bc = new StandaloneBlockchain(); PendingListener l = new PendingListener(); - bc.addEthereumListener(l); - Triple txUpd = null; + StandaloneBlockchain bc = new StandaloneBlockchain() + .subscribe(to(BLOCK_ADDED, l::onBlock)) + .subscribe(to(PENDING_STATE_CHANGED, l::onPendingStateChanged)) + .subscribe(to(PENDING_TRANSACTION_UPDATED, l::onPendingTransactionUpdate)); + + Triple txUpd = null; PendingStateImpl pendingState = (PendingStateImpl) bc.getBlockchain().getPendingState(); ECKey alice = new ECKey(); @@ -261,7 +284,8 @@ public void testRebranch2() throws InterruptedException { Transaction tx1 = bc.createTransaction(bob, 0, alice.getAddress(), BigInteger.valueOf(1000000), new byte[0]); pendingState.addPendingTransaction(tx1); - Transaction tx2 = bc.createTransaction(charlie, 0, alice.getAddress(), BigInteger.valueOf(1000000), new byte[0]);; + Transaction tx2 = bc.createTransaction(charlie, 0, alice.getAddress(), BigInteger.valueOf(1000000), new byte[0]); + ; pendingState.addPendingTransaction(tx2); Assert.assertEquals(l.pollTxUpdateState(tx1), NEW_PENDING); @@ -330,10 +354,13 @@ public void testRebranch2() throws InterruptedException { @Test public void testRebranch3() throws InterruptedException { - StandaloneBlockchain bc = new StandaloneBlockchain(); PendingListener l = new PendingListener(); - bc.addEthereumListener(l); - Triple txUpd = null; + StandaloneBlockchain bc = new StandaloneBlockchain() + .subscribe(to(BLOCK_ADDED, l::onBlock)) + .subscribe(to(PENDING_STATE_CHANGED, l::onPendingStateChanged)) + .subscribe(to(PENDING_TRANSACTION_UPDATED, l::onPendingTransactionUpdate)); + + Triple txUpd = null; PendingStateImpl pendingState = (PendingStateImpl) bc.getBlockchain().getPendingState(); ECKey alice = new ECKey(); @@ -379,10 +406,13 @@ public void testRebranch3() throws InterruptedException { @Test public void testOldBlockIncluded() throws InterruptedException { - StandaloneBlockchain bc = new StandaloneBlockchain(); PendingListener l = new PendingListener(); - bc.addEthereumListener(l); - Triple txUpd = null; + StandaloneBlockchain bc = new StandaloneBlockchain() + .subscribe(to(BLOCK_ADDED, l::onBlock)) + .subscribe(to(PENDING_STATE_CHANGED, l::onPendingStateChanged)) + .subscribe(to(PENDING_TRANSACTION_UPDATED, l::onPendingTransactionUpdate)); + + Triple txUpd = null; PendingStateImpl pendingState = (PendingStateImpl) bc.getBlockchain().getPendingState(); ECKey alice = new ECKey(); @@ -414,10 +444,13 @@ public void testOldBlockIncluded() throws InterruptedException { @Test public void testBlockOnlyIncluded() throws InterruptedException { - StandaloneBlockchain bc = new StandaloneBlockchain(); PendingListener l = new PendingListener(); - bc.addEthereumListener(l); - Triple txUpd = null; + StandaloneBlockchain bc = new StandaloneBlockchain() + .subscribe(to(BLOCK_ADDED, l::onBlock)) + .subscribe(to(PENDING_STATE_CHANGED, l::onPendingStateChanged)) + .subscribe(to(PENDING_TRANSACTION_UPDATED, l::onPendingTransactionUpdate)); + + Triple txUpd = null; PendingStateImpl pendingState = (PendingStateImpl) bc.getBlockchain().getPendingState(); ECKey alice = new ECKey(); @@ -440,10 +473,13 @@ public void testBlockOnlyIncluded() throws InterruptedException { @Test public void testTrackTx1() throws InterruptedException { - StandaloneBlockchain bc = new StandaloneBlockchain(); PendingListener l = new PendingListener(); - bc.addEthereumListener(l); - Triple txUpd = null; + StandaloneBlockchain bc = new StandaloneBlockchain() + .subscribe(to(BLOCK_ADDED, l::onBlock)) + .subscribe(to(PENDING_STATE_CHANGED, l::onPendingStateChanged)) + .subscribe(to(PENDING_TRANSACTION_UPDATED, l::onPendingTransactionUpdate)); + + Triple txUpd = null; PendingStateImpl pendingState = (PendingStateImpl) bc.getBlockchain().getPendingState(); ECKey alice = new ECKey(); @@ -492,8 +528,12 @@ public void testPrevBlock() throws InterruptedException { Block b3 = bc.createBlock(); PendingListener l = new PendingListener(); - bc.addEthereumListener(l); - Triple txUpd; + bc + .subscribe(to(BLOCK_ADDED, l::onBlock)) + .subscribe(to(PENDING_STATE_CHANGED, l::onPendingStateChanged)) + .subscribe(to(PENDING_TRANSACTION_UPDATED, l::onPendingTransactionUpdate)); + + Triple txUpd; contract.callFunction("getPrevBlockHash"); bc.generatePendingTransactions(); @@ -504,10 +544,13 @@ public void testPrevBlock() throws InterruptedException { @Test public void testTrackTx2() throws InterruptedException { - StandaloneBlockchain bc = new StandaloneBlockchain(); PendingListener l = new PendingListener(); - bc.addEthereumListener(l); - Triple txUpd = null; + StandaloneBlockchain bc = new StandaloneBlockchain() + .subscribe(to(BLOCK_ADDED, l::onBlock)) + .subscribe(to(PENDING_STATE_CHANGED, l::onPendingStateChanged)) + .subscribe(to(PENDING_TRANSACTION_UPDATED, l::onPendingTransactionUpdate)); + + Triple txUpd = null; PendingStateImpl pendingState = (PendingStateImpl) bc.getBlockchain().getPendingState(); ECKey alice = new ECKey(); @@ -534,10 +577,13 @@ public void testTrackTx2() throws InterruptedException { @Test public void testRejected1() throws InterruptedException { - StandaloneBlockchain bc = new StandaloneBlockchain(); PendingListener l = new PendingListener(); - bc.addEthereumListener(l); - Triple txUpd = null; + StandaloneBlockchain bc = new StandaloneBlockchain() + .subscribe(to(BLOCK_ADDED, l::onBlock)) + .subscribe(to(PENDING_STATE_CHANGED, l::onPendingStateChanged)) + .subscribe(to(PENDING_TRANSACTION_UPDATED, l::onPendingTransactionUpdate)); + + Triple txUpd = null; PendingStateImpl pendingState = (PendingStateImpl) bc.getBlockchain().getPendingState(); ECKey alice = new ECKey(); @@ -566,8 +612,8 @@ public void testRejected1() throws InterruptedException { for (int i = 0; i < 16; i++) { bc.createBlock(); - EthereumListener.PendingTransactionState state = l.pollTxUpdateState(tx1); - if (state == EthereumListener.PendingTransactionState.DROPPED) { + PendingTransaction.State state = l.pollTxUpdateState(tx1); + if (state == DROPPED) { break; } if (i == 15) { @@ -580,10 +626,13 @@ public void testRejected1() throws InterruptedException { public void testIncludedRejected() throws InterruptedException { // check INCLUDED => DROPPED state transition when a new (long) fork without // the transaction becomes the main chain - StandaloneBlockchain bc = new StandaloneBlockchain(); PendingListener l = new PendingListener(); - bc.addEthereumListener(l); - Triple txUpd = null; + StandaloneBlockchain bc = new StandaloneBlockchain() + .subscribe(to(BLOCK_ADDED, l::onBlock)) + .subscribe(to(PENDING_STATE_CHANGED, l::onPendingStateChanged)) + .subscribe(to(PENDING_TRANSACTION_UPDATED, l::onPendingTransactionUpdate)); + + Triple txUpd = null; PendingStateImpl pendingState = (PendingStateImpl) bc.getBlockchain().getPendingState(); ECKey alice = new ECKey(); @@ -621,18 +670,22 @@ public void testIncludedRejected() throws InterruptedException { @Test public void testInvalidTransaction() throws InterruptedException { - StandaloneBlockchain bc = new StandaloneBlockchain(); final CountDownLatch txHandle = new CountDownLatch(1); PendingListener l = new PendingListener() { @Override - public void onPendingTransactionUpdate(TransactionReceipt txReceipt, PendingTransactionState state, Block block) { + public void onPendingTransactionUpdate(TransactionReceipt txReceipt, PendingTransaction.State state, Block block) { assert !txReceipt.isSuccessful(); assert txReceipt.getError().toLowerCase().contains("invalid"); assert txReceipt.getError().toLowerCase().contains("receive address"); txHandle.countDown(); } }; - bc.addEthereumListener(l); + + StandaloneBlockchain bc = new StandaloneBlockchain() + .subscribe(to(BLOCK_ADDED, l::onBlock)) + .subscribe(to(PENDING_STATE_CHANGED, l::onPendingStateChanged)) + .subscribe(to(PENDING_TRANSACTION_UPDATED, l::onPendingTransactionUpdate)); + PendingStateImpl pendingState = (PendingStateImpl) bc.getBlockchain().getPendingState(); ECKey alice = new ECKey(); diff --git a/ethereumj-core/src/test/java/org/ethereum/core/TransactionTest.java b/ethereumj-core/src/test/java/org/ethereum/core/TransactionTest.java index fed322ebe3..bf808643b2 100644 --- a/ethereumj-core/src/test/java/org/ethereum/core/TransactionTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/core/TransactionTest.java @@ -563,10 +563,10 @@ public void multiSuicideTest() throws IOException, InterruptedException { " }" + " function multipleHomocide() {" + " PsychoKiller k = this;" + - " k.homicide();" + - " k.homicide();" + - " k.homicide();" + - " k.homicide();" + + " k.homicide.gas(10000)();" + + " k.homicide.gas(10000)();" + + " k.homicide.gas(10000)();" + + " k.homicide.gas(10000)();" + " }" + "}"; SolidityCompiler.Result res = SolidityCompiler.compile( diff --git a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/TestRunner.java b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/TestRunner.java index a09db7b7f4..0b2f1fbb71 100644 --- a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/TestRunner.java +++ b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/TestRunner.java @@ -31,9 +31,8 @@ import org.ethereum.jsontestsuite.suite.model.BlockTck; import org.ethereum.jsontestsuite.suite.validators.BlockHeaderValidator; import org.ethereum.jsontestsuite.suite.validators.RepositoryValidator; -import org.ethereum.listener.EthereumListenerAdapter; +import org.ethereum.listener.BackwardCompatibilityEthereumListenerProxy; import org.ethereum.util.ByteUtil; -import org.ethereum.validator.DependentBlockHeaderRuleAdapter; import org.ethereum.vm.DataWord; import org.ethereum.vm.LogInfo; import org.ethereum.vm.VM; @@ -99,11 +98,12 @@ public List runTestCase(BlockTestCase testCase) { ProgramInvokeFactoryImpl programInvokeFactory = new ProgramInvokeFactoryImpl(); - BlockchainImpl blockchain = new BlockchainImpl(blockStore, repository) + BackwardCompatibilityEthereumListenerProxy listenerProxy = BackwardCompatibilityEthereumListenerProxy.createDefault(); + BlockchainImpl blockchain = new BlockchainImpl(blockStore, repository, listenerProxy) .withParentBlockHeaderValidator(CommonConfig.getDefault().parentHeaderValidator()); blockchain.byTest = true; - PendingStateImpl pendingState = new PendingStateImpl(new EthereumListenerAdapter()); + PendingStateImpl pendingState = new PendingStateImpl(listenerProxy); blockchain.setBestBlock(genesis); blockchain.setTotalDifficulty(genesis.getDifficultyBI()); diff --git a/ethereumj-core/src/test/java/org/ethereum/longrun/BasicNode.java b/ethereumj-core/src/test/java/org/ethereum/longrun/BasicNode.java index f0939da845..fa30e9dfd0 100644 --- a/ethereumj-core/src/test/java/org/ethereum/longrun/BasicNode.java +++ b/ethereumj-core/src/test/java/org/ethereum/longrun/BasicNode.java @@ -20,15 +20,15 @@ import org.ethereum.config.CommonConfig; import org.ethereum.config.SystemProperties; import org.ethereum.core.Block; -import org.ethereum.core.TransactionReceipt; import org.ethereum.db.DbFlushManager; import org.ethereum.facade.Ethereum; import org.ethereum.facade.EthereumFactory; -import org.ethereum.listener.EthereumListener; -import org.ethereum.listener.EthereumListenerAdapter; import org.ethereum.net.eth.message.StatusMessage; import org.ethereum.net.rlpx.Node; import org.ethereum.net.server.Channel; +import org.ethereum.publish.event.BlockAdded; +import org.ethereum.publish.event.message.EthStatusUpdated; +import org.ethereum.sync.SyncManager; import org.ethereum.sync.SyncPool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,6 +42,11 @@ import java.util.Vector; import static java.lang.Thread.sleep; +import static org.ethereum.publish.event.Events.Type.BLOCK_ADDED; +import static org.ethereum.publish.event.Events.Type.ETH_STATUS_UPDATED; +import static org.ethereum.publish.event.Events.Type.PEER_ADDED_TO_SYNC_POOL; +import static org.ethereum.publish.event.Events.Type.SYNC_DONE; +import static org.ethereum.sync.SyncManager.State.COMPLETE; /** * BasicNode of ethereum instance @@ -103,7 +108,11 @@ public BasicNode(String loggerName) { private void springInit() { logger = LoggerFactory.getLogger(loggerName); // adding the main EthereumJ callback to be notified on different kind of events - ethereum.addListener(listener); + this.ethereum + .subscribe(SYNC_DONE, this::onSyncDone) + .subscribe(BLOCK_ADDED, this::onBlock) + .subscribe(ETH_STATUS_UPDATED, this::onEthStatusUpdated) + .subscribe(PEER_ADDED_TO_SYNC_POOL, this::onPeerAddedToSyncPool); logger.info("Sample component created. Listening for ethereum events..."); @@ -155,7 +164,7 @@ public void onSyncDone() throws Exception { logger.info("Monitoring new blocks in real-time..."); } - public void onSyncDoneImpl(EthereumListener.SyncState state) { + public void onSyncDoneImpl(SyncManager.State state) { logger.info("onSyncDone: " + state); } @@ -164,37 +173,32 @@ public void onSyncDoneImpl(EthereumListener.SyncState state) { protected Block bestBlock = null; - EthereumListener.SyncState syncState = null; + SyncManager.State syncState = null; boolean syncComplete = false; /** * The main EthereumJ callback. */ - EthereumListener listener = new EthereumListenerAdapter() { - @Override - public void onSyncDone(SyncState state) { - syncState = state; - if (state.equals(SyncState.COMPLETE)) syncComplete = true; - onSyncDoneImpl(state); - } + public void onSyncDone(SyncManager.State state) { + syncState = state; + if (state == COMPLETE) syncComplete = true; + onSyncDoneImpl(state); + } - @Override - public void onEthStatusUpdated(Channel channel, StatusMessage statusMessage) { - ethNodes.put(channel.getNode(), statusMessage); - } + public void onEthStatusUpdated(EthStatusUpdated.Data data) { + ethNodes.put(data.getChannel().getNode(), data.getMessage()); + } - @Override - public void onPeerAddedToSyncPool(Channel peer) { - syncPeers.add(peer.getNode()); - } + public void onPeerAddedToSyncPool(Channel peer) { + syncPeers.add(peer.getNode()); + } - @Override - public void onBlock(Block block, List receipts) { - bestBlock = block; + public void onBlock(BlockAdded.Data data) { + Block block = data.getBlockSummary().getBlock(); + bestBlock = block; - if (syncComplete) { - logger.info("New block: " + block.getShortDescr()); - } + if (syncComplete) { + logger.info("New block: " + block.getShortDescr()); } - }; + } } diff --git a/ethereumj-core/src/test/java/org/ethereum/longrun/SyncWithLoadTest.java b/ethereumj-core/src/test/java/org/ethereum/longrun/SyncWithLoadTest.java index a50b7e0864..c589e43cfc 100644 --- a/ethereumj-core/src/test/java/org/ethereum/longrun/SyncWithLoadTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/longrun/SyncWithLoadTest.java @@ -29,11 +29,9 @@ import org.ethereum.core.TransactionExecutor; import org.ethereum.core.TransactionReceipt; import org.ethereum.db.ContractDetails; -import org.ethereum.db.RepositoryImpl; import org.ethereum.facade.Ethereum; import org.ethereum.facade.EthereumFactory; -import org.ethereum.listener.EthereumListener; -import org.ethereum.listener.EthereumListenerAdapter; +import org.ethereum.publish.event.PendingTransactionUpdated; import org.ethereum.sync.SyncManager; import org.ethereum.util.FastByteComparisons; import org.ethereum.vm.program.invoke.ProgramInvokeFactory; @@ -44,34 +42,36 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; -import java.util.List; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import static java.lang.Thread.sleep; +import static org.ethereum.core.PendingTransaction.State.NEW_PENDING; +import static org.ethereum.publish.Subscription.to; +import static org.ethereum.publish.event.Events.Type.BLOCK_ADDED; +import static org.ethereum.publish.event.Events.Type.PENDING_TRANSACTION_UPDATED; /** * Regular sync with load * Loads ethereumJ during sync with various onBlock/repo track/callback usages - * + *

      * Runs sync with defined config for 1-30 minutes * - checks State Trie is not broken * - checks whether all blocks are in blockstore, validates parent connection and bodies * - checks and validate transaction receipts * Stopped, than restarts in 1 minute, syncs and pass all checks again. * Repeats forever or until first error occurs - * + *

      * Run with '-Dlogback.configurationFile=longrun/logback.xml' for proper logging * Also following flags are available: - * -Dreset.db.onFirstRun=true - * -Doverride.config.res=longrun/conf/live.conf + * -Dreset.db.onFirstRun=true + * -Doverride.config.res=longrun/conf/live.conf */ @Ignore public class SyncWithLoadTest { @@ -88,7 +88,7 @@ public class SyncWithLoadTest { private static final MutableObject resetDBOnFirstRun = new MutableObject<>(null); // Timer stops while not syncing - private static final AtomicLong lastImport = new AtomicLong(); + private static final AtomicLong lastImport = new AtomicLong(); private static final int LAST_IMPORT_TIMEOUT = 10 * 60 * 1000; public SyncWithLoadTest() throws Exception { @@ -164,67 +164,61 @@ static class RegularNode extends BasicNode { /** * The main EthereumJ callback. */ - EthereumListener blockListener = new EthereumListenerAdapter() { - @Override - public void onBlock(BlockSummary blockSummary) { - lastImport.set(System.currentTimeMillis()); - } - - @Override - public void onBlock(Block block, List receipts) { - for (TransactionReceipt receipt : receipts) { - // Getting contract details - byte[] contractAddress = receipt.getTransaction().getContractAddress(); - if (contractAddress != null) { - ContractDetails details = ((Repository) ethereum.getRepository()).getContractDetails(contractAddress); - assert FastByteComparisons.equal(details.getAddress(), contractAddress); - } - - // Getting AccountState for sender in the past - Random rnd = new Random(); - Block bestBlock = ethereum.getBlockchain().getBestBlock(); - Block randomBlock = ethereum.getBlockchain().getBlockByNumber(rnd.nextInt((int) bestBlock.getNumber())); - byte[] sender = receipt.getTransaction().getSender(); - AccountState senderState = ((Repository) ethereum.getRepository()).getSnapshotTo(randomBlock.getStateRoot()).getAccountState(sender); - if (senderState != null) senderState.getBalance(); - - // Getting receiver's nonce somewhere in the past - Block anotherRandomBlock = ethereum.getBlockchain().getBlockByNumber(rnd.nextInt((int) bestBlock.getNumber())); - byte[] receiver = receipt.getTransaction().getReceiveAddress(); - if (receiver != null) { - ((Repository) ethereum.getRepository()).getSnapshotTo(anotherRandomBlock.getStateRoot()).getNonce(receiver); - } + public void onBlock(BlockSummary blockSummary) { + lastImport.set(System.currentTimeMillis()); + + for (TransactionReceipt receipt : blockSummary.getReceipts()) { + // Getting contract details + byte[] contractAddress = receipt.getTransaction().getContractAddress(); + if (contractAddress != null) { + ContractDetails details = ((Repository) ethereum.getRepository()).getContractDetails(contractAddress); + assert FastByteComparisons.equal(details.getAddress(), contractAddress); } - } - @Override - public void onPendingTransactionsReceived(List transactions) { + // Getting AccountState for sender in the past Random rnd = new Random(); Block bestBlock = ethereum.getBlockchain().getBestBlock(); - for (Transaction tx : transactions) { - Block block = ethereum.getBlockchain().getBlockByNumber(rnd.nextInt((int) bestBlock.getNumber())); - Repository repository = ((Repository) ethereum.getRepository()) - .getSnapshotTo(block.getStateRoot()) - .startTracking(); - try { - TransactionExecutor executor = new TransactionExecutor - (tx, block.getCoinbase(), repository, ethereum.getBlockchain().getBlockStore(), - programInvokeFactory, block) - .withCommonConfig(commonConfig) - .setLocalCall(true); - - executor.init(); - executor.execute(); - executor.go(); - executor.finalization(); - - executor.getReceipt(); - } finally { - repository.rollback(); - } + Block randomBlock = ethereum.getBlockchain().getBlockByNumber(rnd.nextInt((int) bestBlock.getNumber())); + byte[] sender = receipt.getTransaction().getSender(); + AccountState senderState = ((Repository) ethereum.getRepository()).getSnapshotTo(randomBlock.getStateRoot()).getAccountState(sender); + if (senderState != null) senderState.getBalance(); + + // Getting receiver's nonce somewhere in the past + Block anotherRandomBlock = ethereum.getBlockchain().getBlockByNumber(rnd.nextInt((int) bestBlock.getNumber())); + byte[] receiver = receipt.getTransaction().getReceiveAddress(); + if (receiver != null) { + ((Repository) ethereum.getRepository()).getSnapshotTo(anotherRandomBlock.getStateRoot()).getNonce(receiver); } } - }; + } + + public void onPendingTransactionUpdated(PendingTransactionUpdated.Data data) { + Random rnd = new Random(); + Block bestBlock = ethereum.getBlockchain().getBestBlock(); + Block block = ethereum.getBlockchain().getBlockByNumber(rnd.nextInt((int) bestBlock.getNumber())); + + Transaction tx = data.getReceipt().getTransaction(); + + Repository repository = ((Repository) ethereum.getRepository()) + .getSnapshotTo(block.getStateRoot()) + .startTracking(); + try { + TransactionExecutor executor = new TransactionExecutor + (tx, block.getCoinbase(), repository, ethereum.getBlockchain().getBlockStore(), + programInvokeFactory, block) + .withCommonConfig(commonConfig) + .setLocalCall(true); + + executor.init(); + executor.execute(); + executor.go(); + executor.finalization(); + + executor.getReceipt(); + } finally { + repository.rollback(); + } + } public RegularNode() { super("sampleNode"); @@ -233,7 +227,10 @@ public RegularNode() { @Override public void run() { try { - ethereum.addListener(blockListener); + this.ethereum + .subscribe(to(BLOCK_ADDED, this::onBlock)) + .subscribe(to(PENDING_TRANSACTION_UPDATED, this::onPendingTransactionUpdated) + .conditionally(data -> data.getState() == NEW_PENDING)); // Run 1-30 minutes Random generator = new Random(); @@ -285,13 +282,13 @@ public void testDelayedCheck() throws Exception { new Thread(() -> { try { - while(firstRun.get()) { + while (firstRun.get()) { sleep(1000); } testLogger.info("Stopping first run"); - while(true) { - while(isRunning.get()) { + while (true) { + while (isRunning.get()) { sleep(1000); } regularNode.close(); diff --git a/ethereumj-core/src/test/java/org/ethereum/manager/BlockLoaderTest.java b/ethereumj-core/src/test/java/org/ethereum/manager/BlockLoaderTest.java index 5a7fba1796..24f0a8b4e2 100644 --- a/ethereumj-core/src/test/java/org/ethereum/manager/BlockLoaderTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/manager/BlockLoaderTest.java @@ -2,14 +2,13 @@ import org.ethereum.core.Block; import org.ethereum.core.Blockchain; -import org.ethereum.core.EventDispatchThread; import org.ethereum.core.Genesis; import org.ethereum.core.ImportResult; import org.ethereum.db.DbFlushManager; import org.ethereum.listener.CompositeEthereumListener; +import org.ethereum.publish.Publisher; import org.ethereum.validator.BlockHeaderRule; import org.ethereum.validator.BlockHeaderValidator; -import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; @@ -28,7 +27,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; import static java.util.Objects.isNull; import static java.util.stream.Collectors.toList; @@ -104,8 +102,8 @@ public CompositeEthereumListener ethereumListener() { } @Bean - public EventDispatchThread dispatchThread() { - return EventDispatchThread.getDefault(); + public Publisher publisher() { + return Mockito.mock(Publisher.class); } @Bean diff --git a/ethereumj-core/src/test/java/org/ethereum/mine/ExternalMinerTest.java b/ethereumj-core/src/test/java/org/ethereum/mine/ExternalMinerTest.java index 3808cc4427..f09702edfa 100644 --- a/ethereumj-core/src/test/java/org/ethereum/mine/ExternalMinerTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/mine/ExternalMinerTest.java @@ -23,14 +23,11 @@ import org.ethereum.config.blockchain.FrontierConfig; import org.ethereum.core.Block; import org.ethereum.core.BlockHeader; -import org.ethereum.core.BlockchainImpl; +import org.ethereum.core.EventDispatchThread; import org.ethereum.core.ImportResult; -import org.ethereum.db.PruneManager; -import org.ethereum.facade.Ethereum; import org.ethereum.facade.EthereumImpl; -import org.ethereum.listener.CompositeEthereumListener; +import org.ethereum.publish.Publisher; import org.ethereum.util.ByteUtil; -import org.ethereum.util.blockchain.LocalBlockchain; import org.ethereum.util.blockchain.StandaloneBlockchain; import org.junit.Before; import org.junit.Test; @@ -39,14 +36,13 @@ import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; -import org.springframework.beans.factory.annotation.Autowired; import javax.annotation.Resource; import java.math.BigInteger; import java.util.Collection; -import static java.util.Collections.*; -import static org.hamcrest.CoreMatchers.*; +import static java.util.Collections.EMPTY_LIST; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Mockito.when; @@ -59,15 +55,14 @@ public class ExternalMinerTest { private StandaloneBlockchain bc = new StandaloneBlockchain().withAutoblock(false); - private CompositeEthereumListener listener = new CompositeEthereumListener(); - @Mock private EthereumImpl ethereum; @InjectMocks @Resource - private BlockMiner blockMiner = new BlockMiner(SystemProperties.getDefault(), listener, bc.getBlockchain(), - bc.getBlockchain().getBlockStore(), bc.getPendingState());; + private BlockMiner blockMiner = new BlockMiner(SystemProperties.getDefault(), new Publisher(EventDispatchThread.getDefault()), bc.getBlockchain(), + bc.getBlockchain().getBlockStore(), bc.getPendingState()); + ; @Before public void setup() { @@ -110,7 +105,8 @@ public boolean validate(BlockHeader blockHeader) { } @Override - public void setListeners(Collection listeners) {} + public void setListeners(Collection listeners) { + } }); Block b = bc.getBlockchain().createNewBlock(startBestBlock, EMPTY_LIST, EMPTY_LIST); Ethash.getForBlock(SystemProperties.getDefault(), b.getNumber()).mineLight(b).get(); diff --git a/ethereumj-core/src/test/java/org/ethereum/mine/MinerTest.java b/ethereumj-core/src/test/java/org/ethereum/mine/MinerTest.java index f3f9b1a85d..02dc1ba93d 100644 --- a/ethereumj-core/src/test/java/org/ethereum/mine/MinerTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/mine/MinerTest.java @@ -21,14 +21,15 @@ import org.apache.commons.lang3.tuple.Pair; import org.ethereum.config.NoAutoscan; import org.ethereum.config.SystemProperties; -import org.ethereum.core.*; +import org.ethereum.core.Block; +import org.ethereum.core.BlockHeader; +import org.ethereum.core.Transaction; import org.ethereum.crypto.ECKey; import org.ethereum.db.ByteArrayWrapper; import org.ethereum.facade.Ethereum; import org.ethereum.facade.EthereumFactory; -import org.ethereum.listener.EthereumListenerAdapter; import org.ethereum.net.eth.handler.Eth62; -import org.ethereum.net.eth.message.*; +import org.ethereum.net.eth.message.NewBlockMessage; import org.ethereum.util.ByteUtil; import org.junit.BeforeClass; import org.junit.Ignore; @@ -38,10 +39,18 @@ import org.springframework.context.annotation.Configuration; import java.io.FileNotFoundException; -import java.util.*; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import static org.ethereum.core.PendingTransaction.State.NEW_PENDING; +import static org.ethereum.publish.Subscription.to; +import static org.ethereum.publish.event.Events.Type.BLOCK_ADDED; +import static org.ethereum.publish.event.Events.Type.PENDING_TRANSACTION_UPDATED; +import static org.ethereum.publish.event.Events.Type.SYNC_DONE; + /** * Long running test * @@ -99,13 +108,12 @@ public void startMiningConsumer() throws Exception { Ethereum ethereum2 = EthereumFactory.createEthereum(SysPropConfig2.props, SysPropConfig2.class); final CountDownLatch semaphore = new CountDownLatch(1); - ethereum2.addListener(new EthereumListenerAdapter() { - @Override - public void onBlock(Block block, List receipts) { - System.err.println("=== New block: " + blockInfo(block)); - System.err.println(block); + ethereum2.subscribe(BLOCK_ADDED, data -> { + Block block = data.getBlockSummary().getBlock(); + System.err.println("=== New block: " + blockInfo(block)); + System.err.println(block); - for (Transaction tx : block.getTransactionsList()) { + for (Transaction tx : block.getTransactionsList()) { // Pair remove = submittedTxs.remove(new ByteArrayWrapper(tx.getHash())); // if (remove == null) { // System.err.println("===== !!! Unknown Tx: " + tx); @@ -114,7 +122,7 @@ public void onBlock(Block block, List receipts) { // + " sec: " + tx); // } - } + } // for (Pair pair : submittedTxs.values()) { // if (System.currentTimeMillis() - pair.getRight() > 60 * 1000) { @@ -122,19 +130,14 @@ public void onBlock(Block block, List receipts) { // + " sec: " + pair.getLeft()); // } // } - } - @Override - public void onPendingTransactionsReceived(List transactions) { - System.err.println("=== Tx: " + transactions); - } - - @Override - public void onSyncDone(SyncState state) { - semaphore.countDown(); - System.err.println("=== Sync Done!"); - } - }); + }) + .subscribe(to(PENDING_TRANSACTION_UPDATED, data -> System.err.println("=== Tx: " + data.getReceipt().getTransaction())) + .conditionally(data -> data.getState() == NEW_PENDING)) + .subscribe(to(SYNC_DONE, syncState -> { + semaphore.countDown(); + System.err.println("=== Sync Done!"); + })); System.out.println("Waiting for sync..."); semaphore.await(); @@ -203,17 +206,9 @@ public void startMiningTest() throws FileNotFoundException, InterruptedException // Ethereum ethereum2 = EthereumFactory.createEthereum(SysPropConfig2.props, SysPropConfig2.class); final CountDownLatch semaphore = new CountDownLatch(1); - ethereum1.addListener(new EthereumListenerAdapter() { - @Override - public void onBlock(Block block, List receipts) { - System.out.println("=== New block: " + blockInfo(block)); - } - - @Override - public void onSyncDone(SyncState state) { - semaphore.countDown(); - } - }); + ethereum1 + .subscribe(BLOCK_ADDED, data -> System.out.println("=== New block: " + blockInfo(data.getBlockSummary().getBlock()))) + .subscribe(SYNC_DONE, syncState -> semaphore.countDown()); // ethereum2.addListener(new EthereumListenerAdapter() { // @Override diff --git a/ethereumj-core/src/test/java/org/ethereum/mine/SyncDoneTest.java b/ethereumj-core/src/test/java/org/ethereum/mine/SyncDoneTest.java index d473cb12f9..ad1a4cbeb2 100644 --- a/ethereumj-core/src/test/java/org/ethereum/mine/SyncDoneTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/mine/SyncDoneTest.java @@ -20,43 +20,30 @@ import org.ethereum.config.NoAutoscan; import org.ethereum.config.SystemProperties; import org.ethereum.config.net.MainNetConfig; -import org.ethereum.core.Block; -import org.ethereum.core.BlockSummary; -import org.ethereum.core.Blockchain; -import org.ethereum.core.ImportResult; -import org.ethereum.core.Transaction; +import org.ethereum.core.*; import org.ethereum.crypto.ECKey; import org.ethereum.facade.Ethereum; import org.ethereum.facade.EthereumFactory; import org.ethereum.facade.EthereumImpl; import org.ethereum.facade.SyncStatus; -import org.ethereum.listener.EthereumListenerAdapter; import org.ethereum.net.eth.handler.Eth62; import org.ethereum.net.rlpx.Node; -import org.ethereum.net.server.Channel; import org.ethereum.util.FastByteComparisons; import org.ethereum.util.blockchain.EtherUtil; import org.ethereum.util.blockchain.StandaloneBlockchain; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Test; +import org.junit.*; import org.spongycastle.util.encoders.Hex; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; -import java.io.File; import java.io.IOException; import java.math.BigInteger; import java.net.URISyntaxException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import java.util.ArrayList; +import java.nio.file.Paths; import java.util.List; import java.util.Random; import java.util.concurrent.CountDownLatch; @@ -65,13 +52,13 @@ import java.util.concurrent.TimeoutException; import static java.util.concurrent.TimeUnit.SECONDS; +import static java.util.stream.Collectors.toList; import static org.ethereum.crypto.HashUtil.sha3; +import static org.ethereum.publish.Subscription.to; +import static org.ethereum.publish.event.Events.Type.BLOCK_ADDED; +import static org.ethereum.publish.event.Events.Type.PEER_ADDED_TO_SYNC_POOL; import static org.ethereum.util.FileUtil.recursiveDelete; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.spongycastle.util.encoders.Hex.decode; +import static org.junit.Assert.*; /** @@ -81,7 +68,7 @@ * While automatic detection of long sync works correctly in any live network * with big number of peers, automatic detection of Short Sync condition in * detached or small private networks looks not doable. - * + *

      * To resolve this and any other similar issues manual switch to Short Sync mode * was added: {@link EthereumImpl#switchToShortSync()} * This test verifies that manual switching to Short Sync works correctly @@ -124,17 +111,11 @@ public static void setup() throws IOException, URISyntaxException { } private static List loadBlocks(String path) throws URISyntaxException, IOException { - URL url = ClassLoader.getSystemResource(path); - File file = new File(url.toURI()); - List strData = Files.readAllLines(file.toPath(), StandardCharsets.UTF_8); - - List blocks = new ArrayList<>(strData.size()); - for (String rlp : strData) { - blocks.add(new Block(decode(rlp))); - } - - return blocks; + return Files.lines(Paths.get(url.toURI()), StandardCharsets.UTF_8) + .map(Hex::decode) + .map(Block::new) + .collect(toList()); } @AfterClass @@ -143,7 +124,7 @@ public static void cleanup() { } @Before - public void setupTest() throws InterruptedException { + public void setupTest() { testDbA = "test_db_" + new BigInteger(32, new Random()); testDbB = "test_db_" + new BigInteger(32, new Random()); @@ -179,38 +160,27 @@ public void test1() throws InterruptedException { // Check that we are synced and on the same block assertTrue(loadedBlocks > 0); final CountDownLatch semaphore = new CountDownLatch(1); - ethereumB.addListener(new EthereumListenerAdapter() { - @Override - public void onBlock(BlockSummary blockSummary) { - if (blockSummary.getBlock().getNumber() == loadedBlocks) { - semaphore.countDown(); - } - } - }); + + ethereumB.subscribe(to(BLOCK_ADDED, data -> semaphore.countDown()) + .conditionally(data -> isBlockNumber(data.getBlockSummary(), loadedBlocks))); + semaphore.await(MAX_SECONDS_WAIT, SECONDS); - Assert.assertEquals(0, semaphore.getCount()); + assertEquals(0, semaphore.getCount()); assertEquals(loadedBlocks, ethereumB.getBlockchain().getBestBlock().getNumber()); ethereumA.getBlockMiner().startMining(); final CountDownLatch semaphore2 = new CountDownLatch(2); - ethereumB.addListener(new EthereumListenerAdapter() { - @Override - public void onBlock(BlockSummary blockSummary) { - if (blockSummary.getBlock().getNumber() == loadedBlocks + 2) { - semaphore2.countDown(); - ethereumA.getBlockMiner().stopMining(); - } - } - }); - ethereumA.addListener(new EthereumListenerAdapter(){ - @Override - public void onBlock(BlockSummary blockSummary) { - if (blockSummary.getBlock().getNumber() == loadedBlocks + 2) { - semaphore2.countDown(); - } + ethereumB.subscribe(BLOCK_ADDED, data -> { + if (isBlockNumber(data.getBlockSummary(), loadedBlocks + 2)) { + semaphore2.countDown(); + ethereumA.getBlockMiner().stopMining(); } }); + + ethereumA.subscribe(to(BLOCK_ADDED, data -> semaphore2.countDown()) + .conditionally(data -> isBlockNumber(data.getBlockSummary(), loadedBlocks + 2))); + semaphore2.await(MAX_SECONDS_WAIT, SECONDS); Assert.assertEquals(0, semaphore2.getCount()); @@ -230,34 +200,25 @@ public void onBlock(BlockSummary blockSummary) { ); tx.sign(sender); final CountDownLatch txSemaphore = new CountDownLatch(1); - ethereumA.addListener(new EthereumListenerAdapter(){ - @Override - public void onBlock(BlockSummary blockSummary) { - if (!blockSummary.getBlock().getTransactionsList().isEmpty() && - FastByteComparisons.equal(blockSummary.getBlock().getTransactionsList().get(0).getSender(), sender.getAddress()) && - blockSummary.getReceipts().get(0).isSuccessful()) { - txSemaphore.countDown(); - } + ethereumA.subscribe(BLOCK_ADDED, data -> { + BlockSummary blockSummary = data.getBlockSummary(); + if (!blockSummary.getBlock().getTransactionsList().isEmpty() && + FastByteComparisons.equal(blockSummary.getBlock().getTransactionsList().get(0).getSender(), sender.getAddress()) && + blockSummary.getReceipts().get(0).isSuccessful()) { + txSemaphore.countDown(); } }); + ethereumB.submitTransaction(tx); final CountDownLatch semaphore3 = new CountDownLatch(2); - ethereumB.addListener(new EthereumListenerAdapter() { - @Override - public void onBlock(BlockSummary blockSummary) { - if (blockSummary.getBlock().getNumber() == loadedBlocks + 5) { - semaphore3.countDown(); - } - } - }); - ethereumA.addListener(new EthereumListenerAdapter(){ - @Override - public void onBlock(BlockSummary blockSummary) { - if (blockSummary.getBlock().getNumber() == loadedBlocks + 5) { - semaphore3.countDown(); - ethereumA.getBlockMiner().stopMining(); - } + ethereumB.subscribe(to(BLOCK_ADDED, data -> semaphore3.countDown()) + .conditionally(data -> isBlockNumber(data.getBlockSummary(), loadedBlocks + 5))); + + ethereumA.subscribe(BLOCK_ADDED, data -> { + if (isBlockNumber(data.getBlockSummary(), loadedBlocks + 5)) { + semaphore3.countDown(); + ethereumA.getBlockMiner().stopMining(); } }); ethereumA.getBlockMiner().startMining(); @@ -290,30 +251,24 @@ public void onBlock(BlockSummary blockSummary) { ethereumB.submitTransaction(tx2); final CountDownLatch semaphore4 = new CountDownLatch(2); - ethereumB.addListener(new EthereumListenerAdapter() { - @Override - public void onBlock(BlockSummary blockSummary) { - if (blockSummary.getBlock().getNumber() == loadedBlocks + 9) { - semaphore4.countDown(); - ethereumA.getBlockMiner().stopMining(); - } - } - }); - ethereumA.addListener(new EthereumListenerAdapter(){ - @Override - public void onBlock(BlockSummary blockSummary) { - if (blockSummary.getBlock().getNumber() == loadedBlocks + 9) { - semaphore4.countDown(); - } + + ethereumB.subscribe(BLOCK_ADDED, data -> { + if (isBlockNumber(data.getBlockSummary(), loadedBlocks + 9)) { + semaphore4.countDown(); + ethereumA.getBlockMiner().stopMining(); } }); + ethereumA.subscribe(to(BLOCK_ADDED, data -> semaphore4.countDown()) + .conditionally(data -> isBlockNumber(data.getBlockSummary(), loadedBlocks + 9))); + semaphore4.await(MAX_SECONDS_WAIT, SECONDS); - Assert.assertEquals(0, semaphore4.getCount()); - Assert.assertEquals(loadedBlocks + 9, ethereumA.getBlockchain().getBestBlock().getNumber()); - Assert.assertEquals(loadedBlocks + 9, ethereumB.getBlockchain().getBestBlock().getNumber()); - assertTrue(ethereumA.getSyncStatus().getStage().equals(SyncStatus.SyncStage.Complete)); - assertTrue(ethereumB.getSyncStatus().getStage().equals(SyncStatus.SyncStage.Complete)); + + assertEquals(0, semaphore4.getCount()); + assertEquals(loadedBlocks + 9, ethereumA.getBlockchain().getBestBlock().getNumber()); + assertEquals(loadedBlocks + 9, ethereumB.getBlockchain().getBestBlock().getNumber()); + assertEquals(SyncStatus.SyncStage.Complete, ethereumA.getSyncStatus().getStage()); + assertEquals(SyncStatus.SyncStage.Complete, ethereumB.getSyncStatus().getStage()); // Tx is included! assertTrue(txSemaphore.getCount() == 0); } @@ -325,21 +280,20 @@ private void setupPeers() throws InterruptedException { final CountDownLatch semaphore = new CountDownLatch(1); - ethereumB.addListener(new EthereumListenerAdapter() { - @Override - public void onPeerAddedToSyncPool(Channel peer) { - semaphore.countDown(); - } - }); + ethereumB.subscribe(PEER_ADDED_TO_SYNC_POOL, channel -> semaphore.countDown()); ethereumB.connect(nodeA); semaphore.await(10, SECONDS); - if(semaphore.getCount() > 0) { + if (semaphore.getCount() > 0) { fail("Failed to set up peers"); } } + private static boolean isBlockNumber(BlockSummary blockSummary, long number) { + return blockSummary.getBlock().getNumber() == number; + } + @Configuration @NoAutoscan public static class SysPropConfigA { @@ -354,7 +308,7 @@ public SystemProperties systemProperties() { @Bean @Scope("prototype") - public Eth62 eth62() throws IllegalAccessException, InstantiationException { + public Eth62 eth62() { if (eth62 != null) return eth62; return new Eth62(); } diff --git a/ethereumj-core/src/test/java/org/ethereum/net/TwoPeerTest.java b/ethereumj-core/src/test/java/org/ethereum/net/TwoPeerTest.java index 5ebc95ec98..c7c02f91ed 100644 --- a/ethereumj-core/src/test/java/org/ethereum/net/TwoPeerTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/net/TwoPeerTest.java @@ -24,10 +24,12 @@ import org.ethereum.crypto.ECKey; import org.ethereum.facade.Ethereum; import org.ethereum.facade.EthereumFactory; -import org.ethereum.listener.EthereumListenerAdapter; import org.ethereum.mine.Ethash; import org.ethereum.net.eth.handler.Eth62; -import org.ethereum.net.eth.message.*; +import org.ethereum.net.eth.message.BlockBodiesMessage; +import org.ethereum.net.eth.message.BlockHeadersMessage; +import org.ethereum.net.eth.message.GetBlockBodiesMessage; +import org.ethereum.net.eth.message.GetBlockHeadersMessage; import org.ethereum.net.server.Channel; import org.ethereum.util.RLP; import org.junit.Ignore; @@ -48,6 +50,8 @@ import static java.lang.Math.max; import static java.lang.Math.min; import static org.ethereum.crypto.HashUtil.sha3; +import static org.ethereum.publish.event.Events.Type.BLOCK_ADDED; +import static org.ethereum.publish.event.Events.Type.ETH_STATUS_UPDATED; /** * Created by Anton Nashatyrev on 13.10.2015. @@ -221,21 +225,16 @@ protected void processGetBlockBodies(GetBlockBodiesMessage msg) { final CountDownLatch semaphore = new CountDownLatch(1); final Channel[] channel1 = new Channel[1]; - ethereum1.addListener(new EthereumListenerAdapter() { - @Override - public void onEthStatusUpdated(Channel channel, StatusMessage statusMessage) { - channel1[0] = channel; - System.out.println("==== Got the Channel: " + channel); - } + ethereum1.subscribe(ETH_STATUS_UPDATED, data -> { + channel1[0] = data.getChannel(); + System.out.println("==== Got the Channel: " + data.getChannel()); }); - ethereum2.addListener(new EthereumListenerAdapter() { - @Override - public void onBlock(Block block, List receipts) { - if (block.getNumber() == 4) { - semaphore.countDown(); - } - } + ethereum2.subscribe(BLOCK_ADDED, data -> { + Block block = data.getBlockSummary().getBlock(); + if (block.getNumber() == 4) { + semaphore.countDown(); + } }); System.out.println("======= Waiting for block #4"); diff --git a/ethereumj-core/src/test/java/org/ethereum/net/eth/handler/LockBlockchainTest.java b/ethereumj-core/src/test/java/org/ethereum/net/eth/handler/LockBlockchainTest.java index 3b0c03011e..c56b102e46 100644 --- a/ethereumj-core/src/test/java/org/ethereum/net/eth/handler/LockBlockchainTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/net/eth/handler/LockBlockchainTest.java @@ -17,22 +17,21 @@ */ package org.ethereum.net.eth.handler; -import org.ethereum.config.CommonConfig; import org.ethereum.config.NoAutoscan; import org.ethereum.config.SystemProperties; import org.ethereum.core.Block; import org.ethereum.core.BlockHeader; import org.ethereum.core.Blockchain; import org.ethereum.core.BlockchainImpl; +import org.ethereum.core.EventDispatchThread; import org.ethereum.db.BlockStore; import org.ethereum.db.BlockStoreDummy; -import org.ethereum.listener.CompositeEthereumListener; import org.ethereum.net.eth.message.EthMessage; import org.ethereum.net.eth.message.GetBlockBodiesMessage; import org.ethereum.net.eth.message.GetBlockHeadersMessage; +import org.ethereum.publish.Publisher; import org.ethereum.sync.SyncManager; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.mockito.Mockito; import org.spongycastle.util.encoders.Hex; @@ -97,12 +96,13 @@ public synchronized boolean isBlockExist(byte[] hash) { } }; - SysPropConfig1.testHandler = new Eth62(SysPropConfig1.props, blockchain, blockStoreDummy, - new CompositeEthereumListener()) { + Publisher publisher = new Publisher(EventDispatchThread.getDefault()); + SysPropConfig1.testHandler = new Eth62(SysPropConfig1.props, blockchain, blockStoreDummy, publisher) { { this.blockstore = blockStoreDummy; this.syncManager = Mockito.mock(SyncManager.class); } + @Override public synchronized void sendStatus() { super.sendStatus(); @@ -144,15 +144,15 @@ public SystemProperties systemProperties() { public synchronized void testHeadersWithoutSkip() throws FileNotFoundException, InterruptedException { ExecutorService executor1 = Executors.newSingleThreadExecutor(); executor1.submit(() -> { - blockchain.isBlockExist(null); - } + blockchain.isBlockExist(null); + } ); this.wait(DELAY); ExecutorService executor2 = Executors.newSingleThreadExecutor(); executor2.submit(() -> { - GetBlockHeadersMessage msg = new GetBlockHeadersMessage(1L, new byte[0], 10, 0, false); - SysPropConfig1.testHandler.processGetBlockHeaders(msg); - } + GetBlockHeadersMessage msg = new GetBlockHeadersMessage(1L, new byte[0], 10, 0, false); + SysPropConfig1.testHandler.processGetBlockHeaders(msg); + } ); this.wait(DELAY); assert result.get(); @@ -162,15 +162,15 @@ public synchronized void testHeadersWithoutSkip() throws FileNotFoundException, public synchronized void testHeadersWithSkip() throws FileNotFoundException, InterruptedException { ExecutorService executor1 = Executors.newSingleThreadExecutor(); executor1.submit(() -> { - blockchain.isBlockExist(null); - } + blockchain.isBlockExist(null); + } ); this.wait(DELAY); ExecutorService executor2 = Executors.newSingleThreadExecutor(); executor2.submit(() -> { - GetBlockHeadersMessage msg = new GetBlockHeadersMessage(1L, new byte[0], 10, 5, false); - SysPropConfig1.testHandler.processGetBlockHeaders(msg); - } + GetBlockHeadersMessage msg = new GetBlockHeadersMessage(1L, new byte[0], 10, 5, false); + SysPropConfig1.testHandler.processGetBlockHeaders(msg); + } ); this.wait(DELAY); assert result.get(); @@ -180,18 +180,18 @@ public synchronized void testHeadersWithSkip() throws FileNotFoundException, Int public synchronized void testBodies() throws FileNotFoundException, InterruptedException { ExecutorService executor1 = Executors.newSingleThreadExecutor(); executor1.submit(() -> { - blockchain.isBlockExist(null); - } + blockchain.isBlockExist(null); + } ); this.wait(DELAY); ExecutorService executor2 = Executors.newSingleThreadExecutor(); executor2.submit(() -> { - List hashes = new ArrayList<>(); - hashes.add(new byte[] {1, 2, 3}); - hashes.add(new byte[] {4, 5, 6}); - GetBlockBodiesMessage msg = new GetBlockBodiesMessage(hashes); - SysPropConfig1.testHandler.processGetBlockBodies(msg); - } + List hashes = new ArrayList<>(); + hashes.add(new byte[]{1, 2, 3}); + hashes.add(new byte[]{4, 5, 6}); + GetBlockBodiesMessage msg = new GetBlockBodiesMessage(hashes); + SysPropConfig1.testHandler.processGetBlockBodies(msg); + } ); this.wait(DELAY); assert result.get(); @@ -201,18 +201,18 @@ public synchronized void testBodies() throws FileNotFoundException, InterruptedE public synchronized void testStatus() throws FileNotFoundException, InterruptedException { ExecutorService executor1 = Executors.newSingleThreadExecutor(); executor1.submit(() -> { - blockchain.isBlockExist(null); - } + blockchain.isBlockExist(null); + } ); this.wait(DELAY); ExecutorService executor2 = Executors.newSingleThreadExecutor(); executor2.submit(() -> { - try { - SysPropConfig1.testHandler.sendStatus(); - } catch (Exception e) { - e.printStackTrace(); - } - } + try { + SysPropConfig1.testHandler.sendStatus(); + } catch (Exception e) { + e.printStackTrace(); + } + } ); this.wait(DELAY); assert result.get(); diff --git a/ethereumj-core/src/test/java/org/ethereum/net/rlpx/FramingTest.java b/ethereumj-core/src/test/java/org/ethereum/net/rlpx/FramingTest.java index a67d78d3d3..f82762c5d0 100644 --- a/ethereumj-core/src/test/java/org/ethereum/net/rlpx/FramingTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/net/rlpx/FramingTest.java @@ -21,10 +21,8 @@ import org.ethereum.config.SystemProperties; import org.ethereum.facade.Ethereum; import org.ethereum.facade.EthereumFactory; -import org.ethereum.listener.EthereumListenerAdapter; import org.ethereum.net.eth.message.StatusMessage; import org.ethereum.net.message.Message; -import org.ethereum.net.server.Channel; import org.junit.Ignore; import org.junit.Test; import org.springframework.context.annotation.Bean; @@ -35,6 +33,8 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import static org.ethereum.publish.event.Events.Type.MESSAGE_RECEIVED; + /** * Created by Anton Nashatyrev on 13.10.2015. */ @@ -98,24 +98,18 @@ public void testTest() throws FileNotFoundException, InterruptedException { final CountDownLatch semaphore = new CountDownLatch(2); - ethereum1.addListener(new EthereumListenerAdapter() { - @Override - public void onRecvMessage(Channel channel, Message message) { - if (message instanceof StatusMessage) { - System.out.println("1: -> " + message); - semaphore.countDown(); - } + ethereum1.subscribe(MESSAGE_RECEIVED, messageData -> { + if (messageData.getMessage() instanceof StatusMessage) { + System.out.println("1: -> " + messageData.getMessage()); + semaphore.countDown(); } }); - ethereum2.addListener(new EthereumListenerAdapter() { - @Override - public void onRecvMessage(Channel channel, Message message) { - if (message instanceof StatusMessage) { - System.out.println("2: -> " + message); - semaphore.countDown(); - } + ethereum2.subscribe(MESSAGE_RECEIVED, messageData -> { + Message message = messageData.getMessage(); + if (message instanceof StatusMessage) { + System.out.println("2: -> " + message); + semaphore.countDown(); } - }); ethereum2.connect(new Node("enode://a560c55a0a5b5d137c638eb6973812f431974e4398c6644fa0c19181954fab530bb2a1e2c4eec7cc855f6bab9193ea41d6cf0bf2b8b41ed6b8b9e09c072a1e5a" + diff --git a/ethereumj-core/src/test/java/org/ethereum/net/rlpx/SanityLongRunTest.java b/ethereumj-core/src/test/java/org/ethereum/net/rlpx/SanityLongRunTest.java index c4874f8c1e..a911402dcd 100644 --- a/ethereumj-core/src/test/java/org/ethereum/net/rlpx/SanityLongRunTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/net/rlpx/SanityLongRunTest.java @@ -20,15 +20,11 @@ import com.typesafe.config.ConfigFactory; import org.ethereum.config.NoAutoscan; import org.ethereum.config.SystemProperties; -import org.ethereum.core.Block; -import org.ethereum.core.TransactionReceipt; import org.ethereum.crypto.ECKey; import org.ethereum.facade.Ethereum; import org.ethereum.facade.EthereumFactory; -import org.ethereum.listener.EthereumListenerAdapter; import org.ethereum.net.eth.message.StatusMessage; import org.ethereum.net.message.Message; -import org.ethereum.net.server.Channel; import org.ethereum.net.shh.MessageWatcher; import org.ethereum.net.shh.WhisperMessage; import org.junit.Ignore; @@ -38,9 +34,13 @@ import org.springframework.context.annotation.Configuration; import java.io.FileNotFoundException; -import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.ethereum.publish.Subscription.to; +import static org.ethereum.publish.event.Events.Type.BLOCK_ADDED; +import static org.ethereum.publish.event.Events.Type.MESSAGE_RECEIVED; /** * Created by Anton Nashatyrev on 13.10.2015. @@ -112,15 +112,12 @@ public void testTest() throws FileNotFoundException, InterruptedException { final CountDownLatch semaphore = new CountDownLatch(1); - ethereum2.addListener(new EthereumListenerAdapter() { - @Override - public void onRecvMessage(Channel channel, Message message) { - if (message instanceof StatusMessage) { - System.out.println("=== Status received: " + message); - semaphore.countDown(); - } + ethereum2.subscribe(MESSAGE_RECEIVED, messageData -> { + Message message = messageData.getMessage(); + if (message instanceof StatusMessage) { + System.out.println("=== Status received: " + message); + semaphore.countDown(); } - }); semaphore.await(60, TimeUnit.SECONDS); @@ -131,23 +128,19 @@ public void onRecvMessage(Channel channel, Message message) { final CountDownLatch semaphoreBlocks = new CountDownLatch(1); final CountDownLatch semaphoreFirstBlock = new CountDownLatch(1); - ethereum2.addListener(new EthereumListenerAdapter() { - int blocksCnt = 0; - - @Override - public void onBlock(Block block, List receipts) { - blocksCnt++; - if (blocksCnt % 1000 == 0 || blocksCnt == 1) { - System.out.println("=== Blocks imported: " + blocksCnt); - if (blocksCnt == 1) { - semaphoreFirstBlock.countDown(); - } - } - if (blocksCnt >= 10_000) { - semaphoreBlocks.countDown(); - System.out.println("=== Blocks task done."); + AtomicInteger blocksCnt = new AtomicInteger(); + ethereum2.subscribe(BLOCK_ADDED, data -> { + blocksCnt.addAndGet(1); + if (blocksCnt.get() % 1000 == 0 || blocksCnt.get() == 1) { + System.out.println("=== Blocks imported: " + blocksCnt); + if (blocksCnt.get() == 1) { + semaphoreFirstBlock.countDown(); } } + if (blocksCnt.get() >= 10_000) { + semaphoreBlocks.countDown(); + System.out.println("=== Blocks task done."); + } }); semaphoreFirstBlock.await(180, TimeUnit.SECONDS); diff --git a/ethereumj-core/src/test/java/org/ethereum/net/rlpx/SnappyConnectionTest.java b/ethereumj-core/src/test/java/org/ethereum/net/rlpx/SnappyConnectionTest.java index 082ff6dbc3..007aab978b 100644 --- a/ethereumj-core/src/test/java/org/ethereum/net/rlpx/SnappyConnectionTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/net/rlpx/SnappyConnectionTest.java @@ -21,9 +21,6 @@ import org.ethereum.config.SystemProperties; import org.ethereum.facade.Ethereum; import org.ethereum.facade.EthereumFactory; -import org.ethereum.listener.EthereumListenerAdapter; -import org.ethereum.net.eth.message.StatusMessage; -import org.ethereum.net.server.Channel; import org.junit.Ignore; import org.junit.Test; import org.springframework.context.annotation.Bean; @@ -33,6 +30,8 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import static org.ethereum.publish.event.Events.Type.ETH_STATUS_UPDATED; + /** * @author Mikhail Kalinin * @since 02.11.2017 @@ -99,19 +98,13 @@ private void runScenario(int vOutbound, int vInbound) throws FileNotFoundExcepti final CountDownLatch semaphore = new CountDownLatch(2); - ethereum1.addListener(new EthereumListenerAdapter() { - @Override - public void onEthStatusUpdated(Channel channel, StatusMessage statusMessage) { - System.out.println("1: -> " + statusMessage); - semaphore.countDown(); - } + ethereum1.subscribe(ETH_STATUS_UPDATED, data -> { + System.out.println("1: -> " + data.getMessage()); + semaphore.countDown(); }); - ethereum2.addListener(new EthereumListenerAdapter() { - @Override - public void onEthStatusUpdated(Channel channel, StatusMessage statusMessage) { - System.out.println("2: -> " + statusMessage); - semaphore.countDown(); - } + ethereum2.subscribe(ETH_STATUS_UPDATED, data -> { + System.out.println("2: -> " + data.getMessage()); + semaphore.countDown(); }); ethereum2.connect(new Node("enode://a560c55a0a5b5d137c638eb6973812f431974e4398c6644fa0c19181954fab530bb2a1e2c4eec7cc855f6bab9193ea41d6cf0bf2b8b41ed6b8b9e09c072a1e5a" + diff --git a/ethereumj-core/src/test/java/org/ethereum/net/shh/ShhLongRun.java b/ethereumj-core/src/test/java/org/ethereum/net/shh/ShhLongRun.java index ced3831f01..8dc6a55a5f 100644 --- a/ethereumj-core/src/test/java/org/ethereum/net/shh/ShhLongRun.java +++ b/ethereumj-core/src/test/java/org/ethereum/net/shh/ShhLongRun.java @@ -21,11 +21,8 @@ import org.ethereum.config.NoAutoscan; import org.ethereum.facade.Ethereum; import org.ethereum.facade.EthereumFactory; -import org.ethereum.listener.EthereumListenerAdapter; import org.ethereum.manager.WorldManager; -import org.ethereum.net.p2p.HelloMessage; import org.ethereum.net.rlpx.Node; -import org.ethereum.net.server.Channel; import org.junit.Ignore; import org.junit.Test; import org.spongycastle.util.encoders.Hex; @@ -41,6 +38,9 @@ import java.util.Date; import java.util.List; +import static org.ethereum.publish.Subscription.to; +import static org.ethereum.publish.event.Events.Type.PEER_HANDSHAKED; + /** * This is not a JUnit test but rather a long running standalone test for messages exchange with another peer. * To start it another peer with JSON PRC API should be started. @@ -110,13 +110,10 @@ public TestComponent() { @PostConstruct void init() { System.out.println("========= init"); - worldManager.addListener(new EthereumListenerAdapter() { - @Override - public void onHandShakePeer(Channel channel, HelloMessage helloMessage) { - System.out.println("========= onHandShakePeer"); - if (!isAlive()) { - start(); - } + worldManager.getPublisher().subscribe(PEER_HANDSHAKED, data -> { + System.out.println("========= onHandShakePeer"); + if (!isAlive()) { + start(); } }); } diff --git a/ethereumj-core/src/test/java/org/ethereum/publish/EventGenerator.java b/ethereumj-core/src/test/java/org/ethereum/publish/EventGenerator.java new file mode 100644 index 0000000000..0ca909ff5a --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/publish/EventGenerator.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) [2016] [ ] + * 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 . + */ +package org.ethereum.publish; + +import com.google.common.base.Function; +import org.ethereum.publish.event.Event; +import org.ethereum.util.RandomGenerator; + +import java.util.Random; + +public class EventGenerator extends RandomGenerator { + + public EventGenerator(Random random) { + super(random); + } + + private EventGenerator withGenFunction(Function function) { + return (EventGenerator) addGenFunction(function); + } + + public EventGenerator withIntEvent(int bound) { + return withGenFunction(r -> new Events.IntEvent(r.nextInt(bound))); + } + + public EventGenerator withLongEvent(int bound) { + return withGenFunction(r -> new Events.LongEvent(r.nextInt(bound))); + } + + public EventGenerator withStringEvent(String... strings) { + return withGenFunction(r -> new Events.StringEvent(randomFrom(strings))); + } + + public EventGenerator withOneOffStringEvent(String... strings) { + return withGenFunction(random -> new Events.OneOffStringEvent(randomFrom(strings))); + } +} diff --git a/ethereumj-core/src/test/java/org/ethereum/publish/Events.java b/ethereumj-core/src/test/java/org/ethereum/publish/Events.java new file mode 100644 index 0000000000..d2c684b3ab --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/publish/Events.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) [2016] [ ] + * 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 . + */ +package org.ethereum.publish; + +import org.ethereum.publish.event.Event; +import org.ethereum.publish.event.OneOffEvent; + +public class Events { + + static class IntEvent extends Event { + IntEvent(Integer payload) { + super(payload); + } + } + + static class LongEvent extends Event { + LongEvent(long payload) { + super(payload); + } + } + + static class StringEvent extends Event { + StringEvent(String payload) { + super(payload); + } + } + + static class OneOffStringEvent extends StringEvent implements OneOffEvent { + OneOffStringEvent(String payload) { + super(payload); + } + } +} diff --git a/ethereumj-core/src/test/java/org/ethereum/publish/PublisherTest.java b/ethereumj-core/src/test/java/org/ethereum/publish/PublisherTest.java new file mode 100644 index 0000000000..172b120795 --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/publish/PublisherTest.java @@ -0,0 +1,296 @@ +/* + * Copyright (c) [2016] [ ] + * 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 . + */ +package org.ethereum.publish; + +import org.ethereum.core.EventDispatchThread; +import org.ethereum.util.RandomGenerator; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.IntStream; + +import static java.util.Arrays.asList; +import static org.ethereum.publish.Subscription.to; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class PublisherTest { + + @Test + public void testDuplicateSubscription() { + Subscription subscription = Subscription.to(Events.IntEvent.class, System.out::print); + + int subscribersCount = createPublisher() + .subscribe(subscription) + .subscribe(subscription) + .subscribersCount(subscription.getEventType()); + + assertEquals(1, subscribersCount); + } + + @Test + public void testSingleEvent() { + final String payload = "desired event"; + final List strings = new ArrayList<>(); + + int subscribersCount = createPublisher() + .subscribe(Events.OneOffStringEvent.class, s -> strings.add(s)) + .publish(new Events.OneOffStringEvent(payload)) + .subscribersCount(Events.OneOffStringEvent.class); + + assertEquals(0, subscribersCount); + assertTrue(strings.contains(payload)); + } + + @Test + public void testConditionallySubscription() { + + AtomicLong actualSum = new AtomicLong(); + AtomicInteger actualEvenSum = new AtomicInteger(); + + int[] numbers = IntStream.rangeClosed(1, 10).toArray(); + int sum = IntStream.of(numbers).sum(); + int evenSum = IntStream.of(numbers).filter(num -> isEven(num)).sum(); + + Publisher publisher = createPublisher() + .subscribe(to(Events.LongEvent.class, actualSum::getAndAdd)) + .subscribe(to(Events.IntEvent.class, actualEvenSum::getAndAdd) + .conditionally(PublisherTest::isEven)); + + IntStream.of(numbers) + .forEach(num -> publisher + .publish(new Events.LongEvent(num)) + .publish(new Events.IntEvent(num))); + + assertEquals(sum, actualSum.get()); + assertEquals(evenSum, actualEvenSum.get()); + } + + @Test + public void testUnsubscribeAfter() { + AtomicInteger actualSum = new AtomicInteger(); + + int limit = 10; + int[] numbers = IntStream.rangeClosed(1, limit).toArray(); + int sum = IntStream.of(numbers).sum(); + + Publisher publisher = createPublisher() + .subscribe(to(Events.IntEvent.class, actualSum::addAndGet) + .unsubscribeAfter(num -> num == limit)); + + IntStream.rangeClosed(1, limit * 2) + .mapToObj(Events.IntEvent::new) + .forEach(publisher::publish); + + assertEquals(sum, actualSum.get()); + } + + @Test + public void testOneOffSubscription() { + AtomicInteger actual = new AtomicInteger(); + final int expected = 5; + + Publisher publisher = createPublisher() + .subscribe(to(Events.IntEvent.class, actual::set) + .oneOff(num -> num == expected)); + + IntStream.rangeClosed(1, 10) + .mapToObj(Events.IntEvent::new) + .forEach(publisher::publish); + + assertEquals(expected, actual.get()); + } + + @Test + public void testLifeCycleSubscription() { + AtomicInteger actual = new AtomicInteger(); + final int expected = 5; + + Publisher publisher = createPublisher() + .subscribe(to(Events.IntEvent.class, (num, lc) -> { + if (num == expected) { + actual.set(num); + lc.unsubscribe(); + } + })); + + IntStream.rangeClosed(1, 10) + .mapToObj(Events.IntEvent::new) + .forEach(publisher::publish); + + assertEquals(expected, actual.get()); + } + + @Test + public void testPublishing() { + + AtomicLong longEvenSum = new AtomicLong(); + AtomicInteger firstIntSum = new AtomicInteger(); + AtomicInteger secondIntSum = new AtomicInteger(); + List expectedStrings = new ArrayList<>(); + + List strings = asList("some event", "another event", "incredible event"); + int[] numbers = IntStream.rangeClosed(1, 10).toArray(); + int sum = IntStream.of(numbers).sum(); + int evenSum = IntStream.of(numbers).filter(num -> isEven(num)).sum(); + + + Publisher publisher = createPublisher() + .subscribe(to(Events.IntEvent.class, firstIntSum::getAndAdd)) + .subscribe(to(Events.IntEvent.class, secondIntSum::getAndAdd)) + .subscribe(to(Events.StringEvent.class, s -> expectedStrings.add(s))) + .subscribe(to(Events.LongEvent.class, longEvenSum::getAndAdd) + .conditionally(PublisherTest::isEven)); + + IntStream.of(numbers) + .forEach(num -> publisher + .publish(new Events.IntEvent(num)) + .publish(new Events.LongEvent(num))); + + strings.stream() + .forEach(str -> publisher + .publish(new Events.StringEvent(str))); + + assertEquals(sum, firstIntSum.get()); + assertEquals(sum, secondIntSum.get()); + assertEquals(evenSum, longEvenSum.intValue()); + assertEquals(strings.size(), expectedStrings.stream() + .filter(strings::contains) + .count()); + } + + @Test + public void testHandlingWithException() { + AtomicInteger actual = new AtomicInteger(); + int expected = 5; + + createPublisher() + .subscribe(to(Events.IntEvent.class, num -> { + throw new RuntimeException(); + })) + .subscribe(to(Events.IntEvent.class, num -> actual.set(num))) + .publish(new Events.IntEvent(expected)); + + assertEquals(expected, actual.get()); + } + + @Test + public void testConcurrentAccess() { + BlockingQueue executorQueue = new LinkedBlockingQueue<>(); + ExecutorService executor = new ThreadPoolExecutor(10, 10, 0L, + TimeUnit.MILLISECONDS, executorQueue); + + Random random = new Random(); + final String[] strings1 = {"one", "two", "three", "thousand", "one hundred", "zero"}; + final int limit = 1000; + + EventGenerator eGen = new EventGenerator(random) + .withIntEvent(limit) + .withLongEvent(limit) + .withStringEvent(strings1) + .withOneOffStringEvent(strings1); + + + RandomGenerator sGen = new RandomGenerator(random) + .addGenFunction(r -> to(Events.OneOffStringEvent.class, s -> sleepSilent(r.nextInt(10)))) + .addGenFunction(r -> to(Events.StringEvent.class, s -> sleepSilent(r.nextInt(10))) + .conditionally(s -> s.startsWith("t")) + .unsubscribeAfter(s -> s.startsWith("z"))) + .addGenFunction(r -> to(Events.IntEvent.class, i -> sleepSilent(r.nextInt(10))) + .unsubscribeAfter(i -> i < r.nextInt(limit))) + .addGenFunction(r -> to(Events.IntEvent.class, (i, lifeCycle) -> { + sleepSilent(r.nextInt(10)); + if (i < r.nextInt(limit)) { + lifeCycle.unsubscribe(); + } + })) + .addGenFunction(r -> to(Events.LongEvent.class, i -> sleepSilent(r.nextInt(10))) + .oneOff(i -> i < r.nextInt(limit))); + + + try { + Publisher publisher = new Publisher(executor); + // prints publisher state info + executor.execute(() -> { + while (true) { + System.out.println(publisher); + sleepSilent(300); + } + }); + + AtomicBoolean running = new AtomicBoolean(true); + try { + // generates events + executor.execute(() -> { + while (running.get()) { + publisher.publish(eGen.genNext()); + if (executorQueue.size() > limit) { + sleepSilent(100); + } + } + }); + // generates subscriptions + executor.execute(() -> { + while (running.get()) { + publisher.subscribe(sGen.genNext()); + if (publisher.subscribersCount() > limit) { + sleepSilent(100); + } + } + }); + + sleepSilent(5000); + } finally { + running.set(false); + } + + while (!executorQueue.isEmpty()) { + sleepSilent(100); + } + + } finally { + executor.shutdown(); + } + } + + private static void sleepSilent(int millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + private static Publisher createPublisher() { + return new Publisher(EventDispatchThread.getDefault()); + } + + private static boolean isEven(long number) { + return number % 2 == 0; + } +} \ No newline at end of file diff --git a/ethereumj-core/src/test/java/org/ethereum/solidity/SolidityTypeTest.java b/ethereumj-core/src/test/java/org/ethereum/solidity/SolidityTypeTest.java new file mode 100644 index 0000000000..28fe7d4d7d --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/solidity/SolidityTypeTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) [2016] [ ] + * 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 . + */ +package org.ethereum.solidity; + +import static org.junit.Assert.assertEquals; + +import java.math.BigInteger; + +import org.junit.Test; +import org.spongycastle.util.encoders.Hex; + +/** + * Created by Maximilian Schmidt on 25.09.2018. + */ +public class SolidityTypeTest { + @Test + public void ensureUnsignedInteger_isDecodedWithCorrectSignum() { + byte[] bigNumberByteArray = { -13, -75, 19, 86, -119, 67, 112, -4, 118, -86, 98, -46, 103, -42, -126, 63, -60, -15, -87, 57, 43, 11, -17, -52, + 0, 3, -65, 14, -67, -40, 65, 119 }; + SolidityType testObject = new SolidityType.UnsignedIntType("uint256"); + Object decode = testObject.decode(bigNumberByteArray); + assertEquals(decode.getClass(), BigInteger.class); + BigInteger actualBigInteger = (BigInteger) decode; + BigInteger expectedBigInteger = new BigInteger(Hex.toHexString(bigNumberByteArray), 16); + assertEquals(expectedBigInteger, actualBigInteger); + } + + @Test + public void ensureSignedInteger_isDecoded() { + byte[] bigNumberByteArray = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 127, -1, -1, -1, -1, -1, -1, -1 }; + SolidityType testObject = new SolidityType.IntType("int256"); + Object decode = testObject.decode(bigNumberByteArray); + assertEquals(decode.getClass(), BigInteger.class); + BigInteger actualBigInteger = (BigInteger) decode; + BigInteger expectedBigInteger = new BigInteger(bigNumberByteArray); + assertEquals(expectedBigInteger, actualBigInteger); + } +} diff --git a/ethereumj-core/src/test/java/org/ethereum/sync/BlockTxForwardTest.java b/ethereumj-core/src/test/java/org/ethereum/sync/BlockTxForwardTest.java index 06a0d40539..d3671967ca 100644 --- a/ethereumj-core/src/test/java/org/ethereum/sync/BlockTxForwardTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/sync/BlockTxForwardTest.java @@ -22,25 +22,15 @@ import org.ethereum.config.SystemProperties; import org.ethereum.core.Block; import org.ethereum.core.BlockIdentifier; -import org.ethereum.core.BlockSummary; import org.ethereum.core.Transaction; -import org.ethereum.core.TransactionReceipt; import org.ethereum.crypto.ECKey; import org.ethereum.facade.Ethereum; import org.ethereum.facade.EthereumFactory; -import org.ethereum.listener.EthereumListener; -import org.ethereum.listener.EthereumListenerAdapter; import org.ethereum.mine.Ethash; import org.ethereum.mine.MinerListener; -import org.ethereum.net.eth.message.EthMessage; -import org.ethereum.net.eth.message.EthMessageCodes; -import org.ethereum.net.eth.message.NewBlockHashesMessage; -import org.ethereum.net.eth.message.NewBlockMessage; -import org.ethereum.net.eth.message.StatusMessage; -import org.ethereum.net.eth.message.TransactionsMessage; +import org.ethereum.net.eth.message.*; import org.ethereum.net.message.Message; import org.ethereum.net.rlpx.Node; -import org.ethereum.net.server.Channel; import org.ethereum.util.ByteUtil; import org.junit.Ignore; import org.junit.Test; @@ -51,22 +41,24 @@ import org.springframework.context.annotation.Bean; import javax.annotation.PostConstruct; -import java.util.Collections; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.List; -import java.util.Map; -import java.util.Vector; +import java.util.*; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import static org.ethereum.publish.Subscription.to; +import static org.ethereum.publish.event.Events.Type.BLOCK_ADDED; +import static org.ethereum.publish.event.Events.Type.ETH_STATUS_UPDATED; +import static org.ethereum.publish.event.Events.Type.MESSAGE_RECEIVED; +import static org.ethereum.publish.event.Events.Type.MESSAGE_SENT; +import static org.ethereum.publish.event.Events.Type.PEER_ADDED_TO_SYNC_POOL; +import static org.ethereum.publish.event.Events.Type.SYNC_DONE; + /** * Long running test - * + *

      * 3 peers: A <-> B <-> C where A is miner, C is issuing txs, and B should forward Txs/Blocks */ @Ignore @@ -128,7 +120,16 @@ public BasicSample(String loggerName) { private void springInit() { logger = LoggerFactory.getLogger(loggerName); // adding the main EthereumJ callback to be notified on different kind of events - ethereum.addListener(listener); + this.ethereum + .subscribe(SYNC_DONE, syncState -> synced = true) + .subscribe(ETH_STATUS_UPDATED, data -> ethNodes.put(data.getChannel().getNode(), data.getMessage())) + .subscribe(PEER_ADDED_TO_SYNC_POOL, peer -> syncPeers.add(peer.getNode())) + .subscribe(BLOCK_ADDED, data -> { + bestBlock = data.getBlockSummary().getBlock(); + if (syncComplete) { + logger.info("New block: " + bestBlock.getShortDescr()); + } + }); logger.info("Sample component created. Listening for ethereum events..."); @@ -164,7 +165,7 @@ public void run() { */ private void waitForSync() throws Exception { logger.info("Waiting for the whole blockchain sync (will take up to several hours for the whole chain)..."); - while(true) { + while (true) { Thread.sleep(10000); if (synced) { @@ -189,35 +190,6 @@ public void onSyncDone() throws Exception { boolean synced = false; boolean syncComplete = false; - - /** - * The main EthereumJ callback. - */ - EthereumListener listener = new EthereumListenerAdapter() { - @Override - public void onSyncDone(SyncState state) { - synced = true; - } - - @Override - public void onEthStatusUpdated(Channel channel, StatusMessage statusMessage) { - ethNodes.put(channel.getNode(), statusMessage); - } - - @Override - public void onPeerAddedToSyncPool(Channel peer) { - syncPeers.add(peer.getNode()); - } - - @Override - public void onBlock(Block block, List receipts) { - bestBlock = block; - - if (syncComplete) { - logger.info("New block: " + block.getShortDescr()); - } - } - }; } /** @@ -239,7 +211,7 @@ private static class MinerConfig { "genesis = sample-genesis.json \n" + // two peers need to have separate database dirs "database.dir = sampleDB-1 \n" + - "keyvalue.datasource = leveldb \n" + + "keyvalue.datasource = rocksdb \n" + // when more than 1 miner exist on the network extraData helps to identify the block creator "mine.extraDataHex = cccccccccccccccccccc \n" + "mine.cpuMineThreads = 2 \n" + @@ -266,7 +238,7 @@ public SystemProperties systemProperties() { /** * Miner bean, which just start a miner upon creation and prints miner events */ - static class MinerNode extends BasicSample implements MinerListener{ + static class MinerNode extends BasicSample implements MinerListener { public MinerNode() { // peers need different loggers super("sampleMiner"); @@ -338,7 +310,7 @@ private static class RegularConfig { "genesis = sample-genesis.json \n" + // two peers need to have separate database dirs "database.dir = sampleDB-2 \n" + - "keyvalue.datasource = leveldb \n"; + "keyvalue.datasource = rocksdb \n"; @Bean public RegularNode node() { @@ -383,7 +355,7 @@ private static class GeneratorConfig { "genesis = sample-genesis.json \n" + // two peers need to have separate database dirs "database.dir = sampleDB-3 \n" + - "keyvalue.datasource = leveldb \n"; + "keyvalue.datasource = rocksdb \n"; @Bean public GeneratorNode node() { @@ -455,7 +427,7 @@ private void generateTransactions() throws Exception{ } private final static Map blocks = Collections.synchronizedMap(new HashMap()); - private final static Map txs = Collections.synchronizedMap(new HashMap()); + private final static Map txs = Collections.synchronizedMap(new HashMap()); private final static AtomicInteger fatalErrors = new AtomicInteger(0); private final static AtomicBoolean stopTxGeneration = new AtomicBoolean(false); @@ -485,12 +457,12 @@ private boolean logStats() { } /** - * Creating 3 EthereumJ instances with different config classes - * 1st - Miner node, no sync - * 2nd - Regular node, synced with both Miner and Generator - * 3rd - Generator node, sync is on, but can see only 2nd node - * We want to check that blocks mined on Miner will reach Generator and - * txs from Generator will reach Miner node + * Creating 3 EthereumJ instances with different config classes + * 1st - Miner node, no sync + * 2nd - Regular node, synced with both Miner and Generator + * 3rd - Generator node, sync is on, but can see only 2nd node + * We want to check that blocks mined on Miner will reach Generator and + * txs from Generator will reach Miner node */ @Test public void testTest() throws Exception { @@ -509,99 +481,93 @@ public void testTest() throws Exception { testLogger.info("Starting EthereumJ miner instance!"); Ethereum miner = EthereumFactory.createEthereum(MinerConfig.class); - miner.addListener(new EthereumListenerAdapter() { - @Override - public void onBlock(BlockSummary blockSummary) { - if (blockSummary.getBlock().getNumber() != 0L) { - blocks.put(Hex.toHexString(blockSummary.getBlock().getHash()), Boolean.FALSE); - } - } - - @Override - public void onRecvMessage(Channel channel, Message message) { - super.onRecvMessage(channel, message); - if (!(message instanceof EthMessage)) return; - switch (((EthMessage) message).getCommand()) { - case NEW_BLOCK_HASHES: - testLogger.error("Received new block hash message at miner: {}", message.toString()); - fatalErrors.incrementAndGet(); - break; - case NEW_BLOCK: - testLogger.error("Received new block message at miner: {}", message.toString()); - fatalErrors.incrementAndGet(); - break; - case TRANSACTIONS: - TransactionsMessage msgCopy = new TransactionsMessage(message.getEncoded()); - for (Transaction transaction : msgCopy.getTransactions()) { - if (txs.put(Hex.toHexString(transaction.getHash()), Boolean.TRUE) == null) { - testLogger.error("Received strange transaction at miner: {}", transaction); - fatalErrors.incrementAndGet(); - }; - } - break; - default: - break; - } - } - }); + miner + .subscribe(BLOCK_ADDED, data -> { + Block block = data.getBlockSummary().getBlock(); + if (block.getNumber() != 0L) { + blocks.put(Hex.toHexString(block.getHash()), Boolean.FALSE); + } + }) + .subscribe(MESSAGE_RECEIVED, messageData -> { + Message message = messageData.getMessage(); + if (!(message instanceof EthMessage)) return; + switch (((EthMessage) message).getCommand()) { + case NEW_BLOCK_HASHES: + testLogger.error("Received new block hash message at miner: {}", message.toString()); + fatalErrors.incrementAndGet(); + break; + case NEW_BLOCK: + testLogger.error("Received new block message at miner: {}", message.toString()); + fatalErrors.incrementAndGet(); + break; + case TRANSACTIONS: + TransactionsMessage msgCopy = new TransactionsMessage(message.getEncoded()); + for (Transaction transaction : msgCopy.getTransactions()) { + if (txs.put(Hex.toHexString(transaction.getHash()), Boolean.TRUE) == null) { + testLogger.error("Received strange transaction at miner: {}", transaction); + fatalErrors.incrementAndGet(); + } + } + break; + default: + break; + } + }); testLogger.info("Starting EthereumJ regular instance!"); EthereumFactory.createEthereum(RegularConfig.class); testLogger.info("Starting EthereumJ txSender instance!"); Ethereum txGenerator = EthereumFactory.createEthereum(GeneratorConfig.class); - txGenerator.addListener(new EthereumListenerAdapter() { - @Override - public void onRecvMessage(Channel channel, Message message) { - super.onRecvMessage(channel, message); - if (!(message instanceof EthMessage)) return; - switch (((EthMessage) message).getCommand()) { - case NEW_BLOCK_HASHES: - testLogger.info("Received new block hash message at generator: {}", message.toString()); - NewBlockHashesMessage msgCopy = new NewBlockHashesMessage(message.getEncoded()); - for (BlockIdentifier identifier : msgCopy.getBlockIdentifiers()) { - if (blocks.put(Hex.toHexString(identifier.getHash()), Boolean.TRUE) == null) { - testLogger.error("Received strange block: {}", identifier); + txGenerator + .subscribe(to(MESSAGE_RECEIVED, messageData -> { + Message message = messageData.getMessage(); + if (!(message instanceof EthMessage)) return; + switch (((EthMessage) message).getCommand()) { + case NEW_BLOCK_HASHES: + testLogger.info("Received new block hash message at generator: {}", message.toString()); + NewBlockHashesMessage msgCopy = new NewBlockHashesMessage(message.getEncoded()); + for (BlockIdentifier identifier : msgCopy.getBlockIdentifiers()) { + if (blocks.put(Hex.toHexString(identifier.getHash()), Boolean.TRUE) == null) { + testLogger.error("Received strange block: {}", identifier); + fatalErrors.incrementAndGet(); + } + } + break; + case NEW_BLOCK: + testLogger.info("Received new block message at generator: {}", message.toString()); + NewBlockMessage msgCopy2 = new NewBlockMessage(message.getEncoded()); + Block block = msgCopy2.getBlock(); + if (blocks.put(Hex.toHexString(block.getHash()), Boolean.TRUE) == null) { + testLogger.error("Received strange block: {}", block); fatalErrors.incrementAndGet(); - }; + } + break; + case BLOCK_BODIES: + testLogger.info("Received block bodies message at generator: {}", message.toString()); + break; + case TRANSACTIONS: + testLogger.warn("Received new transaction message at generator: {}, " + + "allowed only after disconnect.", message.toString()); + break; + default: + break; + } + + })) + .subscribe(to(MESSAGE_SENT, messageData -> { + Message message = messageData.getMessage(); + if (!(message instanceof EthMessage)) return; + if (((EthMessage) message).getCommand().equals(EthMessageCodes.TRANSACTIONS)) { + TransactionsMessage msgCopy = new TransactionsMessage(message.getEncoded()); + for (Transaction transaction : msgCopy.getTransactions()) { + Transaction copyTransaction = new Transaction(transaction.getEncoded()); + txs.put(Hex.toHexString(copyTransaction.getHash()), Boolean.FALSE); } - break; - case NEW_BLOCK: - testLogger.info("Received new block message at generator: {}", message.toString()); - NewBlockMessage msgCopy2 = new NewBlockMessage(message.getEncoded()); - Block block = msgCopy2.getBlock(); - if (blocks.put(Hex.toHexString(block.getHash()), Boolean.TRUE) == null) { - testLogger.error("Received strange block: {}", block); - fatalErrors.incrementAndGet(); - }; - break; - case BLOCK_BODIES: - testLogger.info("Received block bodies message at generator: {}", message.toString()); - break; - case TRANSACTIONS: - testLogger.warn("Received new transaction message at generator: {}, " + - "allowed only after disconnect.", message.toString()); - break; - default: - break; - } - } - - @Override - public void onSendMessage(Channel channel, Message message) { - super.onSendMessage(channel, message); - if (!(message instanceof EthMessage)) return; - if (((EthMessage) message).getCommand().equals(EthMessageCodes.TRANSACTIONS)) { - TransactionsMessage msgCopy = new TransactionsMessage(message.getEncoded()); - for (Transaction transaction : msgCopy.getTransactions()) { - Transaction copyTransaction = new Transaction(transaction.getEncoded()); - txs.put(Hex.toHexString(copyTransaction.getHash()), Boolean.FALSE); - }; - } - } - }); + } + })); - if(statTimer.awaitTermination(MAX_RUN_MINUTES, TimeUnit.MINUTES)) { + if (statTimer.awaitTermination(MAX_RUN_MINUTES, TimeUnit.MINUTES)) { logStats(); // Stop generating new txs stopTxGeneration.set(true); diff --git a/ethereumj-core/src/test/java/org/ethereum/sync/LongSyncTest.java b/ethereumj-core/src/test/java/org/ethereum/sync/LongSyncTest.java index 856cf7ae01..02419c1a25 100644 --- a/ethereumj-core/src/test/java/org/ethereum/sync/LongSyncTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/sync/LongSyncTest.java @@ -19,19 +19,20 @@ import org.ethereum.config.NoAutoscan; import org.ethereum.config.SystemProperties; -import org.ethereum.config.blockchain.FrontierConfig; -import org.ethereum.config.net.MainNetConfig; -import org.ethereum.core.*; +import org.ethereum.core.Block; +import org.ethereum.core.BlockHeader; +import org.ethereum.core.Blockchain; +import org.ethereum.core.ImportResult; import org.ethereum.facade.Ethereum; import org.ethereum.facade.EthereumFactory; -import org.ethereum.listener.EthereumListenerAdapter; import org.ethereum.net.eth.handler.Eth62; import org.ethereum.net.eth.handler.EthHandler; -import org.ethereum.net.eth.message.*; -import org.ethereum.net.message.Message; +import org.ethereum.net.eth.message.BlockBodiesMessage; +import org.ethereum.net.eth.message.BlockHeadersMessage; +import org.ethereum.net.eth.message.GetBlockBodiesMessage; +import org.ethereum.net.eth.message.GetBlockHeadersMessage; import org.ethereum.net.p2p.DisconnectMessage; import org.ethereum.net.rlpx.Node; -import org.ethereum.net.server.Channel; import org.ethereum.util.blockchain.StandaloneBlockchain; import org.junit.*; import org.springframework.context.annotation.Bean; @@ -49,6 +50,10 @@ import java.util.concurrent.CountDownLatch; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.ethereum.publish.event.Events.Type.BLOCK_ADDED; +import static org.ethereum.publish.event.Events.Type.ETH_STATUS_UPDATED; +import static org.ethereum.publish.event.Events.Type.MESSAGE_RECEIVED; +import static org.ethereum.publish.event.Events.Type.PEER_ADDED_TO_SYNC_POOL; import static org.ethereum.util.FileUtil.recursiveDelete; import static org.junit.Assert.fail; import static org.spongycastle.util.encoders.Hex.decode; @@ -155,12 +160,9 @@ public void test1() throws InterruptedException { // A == b10, B == genesis final CountDownLatch semaphore = new CountDownLatch(1); - ethereumB.addListener(new EthereumListenerAdapter() { - @Override - public void onBlock(Block block, List receipts) { - if (block.isEqual(b10)) { - semaphore.countDown(); - } + ethereumB.subscribe(BLOCK_ADDED, data -> { + if (data.getBlockSummary().getBlock().isEqual(b10)) { + semaphore.countDown(); } }); @@ -195,12 +197,9 @@ protected void processGetBlockBodies(GetBlockBodiesMessage msg) { // A == b10, B == genesis final CountDownLatch semaphoreDisconnect = new CountDownLatch(1); - ethereumA.addListener(new EthereumListenerAdapter() { - @Override - public void onRecvMessage(Channel channel, Message message) { - if (message instanceof DisconnectMessage) { - semaphoreDisconnect.countDown(); - } + ethereumA.subscribe(MESSAGE_RECEIVED, messageData -> { + if (messageData.getMessage() instanceof DisconnectMessage) { + semaphoreDisconnect.countDown(); } }); @@ -245,12 +244,9 @@ protected void processGetBlockHeaders(GetBlockHeadersMessage msg) { // A == b10, B == genesis final CountDownLatch semaphoreDisconnect = new CountDownLatch(1); - ethereumA.addListener(new EthereumListenerAdapter() { - @Override - public void onRecvMessage(Channel channel, Message message) { - if (message instanceof DisconnectMessage) { - semaphoreDisconnect.countDown(); - } + ethereumA.subscribe(MESSAGE_RECEIVED, messageData -> { + if (messageData.getMessage() instanceof DisconnectMessage) { + semaphoreDisconnect.countDown(); } }); @@ -290,12 +286,9 @@ protected void processGetBlockHeaders(GetBlockHeadersMessage msg) { // A == b10, B == genesis final CountDownLatch semaphoreDisconnect = new CountDownLatch(1); - ethereumA.addListener(new EthereumListenerAdapter() { - @Override - public void onRecvMessage(Channel channel, Message message) { - if (message instanceof DisconnectMessage) { - semaphoreDisconnect.countDown(); - } + ethereumA.subscribe(MESSAGE_RECEIVED, messageData -> { + if (messageData.getMessage() instanceof DisconnectMessage) { + semaphoreDisconnect.countDown(); } }); @@ -339,12 +332,9 @@ protected void processGetBlockHeaders(GetBlockHeadersMessage msg) { // A == b10, B == genesis final CountDownLatch semaphoreDisconnect = new CountDownLatch(1); - ethereumA.addListener(new EthereumListenerAdapter() { - @Override - public void onRecvMessage(Channel channel, Message message) { - if (message instanceof DisconnectMessage) { - semaphoreDisconnect.countDown(); - } + ethereumA.subscribe(MESSAGE_RECEIVED, messageData -> { + if (messageData.getMessage() instanceof DisconnectMessage) { + semaphoreDisconnect.countDown(); } }); @@ -392,12 +382,9 @@ protected void processGetBlockHeaders(GetBlockHeadersMessage msg) { // A == b10, B == genesis final CountDownLatch semaphoreDisconnect = new CountDownLatch(1); - ethereumA.addListener(new EthereumListenerAdapter() { - @Override - public void onRecvMessage(Channel channel, Message message) { - if (message instanceof DisconnectMessage) { - semaphoreDisconnect.countDown(); - } + ethereumA.subscribe(MESSAGE_RECEIVED, messageData -> { + if (messageData.getMessage() instanceof DisconnectMessage) { + semaphoreDisconnect.countDown(); } }); @@ -441,12 +428,9 @@ protected void processGetBlockHeaders(GetBlockHeadersMessage msg) { // A == b10, B == genesis final CountDownLatch semaphoreDisconnect = new CountDownLatch(1); - ethereumA.addListener(new EthereumListenerAdapter() { - @Override - public void onRecvMessage(Channel channel, Message message) { - if (message instanceof DisconnectMessage) { - semaphoreDisconnect.countDown(); - } + ethereumA.subscribe(MESSAGE_RECEIVED, messageData -> { + if (messageData.getMessage() instanceof DisconnectMessage) { + semaphoreDisconnect.countDown(); } }); @@ -491,12 +475,9 @@ protected void processGetBlockHeaders(GetBlockHeadersMessage msg) { // A == b10, B == genesis final CountDownLatch semaphoreDisconnect = new CountDownLatch(1); - ethereumA.addListener(new EthereumListenerAdapter() { - @Override - public void onRecvMessage(Channel channel, Message message) { - if (message instanceof DisconnectMessage) { - semaphoreDisconnect.countDown(); - } + ethereumA.subscribe(MESSAGE_RECEIVED, messageData -> { + if (messageData.getMessage() instanceof DisconnectMessage) { + semaphoreDisconnect.countDown(); } }); @@ -526,23 +507,11 @@ private void setupPeers(Block best) throws InterruptedException { // A == best ethereumB = EthereumFactory.createEthereum(SysPropConfigB.props, SysPropConfigB.class); - - ethereumA.addListener(new EthereumListenerAdapter() { - @Override - public void onEthStatusUpdated(Channel channel, StatusMessage statusMessage) { - ethA = (EthHandler) channel.getEthHandler(); - } - }); + ethereumA.subscribe(ETH_STATUS_UPDATED, data -> ethA = (EthHandler) data.getChannel().getEthHandler()); final CountDownLatch semaphore = new CountDownLatch(1); - ethereumB.addListener(new EthereumListenerAdapter() { - @Override - public void onPeerAddedToSyncPool(Channel peer) { - semaphore.countDown(); - } - }); - + ethereumB.subscribe(PEER_ADDED_TO_SYNC_POOL, channel -> semaphore.countDown()); ethereumB.connect(nodeA); semaphore.await(10, SECONDS); diff --git a/ethereumj-core/src/test/java/org/ethereum/sync/ShortSyncTest.java b/ethereumj-core/src/test/java/org/ethereum/sync/ShortSyncTest.java index 21cd330fe0..f766e41d80 100644 --- a/ethereumj-core/src/test/java/org/ethereum/sync/ShortSyncTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/sync/ShortSyncTest.java @@ -19,22 +19,17 @@ import org.ethereum.config.NoAutoscan; import org.ethereum.config.SystemProperties; -import org.ethereum.config.blockchain.FrontierConfig; import org.ethereum.config.net.MainNetConfig; import org.ethereum.core.Block; import org.ethereum.core.BlockHeader; import org.ethereum.core.Blockchain; -import org.ethereum.core.TransactionReceipt; import org.ethereum.facade.Ethereum; import org.ethereum.facade.EthereumFactory; -import org.ethereum.listener.EthereumListenerAdapter; import org.ethereum.net.eth.handler.Eth62; import org.ethereum.net.eth.handler.EthHandler; import org.ethereum.net.eth.message.*; -import org.ethereum.net.message.Message; import org.ethereum.net.p2p.DisconnectMessage; import org.ethereum.net.rlpx.Node; -import org.ethereum.net.server.Channel; import org.ethereum.util.blockchain.StandaloneBlockchain; import org.junit.*; import org.springframework.context.annotation.Bean; @@ -55,6 +50,11 @@ import java.util.concurrent.CountDownLatch; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.ethereum.publish.Subscription.to; +import static org.ethereum.publish.event.Events.Type.BLOCK_ADDED; +import static org.ethereum.publish.event.Events.Type.ETH_STATUS_UPDATED; +import static org.ethereum.publish.event.Events.Type.MESSAGE_RECEIVED; +import static org.ethereum.publish.event.Events.Type.PEER_ADDED_TO_SYNC_POOL; import static org.ethereum.util.FileUtil.recursiveDelete; import static org.junit.Assert.fail; import static org.spongycastle.util.encoders.Hex.decode; @@ -160,21 +160,15 @@ public void test1() throws InterruptedException { // A == b10, B == genesis final CountDownLatch semaphore = new CountDownLatch(1); - ethereumB.addListener(new EthereumListenerAdapter() { - @Override - public void onBlock(Block block, List receipts) { - if (block.isEqual(b10)) { - semaphore.countDown(); - } - } - }); + ethereumB.subscribe(to(BLOCK_ADDED, data -> semaphore.countDown()) + .conditionally(data -> data.getBlockSummary().getBlock().isEqual(b10))); ethA.sendNewBlock(b10); semaphore.await(10, SECONDS); // check if B == b10 - if(semaphore.getCount() > 0) { + if (semaphore.getCount() > 0) { fail("PeerB bestBlock is incorrect"); } } @@ -198,21 +192,15 @@ public void test2() throws InterruptedException { // A == b8', B == genesis final CountDownLatch semaphore = new CountDownLatch(1); - ethereumB.addListener(new EthereumListenerAdapter() { - @Override - public void onBlock(Block block, List receipts) { - if (block.isEqual(b8_)) { - semaphore.countDown(); - } - } - }); + ethereumB.subscribe(to(BLOCK_ADDED, data -> semaphore.countDown()) + .conditionally(data -> data.getBlockSummary().getBlock().isEqual(b8_))); ethA.sendNewBlock(b8_); semaphore.await(10, SECONDS); // check if B == b8' - if(semaphore.getCount() > 0) { + if (semaphore.getCount() > 0) { fail("PeerB bestBlock is incorrect"); } } @@ -240,12 +228,10 @@ public void test3() throws InterruptedException { // A == b10, B == b8' final CountDownLatch semaphore = new CountDownLatch(1); - ethereumB.addListener(new EthereumListenerAdapter() { - @Override - public void onBlock(Block block, List receipts) { - if (block.isEqual(b10)) { - semaphore.countDown(); - } + ethereumB.subscribe(BLOCK_ADDED, data -> { + Block block = data.getBlockSummary().getBlock(); + if (block.isEqual(b10)) { + semaphore.countDown(); } }); @@ -254,7 +240,7 @@ public void onBlock(Block block, List receipts) { semaphore.await(10, SECONDS); // check if B == b10 - if(semaphore.getCount() > 0) { + if (semaphore.getCount() > 0) { fail("PeerB bestBlock is incorrect"); } } @@ -287,14 +273,8 @@ public void test4() throws InterruptedException { // A == b5, B == b9 final CountDownLatch semaphore = new CountDownLatch(1); - ethereumB.addListener(new EthereumListenerAdapter() { - @Override - public void onBlock(Block block, List receipts) { - if (block.isEqual(b10)) { - semaphore.countDown(); - } - } - }); + ethereumB.subscribe(to(BLOCK_ADDED, blockSummary -> semaphore.countDown()) + .conditionally(data -> data.getBlockSummary().getBlock().equals(b10))); ethA.sendNewBlockHashes(b5); @@ -309,7 +289,7 @@ public void onBlock(Block block, List receipts) { semaphore.await(10, SECONDS); // check if B == b10 - if(semaphore.getCount() > 0) { + if (semaphore.getCount() > 0) { fail("PeerB bestBlock is incorrect"); } } @@ -342,22 +322,20 @@ public void test5() throws InterruptedException { final CountDownLatch semaphore = new CountDownLatch(1); final CountDownLatch semaphoreB8_ = new CountDownLatch(1); - ethereumB.addListener(new EthereumListenerAdapter() { - @Override - public void onBlock(Block block, List receipts) { - if (block.isEqual(b10)) { - semaphore.countDown(); - } - if (block.isEqual(b8_)) { - semaphoreB8_.countDown(); - } + ethereumB.subscribe(BLOCK_ADDED, data -> { + Block block = data.getBlockSummary().getBlock(); + if (block.isEqual(b10)) { + semaphore.countDown(); + } + if (block.isEqual(b8_)) { + semaphoreB8_.countDown(); } }); ethA.sendNewBlockHashes(b8_); semaphoreB8_.await(10, SECONDS); - if(semaphoreB8_.getCount() > 0) { + if (semaphoreB8_.getCount() > 0) { fail("PeerB didn't import b8'"); } @@ -372,7 +350,7 @@ public void onBlock(Block block, List receipts) { semaphore.await(10, SECONDS); // check if B == b10 - if(semaphore.getCount() > 0) { + if (semaphore.getCount() > 0) { fail("PeerB bestBlock is incorrect"); } } @@ -404,15 +382,13 @@ public void test6() throws InterruptedException { final CountDownLatch semaphore = new CountDownLatch(1); final CountDownLatch semaphoreB7 = new CountDownLatch(1); - ethereumB.addListener(new EthereumListenerAdapter() { - @Override - public void onBlock(Block block, List receipts) { - if (block.isEqual(b7)) { - semaphoreB7.countDown(); - } - if (block.isEqual(b10)) { - semaphore.countDown(); - } + ethereumB.subscribe(BLOCK_ADDED, data -> { + Block block = data.getBlockSummary().getBlock(); + if (block.isEqual(b7)) { + semaphoreB7.countDown(); + } + if (block.isEqual(b10)) { + semaphore.countDown(); } }); @@ -421,7 +397,7 @@ public void onBlock(Block block, List receipts) { semaphoreB7.await(10, SECONDS); // check if B == b7 - if(semaphoreB7.getCount() > 0) { + if (semaphoreB7.getCount() > 0) { fail("PeerB didn't recover a gap"); } @@ -435,7 +411,7 @@ public void onBlock(Block block, List receipts) { semaphore.await(10, SECONDS); // check if B == b10 - if(semaphore.getCount() > 0) { + if (semaphore.getCount() > 0) { fail("PeerB bestBlock is incorrect"); } } @@ -466,25 +442,21 @@ public void test7() throws InterruptedException { // A == b8', B == b4 - ethereumB.addListener(new EthereumListenerAdapter() { - @Override - public void onRecvMessage(Channel channel, Message message) { - if (message instanceof NewBlockMessage) { - // it's time to do a re-branch - for (Block b : mainB1B10) { - blockchainA.tryToConnect(b); - } + ethereumB.subscribe(MESSAGE_RECEIVED, data -> { + if (data.getMessage() instanceof NewBlockMessage) { + // it's time to do a re-branch + for (Block b : mainB1B10) { + blockchainA.tryToConnect(b); } } + }); final CountDownLatch semaphore = new CountDownLatch(1); - ethereumB.addListener(new EthereumListenerAdapter() { - @Override - public void onBlock(Block block, List receipts) { - if (block.isEqual(b10)) { - semaphore.countDown(); - } + ethereumB.subscribe(BLOCK_ADDED, data -> { + Block block = data.getBlockSummary().getBlock(); + if (block.isEqual(b10)) { + semaphore.countDown(); } }); @@ -494,7 +466,7 @@ public void onBlock(Block block, List receipts) { semaphore.await(10, SECONDS); // check if B == b10 - if(semaphore.getCount() > 0) { + if (semaphore.getCount() > 0) { fail("PeerB bestBlock is incorrect"); } } @@ -529,27 +501,25 @@ public void test8() throws InterruptedException { final CountDownLatch semaphore = new CountDownLatch(1); final CountDownLatch semaphoreB7_ = new CountDownLatch(1); - ethereumB.addListener(new EthereumListenerAdapter() { - @Override - public void onBlock(Block block, List receipts) { - if (block.isEqual(b7_)) { - // it's time to do a re-branch - for (Block b : mainB1B10) { - blockchainA.tryToConnect(b); - } - - semaphoreB7_.countDown(); - } - if (block.isEqual(b10)) { - semaphore.countDown(); + ethereumB.subscribe(BLOCK_ADDED, data -> { + Block block = data.getBlockSummary().getBlock(); + if (block.isEqual(b7_)) { + // it's time to do a re-branch + for (Block b : mainB1B10) { + blockchainA.tryToConnect(b); } + + semaphoreB7_.countDown(); + } + if (block.isEqual(b10)) { + semaphore.countDown(); } }); ethA.sendNewBlockHashes(b7_); semaphoreB7_.await(10, SECONDS); - if(semaphoreB7_.getCount() > 0) { + if (semaphoreB7_.getCount() > 0) { fail("PeerB didn't import b7'"); } @@ -558,7 +528,7 @@ public void onBlock(Block block, List receipts) { semaphore.await(10, SECONDS); // check if B == b10 - if(semaphore.getCount() > 0) { + if (semaphore.getCount() > 0) { fail("PeerB bestBlock is incorrect"); } } @@ -613,19 +583,16 @@ protected void processGetBlockHeaders(GetBlockHeadersMessage msg) { ethA.sendNewBlock(b8_); final CountDownLatch semaphoreDisconnect = new CountDownLatch(1); - ethereumA.addListener(new EthereumListenerAdapter() { - @Override - public void onRecvMessage(Channel channel, Message message) { - if (message instanceof DisconnectMessage) { - semaphoreDisconnect.countDown(); - } + ethereumA.subscribe(MESSAGE_RECEIVED, messageData -> { + if (messageData.getMessage() instanceof DisconnectMessage) { + semaphoreDisconnect.countDown(); } }); semaphoreDisconnect.await(10, SECONDS); // check if peer was dropped - if(semaphoreDisconnect.getCount() > 0) { + if (semaphoreDisconnect.getCount() > 0) { fail("PeerA is not dropped"); } @@ -637,27 +604,21 @@ public void onRecvMessage(Channel channel, Message message) { } final CountDownLatch semaphore = new CountDownLatch(1); - ethereumB.addListener(new EthereumListenerAdapter() { - @Override - public void onBlock(Block block, List receipts) { - if (block.isEqual(b10)) { - semaphore.countDown(); - } + ethereumB.subscribe(BLOCK_ADDED, data -> { + Block block = data.getBlockSummary().getBlock(); + if (block.isEqual(b10)) { + semaphore.countDown(); } }); + final CountDownLatch semaphoreConnect = new CountDownLatch(1); - ethereumB.addListener(new EthereumListenerAdapter() { - @Override - public void onPeerAddedToSyncPool(Channel peer) { - semaphoreConnect.countDown(); - } - }); + ethereumB.subscribe(PEER_ADDED_TO_SYNC_POOL, channel -> semaphoreConnect.countDown()); ethereumB.connect(nodeA); // await connection semaphoreConnect.await(10, SECONDS); - if(semaphoreConnect.getCount() > 0) { + if (semaphoreConnect.getCount() > 0) { fail("PeerB is not able to connect to PeerA"); } @@ -666,7 +627,7 @@ public void onPeerAddedToSyncPool(Channel peer) { semaphore.await(10, SECONDS); // check if B == b10 - if(semaphore.getCount() > 0) { + if (semaphore.getCount() > 0) { fail("PeerB bestBlock is incorrect"); } } @@ -720,47 +681,38 @@ protected void processGetBlockHeaders(GetBlockHeadersMessage msg) { ethA.sendNewBlockHashes(b8_); final CountDownLatch semaphoreDisconnect = new CountDownLatch(1); - ethereumA.addListener(new EthereumListenerAdapter() { - @Override - public void onRecvMessage(Channel channel, Message message) { - if (message instanceof DisconnectMessage) { - semaphoreDisconnect.countDown(); - } + ethereumA.subscribe(MESSAGE_RECEIVED, data -> { + if (data.getMessage() instanceof DisconnectMessage) { + semaphoreDisconnect.countDown(); } }); + semaphoreDisconnect.await(10, SECONDS); // check if peer was dropped - if(semaphoreDisconnect.getCount() > 0) { + if (semaphoreDisconnect.getCount() > 0) { fail("PeerA is not dropped"); } // back to usual handler - SysPropConfigA.eth62 = null; + LongSyncTest.SysPropConfigA.eth62 = null; final CountDownLatch semaphore = new CountDownLatch(1); - ethereumB.addListener(new EthereumListenerAdapter() { - @Override - public void onBlock(Block block, List receipts) { - if (block.isEqual(b10)) { - semaphore.countDown(); - } + ethereumB.subscribe(BLOCK_ADDED, data -> { + Block block = data.getBlockSummary().getBlock(); + if (block.isEqual(b10)) { + semaphore.countDown(); } }); final CountDownLatch semaphoreConnect = new CountDownLatch(1); - ethereumB.addListener(new EthereumListenerAdapter() { - @Override - public void onPeerAddedToSyncPool(Channel peer) { - semaphoreConnect.countDown(); - } - }); + ethereumB.subscribe(PEER_ADDED_TO_SYNC_POOL, channel -> semaphoreConnect.countDown()); ethereumB.connect(nodeA); // await connection semaphoreConnect.await(10, SECONDS); - if(semaphoreConnect.getCount() > 0) { + if (semaphoreConnect.getCount() > 0) { fail("PeerB is not able to connect to PeerA"); } @@ -775,7 +727,7 @@ public void onPeerAddedToSyncPool(Channel peer) { semaphore.await(10, SECONDS); // check if B == b10 - if(semaphore.getCount() > 0) { + if (semaphore.getCount() > 0) { fail("PeerB bestBlock is incorrect"); } } @@ -808,15 +760,12 @@ public void test11() throws InterruptedException { final CountDownLatch semaphore1 = new CountDownLatch(1); final CountDownLatch semaphore2 = new CountDownLatch(1); - ethereumB.addListener(new EthereumListenerAdapter() { - @Override - public void onBlock(Block block, List receipts) { - if (block.isEqual(b6_)) { - if (semaphore1.getCount() > 0) { - semaphore1.countDown(); - } else { - semaphore2.countDown(); - } + ethereumB.subscribe(BLOCK_ADDED, data -> { + if (data.getBlockSummary().getBlock().isEqual(b6_)) { + if (semaphore1.getCount() > 0) { + semaphore1.countDown(); + } else { + semaphore2.countDown(); } } }); @@ -824,7 +773,7 @@ public void onBlock(Block block, List receipts) { ethA.sendNewBlock(b6_); semaphore1.await(10, SECONDS); - if(semaphore1.getCount() > 0) { + if (semaphore1.getCount() > 0) { fail("PeerB doesn't accept block with higher TD"); } @@ -839,7 +788,7 @@ public void onBlock(Block block, List receipts) { semaphore2.await(5, SECONDS); // check if B skips b6' - if(semaphore2.getCount() == 0) { + if (semaphore2.getCount() == 0) { fail("PeerB doesn't skip block with lower TD"); } } @@ -873,21 +822,16 @@ protected void processGetBlockBodies(GetBlockBodiesMessage msg) { // A == b10, B == genesis final CountDownLatch semaphoreDisconnect = new CountDownLatch(1); - ethereumA.addListener(new EthereumListenerAdapter() { - @Override - public void onRecvMessage(Channel channel, Message message) { - if (message instanceof DisconnectMessage) { - semaphoreDisconnect.countDown(); - } - } - }); + this.ethereumA + .subscribe(to(MESSAGE_RECEIVED, data -> semaphoreDisconnect.countDown()) + .conditionally(data -> data.getMessage() instanceof DisconnectMessage)); ethA.sendNewBlock(b10); semaphoreDisconnect.await(10, SECONDS); // check if peer was dropped - if(semaphoreDisconnect.getCount() > 0) { + if (semaphoreDisconnect.getCount() > 0) { fail("PeerA is not dropped"); } } @@ -938,21 +882,15 @@ protected void processGetBlockHeaders(GetBlockHeadersMessage msg) { // A == b8', B == b10 final CountDownLatch semaphoreDisconnect = new CountDownLatch(1); - ethereumA.addListener(new EthereumListenerAdapter() { - @Override - public void onRecvMessage(Channel channel, Message message) { - if (message instanceof DisconnectMessage) { - semaphoreDisconnect.countDown(); - } - } - }); + ethereumA.subscribe(to(MESSAGE_RECEIVED, data -> semaphoreDisconnect.countDown()) + .conditionally(data -> data.getMessage() instanceof DisconnectMessage)); ethA.sendNewBlockHashes(b8_); semaphoreDisconnect.await(10, SECONDS); // check if peer was dropped - if(semaphoreDisconnect.getCount() > 0) { + if (semaphoreDisconnect.getCount() > 0) { fail("PeerA is not dropped"); } } @@ -978,7 +916,7 @@ protected void processGetBlockHeaders(GetBlockHeadersMessage msg) { forkB1B5B8_.get(7).getHeader(), forkB1B5B8_.get(6).getHeader(), new BlockHeader(new byte[32], new byte[32], new byte[32], new byte[32], new byte[32], - 6, new byte[] {0}, 0, 0, new byte[0], new byte[0], new byte[0]), + 6, new byte[]{0}, 0, 0, new byte[0], new byte[0], new byte[0]), forkB1B5B8_.get(4).getHeader() ); @@ -1004,21 +942,15 @@ protected void processGetBlockHeaders(GetBlockHeadersMessage msg) { // A == b8', B == b10 final CountDownLatch semaphoreDisconnect = new CountDownLatch(1); - ethereumA.addListener(new EthereumListenerAdapter() { - @Override - public void onRecvMessage(Channel channel, Message message) { - if (message instanceof DisconnectMessage) { - semaphoreDisconnect.countDown(); - } - } - }); + ethereumA.subscribe(to(MESSAGE_RECEIVED, data -> semaphoreDisconnect.countDown()) + .conditionally(data -> data.getMessage() instanceof DisconnectMessage)); ethA.sendNewBlockHashes(b8_); semaphoreDisconnect.await(10, SECONDS); // check if peer was dropped - if(semaphoreDisconnect.getCount() > 0) { + if (semaphoreDisconnect.getCount() > 0) { fail("PeerA is not dropped"); } } @@ -1028,26 +960,15 @@ private void setupPeers() throws InterruptedException { ethereumA = EthereumFactory.createEthereum(SysPropConfigA.props, SysPropConfigA.class); ethereumB = EthereumFactory.createEthereum(SysPropConfigB.props, SysPropConfigB.class); - ethereumA.addListener(new EthereumListenerAdapter() { - @Override - public void onEthStatusUpdated(Channel channel, StatusMessage statusMessage) { - ethA = (EthHandler) channel.getEthHandler(); - } - }); + ethereumA.subscribe(ETH_STATUS_UPDATED, data -> ethA = (EthHandler) data.getChannel().getEthHandler()); final CountDownLatch semaphore = new CountDownLatch(1); - ethereumB.addListener(new EthereumListenerAdapter() { - @Override - public void onPeerAddedToSyncPool(Channel peer) { - semaphore.countDown(); - } - }); - + ethereumB.subscribe(PEER_ADDED_TO_SYNC_POOL, channel -> semaphore.countDown()); ethereumB.connect(nodeA); semaphore.await(10, SECONDS); - if(semaphore.getCount() > 0) { + if (semaphore.getCount() > 0) { fail("Failed to set up peers"); } } diff --git a/ethereumj-core/src/test/java/org/ethereum/util/HashUtilTest.java b/ethereumj-core/src/test/java/org/ethereum/util/HashUtilTest.java index 376e72541d..a7a62d3b59 100644 --- a/ethereumj-core/src/test/java/org/ethereum/util/HashUtilTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/util/HashUtilTest.java @@ -23,6 +23,7 @@ import org.spongycastle.util.encoders.Hex; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; public class HashUtilTest { @@ -101,4 +102,42 @@ public void testRIPEMD160_Multiple() { String result2 = Hex.toHexString(HashUtil.ripemd160("test2".getBytes())); assertEquals(expected2, result2); } + + @Test + public void testCalcSaltAddress() { + assertArrayEquals(Hex.decode("4D1A2e2bB4F88F0250f26Ffff098B0b30B26BF38"), HashUtil.calcSaltAddr( + Hex.decode("0000000000000000000000000000000000000000"), + Hex.decode("00"), + Hex.decode("0000000000000000000000000000000000000000000000000000000000000000"))); + + assertArrayEquals(Hex.decode("B928f69Bb1D91Cd65274e3c79d8986362984fDA3"), HashUtil.calcSaltAddr( + Hex.decode("deadbeef00000000000000000000000000000000"), + Hex.decode("00"), + Hex.decode("0000000000000000000000000000000000000000000000000000000000000000"))); + + assertArrayEquals(Hex.decode("D04116cDd17beBE565EB2422F2497E06cC1C9833"), HashUtil.calcSaltAddr( + Hex.decode("deadbeef00000000000000000000000000000000"), + Hex.decode("00"), + Hex.decode("000000000000000000000000feed000000000000000000000000000000000000"))); + + assertArrayEquals(Hex.decode("70f2b2914A2a4b783FaEFb75f459A580616Fcb5e"), HashUtil.calcSaltAddr( + Hex.decode("0000000000000000000000000000000000000000"), + Hex.decode("deadbeef"), + Hex.decode("0000000000000000000000000000000000000000000000000000000000000000"))); + + assertArrayEquals(Hex.decode("60f3f640a8508fC6a86d45DF051962668E1e8AC7"), HashUtil.calcSaltAddr( + Hex.decode("00000000000000000000000000000000deadbeef"), + Hex.decode("deadbeef"), + Hex.decode("00000000000000000000000000000000000000000000000000000000cafebabe"))); + + assertArrayEquals(Hex.decode("1d8bfDC5D46DC4f61D6b6115972536eBE6A8854C"), HashUtil.calcSaltAddr( + Hex.decode("00000000000000000000000000000000deadbeef"), + Hex.decode("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"), + Hex.decode("00000000000000000000000000000000000000000000000000000000cafebabe"))); + + assertArrayEquals(Hex.decode("E33C0C7F7df4809055C3ebA6c09CFe4BaF1BD9e0"), HashUtil.calcSaltAddr( + Hex.decode("0000000000000000000000000000000000000000"), + Hex.decode(""), + Hex.decode("0000000000000000000000000000000000000000000000000000000000000000"))); + } } diff --git a/ethereumj-core/src/test/java/org/ethereum/util/RandomGenerator.java b/ethereumj-core/src/test/java/org/ethereum/util/RandomGenerator.java new file mode 100644 index 0000000000..23d44527dc --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/util/RandomGenerator.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) [2016] [ ] + * 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 . + */ +package org.ethereum.util; + +import com.google.common.base.Function; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +public class RandomGenerator { + + private final Random random; + private final List> genFunctions = new ArrayList<>(); + + + public RandomGenerator(Random random) { + this.random = random; + } + + public RandomGenerator addGenFunction(Function function) { + genFunctions.add(function); + return this; + } + + public E randomFrom(List list) { + return list.get(random.nextInt(list.size())); + } + + public E randomFrom(E[] array) { + return array[random.nextInt(array.length)]; + } + + public T genNext() { + if (genFunctions.isEmpty()) { + throw new IllegalStateException(); + } + + return randomFrom(genFunctions).apply(random); + } +} diff --git a/ethereumj-core/src/test/java/org/ethereum/util/StandaloneBlockchainTest.java b/ethereumj-core/src/test/java/org/ethereum/util/StandaloneBlockchainTest.java index 3132a9c6d7..e970d90436 100644 --- a/ethereumj-core/src/test/java/org/ethereum/util/StandaloneBlockchainTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/util/StandaloneBlockchainTest.java @@ -115,14 +115,28 @@ public void encodeTest1() { StandaloneBlockchain sb = new StandaloneBlockchain().withAutoblock(true); SolidityContract a = sb.submitNewContract( "contract A {" + - " uint public a;" + - " function f(uint a_) {a = a_;}" + + " int public a;" + + " function f(int a_) {a = a_;}" + "}"); a.callFunction("f", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); BigInteger r = (BigInteger) a.callConstFunction("a")[0]; System.out.println(r.toString(16)); Assert.assertEquals(new BigInteger(Hex.decode("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")), r); } + + @Test + public void encodeTest2() { + StandaloneBlockchain sb = new StandaloneBlockchain().withAutoblock(true); + SolidityContract a = sb.submitNewContract( + "contract A {" + + " uint public a;" + + " function f(uint a_) {a = a_;}" + + "}"); + a.callFunction("f", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + BigInteger r = (BigInteger) a.callConstFunction("a")[0]; + System.out.println(r.toString(16)); + Assert.assertEquals(new BigInteger(1, Hex.decode("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")), r); + } @Test public void invalidTxTest() { diff --git a/ethereumj-core/src/test/java/org/ethereum/validator/EthashRuleTest.java b/ethereumj-core/src/test/java/org/ethereum/validator/EthashRuleTest.java index 6c0d09b269..4d72456822 100644 --- a/ethereumj-core/src/test/java/org/ethereum/validator/EthashRuleTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/validator/EthashRuleTest.java @@ -3,14 +3,17 @@ import org.ethereum.core.Block; import org.ethereum.core.BlockHeader; import org.ethereum.core.BlockSummary; -import org.ethereum.listener.CompositeEthereumListener; -import org.ethereum.listener.EthereumListener; +import org.ethereum.core.EventDispatchThread; import org.ethereum.mine.EthashValidationHelper; +import org.ethereum.publish.Publisher; import org.junit.Test; import org.spongycastle.util.encoders.Hex; import java.util.Collections; +import static org.ethereum.publish.event.Events.onBlockAdded; +import static org.ethereum.publish.event.Events.onSyncDone; +import static org.ethereum.sync.SyncManager.State.COMPLETE; import static org.ethereum.validator.BlockHeaderRule.Success; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; @@ -22,22 +25,6 @@ */ public class EthashRuleTest { - static class CompositeEthereumListenerMock extends CompositeEthereumListener { - @Override - public void onBlock(final BlockSummary blockSummary, final boolean best) { - for (final EthereumListener listener : listeners) { - listener.onBlock(blockSummary, best); - } - } - - @Override - public void onSyncDone(final SyncState state) { - for (final EthereumListener listener : listeners) { - listener.onSyncDone(state); - } - } - } - static class EthashValidationHelperMock extends EthashValidationHelper { long preCacheCounter = 0; @@ -66,8 +53,8 @@ public void fake() { @Test public void testFake() { - CompositeEthereumListener listener = new CompositeEthereumListenerMock(); - EthashRule rule = new EthashRule(EthashRule.Mode.fake, EthashRule.ChainType.main, listener); + Publisher publisher = new Publisher(EventDispatchThread.getDefault()); + EthashRule rule = new EthashRule(EthashRule.Mode.fake, EthashRule.ChainType.main, publisher); assertEquals(Success, rule.validate(validHeader)); assertEquals(Success, rule.validate(partlyValidHeader)); @@ -76,14 +63,14 @@ public void testFake() { @Test public void testStrict() { - CompositeEthereumListener listener = new CompositeEthereumListenerMock(); - EthashRule rule = new EthashRule(EthashRule.Mode.strict, EthashRule.ChainType.main, listener); + Publisher publisher = new Publisher(EventDispatchThread.getDefault()); + EthashRule rule = new EthashRule(EthashRule.Mode.strict, EthashRule.ChainType.main, publisher); // cache is empty, fallback into fake rule assertEquals(Success, rule.validate(partlyValidHeader)); // trigger ethash cache - listener.onBlock(dummySummaryNum_1, true); + publisher.publish(onBlockAdded(dummySummaryNum_1, true)); assertEquals(Success, rule.validate(validHeader)); assertNotEquals(Success, rule.validate(partlyValidHeader)); @@ -97,11 +84,11 @@ public void testStrict() { @Test public void testMixed() { - CompositeEthereumListener listener = new CompositeEthereumListenerMock(); - EthashRule rule = new EthashRule(EthashRule.Mode.mixed, EthashRule.ChainType.main, listener); + Publisher publisher = new Publisher(EventDispatchThread.getDefault()); + EthashRule rule = new EthashRule(EthashRule.Mode.mixed, EthashRule.ChainType.main, publisher); // trigger ethash cache - listener.onBlock(dummySummaryNum_1, true); + publisher.publish(onBlockAdded(dummySummaryNum_1, true)); // check mixed mode randomness boolean fullCheckTriggered = false; @@ -119,7 +106,7 @@ public void testMixed() { assertTrue(partialCheckTriggered); // trigger onSyncDone - listener.onSyncDone(EthereumListener.SyncState.COMPLETE); + publisher.publish(onSyncDone(COMPLETE)); // check that full verification is done on each run in strict mode for (int i = 0; i < 100; i++) { @@ -130,14 +117,15 @@ public void testMixed() { @Test public void testCacheMain() { EthashValidationHelperMock helper = new EthashValidationHelperMock(EthashValidationHelper.CacheOrder.direct); - CompositeEthereumListener listener = new CompositeEthereumListenerMock(); - EthashRule rule = new EthashRule(EthashRule.Mode.mixed, EthashRule.ChainType.main, listener); + Publisher publisher = new Publisher(EventDispatchThread.getDefault()); + EthashRule rule = new EthashRule(EthashRule.Mode.mixed, EthashRule.ChainType.main, publisher); rule.ethashHelper = helper; // trigger cache for (int i = 0; i < 100; i++) { - listener.onBlock(dummySummaryNum_1, false); - listener.onBlock(dummySummaryNum_1, true); + publisher + .publish(onBlockAdded(dummySummaryNum_1, false)) + .publish(onBlockAdded(dummySummaryNum_1, true)); } // must be triggered on best block only @@ -147,8 +135,8 @@ public void testCacheMain() { @Test public void testCacheDirect() { EthashValidationHelperMock helper = new EthashValidationHelperMock(EthashValidationHelper.CacheOrder.direct); - CompositeEthereumListener listener = new CompositeEthereumListenerMock(); - EthashRule rule = new EthashRule(EthashRule.Mode.mixed, EthashRule.ChainType.direct, listener); + Publisher publisher = new Publisher(EventDispatchThread.getDefault()); + EthashRule rule = new EthashRule(EthashRule.Mode.mixed, EthashRule.ChainType.direct, publisher); rule.ethashHelper = helper; // trigger cache @@ -159,11 +147,12 @@ public void testCacheDirect() { // must be triggered each verification attempt, in spite of mixed mode assertEquals(100, helper.preCacheCounter); } + @Test public void testCacheReverse() { EthashValidationHelperMock helper = new EthashValidationHelperMock(EthashValidationHelper.CacheOrder.direct); - CompositeEthereumListener listener = new CompositeEthereumListenerMock(); - EthashRule rule = new EthashRule(EthashRule.Mode.mixed, EthashRule.ChainType.reverse, listener); + Publisher publisher = new Publisher(EventDispatchThread.getDefault()); + EthashRule rule = new EthashRule(EthashRule.Mode.mixed, EthashRule.ChainType.reverse, publisher); rule.ethashHelper = helper; // trigger cache