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 4a0cd1d6..11c78ec9 100644 --- a/core/src/main/java/com/google/bitcoin/core/Wallet.java +++ b/core/src/main/java/com/google/bitcoin/core/Wallet.java @@ -221,9 +221,30 @@ public class Wallet implements Serializable, BlockChainListener { long target = biTarget.longValue(); long total = 0; LinkedList selected = Lists.newLinkedList(); - // Super dumb algorithm: just iterate through candidates and keep adding them in whatever order until we - // have enough. - for (TransactionOutput output : candidates) { + // Sort the inputs by age so we use oldest first. + // TODO: Consider changing the wallets internal format to track just outputs and keep them ordered. + ArrayList sortedOutputs = new ArrayList(candidates); + Collections.sort(sortedOutputs, new Comparator() { + public int compare(TransactionOutput a, TransactionOutput b) { + int depth1 = 0; + int depth2 = 0; + TransactionConfidence conf1 = a.parentTransaction.getConfidence(); + TransactionConfidence conf2 = b.parentTransaction.getConfidence(); + if (conf1.getConfidenceType() == ConfidenceType.BUILDING) depth1 = conf1.getDepthInBlocks(); + if (conf2.getConfidenceType() == ConfidenceType.BUILDING) depth2 = conf2.getDepthInBlocks(); + if (depth1 < depth2) + return 1; + else if (depth1 > depth2) + return -1; + // Their depths are equal (possibly pending) so sort by hash to ensure a total ordering. + BigInteger aHash = a.parentTransaction.getHash().toBigInteger(); + BigInteger bHash = b.parentTransaction.getHash().toBigInteger(); + return aHash.compareTo(bHash); + } + }); + // Now iterate over the sorted outputs until we have got as close to the target as possible or a little + // bit over (excessive value will be change). + for (TransactionOutput output : sortedOutputs) { if (total >= target) break; // Only pick chain-included transactions, or transactions that are ours and pending. TransactionConfidence confidence = output.parentTransaction.getConfidence(); 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 b4fa10cf..72b1782d 100644 --- a/core/src/test/java/com/google/bitcoin/core/WalletTest.java +++ b/core/src/test/java/com/google/bitcoin/core/WalletTest.java @@ -866,6 +866,23 @@ public class WalletTest { assertFalse(o2.isAvailableForSpending()); } + @Test + public void ageMattersDuringSelection() throws Exception { + // Test that we prefer older coins to newer coins when building spends. This reduces required fees and improves + // time to confirmation as the transaction will appear less spammy. + final int ITERATIONS = 10; + Transaction[] txns = new Transaction[ITERATIONS]; + for (int i = 0; i < ITERATIONS; i++) { + txns[i] = sendMoneyToWallet(Utils.toNanoCoins(1, 0), AbstractBlockChain.NewBlockType.BEST_CHAIN); + } + // Check that we spend transactions in order of reception. + for (int i = 0; i < ITERATIONS; i++) { + Transaction spend = wallet.createSend(new ECKey().toAddress(params), Utils.toNanoCoins(1, 0)); + assertEquals("Failed on iteration " + i, spend.getInput(0).getOutpoint().getHash(), txns[i].getHash()); + wallet.commitTx(spend); + } + } + // There is a test for spending a coinbase transaction as it matures in BlockChainTest#coinbaseTransactionAvailability // Support for offline spending is tested in PeerGroupTest