Merge pull request #4710 from oscarguindzberg/fee-estimation

Segwit fee estimation
This commit is contained in:
Christoph Atteneder 2020-11-05 21:18:54 +01:00 committed by GitHub
commit 93806e9c2f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 142 additions and 76 deletions

View file

@ -28,7 +28,7 @@ configure(subprojects) {
ext { // in alphabetical order
bcVersion = '1.63'
bitcoinjVersion = 'fcec3da'
bitcoinjVersion = '60b4f2f'
btcdCli4jVersion = '27b94333'
codecVersion = '1.13'
easybindVersion = '1.0.3'

View file

@ -107,7 +107,7 @@ public class WalletAppSetup {
Runnable downloadCompleteHandler,
Runnable walletInitializedHandler) {
log.info("Initialize WalletAppSetup with BitcoinJ version {} and hash of BitcoinJ commit {}",
VersionMessage.BITCOINJ_VERSION, "fcec3da");
VersionMessage.BITCOINJ_VERSION, "60b4f2f");
ObjectProperty<Throwable> walletServiceException = new SimpleObjectProperty<>();
btcInfoBinding = EasyBind.combine(walletsSetup.downloadPercentageProperty(),

View file

@ -41,9 +41,23 @@ import static com.google.common.base.Preconditions.checkArgument;
*/
@Slf4j
public class TxFeeEstimationService {
public static int TYPICAL_TX_WITH_1_INPUT_SIZE = 260;
private static int DEPOSIT_TX_SIZE = 320;
private static int PAYOUT_TX_SIZE = 380;
// Size/vsize of typical trade txs
// Real txs size/vsize may vary in 1 or 2 bytes from the estimated values.
// Values calculated with https://gist.github.com/oscarguindzberg/3d1349cb65d9fd9af9de0feaa3fd27ac
// legacy fee tx with 1 input, maker/taker fee paid in btc size/vsize = 258
// legacy deposit tx without change size/vsize = 381
// legacy deposit tx with change size/vsize = 414
// legacy payout tx size/vsize = 337
// legacy delayed payout tx size/vsize = 302
// segwit fee tx with 1 input, maker/taker fee paid in btc vsize = 173
// segwit deposit tx without change vsize = 232
// segwit deposit tx with change vsize = 263
// segwit payout tx vsize = 169
// segwit delayed payout tx vsize = 139
public static int TYPICAL_TX_WITH_1_INPUT_SIZE = 175;
private static int DEPOSIT_TX_SIZE = 233;
private static int BSQ_INPUT_INCREASE = 150;
private static int MAX_ITERATIONS = 10;
@ -87,14 +101,14 @@ public class TxFeeEstimationService {
BtcWalletService btcWalletService,
Preferences preferences) {
Coin txFeePerByte = feeService.getTxFeePerByte();
// We start with min taker fee size of 260
// We start with min taker fee size of 175
int estimatedTxSize = TYPICAL_TX_WITH_1_INPUT_SIZE;
try {
estimatedTxSize = getEstimatedTxSize(List.of(tradeFee, amount), estimatedTxSize, txFeePerByte, btcWalletService);
} catch (InsufficientMoneyException e) {
if (isTaker) {
// if we cannot do the estimation we use the payout tx size
estimatedTxSize = PAYOUT_TX_SIZE;
// If we cannot do the estimation, we use the size o the largest of our txs which is the deposit tx.
estimatedTxSize = DEPOSIT_TX_SIZE;
}
log.info("We cannot do the fee estimation because there are not enough funds in the wallet. This is expected " +
"if the user pays from an external wallet. In that case we use an estimated tx size of {} bytes.", estimatedTxSize);
@ -109,13 +123,13 @@ public class TxFeeEstimationService {
Coin txFee;
int size;
if (isTaker) {
int averageSize = (estimatedTxSize + DEPOSIT_TX_SIZE) / 2; // deposit tx has about 320 bytes
// We use at least the size of the payout tx to not underpay at payout.
size = Math.max(PAYOUT_TX_SIZE, averageSize);
int averageSize = (estimatedTxSize + DEPOSIT_TX_SIZE) / 2; // deposit tx has about 233 bytes
// We use at least the size of the deposit tx to not underpay it.
size = Math.max(DEPOSIT_TX_SIZE, averageSize);
txFee = txFeePerByte.multiply(size);
log.info("Fee estimation resulted in a tx size of {} bytes.\n" +
"We use an average between the taker fee tx and the deposit tx (320 bytes) which results in {} bytes.\n" +
"The payout tx has 380 bytes, we use that as our min value. Size for fee calculation is {} bytes.\n" +
"We use an average between the taker fee tx and the deposit tx (233 bytes) which results in {} bytes.\n" +
"The deposit tx has 233 bytes, we use that as our min value. Size for fee calculation is {} bytes.\n" +
"The tx fee of {} Sat", estimatedTxSize, averageSize, size, txFee.value);
} else {
size = estimatedTxSize;
@ -130,7 +144,7 @@ public class TxFeeEstimationService {
FeeService feeService,
BtcWalletService btcWalletService) {
Coin txFeePerByte = feeService.getTxFeePerByte();
// We start with min taker fee size of 260
// We start with min taker fee size of 175
int estimatedTxSize = TYPICAL_TX_WITH_1_INPUT_SIZE;
try {
estimatedTxSize = getEstimatedTxSize(List.of(amount), estimatedTxSize, txFeePerByte, btcWalletService);
@ -145,7 +159,7 @@ public class TxFeeEstimationService {
return new Tuple2<>(txFee, estimatedTxSize);
}
// We start with the initialEstimatedTxSize for a tx with 1 input (260) bytes and get from BitcoinJ a tx back which
// We start with the initialEstimatedTxSize for a tx with 1 input (175) bytes and get from BitcoinJ a tx back which
// contains the required inputs to fund that tx (outputs + miner fee). The miner fee in that case is based on
// the assumption that we only need 1 input. Once we receive back the real tx size from the tx BitcoinJ has created
// with the required inputs we compare if the size is not more then 20% different to our assumed tx size. If we are inside

View file

@ -28,13 +28,14 @@ import bisq.core.provider.fee.FeeService;
import bisq.core.user.Preferences;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.util.Tuple2;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.LegacyAddress;
import org.bitcoinj.core.SegwitAddress;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionConfidence;
import org.bitcoinj.core.TransactionInput;
@ -44,6 +45,7 @@ import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.crypto.KeyCrypterScrypt;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptBuilder;
import org.bitcoinj.script.ScriptPattern;
import org.bitcoinj.wallet.SendRequest;
import org.bitcoinj.wallet.Wallet;
@ -228,7 +230,9 @@ public class BtcWalletService extends WalletService {
preferences.getIgnoreDustThreshold());
List<TransactionInput> preparedBsqTxInputs = preparedTx.getInputs();
List<TransactionOutput> preparedBsqTxOutputs = preparedTx.getOutputs();
int numInputs = preparedBsqTxInputs.size();
Tuple2<Integer, Integer> numInputs = getNumInputs(preparedTx);
int numLegacyInputs = numInputs.first;
int numSegwitInputs = numInputs.second;
Transaction resultTx = null;
boolean isFeeOutsideTolerance;
do {
@ -249,7 +253,10 @@ public class BtcWalletService extends WalletService {
// signInputs needs to be false as it would try to sign all inputs (BSQ inputs are not in this wallet)
sendRequest.signInputs = false;
sendRequest.fee = txFeePerByte.multiply(txSizeWithUnsignedInputs + sigSizePerInput * numInputs);
sendRequest.fee = txFeePerByte.multiply(txSizeWithUnsignedInputs +
sigSizePerInput * numLegacyInputs +
sigSizePerInput * numSegwitInputs / 4);
sendRequest.feePerKb = Coin.ZERO;
sendRequest.ensureMinRequiredFee = false;
@ -262,9 +269,14 @@ public class BtcWalletService extends WalletService {
// add OP_RETURN output
resultTx.addOutput(new TransactionOutput(params, resultTx, Coin.ZERO, ScriptBuilder.createOpReturnScript(opReturnData).getProgram()));
numInputs = resultTx.getInputs().size();
txSizeWithUnsignedInputs = resultTx.bitcoinSerialize().length;
long estimatedFeeAsLong = txFeePerByte.multiply(txSizeWithUnsignedInputs + sigSizePerInput * numInputs).value;
numInputs = getNumInputs(resultTx);
numLegacyInputs = numInputs.first;
numSegwitInputs = numInputs.second;
txSizeWithUnsignedInputs = resultTx.getVsize();
long estimatedFeeAsLong = txFeePerByte.multiply(txSizeWithUnsignedInputs +
sigSizePerInput * numLegacyInputs +
sigSizePerInput * numSegwitInputs / 4).value;
// calculated fee must be inside of a tolerance range with tx fee
isFeeOutsideTolerance = Math.abs(resultTx.getFee().value - estimatedFeeAsLong) > 1000;
}
@ -338,7 +350,9 @@ public class BtcWalletService extends WalletService {
preferences.getIgnoreDustThreshold());
List<TransactionInput> preparedBsqTxInputs = preparedTx.getInputs();
List<TransactionOutput> preparedBsqTxOutputs = preparedTx.getOutputs();
int numInputs = preparedBsqTxInputs.size();
Tuple2<Integer, Integer> numInputs = getNumInputs(preparedTx);
int numLegacyInputs = numInputs.first;
int numSegwitInputs = numInputs.second;
Transaction resultTx = null;
boolean isFeeOutsideTolerance;
do {
@ -359,7 +373,9 @@ public class BtcWalletService extends WalletService {
// signInputs needs to be false as it would try to sign all inputs (BSQ inputs are not in this wallet)
sendRequest.signInputs = false;
sendRequest.fee = txFeePerByte.multiply(txSizeWithUnsignedInputs + sigSizePerInput * numInputs);
sendRequest.fee = txFeePerByte.multiply(txSizeWithUnsignedInputs +
sigSizePerInput * numLegacyInputs +
sigSizePerInput * numSegwitInputs / 4);
sendRequest.feePerKb = Coin.ZERO;
sendRequest.ensureMinRequiredFee = false;
@ -372,9 +388,13 @@ public class BtcWalletService extends WalletService {
// add OP_RETURN output
resultTx.addOutput(new TransactionOutput(params, resultTx, Coin.ZERO, ScriptBuilder.createOpReturnScript(opReturnData).getProgram()));
numInputs = resultTx.getInputs().size();
txSizeWithUnsignedInputs = resultTx.bitcoinSerialize().length;
final long estimatedFeeAsLong = txFeePerByte.multiply(txSizeWithUnsignedInputs + sigSizePerInput * numInputs).value;
numInputs = getNumInputs(resultTx);
numLegacyInputs = numInputs.first;
numSegwitInputs = numInputs.second;
txSizeWithUnsignedInputs = resultTx.getVsize();
final long estimatedFeeAsLong = txFeePerByte.multiply(txSizeWithUnsignedInputs +
sigSizePerInput * numLegacyInputs +
sigSizePerInput * numSegwitInputs / 4).value;
// calculated fee must be inside of a tolerance range with tx fee
isFeeOutsideTolerance = Math.abs(resultTx.getFee().value - estimatedFeeAsLong) > 1000;
}
@ -479,7 +499,10 @@ public class BtcWalletService extends WalletService {
preferences.getIgnoreDustThreshold());
List<TransactionInput> preparedBsqTxInputs = preparedBsqTx.getInputs();
List<TransactionOutput> preparedBsqTxOutputs = preparedBsqTx.getOutputs();
int numInputs = preparedBsqTxInputs.size() + 1; // We add 1 for the BTC fee input
// We don't know at this point what type the btc input would be (segwit/legacy).
// We use legacy to be on the safe side.
int numLegacyInputs = preparedBsqTxInputs.size() + 1; // We add 1 for the BTC fee input
int numSegwitInputs = 0;
Transaction resultTx = null;
boolean isFeeOutsideTolerance;
boolean opReturnIsOnlyOutput;
@ -508,7 +531,9 @@ public class BtcWalletService extends WalletService {
// signInputs needs to be false as it would try to sign all inputs (BSQ inputs are not in this wallet)
sendRequest.signInputs = false;
sendRequest.fee = txFeePerByte.multiply(txSizeWithUnsignedInputs + sigSizePerInput * numInputs);
sendRequest.fee = txFeePerByte.multiply(txSizeWithUnsignedInputs +
sigSizePerInput * numLegacyInputs +
sigSizePerInput * numSegwitInputs / 4);
sendRequest.feePerKb = Coin.ZERO;
sendRequest.ensureMinRequiredFee = false;
@ -528,15 +553,19 @@ public class BtcWalletService extends WalletService {
if (opReturnData != null)
resultTx.addOutput(new TransactionOutput(params, resultTx, Coin.ZERO, ScriptBuilder.createOpReturnScript(opReturnData).getProgram()));
numInputs = resultTx.getInputs().size();
txSizeWithUnsignedInputs = resultTx.bitcoinSerialize().length;
final long estimatedFeeAsLong = txFeePerByte.multiply(txSizeWithUnsignedInputs + sigSizePerInput * numInputs).value;
Tuple2<Integer, Integer> numInputs = getNumInputs(resultTx);
numLegacyInputs = numInputs.first;
numSegwitInputs = numInputs.second;
txSizeWithUnsignedInputs = resultTx.getVsize();
final long estimatedFeeAsLong = txFeePerByte.multiply(txSizeWithUnsignedInputs +
sigSizePerInput * numLegacyInputs +
sigSizePerInput * numSegwitInputs / 4).value;
// calculated fee must be inside of a tolerance range with tx fee
isFeeOutsideTolerance = Math.abs(resultTx.getFee().value - estimatedFeeAsLong) > 1000;
}
while (opReturnIsOnlyOutput ||
isFeeOutsideTolerance ||
resultTx.getFee().value < txFeePerByte.multiply(resultTx.bitcoinSerialize().length).value);
resultTx.getFee().value < txFeePerByte.multiply(resultTx.getVsize()).value);
// Sign all BTC inputs
signAllBtcInputs(preparedBsqTxInputs.size(), resultTx);
@ -548,6 +577,25 @@ public class BtcWalletService extends WalletService {
return resultTx;
}
private Tuple2<Integer, Integer> getNumInputs(Transaction tx) {
int numLegacyInputs = 0;
int numSegwitInputs = 0;
for (TransactionInput input : tx.getInputs()) {
TransactionOutput connectedOutput = input.getConnectedOutput();
if (connectedOutput == null || ScriptPattern.isP2PKH(connectedOutput.getScriptPubKey()) ||
ScriptPattern.isP2PK(connectedOutput.getScriptPubKey())) {
// If connectedOutput is null, we don't know here the input type. To avoid underpaying fees,
// we treat it as a legacy input which will result in a higher fee estimation.
numLegacyInputs++;
} else if (ScriptPattern.isP2WPKH(connectedOutput.getScriptPubKey())) {
numSegwitInputs++;
} else {
throw new IllegalArgumentException("Inputs should spend a P2PKH, P2PK or P2WPKH ouput");
}
}
return new Tuple2(numLegacyInputs, numSegwitInputs);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Commit tx
@ -809,7 +857,7 @@ public class BtcWalletService extends WalletService {
);
log.info("newTransaction no. of inputs " + newTransaction.getInputs().size());
log.info("newTransaction size in kB " + newTransaction.bitcoinSerialize().length / 1024);
log.info("newTransaction size in kB " + newTransaction.getVsize() / 1024);
if (!newTransaction.getInputs().isEmpty()) {
Coin amount = Coin.valueOf(newTransaction.getInputs().stream()
@ -839,7 +887,7 @@ public class BtcWalletService extends WalletService {
sendRequest.changeAddress = toAddress;
wallet.completeTx(sendRequest);
tx = sendRequest.tx;
txSize = tx.bitcoinSerialize().length;
txSize = tx.getVsize();
printTx("FeeEstimationTransaction", tx);
sendRequest.tx.getOutputs().forEach(o -> log.debug("Output value " + o.getValue().toFriendlyString()));
}
@ -947,7 +995,7 @@ public class BtcWalletService extends WalletService {
SendRequest sendRequest = getSendRequest(fromAddress, toAddress, amount, fee, aesKey, context);
wallet.completeTx(sendRequest);
tx = sendRequest.tx;
txSize = tx.bitcoinSerialize().length;
txSize = tx.getVsize();
printTx("FeeEstimationTransaction", tx);
}
while (feeEstimationNotSatisfied(counter, tx));
@ -992,11 +1040,13 @@ public class BtcWalletService extends WalletService {
counter++;
fee = txFeeForWithdrawalPerByte.multiply(txSize);
// We use a dummy address for the output
final String dummyReceiver = LegacyAddress.fromKey(params, new ECKey()).toBase58();
// We don't know here whether the output is segwit or not but we don't care too much because the size of
// a segwit ouput is just 3 byte smaller than the size of a legacy ouput.
final String dummyReceiver = SegwitAddress.fromKey(params, new ECKey()).toString();
SendRequest sendRequest = getSendRequestForMultipleAddresses(fromAddresses, dummyReceiver, amount, fee, null, aesKey);
wallet.completeTx(sendRequest);
tx = sendRequest.tx;
txSize = tx.bitcoinSerialize().length;
txSize = tx.getVsize();
printTx("FeeEstimationTransactionForMultipleAddresses", tx);
}
while (feeEstimationNotSatisfied(counter, tx));
@ -1012,7 +1062,7 @@ public class BtcWalletService extends WalletService {
}
private boolean feeEstimationNotSatisfied(int counter, Transaction tx) {
long targetFee = getTxFeeForWithdrawalPerByte().multiply(tx.bitcoinSerialize().length).value;
long targetFee = getTxFeeForWithdrawalPerByte().multiply(tx.getVsize()).value;
return counter < 10 &&
(tx.getFee().value < targetFee ||
tx.getFee().value - targetFee > 1000);
@ -1021,7 +1071,9 @@ public class BtcWalletService extends WalletService {
public int getEstimatedFeeTxSize(List<Coin> outputValues, Coin txFee)
throws InsufficientMoneyException, AddressFormatException {
Transaction transaction = new Transaction(params);
Address dummyAddress = LegacyAddress.fromKey(params, new ECKey());
// In reality txs have a mix of segwit/legacy ouputs, but we don't care too much because the size of
// a segwit ouput is just 3 byte smaller than the size of a legacy ouput.
Address dummyAddress = SegwitAddress.fromKey(params, new ECKey());
outputValues.forEach(outputValue -> transaction.addOutput(outputValue, dummyAddress));
SendRequest sendRequest = SendRequest.forTx(transaction);
@ -1034,7 +1086,7 @@ public class BtcWalletService extends WalletService {
sendRequest.ensureMinRequiredFee = false;
sendRequest.changeAddress = dummyAddress;
wallet.completeTx(sendRequest);
return transaction.bitcoinSerialize().length;
return transaction.getVsize();
}

View file

@ -195,7 +195,7 @@ public class MyBlindVoteListService implements PersistedDataHost, DaoStateListen
Coin blindVoteFee = BlindVoteConsensus.getFee(daoStateService, daoStateService.getChainHeight());
Transaction dummyTx = getBlindVoteTx(stake, blindVoteFee, new byte[22]);
Coin miningFee = dummyTx.getFee();
int txSize = dummyTx.bitcoinSerialize().length;
int txSize = dummyTx.getVsize();
return new Tuple2<>(miningFee, txSize);
}

View file

@ -95,7 +95,7 @@ public class LockupTxService {
throws InsufficientMoneyException, WalletException, TransactionVerificationException, IOException {
Transaction tx = getLockupTx(lockupAmount, lockTime, lockupReason, hash);
Coin miningFee = tx.getFee();
int txSize = tx.bitcoinSerialize().length;
int txSize = tx.getVsize();
return new Tuple2<>(miningFee, txSize);
}

View file

@ -93,7 +93,7 @@ public class UnlockTxService {
throws InsufficientMoneyException, WalletException, TransactionVerificationException {
Transaction tx = getUnlockTx(lockupTxId);
Coin miningFee = tx.getFee();
int txSize = tx.bitcoinSerialize().length;
int txSize = tx.getVsize();
return new Tuple2<>(miningFee, txSize);
}

View file

@ -45,14 +45,14 @@ public class TxFeeEstimationServiceTest {
int realTxSize;
Coin txFee;
initialEstimatedTxSize = 260;
initialEstimatedTxSize = 175;
txFeePerByte = Coin.valueOf(10);
realTxSize = 260;
realTxSize = 175;
txFee = txFeePerByte.multiply(initialEstimatedTxSize);
when(btcWalletService.getEstimatedFeeTxSize(outputValues, txFee)).thenReturn(realTxSize);
result = TxFeeEstimationService.getEstimatedTxSize(outputValues, initialEstimatedTxSize, txFeePerByte, btcWalletService);
assertEquals(260, result);
assertEquals(175, result);
}
// FIXME @Bernard could you have a look?
@ -67,16 +67,16 @@ public class TxFeeEstimationServiceTest {
int realTxSize;
Coin txFee;
initialEstimatedTxSize = 260;
initialEstimatedTxSize = 175;
txFeePerByte = Coin.valueOf(10);
realTxSize = 2600;
realTxSize = 1750;
txFee = txFeePerByte.multiply(initialEstimatedTxSize);
when(btcWalletService.getEstimatedFeeTxSize(outputValues, txFee)).thenReturn(realTxSize);
// repeated calls to getEstimatedFeeTxSize do not work (returns 0 at second call in loop which cause test to fail)
result = TxFeeEstimationService.getEstimatedTxSize(outputValues, initialEstimatedTxSize, txFeePerByte, btcWalletService);
assertEquals(2600, result);
assertEquals(1750, result);
}
// FIXME @Bernard could you have a look?
@ -91,14 +91,14 @@ public class TxFeeEstimationServiceTest {
int realTxSize;
Coin txFee;
initialEstimatedTxSize = 2600;
initialEstimatedTxSize = 1750;
txFeePerByte = Coin.valueOf(10);
realTxSize = 260;
realTxSize = 175;
txFee = txFeePerByte.multiply(initialEstimatedTxSize);
when(btcWalletService.getEstimatedFeeTxSize(outputValues, txFee)).thenReturn(realTxSize);
result = TxFeeEstimationService.getEstimatedTxSize(outputValues, initialEstimatedTxSize, txFeePerByte, btcWalletService);
assertEquals(260, result);
assertEquals(175, result);
}
@Test

View file

@ -189,7 +189,7 @@ public class AssetFeeView extends ActivatableView<GridPane, Void> implements Bsq
try {
Transaction transaction = assetService.payFee(selectedAsset, listingFee.value);
Coin miningFee = transaction.getFee();
int txSize = transaction.bitcoinSerialize().length;
int txSize = transaction.getVsize();
if (!DevEnv.isDevMode()) {
GUIUtil.showBsqFeeInfoPopup(listingFee, miningFee, txSize, bsqFormatter, btcFormatter,

View file

@ -172,7 +172,7 @@ public class ProofOfBurnView extends ActivatableView<GridPane, Void> implements
String preImageAsString = preImageTextField.getText();
Transaction transaction = proofOfBurnService.burn(preImageAsString, amount.value);
Coin miningFee = transaction.getFee();
int txSize = transaction.bitcoinSerialize().length;
int txSize = transaction.getVsize();
if (!DevEnv.isDevMode()) {
GUIUtil.showBsqFeeInfoPopup(amount, miningFee, txSize, bsqFormatter, btcFormatter,

View file

@ -283,7 +283,7 @@ public class MakeProposalView extends ActivatableView<GridPane, Void> implements
Proposal proposal = proposalWithTransaction.getProposal();
Transaction transaction = proposalWithTransaction.getTransaction();
Coin miningFee = transaction.getFee();
int txSize = transaction.bitcoinSerialize().length;
int txSize = transaction.getVsize();
Coin fee = daoFacade.getProposalFee(daoFacade.getChainHeight());
if (type.equals(ProposalType.BONDED_ROLE)) {

View file

@ -243,7 +243,7 @@ public class BsqSendView extends ActivatableView<GridPane, Void> implements BsqB
Transaction txWithBtcFee = btcWalletService.completePreparedSendBsqTx(preparedSendTx, true);
Transaction signedTx = bsqWalletService.signTx(txWithBtcFee);
Coin miningFee = signedTx.getFee();
int txSize = signedTx.bitcoinSerialize().length;
int txSize = signedTx.getVsize();
showPublishTxPopup(receiverAmount,
txWithBtcFee,
TxType.TRANSFER_BSQ,
@ -305,7 +305,7 @@ public class BsqSendView extends ActivatableView<GridPane, Void> implements BsqB
if (miningFee.getValue() >= receiverAmount.getValue())
GUIUtil.showWantToBurnBTCPopup(miningFee, receiverAmount, btcFormatter);
else {
int txSize = signedTx.bitcoinSerialize().length;
int txSize = signedTx.getVsize();
showPublishTxPopup(receiverAmount,
txWithBtcFee,
TxType.INVALID,

View file

@ -356,7 +356,7 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
}
if (areInputsValid()) {
int txSize = feeEstimationTransaction.bitcoinSerialize().length;
int txSize = feeEstimationTransaction.getVsize();
log.info("Fee for tx with size {}: {} " + Res.getBaseCurrencyCode() + "", txSize, fee.toPlainString());
if (receiverAmount.isPositive()) {

View file

@ -109,8 +109,8 @@ class TakeOfferDataModel extends OfferDataModel {
private PaymentAccount paymentAccount;
private boolean isTabSelected;
Price tradePrice;
// 260 kb is size of typical trade fee tx with 1 input but trade tx (deposit and payout) are larger so we adjust to 320
private int feeTxSize = 320;
// Use an average of a typical trade fee tx with 1 input, deposit tx and payout tx.
private int feeTxSize = 192; // (175+233+169)/3
private boolean freezeFee;
private Coin txFeePerByteFromFeeService;
@ -213,13 +213,13 @@ class TakeOfferDataModel extends OfferDataModel {
// multiple batch-signed payout tx with different fees might be an option but RBF is not supported yet in BitcoinJ
// and batched txs would add more complexity to the trade protocol.
// A typical trade fee tx has about 260 bytes (if one input). The trade txs has about 336-414 bytes.
// We use 320 as a average value.
// A typical trade fee tx has about 175 bytes (if one input). The trade txs has about 169-263 bytes.
// We use 192 as average value.
// trade fee tx: 260 bytes (1 input)
// deposit tx: 336 bytes (1 MS output+ OP_RETURN) - 414 bytes (1 MS output + OP_RETURN + change in case of smaller trade amount)
// payout tx: 371 bytes
// disputed payout tx: 408 bytes
// trade fee tx: 175 bytes (1 input)
// deposit tx: 233 bytes (1 MS output+ OP_RETURN) - 263 bytes (1 MS output + OP_RETURN + change in case of smaller trade amount)
// payout tx: 169 bytes
// disputed payout tx: 139 bytes
// Set the default values (in rare cases if the fee request was not done yet we get the hard coded default values)
// But the "take offer" happens usually after that so we should have already the value from the estimation service.
@ -351,22 +351,22 @@ class TakeOfferDataModel extends OfferDataModel {
// there more deterministic.
// The trade fee tx can be in the worst case very large if there are many inputs so if we take that tx alone
// for the fee estimation we would overpay a lot.
// On the other side if we have the best case of a 1 input tx fee tx then it is only 260 bytes but the
// other 2 txs are larger (320 and 380 bytes) and would get a lower fee/byte as intended.
// On the other side if we have the best case of a 1 input tx fee tx then it is only 175 bytes but the
// other 2 txs are different (233 and 169 bytes) and may get a lower fee/byte as intended.
// We apply following model to not overpay too much but be on the safe side as well.
// We sum the taker fee tx and the deposit tx together as it can be assumed that both be in the same block and
// as they are dependent txs the miner will pick both if the fee in total is good enough.
// We make sure that the fee is sufficient to meet our intended fee/byte for the larger payout tx with 380 bytes.
// We make sure that the fee is sufficient to meet our intended fee/byte for the larger deposit tx with 233 bytes.
Tuple2<Coin, Integer> estimatedFeeAndTxSize = txFeeEstimationService.getEstimatedFeeAndTxSizeForTaker(fundsNeededForTrade,
getTakerFee());
txFeeFromFeeService = estimatedFeeAndTxSize.first;
feeTxSize = estimatedFeeAndTxSize.second;
} else {
feeTxSize = 380;
feeTxSize = 233;
txFeeFromFeeService = txFeePerByteFromFeeService.multiply(feeTxSize);
log.info("We cannot do the fee estimation because there are no funds in the wallet.\nThis is expected " +
"if the user has not funded their wallet yet.\n" +
"In that case we use an estimated tx size of 380 bytes.\n" +
"In that case we use an estimated tx size of 233 bytes.\n" +
"txFee based on estimated size of {} bytes. feeTxSize = {} bytes. Actual tx size = {} bytes. TxFee is {} ({} sat/byte)",
feeTxSize, feeTxSize, txSize, txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerByte());
}
@ -531,7 +531,7 @@ class TakeOfferDataModel extends OfferDataModel {
// With that we avoid that we overpay in case that the trade fee has many inputs and we would apply that fee for the
// other 2 txs as well. We still might overpay a bit for the payout tx.
private int getAverageSize(int txSize) {
return (txSize + 320) / 2;
return (txSize + 233) / 2;
}
private Coin getTxFeeBySize(int sizeInBytes) {
@ -606,7 +606,7 @@ class TakeOfferDataModel extends OfferDataModel {
// Unfortunately we cannot change that to the correct fees as it would break backward compatibility
// We still might find a way with offer version or app version checks so lets keep that commented out
// code as that shows how it should be.
return txFeeFromFeeService; //feeService.getTxFee(320);
return txFeeFromFeeService; //feeService.getTxFee(233);
}
private Coin getTxFeeForPayoutTx() {
@ -614,7 +614,7 @@ class TakeOfferDataModel extends OfferDataModel {
// Unfortunately we cannot change that to the correct fees as it would break backward compatibility
// We still might find a way with offer version or app version checks so lets keep that commented out
// code as that shows how it should be.
return txFeeFromFeeService; //feeService.getTxFee(380);
return txFeeFromFeeService; //feeService.getTxFee(169);
}
public AddressEntry getAddressEntry() {

View file

@ -205,7 +205,7 @@ public class BuyerStep4View extends TradeStepView {
validateWithdrawAddress();
} else if (Restrictions.isAboveDust(receiverAmount)) {
CoinFormatter formatter = model.btcFormatter;
int txSize = feeEstimationTransaction.bitcoinSerialize().length;
int txSize = feeEstimationTransaction.getVsize();
double feePerByte = CoinUtil.getFeePerByte(fee, txSize);
double kb = txSize / 1000d;
String recAmount = formatter.formatCoinWithCode(receiverAmount);

View file

@ -23,7 +23,7 @@ dependencyVerification {
'com.github.bisq-network.netlayer:tor.external:a3606a592d6b6caa6a2fb7db224eaf43c6874c6730da4815bd37ad686b283dcb',
'com.github.bisq-network.netlayer:tor.native:b15aba7fe987185037791c7ec7c529cb001b90d723d047d54aab87aceb3b3d45',
'com.github.bisq-network.netlayer:tor:a974190aa3a031067ccd1dda28a3ae58cad14060792299d86ea38a05fb21afc5',
'com.github.bisq-network.bitcoinj:bitcoinj-core:8af7faa2155feff5afd1fa0fcea6fe7f7fa0d7ee977bdc648d1e73f3dcf2c754',
'com.github.bisq-network:bitcoinj:804f587a44b1ce9cd9b8dd1848fc911329634163b7905bafb86f502a3d08264c',
'com.github.cd2357.tor-binary:tor-binary-geoip:ae27b6aca1a3a50a046eb11e38202b6d21c2fcd2b8643bbeb5ea85e065fbc1be',
'com.github.cd2357.tor-binary:tor-binary-linux32:7b5d6770aa442ef6d235e8a9bfbaa7c62560690f9fe69ff03c7a752eae84f7dc',
'com.github.cd2357.tor-binary:tor-binary-linux64:24111fa35027599a750b0176392dc1e9417d919414396d1b221ac2e707eaba76',