diff --git a/core/src/test/java/com/google/bitcoin/core/MockTransactionBroadcaster.java b/core/src/main/java/com/google/bitcoin/utils/MockTransactionBroadcaster.java similarity index 53% rename from core/src/test/java/com/google/bitcoin/core/MockTransactionBroadcaster.java rename to core/src/main/java/com/google/bitcoin/utils/MockTransactionBroadcaster.java index f0dcee6f..42dd9e46 100644 --- a/core/src/test/java/com/google/bitcoin/core/MockTransactionBroadcaster.java +++ b/core/src/main/java/com/google/bitcoin/utils/MockTransactionBroadcaster.java @@ -14,18 +14,35 @@ * limitations under the License. */ -package com.google.bitcoin.core; +package com.google.bitcoin.utils; -import com.google.bitcoin.utils.Threading; +import com.google.bitcoin.core.Transaction; +import com.google.bitcoin.core.TransactionBroadcaster; +import com.google.bitcoin.core.Wallet; import com.google.common.util.concurrent.SettableFuture; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.locks.ReentrantLock; +/** + * A mock transaction broadcaster can be used in unit tests as a stand-in for a PeerGroup. It catches any transactions + * broadcast through it and makes them available via the {@link #broadcasts} member. Reading from that + * {@link LinkedBlockingQueue} will block the thread until a transaction is available. + */ public class MockTransactionBroadcaster implements TransactionBroadcaster { - private ReentrantLock lock = Threading.lock("mock tx broadcaster"); + private final ReentrantLock lock = Threading.lock("mock tx broadcaster"); - public LinkedBlockingQueue broadcasts = new LinkedBlockingQueue(); + public static class TxFuturePair { + public Transaction tx; + public SettableFuture future; + + public TxFuturePair(Transaction tx, SettableFuture future) { + this.tx = tx; + this.future = future; + } + } + + private final LinkedBlockingQueue broadcasts = new LinkedBlockingQueue(); public MockTransactionBroadcaster(Wallet wallet) { // This code achieves nothing directly, but it sets up the broadcaster/peergroup > wallet lock ordering @@ -40,11 +57,11 @@ public class MockTransactionBroadcaster implements TransactionBroadcaster { @Override public SettableFuture broadcastTransaction(Transaction tx) { - // Use a lock just to catch lock ordering inversions. + // Use a lock just to catch lock ordering inversions e.g. wallet->broadcaster. lock.lock(); try { SettableFuture result = SettableFuture.create(); - broadcasts.put(tx); + broadcasts.put(new TxFuturePair(tx, result)); return result; } catch (InterruptedException e) { throw new RuntimeException(e); @@ -52,4 +69,20 @@ public class MockTransactionBroadcaster implements TransactionBroadcaster { lock.unlock(); } } + + public Transaction waitForTransaction() { + return waitForTxFuture().tx; + } + + public TxFuturePair waitForTxFuture() { + try { + return broadcasts.take(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + public int size() { + return broadcasts.size(); + } } diff --git a/core/src/test/java/com/google/bitcoin/core/WalletTest.java b/core/src/test/java/com/google/bitcoin/core/WalletTest.java index 0bdf43b6..0bedacd4 100644 --- a/core/src/test/java/com/google/bitcoin/core/WalletTest.java +++ b/core/src/test/java/com/google/bitcoin/core/WalletTest.java @@ -24,6 +24,7 @@ import com.google.bitcoin.crypto.KeyCrypterException; import com.google.bitcoin.crypto.KeyCrypterScrypt; import com.google.bitcoin.crypto.TransactionSignature; import com.google.bitcoin.store.WalletProtobufSerializer; +import com.google.bitcoin.utils.MockTransactionBroadcaster; import com.google.bitcoin.utils.Threading; import com.google.bitcoin.wallet.KeyTimeCoinSelector; import com.google.bitcoin.wallet.WalletFiles; @@ -1910,7 +1911,7 @@ public class WalletTest extends TestWithWallet { sendMoneyToWallet(wallet, CENT, key2.toAddress(params), AbstractBlockChain.NewBlockType.BEST_CHAIN); Utils.rollMockClock(86400); Date compromiseTime = Utils.now(); - assertEquals(0, broadcaster.broadcasts.size()); + assertEquals(0, broadcaster.size()); assertFalse(wallet.isKeyRotating(key1)); // Rotate the wallet. @@ -1919,7 +1920,7 @@ public class WalletTest extends TestWithWallet { // We see a broadcast triggered by setting the rotation time. wallet.setKeyRotationTime(compromiseTime); assertTrue(wallet.isKeyRotating(key1)); - Transaction tx = broadcaster.broadcasts.take(); + Transaction tx = broadcaster.waitForTransaction(); final BigInteger THREE_CENTS = CENT.add(CENT).add(CENT); assertEquals(THREE_CENTS, tx.getValueSentFromMe(wallet)); assertEquals(THREE_CENTS.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE), tx.getValueSentToMe(wallet)); @@ -1931,11 +1932,11 @@ public class WalletTest extends TestWithWallet { // Now receive some more money to key3 (secure) via a new block and check that nothing happens. sendMoneyToWallet(wallet, CENT, key3.toAddress(params), AbstractBlockChain.NewBlockType.BEST_CHAIN); - assertTrue(broadcaster.broadcasts.isEmpty()); + assertEquals(0, broadcaster.size()); // Receive money via a new block on key1 and ensure it's immediately moved. sendMoneyToWallet(wallet, CENT, key1.toAddress(params), AbstractBlockChain.NewBlockType.BEST_CHAIN); - tx = broadcaster.broadcasts.take(); + tx = broadcaster.waitForTransaction(); assertArrayEquals(key3.getPubKey(), tx.getOutput(0).getScriptPubKey().getPubKey()); assertEquals(1, tx.getInputs().size()); assertEquals(1, tx.getOutputs().size()); @@ -1958,11 +1959,8 @@ public class WalletTest extends TestWithWallet { // Make a normal spend and check it's all ok. final Address address = new ECKey().toAddress(params); wallet.sendCoins(broadcaster, address, wallet.getBalance()); - tx = broadcaster.broadcasts.take(); + tx = broadcaster.waitForTransaction(); assertArrayEquals(address.getHash160(), tx.getOutput(0).getScriptPubKey().getPubKeyHash()); - // We have to race here because we're checking for the ABSENCE of a broadcast, and if there were to be one, - // it'd be happening in parallel. - assertEquals(null, broadcaster.broadcasts.poll(1, TimeUnit.SECONDS)); } @Test @@ -1985,14 +1983,14 @@ public class WalletTest extends TestWithWallet { wallet.addKey(new ECKey()); wallet.setKeyRotationTime(compromise); - Transaction tx = broadcaster.broadcasts.take(); + Transaction tx = broadcaster.waitForTransaction(); final BigInteger valueSentToMe = tx.getValueSentToMe(wallet); BigInteger fee = tx.getValueSentFromMe(wallet).subtract(valueSentToMe); assertEquals(BigInteger.valueOf(900000), fee); assertEquals(KeyTimeCoinSelector.MAX_SIMULTANEOUS_INPUTS, tx.getInputs().size()); assertEquals(BigInteger.valueOf(599100000), valueSentToMe); - tx = broadcaster.broadcasts.take(); + tx = broadcaster.waitForTransaction(); assertNotNull(tx); assertEquals(200, tx.getInputs().size()); }