Allow spending of unconfirmed change when it's been seen by the network.

Resolves issue 40.
This commit is contained in:
Mike Hearn 2013-02-04 18:57:44 +01:00
parent fd45fa0f17
commit 4273820eac
2 changed files with 46 additions and 12 deletions

View File

@ -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 {
}
}
/*
/**
* <p>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.</p>
*

View File

@ -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<Transaction>());
} 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