Move MockTransactionBroadcaster into utils so third party code can use it in its own tests.

This commit is contained in:
Mike Hearn 2013-09-17 12:37:58 +02:00
parent 8d839ae5ad
commit d4786acb14
2 changed files with 47 additions and 16 deletions

View file

@ -14,18 +14,35 @@
* limitations under the License. * 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 com.google.common.util.concurrent.SettableFuture;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.locks.ReentrantLock; 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 { public class MockTransactionBroadcaster implements TransactionBroadcaster {
private ReentrantLock lock = Threading.lock("mock tx broadcaster"); private final ReentrantLock lock = Threading.lock("mock tx broadcaster");
public LinkedBlockingQueue<Transaction> broadcasts = new LinkedBlockingQueue<Transaction>(); public static class TxFuturePair {
public Transaction tx;
public SettableFuture<Transaction> future;
public TxFuturePair(Transaction tx, SettableFuture<Transaction> future) {
this.tx = tx;
this.future = future;
}
}
private final LinkedBlockingQueue<TxFuturePair> broadcasts = new LinkedBlockingQueue<TxFuturePair>();
public MockTransactionBroadcaster(Wallet wallet) { public MockTransactionBroadcaster(Wallet wallet) {
// This code achieves nothing directly, but it sets up the broadcaster/peergroup > wallet lock ordering // 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 @Override
public SettableFuture<Transaction> broadcastTransaction(Transaction tx) { public SettableFuture<Transaction> 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(); lock.lock();
try { try {
SettableFuture<Transaction> result = SettableFuture.create(); SettableFuture<Transaction> result = SettableFuture.create();
broadcasts.put(tx); broadcasts.put(new TxFuturePair(tx, result));
return result; return result;
} catch (InterruptedException e) { } catch (InterruptedException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
@ -52,4 +69,20 @@ public class MockTransactionBroadcaster implements TransactionBroadcaster {
lock.unlock(); 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();
}
} }

View file

@ -24,6 +24,7 @@ import com.google.bitcoin.crypto.KeyCrypterException;
import com.google.bitcoin.crypto.KeyCrypterScrypt; import com.google.bitcoin.crypto.KeyCrypterScrypt;
import com.google.bitcoin.crypto.TransactionSignature; import com.google.bitcoin.crypto.TransactionSignature;
import com.google.bitcoin.store.WalletProtobufSerializer; import com.google.bitcoin.store.WalletProtobufSerializer;
import com.google.bitcoin.utils.MockTransactionBroadcaster;
import com.google.bitcoin.utils.Threading; import com.google.bitcoin.utils.Threading;
import com.google.bitcoin.wallet.KeyTimeCoinSelector; import com.google.bitcoin.wallet.KeyTimeCoinSelector;
import com.google.bitcoin.wallet.WalletFiles; 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); sendMoneyToWallet(wallet, CENT, key2.toAddress(params), AbstractBlockChain.NewBlockType.BEST_CHAIN);
Utils.rollMockClock(86400); Utils.rollMockClock(86400);
Date compromiseTime = Utils.now(); Date compromiseTime = Utils.now();
assertEquals(0, broadcaster.broadcasts.size()); assertEquals(0, broadcaster.size());
assertFalse(wallet.isKeyRotating(key1)); assertFalse(wallet.isKeyRotating(key1));
// Rotate the wallet. // Rotate the wallet.
@ -1919,7 +1920,7 @@ public class WalletTest extends TestWithWallet {
// We see a broadcast triggered by setting the rotation time. // We see a broadcast triggered by setting the rotation time.
wallet.setKeyRotationTime(compromiseTime); wallet.setKeyRotationTime(compromiseTime);
assertTrue(wallet.isKeyRotating(key1)); assertTrue(wallet.isKeyRotating(key1));
Transaction tx = broadcaster.broadcasts.take(); Transaction tx = broadcaster.waitForTransaction();
final BigInteger THREE_CENTS = CENT.add(CENT).add(CENT); final BigInteger THREE_CENTS = CENT.add(CENT).add(CENT);
assertEquals(THREE_CENTS, tx.getValueSentFromMe(wallet)); assertEquals(THREE_CENTS, tx.getValueSentFromMe(wallet));
assertEquals(THREE_CENTS.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE), tx.getValueSentToMe(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. // 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); 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. // Receive money via a new block on key1 and ensure it's immediately moved.
sendMoneyToWallet(wallet, CENT, key1.toAddress(params), AbstractBlockChain.NewBlockType.BEST_CHAIN); 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()); assertArrayEquals(key3.getPubKey(), tx.getOutput(0).getScriptPubKey().getPubKey());
assertEquals(1, tx.getInputs().size()); assertEquals(1, tx.getInputs().size());
assertEquals(1, tx.getOutputs().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. // Make a normal spend and check it's all ok.
final Address address = new ECKey().toAddress(params); final Address address = new ECKey().toAddress(params);
wallet.sendCoins(broadcaster, address, wallet.getBalance()); wallet.sendCoins(broadcaster, address, wallet.getBalance());
tx = broadcaster.broadcasts.take(); tx = broadcaster.waitForTransaction();
assertArrayEquals(address.getHash160(), tx.getOutput(0).getScriptPubKey().getPubKeyHash()); 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 @Test
@ -1985,14 +1983,14 @@ public class WalletTest extends TestWithWallet {
wallet.addKey(new ECKey()); wallet.addKey(new ECKey());
wallet.setKeyRotationTime(compromise); wallet.setKeyRotationTime(compromise);
Transaction tx = broadcaster.broadcasts.take(); Transaction tx = broadcaster.waitForTransaction();
final BigInteger valueSentToMe = tx.getValueSentToMe(wallet); final BigInteger valueSentToMe = tx.getValueSentToMe(wallet);
BigInteger fee = tx.getValueSentFromMe(wallet).subtract(valueSentToMe); BigInteger fee = tx.getValueSentFromMe(wallet).subtract(valueSentToMe);
assertEquals(BigInteger.valueOf(900000), fee); assertEquals(BigInteger.valueOf(900000), fee);
assertEquals(KeyTimeCoinSelector.MAX_SIMULTANEOUS_INPUTS, tx.getInputs().size()); assertEquals(KeyTimeCoinSelector.MAX_SIMULTANEOUS_INPUTS, tx.getInputs().size());
assertEquals(BigInteger.valueOf(599100000), valueSentToMe); assertEquals(BigInteger.valueOf(599100000), valueSentToMe);
tx = broadcaster.broadcasts.take(); tx = broadcaster.waitForTransaction();
assertNotNull(tx); assertNotNull(tx);
assertEquals(200, tx.getInputs().size()); assertEquals(200, tx.getInputs().size());
} }