/* * Copyright (c) [2016] [ <ether.camp> ] * This file is part of the ethereumJ library. * * The ethereumJ library is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The ethereumJ library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the ethereumJ library. If not, see <http://www.gnu.org/licenses/>. */ package org.ethereum.core; import org.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.util.blockchain.SolidityContract; import org.ethereum.util.blockchain.StandaloneBlockchain; import org.junit.*; import java.math.BigInteger; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import static java.util.concurrent.TimeUnit.SECONDS; import static org.ethereum.listener.EthereumListener.PendingTransactionState.*; import static org.ethereum.util.blockchain.EtherUtil.Unit.ETHER; import static org.ethereum.util.blockchain.EtherUtil.convert; /** * @author Mikhail Kalinin * @since 28.09.2015 */ public class PendingStateTest { @BeforeClass public static void setup() { SystemProperties.getDefault().setBlockchainConfig(StandaloneBlockchain.getEasyMiningConfig()); } @AfterClass public static void cleanup() { SystemProperties.resetToDefault(); } static class PendingListener extends EthereumListenerAdapter { public BlockingQueue<Pair<Block, List<TransactionReceipt>>> onBlock = new LinkedBlockingQueue<>(); public BlockingQueue<Object> onPendingStateChanged = new LinkedBlockingQueue<>(); // public BlockingQueue<Triple<TransactionReceipt, PendingTransactionState, Block>> onPendingTransactionUpdate = new LinkedBlockingQueue<>(); Map<ByteArrayWrapper, BlockingQueue<Triple<TransactionReceipt, PendingTransactionState, Block>>> onPendingTransactionUpdate = new HashMap<>(); @Override public void onBlock(Block block, List<TransactionReceipt> receipts) { System.out.println("PendingStateTest.onBlock:" + "block = [" + block.getShortDescr() + "]"); onBlock.add(Pair.of(block, receipts)); } @Override public void onPendingStateChanged(PendingState pendingState) { System.out.println("PendingStateTest.onPendingStateChanged."); onPendingStateChanged.add(new Object()); } @Override public void onPendingTransactionUpdate(TransactionReceipt txReceipt, PendingTransactionState state, Block block) { System.out.println("PendingStateTest.onPendingTransactionUpdate:" + "txReceipt.err = [" + txReceipt.getError() + "], state = [" + state + "], block: " + block.getShortDescr()); getQueueFor(txReceipt.getTransaction()).add(Triple.of(txReceipt, state, block)); } public synchronized BlockingQueue<Triple<TransactionReceipt, PendingTransactionState, Block>> getQueueFor(Transaction tx) { ByteArrayWrapper hashW = new ByteArrayWrapper(tx.getHash()); BlockingQueue<Triple<TransactionReceipt, PendingTransactionState, Block>> queue = onPendingTransactionUpdate.get(hashW); if (queue == null) { queue = new LinkedBlockingQueue<>(); onPendingTransactionUpdate.put(hashW, queue); } return queue; } public PendingTransactionState pollTxUpdateState(Transaction tx) throws InterruptedException { return getQueueFor(tx).poll(5, SECONDS).getMiddle(); } public Triple<TransactionReceipt, PendingTransactionState, Block> pollTxUpdate(Transaction tx) throws InterruptedException { return getQueueFor(tx).poll(5, SECONDS); } } @Test public void testSimple() throws InterruptedException { StandaloneBlockchain bc = new StandaloneBlockchain(); PendingListener l = new PendingListener(); bc.addEthereumListener(l); Triple<TransactionReceipt, EthereumListener.PendingTransactionState, Block> txUpd; PendingStateImpl pendingState = (PendingStateImpl) bc.getBlockchain().getPendingState(); ECKey alice = new ECKey(); bc.sendEther(new byte[20], BigInteger.valueOf(100000)); bc.sendEther(new byte[20], BigInteger.valueOf(100000)); bc.createBlock(); l.onBlock.poll(5, SECONDS); Transaction tx1 = bc.createTransaction(100, new byte[32], 1000, new byte[0]); pendingState.addPendingTransaction(tx1); // dropped due to large nonce Assert.assertEquals(l.pollTxUpdateState(tx1), DROPPED); Transaction tx1_ = bc.createTransaction(0, new byte[32], 1000, new byte[0]); pendingState.addPendingTransaction(tx1_); // dropped due to low nonce Assert.assertEquals(l.pollTxUpdateState(tx1_), DROPPED); Transaction tx2 = bc.createTransaction(2, alice.getAddress(), 1000000, new byte[0]); Transaction tx3 = bc.createTransaction(3, alice.getAddress(), 1000000, new byte[0]); pendingState.addPendingTransaction(tx2); pendingState.addPendingTransaction(tx3); txUpd = l.pollTxUpdate(tx2); Assert.assertEquals(txUpd.getMiddle(), NEW_PENDING); Assert.assertTrue(txUpd.getLeft().isValid()); txUpd = l.pollTxUpdate(tx3); Assert.assertEquals(txUpd.getMiddle(), NEW_PENDING); Assert.assertTrue(txUpd.getLeft().isValid()); Assert.assertTrue(pendingState.getRepository().getBalance(alice.getAddress()). compareTo(BigInteger.valueOf(2000000 - 100000)) > 0); pendingState.addPendingTransaction(tx2); // double transaction submit Assert.assertTrue(l.getQueueFor(tx2).isEmpty()); bc.createBlock(); Assert.assertEquals(l.pollTxUpdateState(tx2), PENDING); Assert.assertEquals(l.pollTxUpdateState(tx3), PENDING); bc.submitTransaction(tx2); Block b3 = bc.createBlock(); txUpd = l.pollTxUpdate(tx2); Assert.assertEquals(txUpd.getMiddle(), INCLUDED); Assert.assertEquals(txUpd.getRight(), b3); Assert.assertEquals(l.pollTxUpdateState(tx3), PENDING); Assert.assertTrue(pendingState.getRepository().getBalance(alice.getAddress()). compareTo(BigInteger.valueOf(2000000 - 100000)) > 0); for (int i = 0; i < SystemProperties.getDefault().txOutdatedThreshold() + 1; i++) { bc.createBlock(); txUpd = l.pollTxUpdate(tx3); if (txUpd.getMiddle() != PENDING) break; } // tx3 dropped due to timeout Assert.assertEquals(txUpd.getMiddle(), DROPPED); Assert.assertEquals(txUpd.getLeft().getTransaction(), tx3); Assert.assertFalse(pendingState.getRepository().getBalance(alice.getAddress()). compareTo(BigInteger.valueOf(2000000 - 100000)) > 0); } @Test public void testRebranch1() throws InterruptedException { StandaloneBlockchain bc = new StandaloneBlockchain(); PendingListener l = new PendingListener(); bc.addEthereumListener(l); Triple<TransactionReceipt, EthereumListener.PendingTransactionState, Block> txUpd = null; PendingStateImpl pendingState = (PendingStateImpl) bc.getBlockchain().getPendingState(); ECKey alice = new ECKey(); ECKey bob = new ECKey(); ECKey charlie = new ECKey(); bc.sendEther(bob.getAddress(), convert(100, ETHER)); bc.sendEther(charlie.getAddress(), convert(100, ETHER)); Block b1 = bc.createBlock(); Transaction tx1 = bc.createTransaction(bob, 0, alice.getAddress(), BigInteger.valueOf(1000000), new byte[0]); pendingState.addPendingTransaction(tx1); Transaction tx2 = bc.createTransaction(charlie, 0, alice.getAddress(), BigInteger.valueOf(1000000), new byte[0]);; pendingState.addPendingTransaction(tx2); Assert.assertEquals(l.pollTxUpdateState(tx1), NEW_PENDING); Assert.assertEquals(l.pollTxUpdateState(tx2), NEW_PENDING); Assert.assertTrue(pendingState.getRepository().getBalance(alice.getAddress()). compareTo(BigInteger.valueOf(2000000)) == 0); bc.submitTransaction(tx1); Block b2 = bc.createBlock(); Assert.assertEquals(l.pollTxUpdateState(tx1), INCLUDED); Assert.assertEquals(l.pollTxUpdateState(tx2), PENDING); Assert.assertTrue(pendingState.getRepository().getBalance(alice.getAddress()). compareTo(BigInteger.valueOf(2000000)) == 0); bc.submitTransaction(tx2); Block b3 = bc.createBlock(); Assert.assertEquals(l.pollTxUpdateState(tx2), INCLUDED); Assert.assertTrue(pendingState.getRepository().getBalance(alice.getAddress()). compareTo(BigInteger.valueOf(2000000)) == 0); Block b2_ = bc.createForkBlock(b1); Block b3_ = bc.createForkBlock(b2_); bc.submitTransaction(tx2); Block b4_ = bc.createForkBlock(b3_); Assert.assertEquals(l.pollTxUpdateState(tx1), PENDING); Assert.assertTrue(l.getQueueFor(tx1).isEmpty()); Assert.assertEquals(l.pollTxUpdateState(tx2), INCLUDED); Assert.assertTrue(l.getQueueFor(tx2).isEmpty()); Assert.assertTrue(pendingState.getRepository().getBalance(alice.getAddress()). compareTo(BigInteger.valueOf(2000000)) == 0); bc.submitTransaction(tx1); Block b5_ = bc.createForkBlock(b4_); Assert.assertEquals(l.pollTxUpdateState(tx1), INCLUDED); Assert.assertTrue(l.getQueueFor(tx1).isEmpty()); Assert.assertTrue(l.getQueueFor(tx2).isEmpty()); Assert.assertTrue(pendingState.getRepository().getBalance(alice.getAddress()). compareTo(BigInteger.valueOf(2000000)) == 0); } @Test public void testRebranch2() throws InterruptedException { StandaloneBlockchain bc = new StandaloneBlockchain(); PendingListener l = new PendingListener(); bc.addEthereumListener(l); Triple<TransactionReceipt, EthereumListener.PendingTransactionState, Block> txUpd = null; PendingStateImpl pendingState = (PendingStateImpl) bc.getBlockchain().getPendingState(); ECKey alice = new ECKey(); ECKey bob = new ECKey(); ECKey charlie = new ECKey(); bc.sendEther(bob.getAddress(), convert(100, ETHER)); bc.sendEther(charlie.getAddress(), convert(100, ETHER)); Block b1 = bc.createBlock(); Transaction tx1 = bc.createTransaction(bob, 0, alice.getAddress(), BigInteger.valueOf(1000000), new byte[0]); pendingState.addPendingTransaction(tx1); Transaction tx2 = bc.createTransaction(charlie, 0, alice.getAddress(), BigInteger.valueOf(1000000), new byte[0]);; pendingState.addPendingTransaction(tx2); Assert.assertEquals(l.pollTxUpdateState(tx1), NEW_PENDING); Assert.assertEquals(l.pollTxUpdateState(tx2), NEW_PENDING); Assert.assertTrue(pendingState.getRepository().getBalance(alice.getAddress()). compareTo(BigInteger.valueOf(2000000)) == 0); bc.submitTransaction(tx1); bc.sendEther(alice.getAddress(), BigInteger.valueOf(1000000)); Block b2 = bc.createBlock(); Transaction tx3 = b2.getTransactionsList().get(1); Assert.assertEquals(l.pollTxUpdateState(tx1), INCLUDED); Assert.assertEquals(l.pollTxUpdateState(tx2), PENDING); Assert.assertTrue(l.getQueueFor(tx1).isEmpty()); Assert.assertTrue(l.getQueueFor(tx2).isEmpty()); Assert.assertTrue(pendingState.getRepository().getBalance(alice.getAddress()). compareTo(BigInteger.valueOf(3000000)) == 0); bc.sendEther(alice.getAddress(), BigInteger.valueOf(1000000)); bc.submitTransaction(tx2); Block b3 = bc.createBlock(); Transaction tx4 = b3.getTransactionsList().get(0); Assert.assertEquals(l.pollTxUpdateState(tx2), INCLUDED); Assert.assertTrue(pendingState.getRepository().getBalance(alice.getAddress()). compareTo(BigInteger.valueOf(4000000)) == 0); bc.submitTransaction(tx2); Block b2_ = bc.createForkBlock(b1); bc.submitTransaction(tx1); Block b3_ = bc.createForkBlock(b2_); Block b4_ = bc.createForkBlock(b3_); // becoming the best branch txUpd = l.pollTxUpdate(tx1); Assert.assertEquals(txUpd.getMiddle(), INCLUDED); Assert.assertEquals(txUpd.getRight(), b3_); Assert.assertTrue(l.getQueueFor(tx1).isEmpty()); txUpd = l.pollTxUpdate(tx2); Assert.assertEquals(txUpd.getMiddle(), INCLUDED); Assert.assertEquals(txUpd.getRight(), b2_); Assert.assertTrue(l.getQueueFor(tx2).isEmpty()); Assert.assertEquals(l.pollTxUpdateState(tx3), PENDING); Assert.assertEquals(l.pollTxUpdateState(tx4), PENDING); Assert.assertTrue(pendingState.getRepository().getBalance(alice.getAddress()). compareTo(BigInteger.valueOf(4000000)) == 0); // rebranching back Block b4 = bc.createForkBlock(b3); Block b5 = bc.createForkBlock(b4); txUpd = l.pollTxUpdate(tx1); Assert.assertEquals(txUpd.getMiddle(), INCLUDED); Assert.assertEquals(txUpd.getRight(), b2); Assert.assertTrue(l.getQueueFor(tx1).isEmpty()); txUpd = l.pollTxUpdate(tx2); Assert.assertEquals(txUpd.getMiddle(), INCLUDED); Assert.assertEquals(txUpd.getRight(), b3); Assert.assertTrue(l.getQueueFor(tx2).isEmpty()); Assert.assertEquals(l.pollTxUpdateState(tx3), INCLUDED); Assert.assertEquals(l.pollTxUpdateState(tx4), INCLUDED); Assert.assertTrue(pendingState.getRepository().getBalance(alice.getAddress()). compareTo(BigInteger.valueOf(4000000)) == 0); } @Test public void testRebranch3() throws InterruptedException { StandaloneBlockchain bc = new StandaloneBlockchain(); PendingListener l = new PendingListener(); bc.addEthereumListener(l); Triple<TransactionReceipt, EthereumListener.PendingTransactionState, Block> txUpd = null; PendingStateImpl pendingState = (PendingStateImpl) bc.getBlockchain().getPendingState(); ECKey alice = new ECKey(); ECKey bob = new ECKey(); ECKey charlie = new ECKey(); bc.sendEther(bob.getAddress(), convert(100, ETHER)); bc.sendEther(charlie.getAddress(), convert(100, ETHER)); Block b1 = bc.createBlock(); Transaction tx1 = bc.createTransaction(bob, 0, alice.getAddress(), BigInteger.valueOf(1000000), new byte[0]); pendingState.addPendingTransaction(tx1); Assert.assertEquals(l.pollTxUpdateState(tx1), NEW_PENDING); bc.submitTransaction(tx1); Block b2 = bc.createBlock(); txUpd = l.pollTxUpdate(tx1); Assert.assertEquals(txUpd.getMiddle(), INCLUDED); Assert.assertTrue(l.getQueueFor(tx1).isEmpty()); Block b3 = bc.createBlock(); Assert.assertTrue(l.getQueueFor(tx1).isEmpty()); bc.submitTransaction(tx1); Block b2_ = bc.createForkBlock(b1); Assert.assertTrue(l.getQueueFor(tx1).isEmpty()); Block b3_ = bc.createForkBlock(b2_); Block b4_ = bc.createForkBlock(b3_); txUpd = l.pollTxUpdate(tx1); Assert.assertEquals(txUpd.getMiddle(), INCLUDED); Assert.assertArrayEquals(txUpd.getRight().getHash(), b2_.getHash()); Block b4 = bc.createForkBlock(b3); Block b5 = bc.createForkBlock(b4); txUpd = l.pollTxUpdate(tx1); Assert.assertEquals(txUpd.getMiddle(), INCLUDED); Assert.assertArrayEquals(txUpd.getRight().getHash(), b2.getHash()); } @Test public void testOldBlockIncluded() throws InterruptedException { StandaloneBlockchain bc = new StandaloneBlockchain(); PendingListener l = new PendingListener(); bc.addEthereumListener(l); Triple<TransactionReceipt, EthereumListener.PendingTransactionState, Block> txUpd = null; PendingStateImpl pendingState = (PendingStateImpl) bc.getBlockchain().getPendingState(); ECKey alice = new ECKey(); ECKey bob = new ECKey(); ECKey charlie = new ECKey(); bc.sendEther(bob.getAddress(), convert(100, ETHER)); Block b1 = bc.createBlock(); for (int i = 0; i < 16; i++) { bc.createBlock(); } Transaction tx1 = bc.createTransaction(bob, 0, alice.getAddress(), BigInteger.valueOf(1000000), new byte[0]); pendingState.addPendingTransaction(tx1); Assert.assertEquals(l.pollTxUpdateState(tx1), NEW_PENDING); bc.submitTransaction(tx1); Block b2_ = bc.createForkBlock(b1); Assert.assertTrue(l.getQueueFor(tx1).isEmpty()); bc.submitTransaction(tx1); Block b18 = bc.createBlock(); txUpd = l.pollTxUpdate(tx1); Assert.assertEquals(txUpd.getMiddle(), INCLUDED); Assert.assertArrayEquals(txUpd.getRight().getHash(), b18.getHash()); } @Test public void testBlockOnlyIncluded() throws InterruptedException { StandaloneBlockchain bc = new StandaloneBlockchain(); PendingListener l = new PendingListener(); bc.addEthereumListener(l); Triple<TransactionReceipt, EthereumListener.PendingTransactionState, Block> txUpd = null; PendingStateImpl pendingState = (PendingStateImpl) bc.getBlockchain().getPendingState(); ECKey alice = new ECKey(); ECKey bob = new ECKey(); bc.sendEther(bob.getAddress(), convert(100, ETHER)); Block b1 = bc.createBlock(); Transaction tx1 = bc.createTransaction(bob, 0, alice.getAddress(), BigInteger.valueOf(1000000), new byte[0]); bc.submitTransaction(tx1); Block b2 = bc.createBlock(); Block b2_ = bc.createForkBlock(b1); Assert.assertTrue(l.getQueueFor(tx1).isEmpty()); Block b3_ = bc.createForkBlock(b2_); txUpd = l.pollTxUpdate(tx1); Assert.assertEquals(txUpd.getMiddle(), PENDING); } @Test public void testTrackTx1() throws InterruptedException { StandaloneBlockchain bc = new StandaloneBlockchain(); PendingListener l = new PendingListener(); bc.addEthereumListener(l); Triple<TransactionReceipt, EthereumListener.PendingTransactionState, Block> txUpd = null; PendingStateImpl pendingState = (PendingStateImpl) bc.getBlockchain().getPendingState(); ECKey alice = new ECKey(); ECKey bob = new ECKey(); bc.sendEther(bob.getAddress(), convert(100, ETHER)); Block b1 = bc.createBlock(); Block b2 = bc.createBlock(); Block b3 = bc.createBlock(); Transaction tx1 = bc.createTransaction(bob, 0, alice.getAddress(), BigInteger.valueOf(1000000), new byte[0]); bc.submitTransaction(tx1); Block b2_ = bc.createForkBlock(b1); Assert.assertTrue(l.getQueueFor(tx1).isEmpty()); pendingState.trackTransaction(tx1); Assert.assertEquals(l.pollTxUpdateState(tx1), NEW_PENDING); Block b3_ = bc.createForkBlock(b2_); Block b4_ = bc.createForkBlock(b3_); txUpd = l.pollTxUpdate(tx1); Assert.assertEquals(txUpd.getMiddle(), INCLUDED); Assert.assertArrayEquals(txUpd.getRight().getHash(), b2_.getHash()); } @Test public void testPrevBlock() throws InterruptedException { StandaloneBlockchain bc = new StandaloneBlockchain(); PendingStateImpl pendingState = (PendingStateImpl) bc.getBlockchain().getPendingState(); ECKey alice = new ECKey(); ECKey bob = new ECKey(); SolidityContract contract = bc.submitNewContract("contract A {" + " function getPrevBlockHash() returns (bytes32) {" + " return block.blockhash(block.number - 1);" + " }" + "}"); bc.sendEther(bob.getAddress(), convert(100, ETHER)); Block b1 = bc.createBlock(); Block b2 = bc.createBlock(); Block b3 = bc.createBlock(); PendingListener l = new PendingListener(); bc.addEthereumListener(l); Triple<TransactionReceipt, EthereumListener.PendingTransactionState, Block> txUpd; contract.callFunction("getPrevBlockHash"); bc.generatePendingTransactions(); txUpd = l.onPendingTransactionUpdate.values().iterator().next().poll(); Assert.assertArrayEquals(txUpd.getLeft().getExecutionResult(), b3.getHash()); } @Test public void testTrackTx2() throws InterruptedException { StandaloneBlockchain bc = new StandaloneBlockchain(); PendingListener l = new PendingListener(); bc.addEthereumListener(l); Triple<TransactionReceipt, EthereumListener.PendingTransactionState, Block> txUpd = null; PendingStateImpl pendingState = (PendingStateImpl) bc.getBlockchain().getPendingState(); ECKey alice = new ECKey(); ECKey bob = new ECKey(); bc.sendEther(bob.getAddress(), convert(100, ETHER)); Block b1 = bc.createBlock(); Transaction tx1 = bc.createTransaction(bob, 0, alice.getAddress(), BigInteger.valueOf(1000000), new byte[0]); bc.submitTransaction(tx1); Block b2 = bc.createBlock(); Assert.assertTrue(l.getQueueFor(tx1).isEmpty()); pendingState.trackTransaction(tx1); txUpd = l.pollTxUpdate(tx1); Assert.assertEquals(txUpd.getMiddle(), INCLUDED); Assert.assertArrayEquals(txUpd.getRight().getHash(), b2.getHash()); Block b2_ = bc.createForkBlock(b1); Block b3_ = bc.createForkBlock(b2_); Assert.assertEquals(l.pollTxUpdateState(tx1), PENDING); } @Test public void testRejected1() throws InterruptedException { StandaloneBlockchain bc = new StandaloneBlockchain(); PendingListener l = new PendingListener(); bc.addEthereumListener(l); Triple<TransactionReceipt, EthereumListener.PendingTransactionState, Block> txUpd = null; PendingStateImpl pendingState = (PendingStateImpl) bc.getBlockchain().getPendingState(); ECKey alice = new ECKey(); ECKey bob = new ECKey(); ECKey charlie = new ECKey(); bc.sendEther(bob.getAddress(), convert(100, ETHER)); bc.sendEther(charlie.getAddress(), convert(100, ETHER)); Block b1 = bc.createBlock(); Transaction tx1 = bc.createTransaction(bob, 0, alice.getAddress(), BigInteger.valueOf(1000000), new byte[0]); pendingState.addPendingTransaction(tx1); Assert.assertEquals(l.pollTxUpdateState(tx1), NEW_PENDING); bc.submitTransaction(tx1); Block b2_ = bc.createForkBlock(b1); Assert.assertEquals(l.pollTxUpdateState(tx1), INCLUDED); Block b2 = bc.createForkBlock(b1); Block b3 = bc.createForkBlock(b2); Assert.assertEquals(l.pollTxUpdateState(tx1), PENDING); Assert.assertTrue(l.getQueueFor(tx1).isEmpty()); for (int i = 0; i < 16; i++) { bc.createBlock(); EthereumListener.PendingTransactionState state = l.pollTxUpdateState(tx1); if (state == EthereumListener.PendingTransactionState.DROPPED) { break; } if (i == 15) { throw new RuntimeException("Transaction was not dropped"); } } } @Test public void testIncludedRejected() throws InterruptedException { // check INCLUDED => DROPPED state transition when a new (long) fork without // the transaction becomes the main chain StandaloneBlockchain bc = new StandaloneBlockchain(); PendingListener l = new PendingListener(); bc.addEthereumListener(l); Triple<TransactionReceipt, EthereumListener.PendingTransactionState, Block> txUpd = null; PendingStateImpl pendingState = (PendingStateImpl) bc.getBlockchain().getPendingState(); ECKey alice = new ECKey(); ECKey bob = new ECKey(); ECKey charlie = new ECKey(); bc.sendEther(bob.getAddress(), convert(100, ETHER)); bc.sendEther(charlie.getAddress(), convert(100, ETHER)); Block b1 = bc.createBlock(); Transaction tx1 = bc.createTransaction(bob, 0, alice.getAddress(), BigInteger.valueOf(1000000), new byte[0]); pendingState.addPendingTransaction(tx1); Assert.assertEquals(l.pollTxUpdateState(tx1), NEW_PENDING); bc.submitTransaction(tx1); Block b2 = bc.createForkBlock(b1); Assert.assertEquals(l.pollTxUpdateState(tx1), INCLUDED); for (int i = 0; i < 10; i++) { bc.createBlock(); } Block b_ = bc.createForkBlock(b1); for (int i = 0; i < 11; i++) { b_ = bc.createForkBlock(b_); } Assert.assertEquals(l.pollTxUpdateState(tx1), DROPPED); Assert.assertTrue(l.getQueueFor(tx1).isEmpty()); } @Test public void testInvalidTransaction() throws InterruptedException { StandaloneBlockchain bc = new StandaloneBlockchain(); final CountDownLatch txHandle = new CountDownLatch(1); PendingListener l = new PendingListener() { @Override public void onPendingTransactionUpdate(TransactionReceipt txReceipt, PendingTransactionState state, Block block) { assert !txReceipt.isSuccessful(); assert txReceipt.getError().toLowerCase().contains("invalid"); assert txReceipt.getError().toLowerCase().contains("receive address"); txHandle.countDown(); } }; bc.addEthereumListener(l); PendingStateImpl pendingState = (PendingStateImpl) bc.getBlockchain().getPendingState(); ECKey alice = new ECKey(); Random rnd = new Random(); Block b1 = bc.createBlock(); byte[] b = new byte[21]; rnd.nextBytes(b); Transaction tx1 = bc.createTransaction(alice, 0, b, BigInteger.ONE, new byte[0]); pendingState.addPendingTransaction(tx1); assert txHandle.await(3, TimeUnit.SECONDS); } }