package org.ethereum.longrun; import com.googlecode.jsonrpc4j.JsonRpcHttpClient; import com.googlecode.jsonrpc4j.ProxyUtil; import com.typesafe.config.ConfigFactory; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Triple; import org.ethereum.config.SystemProperties; import org.ethereum.core.Block; import org.ethereum.core.TransactionReceipt; import org.ethereum.facade.EthereumFactory; import org.ethereum.jsonrpc.JsonRpc; import org.ethereum.jsonrpc.TransactionResultDTO; import org.ethereum.listener.EthereumListener; import org.ethereum.listener.EthereumListenerAdapter; import org.ethereum.util.ByteArrayMap; import org.junit.Ignore; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.spongycastle.util.encoders.Hex; import org.springframework.context.annotation.Bean; import java.net.URL; import java.util.HashSet; /** * Matches pending transactions from EthereumJ and any other JSON-RPC client * * Created by Anton Nashatyrev on 15.02.2017. */ @Ignore public class PendingTxMonitor extends BasicNode { private static final Logger testLogger = LoggerFactory.getLogger("TestLogger"); /** * Spring configuration class for the Regular peer */ private static class RegularConfig { @Bean public PendingTxMonitor node() { return new PendingTxMonitor(); } /** * Instead of supplying properties via config file for the peer * we are substituting the corresponding bean which returns required * config for this instance. */ @Bean public SystemProperties systemProperties() { SystemProperties props = new SystemProperties(); props.overrideParams(ConfigFactory.parseString( "peer.discovery.enabled = true\n" + "sync.enabled = true\n" + "sync.fast.enabled = true\n" + "database.dir = database-test-ptx\n" + "database.reset = false\n" )); return props; } } public PendingTxMonitor() { super("sampleNode"); } @Override public void run() { try { setupRemoteRpc(); } catch (Exception e) { throw new RuntimeException(e); } super.run(); } private void setupRemoteRpc() throws Exception { System.out.println("Creating RPC interface..."); JsonRpcHttpClient httpClient = new JsonRpcHttpClient(new URL("http://localhost:8545")); final JsonRpc jsonRpc = ProxyUtil.createClientProxy(getClass().getClassLoader(), JsonRpc.class, httpClient); System.out.println("Pinging remote RPC..."); String protocolVersion = jsonRpc.eth_protocolVersion(); System.out.println("Remote OK. Version: " + protocolVersion); final String pTxFilterId = jsonRpc.eth_newPendingTransactionFilter(); new Thread(new Runnable() { @Override public void run() { try { while (Boolean.TRUE) { Object[] changes = jsonRpc.eth_getFilterChanges(pTxFilterId); if (changes.length > 0) { for (Object change : changes) { TransactionResultDTO tx = jsonRpc.eth_getTransactionByHash((String) change); newRemotePendingTx(tx); } } Thread.sleep(100); } } catch (Exception e) { e.printStackTrace(); } } }).start(); } @Override public void onSyncDoneImpl(EthereumListener.SyncState state) { super.onSyncDoneImpl(state); if (remoteTxs == null) { remoteTxs = new ByteArrayMap<>(); System.out.println("Sync Done!!!"); ethereum.addListener(new EthereumListenerAdapter() { @Override public void onPendingTransactionUpdate(TransactionReceipt txReceipt, PendingTransactionState state, Block block) { PendingTxMonitor.this.onPendingTransactionUpdate(txReceipt, state, block); } }); } } ByteArrayMap<Triple<Long, TransactionReceipt, EthereumListener.PendingTransactionState>> localTxs = new ByteArrayMap<>(); ByteArrayMap<Pair<Long, TransactionResultDTO>> remoteTxs; private void checkUnmatched() { for (byte[] txHash : new HashSet<>(localTxs.keySet())) { Triple<Long, TransactionReceipt, EthereumListener.PendingTransactionState> tx = localTxs.get(txHash); if (System.currentTimeMillis() - tx.getLeft() > 60_000) { localTxs.remove(txHash); System.err.println("Local tx doesn't match: " + tx.getMiddle().getTransaction()); } } for (byte[] txHash : new HashSet<>(remoteTxs.keySet())) { Pair<Long, TransactionResultDTO> tx = remoteTxs.get(txHash); if (System.currentTimeMillis() - tx.getLeft() > 60_000) { remoteTxs.remove(txHash); System.err.println("Remote tx doesn't match: " + tx.getRight()); } } } private void onPendingTransactionUpdate(TransactionReceipt txReceipt, EthereumListener.PendingTransactionState state, Block block) { byte[] txHash = txReceipt.getTransaction().getHash(); Pair<Long, TransactionResultDTO> removed = remoteTxs.remove(txHash); if (state == EthereumListener.PendingTransactionState.DROPPED) { if (localTxs.remove(txHash) != null) { System.out.println("Dropped due to timeout (matchned: " + (removed != null) + "): " + Hex.toHexString(txHash)); } else { if (remoteTxs.containsKey(txHash)) { System.err.println("Dropped but matching: " + Hex.toHexString(txHash) + ": \n" + txReceipt); } } } else if (state == EthereumListener.PendingTransactionState.NEW_PENDING) { System.out.println("Local: " + Hex.toHexString(txHash)); if (removed == null) { localTxs.put(txHash, Triple.of(System.currentTimeMillis(), txReceipt, state)); } else { System.out.println("Tx matched: " + Hex.toHexString(txHash)); } } checkUnmatched(); } public void newRemotePendingTx(TransactionResultDTO tx) { byte[] txHash = Hex.decode(tx.hash.substring(2)); if (remoteTxs == null) return; System.out.println("Remote: " + Hex.toHexString(txHash)); Triple<Long, TransactionReceipt, EthereumListener.PendingTransactionState> removed = localTxs.remove(txHash); if (removed == null) { remoteTxs.put(txHash, Pair.of(System.currentTimeMillis(), tx)); } else { System.out.println("Tx matched: " + Hex.toHexString(txHash)); } checkUnmatched(); } @Test public void test() throws Exception { testLogger.info("Starting EthereumJ regular instance!"); EthereumFactory.createEthereum(RegularConfig.class); Thread.sleep(100000000000L); } }