From 4273820eacb2811293666cbaea6784bc18e5760d Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Mon, 4 Feb 2013 18:57:44 +0100 Subject: [PATCH] Allow spending of unconfirmed change when it's been seen by the network. Resolves issue 40. --- .../java/com/google/bitcoin/core/Wallet.java | 22 +++++++++--- .../com/google/bitcoin/core/WalletTest.java | 36 +++++++++++++++---- 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/com/google/bitcoin/core/Wallet.java b/core/src/main/java/com/google/bitcoin/core/Wallet.java index 91f30fa6..1f45aeec 100644 --- a/core/src/main/java/com/google/bitcoin/core/Wallet.java +++ b/core/src/main/java/com/google/bitcoin/core/Wallet.java @@ -225,11 +225,23 @@ public class Wallet implements Serializable, BlockChainListener { // have enough. for (TransactionOutput output : candidates) { if (total >= target) break; - // Only pick chain-included transactions. - if (output.parentTransaction.getConfidence().getConfidenceType().equals(ConfidenceType.BUILDING)) { - selected.add(output); - total += output.getValue().longValue(); + // Only pick chain-included transactions, or transactions that are ours and pending. + TransactionConfidence confidence = output.parentTransaction.getConfidence(); + ConfidenceType type = confidence.getConfidenceType(); + boolean pending = type.equals(ConfidenceType.NOT_SEEN_IN_CHAIN) || + type.equals(ConfidenceType.NOT_IN_BEST_CHAIN); + boolean confirmed = type.equals(ConfidenceType.BUILDING); + if (!confirmed) { + // If the transaction is still pending ... + if (!pending) continue; + // And it was created by us ... + if (!confidence.getSource().equals(TransactionConfidence.Source.SELF)) continue; + // And it's been seen by the network and propagated ... + if (confidence.numBroadcastPeers() <= 1) continue; + // Then it's OK to select. } + selected.add(output); + total += output.getValue().longValue(); } // Total may be lower than target here, if the given candidates were insufficient to create to requested // transaction. @@ -1518,7 +1530,7 @@ public class Wallet implements Serializable, BlockChainListener { } } - /* + /** *

Statelessly creates a transaction that sends the given value to address. The change is sent to * {@link Wallet#getChangeAddress()}, so you must have added at least one key.

* 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 149df7b5..188754d1 100644 --- a/core/src/test/java/com/google/bitcoin/core/WalletTest.java +++ b/core/src/test/java/com/google/bitcoin/core/WalletTest.java @@ -28,6 +28,8 @@ import org.junit.Test; import java.io.File; import java.io.IOException; import java.math.BigInteger; +import java.net.InetAddress; +import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.concurrent.CountDownLatch; @@ -60,10 +62,16 @@ public class WalletTest { private Transaction sendMoneyToWallet(Transaction tx, AbstractBlockChain.NewBlockType type) throws IOException, ProtocolException, VerificationException { - BlockPair bp = createFakeBlock(blockStore, tx); - wallet.receiveFromBlock(tx, bp.storedBlock, type); - if (type == AbstractBlockChain.NewBlockType.BEST_CHAIN) - wallet.notifyNewBestBlock(bp.block); + if (type == null) { + // Pending/broadcast tx. + if (wallet.isPendingTransactionRelevant(tx)) + wallet.receivePending(tx, new ArrayList()); + } else { + BlockPair bp = createFakeBlock(blockStore, tx); + wallet.receiveFromBlock(tx, bp.storedBlock, type); + if (type == AbstractBlockChain.NewBlockType.BEST_CHAIN) + wallet.notifyNewBestBlock(bp.block); + } return tx; } @@ -78,9 +86,14 @@ public class WalletTest { // will attach a small fee. Because the Bitcoin protocol makes it difficult to determine the fee of an // arbitrary transaction in isolation, we'll check that the fee was set by examining the size of the change. - // Receive some money. + // Receive some money as a pending transaction. BigInteger v1 = Utils.toNanoCoins(1, 0); - sendMoneyToWallet(v1, AbstractBlockChain.NewBlockType.BEST_CHAIN); + Transaction t1 = sendMoneyToWallet(v1, null); + assertEquals(BigInteger.ZERO, wallet.getBalance()); + assertEquals(v1, wallet.getBalance(Wallet.BalanceType.ESTIMATED)); + assertEquals(1, wallet.getPoolSize(Pool.PENDING)); + assertEquals(0, wallet.getPoolSize(WalletTransaction.Pool.UNSPENT)); + sendMoneyToWallet(t1, AbstractBlockChain.NewBlockType.BEST_CHAIN); assertEquals(v1, wallet.getBalance()); assertEquals(1, wallet.getPoolSize(WalletTransaction.Pool.UNSPENT)); assertEquals(1, wallet.getPoolSize(WalletTransaction.Pool.ALL)); @@ -103,7 +116,8 @@ public class WalletTest { assertEquals(2, t2.getOutputs().size()); assertEquals(destination, t2.getOutputs().get(0).getScriptPubKey().getToAddress()); assertEquals(wallet.getChangeAddress(), t2.getOutputs().get(1).getScriptPubKey().getToAddress()); - assertEquals(toNanoCoins(0, 49), t2.getOutputs().get(1).getValue()); + BigInteger v3 = toNanoCoins(0, 49); + assertEquals(v3, t2.getOutputs().get(1).getValue()); // Check the script runs and signatures verify. t2.getInputs().get(0).verify(); @@ -114,12 +128,20 @@ public class WalletTest { txns.add(tx); } }); + // We broadcast the TX over the network, and then commit to it. + t2.getConfidence().markBroadcastBy(new PeerAddress(InetAddress.getByAddress(new byte[]{1,2,3,4}))); + t2.getConfidence().markBroadcastBy(new PeerAddress(InetAddress.getByAddress(new byte[]{10,2,3,4}))); wallet.commitTx(t2); assertEquals(1, wallet.getPoolSize(WalletTransaction.Pool.PENDING)); assertEquals(1, wallet.getPoolSize(WalletTransaction.Pool.SPENT)); assertEquals(2, wallet.getPoolSize(WalletTransaction.Pool.ALL)); assertEquals(t2, txns.getFirst()); assertEquals(1, txns.size()); + + // Now check that we can spend the unconfirmed change. + assertEquals(v3, wallet.getBalance(Wallet.BalanceType.ESTIMATED)); + Transaction t3 = wallet.createSend(new ECKey().toAddress(params), v3); + assertNotNull(t3); } @Test