mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-02-07 23:03:04 +00:00
Refactor fee calculation out of wallet.completeTx(). Introduce a (not widely used yet) InsufficientMoneyException.
This commit is contained in:
parent
46914b12b7
commit
6077d32c4a
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2013 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.bitcoin.core;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown to indicate that you don't have enough money available to perform the requested operation.
|
||||||
|
*/
|
||||||
|
public class InsufficientMoneyException extends Exception {
|
||||||
|
}
|
@ -1956,193 +1956,29 @@ public class Wallet implements Serializable, BlockChainListener {
|
|||||||
LinkedList<TransactionOutput> candidates = calculateSpendCandidates(true);
|
LinkedList<TransactionOutput> candidates = calculateSpendCandidates(true);
|
||||||
Address changeAddress = req.changeAddress;
|
Address changeAddress = req.changeAddress;
|
||||||
int minSize = 0;
|
int minSize = 0;
|
||||||
// There are 3 possibilities for what adding change might do:
|
// This can throw InsufficientMoneyException.
|
||||||
// 1) No effect
|
FeeCalculation feeCalculation = null;
|
||||||
// 2) Causes increase in fee (change < 0.01 COINS)
|
|
||||||
// 3) Causes the transaction to have a dust output or change < fee increase (ie change will be thrown away)
|
|
||||||
// If we get either of the last 2, we keep note of what the inputs looked like at the time and move try to
|
|
||||||
// add inputs as we go up the list (keeping track of minimum inputs for each category). At the end, we pick
|
|
||||||
// the best input set as the one which generates the lowest total fee.
|
|
||||||
BigInteger additionalValueForNextCategory = null;
|
|
||||||
CoinSelection selection3 = null;
|
|
||||||
CoinSelection selection2 = null; TransactionOutput selection2Change = null;
|
|
||||||
CoinSelection selection1 = null; TransactionOutput selection1Change = null;
|
|
||||||
while (true) {
|
|
||||||
req.tx.clearInputs();
|
|
||||||
for (TransactionInput input : originalInputs)
|
|
||||||
req.tx.addInput(input);
|
|
||||||
|
|
||||||
BigInteger fees = req.fee.add(BigInteger.valueOf(minSize/1000).multiply(req.feePerKb));
|
|
||||||
if (needAtLeastReferenceFee && fees.compareTo(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE) < 0)
|
|
||||||
fees = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE;
|
|
||||||
|
|
||||||
BigInteger valueNeeded = value.add(fees);
|
|
||||||
if (additionalValueForNextCategory != null)
|
|
||||||
valueNeeded = valueNeeded.add(additionalValueForNextCategory);
|
|
||||||
BigInteger additionalValueSelected = additionalValueForNextCategory;
|
|
||||||
|
|
||||||
// Of the coins we could spend, pick some that we actually will spend.
|
|
||||||
CoinSelection selection = coinSelector.select(valueNeeded, candidates);
|
|
||||||
// Can we afford this?
|
|
||||||
if (selection.valueGathered.compareTo(valueNeeded) < 0)
|
|
||||||
break;
|
|
||||||
checkState(selection.gathered.size() > 0 || originalInputs.size() > 0);
|
|
||||||
|
|
||||||
// We keep track of an upper bound on transaction size to calculate fees that need added
|
|
||||||
// Note that the difference between the upper bound and lower bound is usually small enough that it
|
|
||||||
// will be very rare that we pay a fee we do not need to
|
|
||||||
int size = 0;
|
|
||||||
|
|
||||||
// We can't be sure a selection is valid until we check fee per kb at the end, so we just store them here temporarily
|
|
||||||
boolean eitherCategory2Or3 = false;
|
|
||||||
boolean isCategory3 = false;
|
|
||||||
|
|
||||||
BigInteger change = selection.valueGathered.subtract(valueNeeded);
|
|
||||||
if (additionalValueSelected != null)
|
|
||||||
change = change.add(additionalValueSelected);
|
|
||||||
|
|
||||||
TransactionOutput changeOutput = null;
|
|
||||||
// If change is < 0.01 BTC, we will need to have at least minfee to be accepted by the network
|
|
||||||
if (req.ensureMinRequiredFee && !change.equals(BigInteger.ZERO) &&
|
|
||||||
change.compareTo(Utils.CENT) < 0 && fees.compareTo(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE) < 0) {
|
|
||||||
// This solution may fit into category 2, but it may also be category 3, we'll check that later
|
|
||||||
eitherCategory2Or3 = true;
|
|
||||||
additionalValueForNextCategory = Utils.CENT;
|
|
||||||
// If the change is smaller than the fee we want to add, this will be negative
|
|
||||||
change = change.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.subtract(fees));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (change.compareTo(BigInteger.ZERO) > 0) {
|
|
||||||
// The value of the inputs is greater than what we want to send. Just like in real life then,
|
|
||||||
// we need to take back some coins ... this is called "change". Add another output that sends the change
|
|
||||||
// back to us. The address comes either from the request or getChangeAddress() as a default..
|
|
||||||
if (changeAddress == null)
|
|
||||||
changeAddress = getChangeAddress();
|
|
||||||
changeOutput = new TransactionOutput(params, req.tx, change, changeAddress);
|
|
||||||
// If the change output would result in this transaction being rejected as dust, just drop the change and make it a fee
|
|
||||||
if (req.ensureMinRequiredFee && Transaction.MIN_NONDUST_OUTPUT.compareTo(change) >= 0) {
|
|
||||||
// This solution definitely fits in category 3
|
|
||||||
isCategory3 = true;
|
|
||||||
additionalValueForNextCategory = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(
|
|
||||||
Transaction.MIN_NONDUST_OUTPUT.add(BigInteger.ONE));
|
|
||||||
} else {
|
|
||||||
size += changeOutput.bitcoinSerialize().length + VarInt.sizeOf(req.tx.getOutputs().size()) - VarInt.sizeOf(req.tx.getOutputs().size() - 1);
|
|
||||||
// This solution is either category 1 or 2
|
|
||||||
if (!eitherCategory2Or3) // must be category 1
|
|
||||||
additionalValueForNextCategory = null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (eitherCategory2Or3) {
|
|
||||||
// This solution definitely fits in category 3 (we threw away change because it was smaller than MIN_TX_FEE)
|
|
||||||
isCategory3 = true;
|
|
||||||
additionalValueForNextCategory = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(BigInteger.ONE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (TransactionOutput output : selection.gathered) {
|
|
||||||
req.tx.addInput(output);
|
|
||||||
// If the scriptBytes don't default to none, our size calculations will be thrown off
|
|
||||||
checkState(req.tx.getInput(req.tx.getInputs().size()-1).getScriptBytes().length == 0);
|
|
||||||
try {
|
try {
|
||||||
if (output.getScriptPubKey().isSentToAddress()) {
|
feeCalculation = new FeeCalculation(req, value, originalInputs, needAtLeastReferenceFee,
|
||||||
// Send-to-address spends usually take maximum pubkey.length (as it may be compressed or not) + 75 bytes
|
candidates, changeAddress, minSize);
|
||||||
size += this.findKeyFromPubHash(output.getScriptPubKey().getPubKeyHash()).getPubKey().length + 75;
|
} catch (InsufficientMoneyException e) {
|
||||||
} else if (output.getScriptPubKey().isSentToRawPubKey())
|
// TODO: Propagate this after 0.9 is released and stop returning a boolean.
|
||||||
size += 74; // Send-to-pubkey spends usually take maximum 74 bytes to spend
|
|
||||||
else
|
|
||||||
throw new RuntimeException("Unknown output type returned in coin selection");
|
|
||||||
} catch (ScriptException e) {
|
|
||||||
// If this happens it means an output script in a wallet tx could not be understood. That should never
|
|
||||||
// happen, if it does it means the wallet has got into an inconsistent state.
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Estimate transaction size and loop again if we need more fee per kb
|
|
||||||
size += req.tx.bitcoinSerialize().length;
|
|
||||||
if (size/1000 > minSize/1000 && req.feePerKb.compareTo(BigInteger.ZERO) > 0) {
|
|
||||||
minSize = size;
|
|
||||||
// We need more fees anyway, just try again with the same additional value
|
|
||||||
additionalValueForNextCategory = additionalValueSelected;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isCategory3) {
|
|
||||||
if (selection3 == null)
|
|
||||||
selection3 = selection;
|
|
||||||
} else if (eitherCategory2Or3) {
|
|
||||||
// If we are in selection2, we will require at least CENT additional. If we do that, there is no way
|
|
||||||
// we can end up back here because CENT additional will always get us to 1
|
|
||||||
checkState(selection2 == null);
|
|
||||||
checkState(additionalValueForNextCategory.equals(Utils.CENT));
|
|
||||||
selection2 = selection;
|
|
||||||
selection2Change = checkNotNull(changeOutput); // If we get no change in category 2, we are actually in category 3
|
|
||||||
} else {
|
|
||||||
// Once we get a category 1 (change kept), we should break out of the loop because we can't do better
|
|
||||||
checkState(selection1 == null);
|
|
||||||
checkState(additionalValueForNextCategory == null);
|
|
||||||
selection1 = selection;
|
|
||||||
selection1Change = changeOutput;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (additionalValueForNextCategory != null) {
|
|
||||||
if (additionalValueSelected != null)
|
|
||||||
checkState(additionalValueForNextCategory.compareTo(additionalValueSelected) > 0);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
req.tx.clearInputs();
|
|
||||||
for (TransactionInput input : originalInputs)
|
|
||||||
req.tx.addInput(input);
|
|
||||||
|
|
||||||
if (selection3 == null && selection2 == null && selection1 == null) {
|
|
||||||
log.warn("Insufficient value in wallet for send");
|
|
||||||
// TODO: Should throw an exception here.
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
CoinSelection bestCoinSelection = feeCalculation.bestCoinSelection;
|
||||||
BigInteger lowestFee = null;
|
TransactionOutput bestChangeOutput = feeCalculation.bestChangeOutput;
|
||||||
CoinSelection bestCoinSelection = null;
|
|
||||||
TransactionOutput bestChangeOutput = null;
|
|
||||||
if (selection1 != null) {
|
|
||||||
if (selection1Change != null)
|
|
||||||
lowestFee = selection1.valueGathered.subtract(selection1Change.getValue());
|
|
||||||
else
|
|
||||||
lowestFee = selection1.valueGathered;
|
|
||||||
bestCoinSelection = selection1;
|
|
||||||
bestChangeOutput = selection1Change;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selection2 != null) {
|
|
||||||
BigInteger fee = selection2.valueGathered.subtract(checkNotNull(selection2Change).getValue());
|
|
||||||
if (lowestFee == null || fee.compareTo(lowestFee) < 0) {
|
|
||||||
lowestFee = fee;
|
|
||||||
bestCoinSelection = selection2;
|
|
||||||
bestChangeOutput = selection2Change;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selection3 != null) {
|
|
||||||
if (lowestFee == null || selection3.valueGathered.compareTo(lowestFee) < 0) {
|
|
||||||
bestCoinSelection = selection3;
|
|
||||||
bestChangeOutput = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (TransactionOutput output : bestCoinSelection.gathered)
|
for (TransactionOutput output : bestCoinSelection.gathered)
|
||||||
req.tx.addInput(output);
|
req.tx.addInput(output);
|
||||||
|
|
||||||
totalInput = totalInput.add(bestCoinSelection.valueGathered);
|
totalInput = totalInput.add(bestCoinSelection.valueGathered);
|
||||||
|
|
||||||
req.tx.getConfidence().setConfidenceType(ConfidenceType.PENDING);
|
|
||||||
|
|
||||||
if (bestChangeOutput != null) {
|
if (bestChangeOutput != null) {
|
||||||
req.tx.addOutput(bestChangeOutput);
|
req.tx.addOutput(bestChangeOutput);
|
||||||
totalOutput = totalOutput.add(bestChangeOutput.getValue());
|
totalOutput = totalOutput.add(bestChangeOutput.getValue());
|
||||||
log.info(" with {} coins change", bitcoinValueToFriendlyString(bestChangeOutput.getValue()));
|
log.info(" with {} coins change", bitcoinValueToFriendlyString(bestChangeOutput.getValue()));
|
||||||
}
|
}
|
||||||
|
final BigInteger calculatedFee = totalInput.subtract(totalOutput);
|
||||||
|
|
||||||
// Now sign the inputs, thus proving that we are entitled to redeem the connected outputs.
|
// Now sign the inputs, thus proving that we are entitled to redeem the connected outputs.
|
||||||
try {
|
try {
|
||||||
@ -2156,18 +1992,19 @@ public class Wallet implements Serializable, BlockChainListener {
|
|||||||
// Check size.
|
// Check size.
|
||||||
int size = req.tx.bitcoinSerialize().length;
|
int size = req.tx.bitcoinSerialize().length;
|
||||||
if (size > Transaction.MAX_STANDARD_TX_SIZE) {
|
if (size > Transaction.MAX_STANDARD_TX_SIZE) {
|
||||||
// TODO: Throw an exception here.
|
// TODO: Throw an unchecked protocol exception here.
|
||||||
log.error("Transaction could not be created without exceeding max size: {} vs {}", size,
|
log.warn(String.format(
|
||||||
Transaction.MAX_STANDARD_TX_SIZE);
|
"Transaction could not be created without exceeding max size: %d vs %d",
|
||||||
|
size, Transaction.MAX_STANDARD_TX_SIZE));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Label the transaction as being self created. We can use this later to spend its change output even before
|
// Label the transaction as being self created. We can use this later to spend its change output even before
|
||||||
// the transaction is confirmed.
|
// the transaction is confirmed.
|
||||||
|
req.tx.getConfidence().setConfidenceType(ConfidenceType.PENDING);
|
||||||
req.tx.getConfidence().setSource(TransactionConfidence.Source.SELF);
|
req.tx.getConfidence().setSource(TransactionConfidence.Source.SELF);
|
||||||
|
|
||||||
req.completed = true;
|
req.completed = true;
|
||||||
req.fee = totalInput.subtract(totalOutput);
|
req.fee = calculatedFee;
|
||||||
log.info(" completed {} with {} inputs", req.tx.getHashAsString(), req.tx.getInputs().size());
|
log.info(" completed {} with {} inputs", req.tx.getHashAsString(), req.tx.getInputs().size());
|
||||||
return true;
|
return true;
|
||||||
} finally {
|
} finally {
|
||||||
@ -3241,4 +3078,192 @@ public class Wallet implements Serializable, BlockChainListener {
|
|||||||
lock.lock();
|
lock.lock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class FeeCalculation {
|
||||||
|
private CoinSelection bestCoinSelection;
|
||||||
|
private TransactionOutput bestChangeOutput;
|
||||||
|
|
||||||
|
public FeeCalculation(SendRequest req, BigInteger value, List<TransactionInput> originalInputs,
|
||||||
|
boolean needAtLeastReferenceFee, LinkedList<TransactionOutput> candidates,
|
||||||
|
Address changeAddress, int minSize) throws InsufficientMoneyException {
|
||||||
|
// There are 3 possibilities for what adding change might do:
|
||||||
|
// 1) No effect
|
||||||
|
// 2) Causes increase in fee (change < 0.01 COINS)
|
||||||
|
// 3) Causes the transaction to have a dust output or change < fee increase (ie change will be thrown away)
|
||||||
|
// If we get either of the last 2, we keep note of what the inputs looked like at the time and try to
|
||||||
|
// add inputs as we go up the list (keeping track of minimum inputs for each category). At the end, we pick
|
||||||
|
// the best input set as the one which generates the lowest total fee.
|
||||||
|
BigInteger additionalValueForNextCategory = null;
|
||||||
|
CoinSelection selection3 = null;
|
||||||
|
CoinSelection selection2 = null;
|
||||||
|
TransactionOutput selection2Change = null;
|
||||||
|
CoinSelection selection1 = null;
|
||||||
|
TransactionOutput selection1Change = null;
|
||||||
|
while (true) {
|
||||||
|
resetTxInputs(req, originalInputs);
|
||||||
|
|
||||||
|
BigInteger fees = req.fee.add(BigInteger.valueOf(minSize/1000).multiply(req.feePerKb));
|
||||||
|
if (needAtLeastReferenceFee && fees.compareTo(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE) < 0)
|
||||||
|
fees = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE;
|
||||||
|
|
||||||
|
BigInteger valueNeeded = value.add(fees);
|
||||||
|
if (additionalValueForNextCategory != null)
|
||||||
|
valueNeeded = valueNeeded.add(additionalValueForNextCategory);
|
||||||
|
BigInteger additionalValueSelected = additionalValueForNextCategory;
|
||||||
|
|
||||||
|
// Of the coins we could spend, pick some that we actually will spend.
|
||||||
|
CoinSelection selection = coinSelector.select(valueNeeded, candidates);
|
||||||
|
// Can we afford this?
|
||||||
|
if (selection.valueGathered.compareTo(valueNeeded) < 0)
|
||||||
|
break;
|
||||||
|
checkState(selection.gathered.size() > 0 || originalInputs.size() > 0);
|
||||||
|
|
||||||
|
// We keep track of an upper bound on transaction size to calculate fees that need to be added.
|
||||||
|
// Note that the difference between the upper bound and lower bound is usually small enough that it
|
||||||
|
// will be very rare that we pay a fee we do not need to.
|
||||||
|
//
|
||||||
|
// We can't be sure a selection is valid until we check fee per kb at the end, so we just store
|
||||||
|
// them here temporarily.
|
||||||
|
boolean eitherCategory2Or3 = false;
|
||||||
|
boolean isCategory3 = false;
|
||||||
|
|
||||||
|
BigInteger change = selection.valueGathered.subtract(valueNeeded);
|
||||||
|
if (additionalValueSelected != null)
|
||||||
|
change = change.add(additionalValueSelected);
|
||||||
|
|
||||||
|
// If change is < 0.01 BTC, we will need to have at least minfee to be accepted by the network
|
||||||
|
if (req.ensureMinRequiredFee && !change.equals(BigInteger.ZERO) &&
|
||||||
|
change.compareTo(Utils.CENT) < 0 && fees.compareTo(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE) < 0) {
|
||||||
|
// This solution may fit into category 2, but it may also be category 3, we'll check that later
|
||||||
|
eitherCategory2Or3 = true;
|
||||||
|
additionalValueForNextCategory = Utils.CENT;
|
||||||
|
// If the change is smaller than the fee we want to add, this will be negative
|
||||||
|
change = change.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.subtract(fees));
|
||||||
|
}
|
||||||
|
|
||||||
|
int size = 0;
|
||||||
|
TransactionOutput changeOutput = null;
|
||||||
|
if (change.compareTo(BigInteger.ZERO) > 0) {
|
||||||
|
// The value of the inputs is greater than what we want to send. Just like in real life then,
|
||||||
|
// we need to take back some coins ... this is called "change". Add another output that sends the change
|
||||||
|
// back to us. The address comes either from the request or getChangeAddress() as a default..
|
||||||
|
if (changeAddress == null)
|
||||||
|
changeAddress = getChangeAddress();
|
||||||
|
changeOutput = new TransactionOutput(params, req.tx, change, changeAddress);
|
||||||
|
// If the change output would result in this transaction being rejected as dust, just drop the change and make it a fee
|
||||||
|
if (req.ensureMinRequiredFee && Transaction.MIN_NONDUST_OUTPUT.compareTo(change) >= 0) {
|
||||||
|
// This solution definitely fits in category 3
|
||||||
|
isCategory3 = true;
|
||||||
|
additionalValueForNextCategory = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(
|
||||||
|
Transaction.MIN_NONDUST_OUTPUT.add(BigInteger.ONE));
|
||||||
|
} else {
|
||||||
|
size += changeOutput.bitcoinSerialize().length + VarInt.sizeOf(req.tx.getOutputs().size()) - VarInt.sizeOf(req.tx.getOutputs().size() - 1);
|
||||||
|
// This solution is either category 1 or 2
|
||||||
|
if (!eitherCategory2Or3) // must be category 1
|
||||||
|
additionalValueForNextCategory = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (eitherCategory2Or3) {
|
||||||
|
// This solution definitely fits in category 3 (we threw away change because it was smaller than MIN_TX_FEE)
|
||||||
|
isCategory3 = true;
|
||||||
|
additionalValueForNextCategory = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(BigInteger.ONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (TransactionOutput output : selection.gathered) {
|
||||||
|
req.tx.addInput(output);
|
||||||
|
// If the scriptBytes don't default to none, our size calculations will be thrown off
|
||||||
|
checkState(req.tx.getInput(req.tx.getInputs().size()-1).getScriptBytes().length == 0);
|
||||||
|
try {
|
||||||
|
if (output.getScriptPubKey().isSentToAddress()) {
|
||||||
|
// Send-to-address spends usually take maximum pubkey.length (as it may be compressed or not) + 75 bytes
|
||||||
|
size += findKeyFromPubHash(output.getScriptPubKey().getPubKeyHash()).getPubKey().length + 75;
|
||||||
|
} else if (output.getScriptPubKey().isSentToRawPubKey())
|
||||||
|
size += 74; // Send-to-pubkey spends usually take maximum 74 bytes to spend
|
||||||
|
else
|
||||||
|
throw new RuntimeException("Unknown output type returned in coin selection");
|
||||||
|
} catch (ScriptException e) {
|
||||||
|
// If this happens it means an output script in a wallet tx could not be understood. That should never
|
||||||
|
// happen, if it does it means the wallet has got into an inconsistent state.
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Estimate transaction size and loop again if we need more fee per kb
|
||||||
|
size += req.tx.bitcoinSerialize().length;
|
||||||
|
if (size/1000 > minSize/1000 && req.feePerKb.compareTo(BigInteger.ZERO) > 0) {
|
||||||
|
minSize = size;
|
||||||
|
// We need more fees anyway, just try again with the same additional value
|
||||||
|
additionalValueForNextCategory = additionalValueSelected;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCategory3) {
|
||||||
|
if (selection3 == null)
|
||||||
|
selection3 = selection;
|
||||||
|
} else if (eitherCategory2Or3) {
|
||||||
|
// If we are in selection2, we will require at least CENT additional. If we do that, there is no way
|
||||||
|
// we can end up back here because CENT additional will always get us to 1
|
||||||
|
checkState(selection2 == null);
|
||||||
|
checkState(additionalValueForNextCategory.equals(Utils.CENT));
|
||||||
|
selection2 = selection;
|
||||||
|
selection2Change = checkNotNull(changeOutput); // If we get no change in category 2, we are actually in category 3
|
||||||
|
} else {
|
||||||
|
// Once we get a category 1 (change kept), we should break out of the loop because we can't do better
|
||||||
|
checkState(selection1 == null);
|
||||||
|
checkState(additionalValueForNextCategory == null);
|
||||||
|
selection1 = selection;
|
||||||
|
selection1Change = changeOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (additionalValueForNextCategory != null) {
|
||||||
|
if (additionalValueSelected != null)
|
||||||
|
checkState(additionalValueForNextCategory.compareTo(additionalValueSelected) > 0);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
resetTxInputs(req, originalInputs);
|
||||||
|
|
||||||
|
if (selection3 == null && selection2 == null && selection1 == null) {
|
||||||
|
log.warn("Insufficient value in wallet for send");
|
||||||
|
throw new InsufficientMoneyException();
|
||||||
|
}
|
||||||
|
|
||||||
|
BigInteger lowestFee = null;
|
||||||
|
bestCoinSelection = null;
|
||||||
|
bestChangeOutput = null;
|
||||||
|
if (selection1 != null) {
|
||||||
|
if (selection1Change != null)
|
||||||
|
lowestFee = selection1.valueGathered.subtract(selection1Change.getValue());
|
||||||
|
else
|
||||||
|
lowestFee = selection1.valueGathered;
|
||||||
|
bestCoinSelection = selection1;
|
||||||
|
bestChangeOutput = selection1Change;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selection2 != null) {
|
||||||
|
BigInteger fee = selection2.valueGathered.subtract(checkNotNull(selection2Change).getValue());
|
||||||
|
if (lowestFee == null || fee.compareTo(lowestFee) < 0) {
|
||||||
|
lowestFee = fee;
|
||||||
|
bestCoinSelection = selection2;
|
||||||
|
bestChangeOutput = selection2Change;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selection3 != null) {
|
||||||
|
if (lowestFee == null || selection3.valueGathered.compareTo(lowestFee) < 0) {
|
||||||
|
bestCoinSelection = selection3;
|
||||||
|
bestChangeOutput = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetTxInputs(SendRequest req, List<TransactionInput> originalInputs) {
|
||||||
|
req.tx.clearInputs();
|
||||||
|
for (TransactionInput input : originalInputs)
|
||||||
|
req.tx.addInput(input);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user