3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-01-30 23:02:15 +00:00

Consider age of outputs when creating spends.

This commit is contained in:
Mike Hearn 2013-02-10 14:27:07 +01:00
parent 757334da80
commit e99e4bd63c
2 changed files with 41 additions and 3 deletions

View File

@ -221,9 +221,30 @@ public class Wallet implements Serializable, BlockChainListener {
long target = biTarget.longValue(); long target = biTarget.longValue();
long total = 0; long total = 0;
LinkedList<TransactionOutput> selected = Lists.newLinkedList(); LinkedList<TransactionOutput> selected = Lists.newLinkedList();
// Super dumb algorithm: just iterate through candidates and keep adding them in whatever order until we // Sort the inputs by age so we use oldest first.
// have enough. // TODO: Consider changing the wallets internal format to track just outputs and keep them ordered.
for (TransactionOutput output : candidates) { ArrayList<TransactionOutput> sortedOutputs = new ArrayList<TransactionOutput>(candidates);
Collections.sort(sortedOutputs, new Comparator<TransactionOutput>() {
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; if (total >= target) break;
// Only pick chain-included transactions, or transactions that are ours and pending. // Only pick chain-included transactions, or transactions that are ours and pending.
TransactionConfidence confidence = output.parentTransaction.getConfidence(); TransactionConfidence confidence = output.parentTransaction.getConfidence();

View File

@ -866,6 +866,23 @@ public class WalletTest {
assertFalse(o2.isAvailableForSpending()); 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 // There is a test for spending a coinbase transaction as it matures in BlockChainTest#coinbaseTransactionAvailability
// Support for offline spending is tested in PeerGroupTest // Support for offline spending is tested in PeerGroupTest