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