mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-02-14 11:15:51 +00:00
Wallet: Split DefaultCoinSelector out into a top level class in the wallet package.
This commit is contained in:
parent
c366c5fa44
commit
2b4595c4f0
@ -25,10 +25,7 @@ import com.google.bitcoin.store.UnreadableWalletException;
|
|||||||
import com.google.bitcoin.store.WalletProtobufSerializer;
|
import com.google.bitcoin.store.WalletProtobufSerializer;
|
||||||
import com.google.bitcoin.utils.ListenerRegistration;
|
import com.google.bitcoin.utils.ListenerRegistration;
|
||||||
import com.google.bitcoin.utils.Threading;
|
import com.google.bitcoin.utils.Threading;
|
||||||
import com.google.bitcoin.wallet.CoinSelection;
|
import com.google.bitcoin.wallet.*;
|
||||||
import com.google.bitcoin.wallet.CoinSelector;
|
|
||||||
import com.google.bitcoin.wallet.KeyTimeCoinSelector;
|
|
||||||
import com.google.bitcoin.wallet.WalletFiles;
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.collect.*;
|
import com.google.common.collect.*;
|
||||||
import com.google.common.util.concurrent.FutureCallback;
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
@ -156,77 +153,6 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
|
|||||||
private volatile long vKeyRotationTimestamp;
|
private volatile long vKeyRotationTimestamp;
|
||||||
private volatile boolean vKeyRotationEnabled;
|
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
|
* 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).
|
* confirmed yet. However immature coinbases will not be included (would be a protocol violation).
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,7 @@ import com.google.bitcoin.core.*;
|
|||||||
import com.google.bitcoin.script.Script;
|
import com.google.bitcoin.script.Script;
|
||||||
import com.google.bitcoin.script.ScriptBuilder;
|
import com.google.bitcoin.script.ScriptBuilder;
|
||||||
import com.google.bitcoin.utils.TestWithWallet;
|
import com.google.bitcoin.utils.TestWithWallet;
|
||||||
|
import com.google.bitcoin.wallet.DefaultCoinSelector;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import com.google.common.util.concurrent.SettableFuture;
|
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
|
// 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())));
|
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
|
@Override
|
||||||
protected boolean shouldSelect(Transaction tx) {
|
protected boolean shouldSelect(Transaction tx) {
|
||||||
if (tx.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING)
|
if (tx.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING)
|
||||||
@ -203,7 +204,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
|
|||||||
}));
|
}));
|
||||||
assertEquals(0, serverWallet.getPendingTransactions().size());
|
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
|
@Override
|
||||||
protected boolean shouldSelect(Transaction tx) {
|
protected boolean shouldSelect(Transaction tx) {
|
||||||
if (tx.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING)
|
if (tx.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user