mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-12-27 10:36:45 +00:00
calculate no inputs fee
This commit is contained in:
parent
9d272c0eb2
commit
832ca8f257
3 changed files with 35 additions and 22 deletions
|
@ -4,19 +4,15 @@ import com.sparrowwallet.drongo.protocol.Transaction;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import static com.sparrowwallet.drongo.protocol.Transaction.WITNESS_SCALE_FACTOR;
|
|
||||||
|
|
||||||
public class BnBUtxoSelector implements UtxoSelector {
|
public class BnBUtxoSelector implements UtxoSelector {
|
||||||
private static final int TOTAL_TRIES = 100000;
|
private static final int TOTAL_TRIES = 100000;
|
||||||
|
|
||||||
private final int noInputsWeightUnits;
|
private final long noInputsFee;
|
||||||
private final Double feeRate;
|
|
||||||
private final long costOfChangeValue;
|
private final long costOfChangeValue;
|
||||||
|
|
||||||
public BnBUtxoSelector(Wallet wallet, int noInputsWeightUnits, Double feeRate, Double longTermFeeRate) {
|
public BnBUtxoSelector(long noInputsFee, long costOfChangeValue) {
|
||||||
this.noInputsWeightUnits = noInputsWeightUnits;
|
this.noInputsFee = noInputsFee;
|
||||||
this.feeRate = feeRate;
|
this.costOfChangeValue = costOfChangeValue;
|
||||||
this.costOfChangeValue = wallet.getCostOfChange(feeRate, longTermFeeRate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -26,7 +22,7 @@ public class BnBUtxoSelector implements UtxoSelector {
|
||||||
long currentValue = 0;
|
long currentValue = 0;
|
||||||
|
|
||||||
ArrayDeque<Boolean> currentSelection = new ArrayDeque<>(utxoPool.size());
|
ArrayDeque<Boolean> currentSelection = new ArrayDeque<>(utxoPool.size());
|
||||||
long actualTargetValue = targetValue + (long)(noInputsWeightUnits * feeRate / WITNESS_SCALE_FACTOR);
|
long actualTargetValue = targetValue + noInputsFee;
|
||||||
System.out.println("Selected must be: " + actualTargetValue + " < x < " + (actualTargetValue + costOfChangeValue));
|
System.out.println("Selected must be: " + actualTargetValue + " < x < " + (actualTargetValue + costOfChangeValue));
|
||||||
|
|
||||||
long currentAvailableValue = utxoPool.stream().mapToLong(OutputGroup::getEffectiveValue).sum();
|
long currentAvailableValue = utxoPool.stream().mapToLong(OutputGroup::getEffectiveValue).sum();
|
||||||
|
@ -131,7 +127,6 @@ public class BnBUtxoSelector implements UtxoSelector {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void printCurrentUtxoSet(List<OutputGroup> utxoPool, ArrayDeque<Boolean> currentSelection, long currentValue) {
|
private void printCurrentUtxoSet(List<OutputGroup> utxoPool, ArrayDeque<Boolean> currentSelection, long currentValue) {
|
||||||
long noInputsFee = (long)(noInputsWeightUnits * feeRate / WITNESS_SCALE_FACTOR);
|
|
||||||
long inputsFee = 0;
|
long inputsFee = 0;
|
||||||
StringJoiner joiner = new StringJoiner(" + ");
|
StringJoiner joiner = new StringJoiner(" + ");
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
|
|
@ -8,8 +8,16 @@ import java.util.stream.Collectors;
|
||||||
public class KnapsackUtxoSelector implements UtxoSelector {
|
public class KnapsackUtxoSelector implements UtxoSelector {
|
||||||
private static final long MIN_CHANGE = Transaction.SATOSHIS_PER_BITCOIN / 100;
|
private static final long MIN_CHANGE = Transaction.SATOSHIS_PER_BITCOIN / 100;
|
||||||
|
|
||||||
|
private final long noInputsFee;
|
||||||
|
|
||||||
|
public KnapsackUtxoSelector(long noInputsFee) {
|
||||||
|
this.noInputsFee = noInputsFee;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<BlockTransactionHashIndex> select(long targetValue, Collection<OutputGroup> candidates) {
|
public Collection<BlockTransactionHashIndex> select(long targetValue, Collection<OutputGroup> candidates) {
|
||||||
|
long actualTargetValue = targetValue + noInputsFee;
|
||||||
|
|
||||||
List<OutputGroup> shuffled = new ArrayList<>(candidates);
|
List<OutputGroup> shuffled = new ArrayList<>(candidates);
|
||||||
Collections.shuffle(shuffled);
|
Collections.shuffle(shuffled);
|
||||||
|
|
||||||
|
@ -18,9 +26,9 @@ public class KnapsackUtxoSelector implements UtxoSelector {
|
||||||
long totalLower = 0;
|
long totalLower = 0;
|
||||||
|
|
||||||
for(OutputGroup outputGroup : shuffled) {
|
for(OutputGroup outputGroup : shuffled) {
|
||||||
if(outputGroup.getEffectiveValue() == targetValue) {
|
if(outputGroup.getEffectiveValue() == actualTargetValue) {
|
||||||
return new ArrayList<>(outputGroup.getUtxos());
|
return new ArrayList<>(outputGroup.getUtxos());
|
||||||
} else if(outputGroup.getEffectiveValue() < targetValue + MIN_CHANGE) {
|
} else if(outputGroup.getEffectiveValue() < actualTargetValue + MIN_CHANGE) {
|
||||||
applicableGroups.add(outputGroup);
|
applicableGroups.add(outputGroup);
|
||||||
totalLower += outputGroup.getEffectiveValue();
|
totalLower += outputGroup.getEffectiveValue();
|
||||||
} else if(lowestLarger == null || outputGroup.getEffectiveValue() < lowestLarger.getEffectiveValue()) {
|
} else if(lowestLarger == null || outputGroup.getEffectiveValue() < lowestLarger.getEffectiveValue()) {
|
||||||
|
@ -28,32 +36,32 @@ public class KnapsackUtxoSelector implements UtxoSelector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(totalLower == targetValue) {
|
if(totalLower == actualTargetValue) {
|
||||||
return applicableGroups.stream().flatMap(outputGroup -> outputGroup.getUtxos().stream()).collect(Collectors.toList());
|
return applicableGroups.stream().flatMap(outputGroup -> outputGroup.getUtxos().stream()).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
if(totalLower < targetValue) {
|
if(totalLower < actualTargetValue) {
|
||||||
if(lowestLarger == null) {
|
if(lowestLarger == null) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
return lowestLarger.getUtxos();
|
return lowestLarger.getUtxos();
|
||||||
}
|
}
|
||||||
|
|
||||||
//We now have a list of UTXOs that are all smaller than the target + MIN_CHANGE, but together sum to greater than targetValue
|
//We now have a list of UTXOs that are all smaller than the target + MIN_CHANGE, but together sum to greater than actualTargetValue
|
||||||
// Solve subset sum by stochastic approximation
|
// Solve subset sum by stochastic approximation
|
||||||
|
|
||||||
applicableGroups.sort((a, b) -> (int)(b.getEffectiveValue() - a.getEffectiveValue()));
|
applicableGroups.sort((a, b) -> (int)(b.getEffectiveValue() - a.getEffectiveValue()));
|
||||||
boolean[] bestSelection = new boolean[applicableGroups.size()];
|
boolean[] bestSelection = new boolean[applicableGroups.size()];
|
||||||
|
|
||||||
long bestValue = findApproximateBestSubset(applicableGroups, totalLower, targetValue, bestSelection);
|
long bestValue = findApproximateBestSubset(applicableGroups, totalLower, actualTargetValue, bestSelection);
|
||||||
if(bestValue != targetValue && totalLower >= targetValue + MIN_CHANGE) {
|
if(bestValue != actualTargetValue && totalLower >= actualTargetValue + MIN_CHANGE) {
|
||||||
bestValue = findApproximateBestSubset(applicableGroups, totalLower, targetValue + MIN_CHANGE, bestSelection);
|
bestValue = findApproximateBestSubset(applicableGroups, totalLower, actualTargetValue + MIN_CHANGE, bestSelection);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have a bigger coin and (either the stochastic approximation didn't find a good solution,
|
// If we have a bigger coin and (either the stochastic approximation didn't find a good solution,
|
||||||
// or the next bigger coin is closer), return the bigger coin
|
// or the next bigger coin is closer), return the bigger coin
|
||||||
|
|
||||||
if(lowestLarger != null && ((bestValue != targetValue && bestValue < targetValue + MIN_CHANGE) || lowestLarger.getEffectiveValue() <= bestValue)) {
|
if(lowestLarger != null && ((bestValue != actualTargetValue && bestValue < actualTargetValue + MIN_CHANGE) || lowestLarger.getEffectiveValue() <= bestValue)) {
|
||||||
return lowestLarger.getUtxos();
|
return lowestLarger.getUtxos();
|
||||||
} else {
|
} else {
|
||||||
List<BlockTransactionHashIndex> utxos = new ArrayList<>();
|
List<BlockTransactionHashIndex> utxos = new ArrayList<>();
|
||||||
|
@ -66,7 +74,7 @@ public class KnapsackUtxoSelector implements UtxoSelector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private long findApproximateBestSubset(List<OutputGroup> groups, long totalLower, long targetValue, boolean[] bestSelection) {
|
private long findApproximateBestSubset(List<OutputGroup> groups, long totalLower, long actualTargetValue, boolean[] bestSelection) {
|
||||||
int iterations = 1000;
|
int iterations = 1000;
|
||||||
|
|
||||||
boolean[] includedSelection;
|
boolean[] includedSelection;
|
||||||
|
@ -76,7 +84,7 @@ public class KnapsackUtxoSelector implements UtxoSelector {
|
||||||
|
|
||||||
Random random = new Random();
|
Random random = new Random();
|
||||||
|
|
||||||
for(int rep = 0; rep < iterations && bestValue != targetValue; rep++) {
|
for(int rep = 0; rep < iterations && bestValue != actualTargetValue; rep++) {
|
||||||
includedSelection = new boolean[groups.size()];
|
includedSelection = new boolean[groups.size()];
|
||||||
Arrays.fill(includedSelection, false);
|
Arrays.fill(includedSelection, false);
|
||||||
long total = 0;
|
long total = 0;
|
||||||
|
@ -94,7 +102,7 @@ public class KnapsackUtxoSelector implements UtxoSelector {
|
||||||
if(pass == 0 ? random.nextBoolean() : !includedSelection[i]) {
|
if(pass == 0 ? random.nextBoolean() : !includedSelection[i]) {
|
||||||
total += groups.get(i).getEffectiveValue();
|
total += groups.get(i).getEffectiveValue();
|
||||||
includedSelection[i] = true;
|
includedSelection[i] = true;
|
||||||
if(total >= targetValue) {
|
if(total >= actualTargetValue) {
|
||||||
reachedTarget = true;
|
reachedTarget = true;
|
||||||
if(total < bestValue) {
|
if(total < bestValue) {
|
||||||
bestValue = total;
|
bestValue = total;
|
||||||
|
|
|
@ -281,6 +281,16 @@ public class Wallet {
|
||||||
return (long)(feeRate * outputVbytes + longTermFeeRate * inputVbytes);
|
return (long)(feeRate * outputVbytes + longTermFeeRate * inputVbytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the fee for a transaction from this wallet that has one output and no inputs
|
||||||
|
*
|
||||||
|
* @param recipientAddress The address to create the output to send to
|
||||||
|
* @return The determined fee
|
||||||
|
*/
|
||||||
|
public long getNoInputsFee(Address recipientAddress, Double feeRate) {
|
||||||
|
return (long)Math.ceil((double)getNoInputsWeightUnits(recipientAddress) * feeRate / (double)WITNESS_SCALE_FACTOR);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines the weight units for a transaction from this wallet that has one output and no inputs
|
* Determines the weight units for a transaction from this wallet that has one output and no inputs
|
||||||
*
|
*
|
||||||
|
|
Loading…
Reference in a new issue