* 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:
+ *
+ *
{@link #subscribe(Class, Consumer)}
+ *
{@link #subscribe(Class, BiConsumer)}
+ *
+ *
+ * @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 extends Event> 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 extends Event> 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 extends Event> type, Consumer handler) {
+ return subscribe(to(type, handler));
+ }
+
+ @Override
+ public Ethereum subscribe(Class extends Event> 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 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 extends Event> 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 extends Event> 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 extends Event> 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 extends Event> ETH_STATUS_UPDATED = EthStatusUpdated.class;
+
+ Class extends Event>> MESSAGE_RECEIVED = MessageReceived.class;
+
+ Class extends Event>> MESSAGE_SENT = MessageSent.class;
+
+ Class extends Event> PEER_HANDSHAKED = PeerHandshaked.class;
+
+ Class extends Event> BLOCK_ADDED = BlockAdded.class;
+
+ Class extends Event> NODE_DISCOVERED = NodeDiscovered.class;
+
+ Class extends Event> PEER_ADDED_TO_SYNC_POOL = PeerAddedToSyncPool.class;
+
+ Class extends Event> PEER_DISCONNECTED = PeerDisconnected.class;
+
+ Class extends Event> PENDING_STATE_CHANGED = PendingStateChanged.class;
+
+ Class extends Event> PENDING_TRANSACTION_UPDATED = PendingTransactionUpdated.class;
+
+ Class extends Event> SYNC_DONE = SyncDone.class;
+
+ Class extends Event> TRANSACTION_EXECUTED = TransactionExecuted.class;
+
+ Class extends Event> 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