Fix wrong fee calculation

This commit is contained in:
Manfred Karrer 2016-04-26 00:56:05 +02:00
parent 117a65f913
commit bcf1b61850

View File

@ -39,10 +39,7 @@ import org.bitcoinj.kits.WalletAppKit;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.params.RegTestParams;
import org.bitcoinj.params.TestNet3Params;
import org.bitcoinj.script.Script;
import org.bitcoinj.utils.Threading;
import org.bitcoinj.wallet.CoinSelection;
import org.bitcoinj.wallet.CoinSelector;
import org.bitcoinj.wallet.DeterministicSeed;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
@ -574,22 +571,24 @@ public class WalletService {
public Coin getRequiredFee(String fromAddress,
String toAddress,
Coin amount,
AddressEntry.Context context) throws AddressFormatException, AddressEntryException {
AddressEntry.Context context)
throws AddressFormatException, AddressEntryException {
Optional<AddressEntry> addressEntry = findAddressEntry(fromAddress, context);
if (!addressEntry.isPresent())
throw new AddressEntryException("WithdrawFromAddress is not found in our wallet.");
checkNotNull(addressEntry.get().getAddress(), "addressEntry.get().getAddress() must nto be null");
CoinSelector selector = new TradeWalletCoinSelector(params, addressEntry.get().getAddress());
return getFee(toAddress,
return getFee(fromAddress,
toAddress,
amount,
selector);
context,
Coin.ZERO);
}
public Coin getRequiredFeeForMultipleAddresses(Set<String> fromAddresses,
String toAddress,
Coin amount) throws AddressFormatException,
AddressEntryException {
Coin amount)
throws AddressFormatException, AddressEntryException {
Set<AddressEntry> addressEntries = fromAddresses.stream()
.map(address -> {
Optional<AddressEntry> addressEntryOptional = findAddressEntry(address, AddressEntry.Context.AVAILABLE);
@ -606,63 +605,55 @@ public class WalletService {
.collect(Collectors.toSet());
if (addressEntries.isEmpty())
throw new AddressEntryException("No Addresses for withdraw found in our wallet");
CoinSelector selector = new MultiAddressesCoinSelector(params, addressEntries);
return getFee(toAddress,
return getFeeForMultipleAddresses(fromAddresses,
toAddress,
amount,
selector);
Coin.ZERO);
}
private Coin getFee(String toAddress,
Coin amount,
CoinSelector selector) throws AddressFormatException, AddressEntryException {
List<TransactionOutput> candidates = wallet.calculateAllSpendCandidates();
CoinSelection bestCoinSelection = selector.select(params.getMaxMoney(), candidates);
Transaction tx = new Transaction(params);
tx.addOutput(amount, new Address(params, toAddress));
if (!adjustOutputDownwardsForFee(tx, bestCoinSelection, Coin.ZERO, FeePolicy.getNonTradeFeePerKb()))
throw new Wallet.CouldNotAdjustDownwards();
Coin fee = amount.subtract(tx.getOutput(0).getValue());
log.info("Required fee " + fee);
private Coin getFee(String fromAddress,
String toAddress,
Coin amount,
AddressEntry.Context context,
Coin fee) throws AddressEntryException, AddressFormatException {
try {
wallet.completeTx(getSendRequest(fromAddress, toAddress, amount, null, context));
} catch (InsufficientMoneyException e) {
if (e.missing != null) {
log.trace("missing fee " + e.missing.toFriendlyString());
fee = fee.add(e.missing);
amount = amount.subtract(fee);
return getFee(fromAddress,
toAddress,
amount,
context,
fee);
}
}
log.trace("result fee " + fee.toFriendlyString());
return fee;
}
private boolean adjustOutputDownwardsForFee(Transaction tx, CoinSelection coinSelection, Coin baseFee, Coin feePerKb) {
TransactionOutput output = tx.getOutput(0);
// Check if we need additional fee due to the transaction's size
int size = tx.bitcoinSerialize().length;
size += estimateBytesForSigning(coinSelection);
Coin fee = baseFee.add(feePerKb.multiply((size / 1000) + 1));
output.setValue(output.getValue().subtract(fee));
// Check if we need additional fee due to the output's value
if (output.getValue().compareTo(Coin.CENT) < 0 && fee.compareTo(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE) < 0)
output.setValue(output.getValue().subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.subtract(fee)));
return output.getMinNonDustValue().compareTo(output.getValue()) <= 0;
}
private int estimateBytesForSigning(CoinSelection selection) {
int size = 0;
for (TransactionOutput output : selection.gathered) {
try {
Script script = output.getScriptPubKey();
ECKey key = null;
Script redeemScript = null;
if (script.isSentToAddress()) {
key = wallet.findKeyFromPubHash(script.getPubKeyHash());
checkNotNull(key, "Coin selection includes unspendable outputs");
} else if (script.isPayToScriptHash()) {
redeemScript = wallet.findRedeemDataFromScriptHash(script.getPubKeyHash()).redeemScript;
checkNotNull(redeemScript, "Coin selection includes unspendable outputs");
}
size += script.getNumberOfBytesRequiredToSpend(key, redeemScript);
} 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 IllegalStateException(e);
private Coin getFeeForMultipleAddresses(Set<String> fromAddresses,
String toAddress,
Coin amount,
Coin fee) throws AddressEntryException, AddressFormatException {
try {
wallet.completeTx(getSendRequestForMultipleAddresses(fromAddresses, toAddress, amount, null, null));
} catch (InsufficientMoneyException e) {
if (e.missing != null) {
log.trace("missing fee " + e.missing.toFriendlyString());
fee = fee.add(e.missing);
amount = amount.subtract(fee);
return getFeeForMultipleAddresses(fromAddresses,
toAddress,
amount,
fee);
}
}
return size;
log.trace("result fee " + fee.toFriendlyString());
return fee;
}