3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-07 14:54:15 +00:00

Wallet: Split DefaultCoinSelector out into a top level class in the wallet package.

This commit is contained in:
Mike Hearn 2013-09-20 16:50:25 +02:00
parent c366c5fa44
commit 2b4595c4f0
3 changed files with 85 additions and 77 deletions

View File

@ -25,10 +25,7 @@ import com.google.bitcoin.store.UnreadableWalletException;
import com.google.bitcoin.store.WalletProtobufSerializer;
import com.google.bitcoin.utils.ListenerRegistration;
import com.google.bitcoin.utils.Threading;
import com.google.bitcoin.wallet.CoinSelection;
import com.google.bitcoin.wallet.CoinSelector;
import com.google.bitcoin.wallet.KeyTimeCoinSelector;
import com.google.bitcoin.wallet.WalletFiles;
import com.google.bitcoin.wallet.*;
import com.google.common.base.Preconditions;
import com.google.common.collect.*;
import com.google.common.util.concurrent.FutureCallback;
@ -156,77 +153,6 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
private volatile long vKeyRotationTimestamp;
private volatile boolean vKeyRotationEnabled;
/**
* This class implements a {@link CoinSelector} which attempts to get the highest priority possible. This means that
* the transaction is the most likely to get confirmed
* Note that this means we may end up "spending" more priority than would be required to get the transaction we are
* creating confirmed.
*/
public static class DefaultCoinSelector implements CoinSelector {
public CoinSelection select(BigInteger biTarget, LinkedList<TransactionOutput> candidates) {
long target = biTarget.longValue();
HashSet<TransactionOutput> selected = new HashSet<TransactionOutput>();
// Sort the inputs by age*value so we get the highest "coindays" spent.
// TODO: Consider changing the wallets internal format to track just outputs and keep them ordered.
ArrayList<TransactionOutput> sortedOutputs = new ArrayList<TransactionOutput>(candidates);
// When calculating the wallet balance, we may be asked to select all possible coins, if so, avoid sorting
// them in order to improve performance.
if (!biTarget.equals(NetworkParameters.MAX_MONEY)) {
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();
BigInteger aValue = a.getValue();
BigInteger bValue = b.getValue();
BigInteger aCoinDepth = aValue.multiply(BigInteger.valueOf(depth1));
BigInteger bCoinDepth = bValue.multiply(BigInteger.valueOf(depth2));
int c1 = bCoinDepth.compareTo(aCoinDepth);
if (c1 != 0) return c1;
// The "coin*days" destroyed are equal, sort by value alone to get the lowest transaction size.
int c2 = bValue.compareTo(aValue);
if (c2 != 0) return c2;
// They are entirely equivalent (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).
long total = 0;
for (TransactionOutput output : sortedOutputs) {
if (total >= target) break;
// Only pick chain-included transactions, or transactions that are ours and pending.
if (!shouldSelect(output.parentTransaction)) continue;
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.
return new CoinSelection(BigInteger.valueOf(total), selected);
}
/** Sub-classes can override this to just customize whether transactions are usable, but keep age sorting. */
protected boolean shouldSelect(Transaction tx) {
return isSelectable(tx);
}
public static boolean isSelectable(Transaction tx) {
// Only pick chain-included transactions, or transactions that are ours and pending.
TransactionConfidence confidence = tx.getConfidence();
ConfidenceType type = confidence.getConfidenceType();
if (type.equals(ConfidenceType.BUILDING)) return true;
return type.equals(ConfidenceType.PENDING) &&
confidence.getSource().equals(TransactionConfidence.Source.SELF) &&
confidence.numBroadcastPeers() > 1;
}
}
/**
* This coin selector will select any transaction at all, regardless of where it came from or whether it was
* confirmed yet. However immature coinbases will not be included (would be a protocol violation).

View File

@ -0,0 +1,81 @@
package com.google.bitcoin.wallet;
import com.google.bitcoin.core.NetworkParameters;
import com.google.bitcoin.core.Transaction;
import com.google.bitcoin.core.TransactionConfidence;
import com.google.bitcoin.core.TransactionOutput;
import java.math.BigInteger;
import java.util.*;
/**
* This class implements a {@link com.google.bitcoin.wallet.CoinSelector} which attempts to get the highest priority
* possible. This means that the transaction is the most likely to get confirmed. Note that this means we may end up
* "spending" more priority than would be required to get the transaction we are creating confirmed.
*/
public class DefaultCoinSelector implements CoinSelector {
public CoinSelection select(BigInteger biTarget, LinkedList<TransactionOutput> candidates) {
long target = biTarget.longValue();
HashSet<TransactionOutput> selected = new HashSet<TransactionOutput>();
// Sort the inputs by age*value so we get the highest "coindays" spent.
// TODO: Consider changing the wallets internal format to track just outputs and keep them ordered.
ArrayList<TransactionOutput> sortedOutputs = new ArrayList<TransactionOutput>(candidates);
// When calculating the wallet balance, we may be asked to select all possible coins, if so, avoid sorting
// them in order to improve performance.
if (!biTarget.equals(NetworkParameters.MAX_MONEY)) {
Collections.sort(sortedOutputs, new Comparator<TransactionOutput>() {
public int compare(TransactionOutput a, TransactionOutput b) {
int depth1 = 0;
int depth2 = 0;
TransactionConfidence conf1 = a.getParentTransaction().getConfidence();
TransactionConfidence conf2 = b.getParentTransaction().getConfidence();
if (conf1.getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING)
depth1 = conf1.getDepthInBlocks();
if (conf2.getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING)
depth2 = conf2.getDepthInBlocks();
BigInteger aValue = a.getValue();
BigInteger bValue = b.getValue();
BigInteger aCoinDepth = aValue.multiply(BigInteger.valueOf(depth1));
BigInteger bCoinDepth = bValue.multiply(BigInteger.valueOf(depth2));
int c1 = bCoinDepth.compareTo(aCoinDepth);
if (c1 != 0) return c1;
// The "coin*days" destroyed are equal, sort by value alone to get the lowest transaction size.
int c2 = bValue.compareTo(aValue);
if (c2 != 0) return c2;
// They are entirely equivalent (possibly pending) so sort by hash to ensure a total ordering.
BigInteger aHash = a.getParentTransaction().getHash().toBigInteger();
BigInteger bHash = b.getParentTransaction().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).
long total = 0;
for (TransactionOutput output : sortedOutputs) {
if (total >= target) break;
// Only pick chain-included transactions, or transactions that are ours and pending.
if (!shouldSelect(output.getParentTransaction())) continue;
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.
return new CoinSelection(BigInteger.valueOf(total), selected);
}
/** Sub-classes can override this to just customize whether transactions are usable, but keep age sorting. */
protected boolean shouldSelect(Transaction tx) {
return isSelectable(tx);
}
public static boolean isSelectable(Transaction tx) {
// Only pick chain-included transactions, or transactions that are ours and pending.
TransactionConfidence confidence = tx.getConfidence();
TransactionConfidence.ConfidenceType type = confidence.getConfidenceType();
if (type.equals(TransactionConfidence.ConfidenceType.BUILDING)) return true;
return type.equals(TransactionConfidence.ConfidenceType.PENDING) &&
confidence.getSource().equals(TransactionConfidence.Source.SELF) &&
confidence.numBroadcastPeers() > 1;
}
}

View File

@ -20,6 +20,7 @@ import com.google.bitcoin.core.*;
import com.google.bitcoin.script.Script;
import com.google.bitcoin.script.ScriptBuilder;
import com.google.bitcoin.utils.TestWithWallet;
import com.google.bitcoin.wallet.DefaultCoinSelector;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
@ -193,7 +194,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
// Create a block with the payment transaction in it and give it to both wallets
chain.add(makeSolvedTestBlock(blockStore.getChainHead().getHeader(), new Transaction(params, closeTx.bitcoinSerialize())));
assertEquals(size.multiply(BigInteger.valueOf(5)), serverWallet.getBalance(new Wallet.DefaultCoinSelector() {
assertEquals(size.multiply(BigInteger.valueOf(5)), serverWallet.getBalance(new DefaultCoinSelector() {
@Override
protected boolean shouldSelect(Transaction tx) {
if (tx.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING)
@ -203,7 +204,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
}));
assertEquals(0, serverWallet.getPendingTransactions().size());
assertEquals(Utils.COIN.subtract(size.multiply(BigInteger.valueOf(5))), wallet.getBalance(new Wallet.DefaultCoinSelector() {
assertEquals(Utils.COIN.subtract(size.multiply(BigInteger.valueOf(5))), wallet.getBalance(new DefaultCoinSelector() {
@Override
protected boolean shouldSelect(Transaction tx) {
if (tx.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING)