mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-07-28 01:15:26 -04:00
Fix wrong fee calculation
This commit is contained in:
parent
117a65f913
commit
bcf1b61850
1 changed files with 48 additions and 57 deletions
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue