Rename "tx size" to "tx vsize"

This commit is contained in:
Oscar Guindzberg 2020-11-05 16:43:57 -03:00
parent 52f1d37863
commit c4131c7398
No known key found for this signature in database
GPG key ID: 209796BF2E1D4F75
35 changed files with 344 additions and 346 deletions

View file

@ -72,7 +72,7 @@ public enum BaseCurrencyNetwork {
return "BTC_REGTEST".equals(name());
}
public long getDefaultMinFeePerByte() {
public long getDefaultMinFeePerVbyte() {
return 2;
}
}

View file

@ -117,7 +117,7 @@ public class WalletAppSetup {
String result;
if (exception == null) {
double percentage = (double) downloadPercentage;
long fees = feeService.getTxFeePerByte().longValue();
long fees = feeService.getTxFeePerVbyte().longValue();
btcSyncProgress.set(percentage);
if (percentage == 1) {
String feeRate = Res.get("mainView.footer.btcFeeRate", fees);

View file

@ -55,8 +55,8 @@ public class TxFeeEstimationService {
// 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;
public static int TYPICAL_TX_WITH_1_INPUT_VSIZE = 175;
private static int DEPOSIT_TX_VSIZE = 233;
private static int BSQ_INPUT_INCREASE = 150;
private static int MAX_ITERATIONS = 10;
@ -75,8 +75,8 @@ public class TxFeeEstimationService {
this.preferences = preferences;
}
public Tuple2<Coin, Integer> getEstimatedFeeAndTxSizeForTaker(Coin fundsNeededForTrade, Coin tradeFee) {
return getEstimatedFeeAndTxSize(true,
public Tuple2<Coin, Integer> getEstimatedFeeAndTxVsizeForTaker(Coin fundsNeededForTrade, Coin tradeFee) {
return getEstimatedFeeAndTxVsize(true,
fundsNeededForTrade,
tradeFee,
feeService,
@ -84,9 +84,9 @@ public class TxFeeEstimationService {
preferences);
}
public Tuple2<Coin, Integer> getEstimatedFeeAndTxSizeForMaker(Coin reservedFundsForOffer,
Coin tradeFee) {
return getEstimatedFeeAndTxSize(false,
public Tuple2<Coin, Integer> getEstimatedFeeAndTxVsizeForMaker(Coin reservedFundsForOffer,
Coin tradeFee) {
return getEstimatedFeeAndTxVsize(false,
reservedFundsForOffer,
tradeFee,
feeService,
@ -94,120 +94,120 @@ public class TxFeeEstimationService {
preferences);
}
private Tuple2<Coin, Integer> getEstimatedFeeAndTxSize(boolean isTaker,
Coin amount,
Coin tradeFee,
FeeService feeService,
BtcWalletService btcWalletService,
Preferences preferences) {
Coin txFeePerByte = feeService.getTxFeePerByte();
// We start with min taker fee size of 175
int estimatedTxSize = TYPICAL_TX_WITH_1_INPUT_SIZE;
private Tuple2<Coin, Integer> getEstimatedFeeAndTxVsize(boolean isTaker,
Coin amount,
Coin tradeFee,
FeeService feeService,
BtcWalletService btcWalletService,
Preferences preferences) {
Coin txFeePerVbyte = feeService.getTxFeePerVbyte();
// We start with min taker fee vsize of 175
int estimatedTxVsize = TYPICAL_TX_WITH_1_INPUT_VSIZE;
try {
estimatedTxSize = getEstimatedTxSize(List.of(tradeFee, amount), estimatedTxSize, txFeePerByte, btcWalletService);
estimatedTxVsize = getEstimatedTxVsize(List.of(tradeFee, amount), estimatedTxVsize, txFeePerVbyte, btcWalletService);
} catch (InsufficientMoneyException e) {
if (isTaker) {
// 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;
// If we cannot do the estimation, we use the vsize o the largest of our txs which is the deposit tx.
estimatedTxVsize = DEPOSIT_TX_VSIZE;
}
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);
"if the user pays from an external wallet. In that case we use an estimated tx vsize of {} vbytes.", estimatedTxVsize);
}
if (!preferences.isPayFeeInBtc()) {
// If we pay the fee in BSQ we have one input more which adds about 150 bytes
// TODO: Clarify if there is always just one additional input or if there can be more.
estimatedTxSize += BSQ_INPUT_INCREASE;
estimatedTxVsize += BSQ_INPUT_INCREASE;
}
Coin txFee;
int size;
int vsize;
if (isTaker) {
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 (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);
int averageVsize = (estimatedTxVsize + DEPOSIT_TX_VSIZE) / 2; // deposit tx has about 233 vbytes
// We use at least the vsize of the deposit tx to not underpay it.
vsize = Math.max(DEPOSIT_TX_VSIZE, averageVsize);
txFee = txFeePerVbyte.multiply(vsize);
log.info("Fee estimation resulted in a tx vsize of {} vbytes.\n" +
"We use an average between the taker fee tx and the deposit tx (233 vbytes) which results in {} vbytes.\n" +
"The deposit tx has 233 vbytes, we use that as our min value. Vsize for fee calculation is {} vbytes.\n" +
"The tx fee of {} Sat", estimatedTxVsize, averageVsize, vsize, txFee.value);
} else {
size = estimatedTxSize;
txFee = txFeePerByte.multiply(size);
log.info("Fee estimation resulted in a tx size of {} bytes and a tx fee of {} Sat.", size, txFee.value);
vsize = estimatedTxVsize;
txFee = txFeePerVbyte.multiply(vsize);
log.info("Fee estimation resulted in a tx vsize of {} vbytes and a tx fee of {} Sat.", vsize, txFee.value);
}
return new Tuple2<>(txFee, size);
return new Tuple2<>(txFee, vsize);
}
public Tuple2<Coin, Integer> getEstimatedFeeAndTxSize(Coin amount,
FeeService feeService,
BtcWalletService btcWalletService) {
Coin txFeePerByte = feeService.getTxFeePerByte();
// We start with min taker fee size of 175
int estimatedTxSize = TYPICAL_TX_WITH_1_INPUT_SIZE;
public Tuple2<Coin, Integer> getEstimatedFeeAndTxVsize(Coin amount,
FeeService feeService,
BtcWalletService btcWalletService) {
Coin txFeePerVbyte = feeService.getTxFeePerVbyte();
// We start with min taker fee vsize of 175
int estimatedTxVsize = TYPICAL_TX_WITH_1_INPUT_VSIZE;
try {
estimatedTxSize = getEstimatedTxSize(List.of(amount), estimatedTxSize, txFeePerByte, btcWalletService);
estimatedTxVsize = getEstimatedTxVsize(List.of(amount), estimatedTxVsize, txFeePerVbyte, btcWalletService);
} catch (InsufficientMoneyException e) {
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);
"if the user pays from an external wallet. In that case we use an estimated tx vsize of {} vbytes.", estimatedTxVsize);
}
Coin txFee = txFeePerByte.multiply(estimatedTxSize);
log.info("Fee estimation resulted in a tx size of {} bytes and a tx fee of {} Sat.", estimatedTxSize, txFee.value);
Coin txFee = txFeePerVbyte.multiply(estimatedTxVsize);
log.info("Fee estimation resulted in a tx vsize of {} vbytes and a tx fee of {} Sat.", estimatedTxVsize, txFee.value);
return new Tuple2<>(txFee, estimatedTxSize);
return new Tuple2<>(txFee, estimatedTxVsize);
}
// We start with the initialEstimatedTxSize for a tx with 1 input (175) bytes and get from BitcoinJ a tx back which
// We start with the initialEstimatedTxVsize for a tx with 1 input (175) vbytes 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
// that tolerance we use that tx size for our fee estimation, if not (if there has been more then 1 inputs) we
// apply the new fee based on the reported tx size and request again from BitcoinJ to fill that tx with the inputs
// the assumption that we only need 1 input. Once we receive back the real tx vsize from the tx BitcoinJ has created
// with the required inputs we compare if the vsize is not more then 20% different to our assumed tx vsize. If we are inside
// that tolerance we use that tx vsize for our fee estimation, if not (if there has been more then 1 inputs) we
// apply the new fee based on the reported tx vsize and request again from BitcoinJ to fill that tx with the inputs
// to be sufficiently funded. The algorithm how BitcoinJ selects utxos is complex and contains several aspects
// (minimize fee, don't create too many tiny utxos,...). We treat that algorithm as an unknown and it is not
// guaranteed that there are more inputs required if we increase the fee (it could be that there is a better
// selection of inputs chosen if we have increased the fee and therefore less inputs and smaller tx size). As the increased fee might
// selection of inputs chosen if we have increased the fee and therefore less inputs and smaller tx vsize). As the increased fee might
// change the number of inputs we need to repeat that process until we are inside of a certain tolerance. To avoid
// potential endless loops we add a counter (we use 10, usually it takes just very few iterations).
// Worst case would be that the last size we got reported is > 20% off to
// the real tx size but as fee estimation is anyway a educated guess in the best case we don't worry too much.
// Worst case would be that the last vsize we got reported is > 20% off to
// the real tx vsize but as fee estimation is anyway a educated guess in the best case we don't worry too much.
// If we have underpaid the tx might take longer to get confirmed.
@VisibleForTesting
static int getEstimatedTxSize(List<Coin> outputValues,
int initialEstimatedTxSize,
Coin txFeePerByte,
BtcWalletService btcWalletService)
static int getEstimatedTxVsize(List<Coin> outputValues,
int initialEstimatedTxVsize,
Coin txFeePerVbyte,
BtcWalletService btcWalletService)
throws InsufficientMoneyException {
boolean isInTolerance;
int estimatedTxSize = initialEstimatedTxSize;
int realTxSize;
int estimatedTxVsize = initialEstimatedTxVsize;
int realTxVsize;
int counter = 0;
do {
Coin txFee = txFeePerByte.multiply(estimatedTxSize);
realTxSize = btcWalletService.getEstimatedFeeTxSize(outputValues, txFee);
isInTolerance = isInTolerance(estimatedTxSize, realTxSize, 0.2);
Coin txFee = txFeePerVbyte.multiply(estimatedTxVsize);
realTxVsize = btcWalletService.getEstimatedFeeTxVsize(outputValues, txFee);
isInTolerance = isInTolerance(estimatedTxVsize, realTxVsize, 0.2);
if (!isInTolerance) {
estimatedTxSize = realTxSize;
estimatedTxVsize = realTxVsize;
}
counter++;
}
while (!isInTolerance && counter < MAX_ITERATIONS);
if (!isInTolerance) {
log.warn("We could not find a tx which satisfies our tolerance requirement of 20%. " +
"realTxSize={}, estimatedTxSize={}",
realTxSize, estimatedTxSize);
"realTxVsize={}, estimatedTxVsize={}",
realTxVsize, estimatedTxVsize);
}
return estimatedTxSize;
return estimatedTxVsize;
}
@VisibleForTesting
static boolean isInTolerance(int estimatedSize, int txSize, double tolerance) {
checkArgument(estimatedSize > 0, "estimatedSize must be positive");
checkArgument(txSize > 0, "txSize must be positive");
static boolean isInTolerance(int estimatedVsize, int txVsize, double tolerance) {
checkArgument(estimatedVsize > 0, "estimatedVsize must be positive");
checkArgument(txVsize > 0, "txVsize must be positive");
checkArgument(tolerance > 0, "tolerance must be positive");
double deviation = Math.abs(1 - ((double) estimatedSize / (double) txSize));
double deviation = Math.abs(1 - ((double) estimatedVsize / (double) txVsize));
return deviation <= tolerance;
}
}

View file

@ -220,8 +220,8 @@ public class BtcWalletService extends WalletService {
// estimated size of input sig
int sigSizePerInput = 106;
// typical size for a tx with 3 inputs
int txSizeWithUnsignedInputs = 300;
Coin txFeePerByte = feeService.getTxFeePerByte();
int txVsizeWithUnsignedInputs = 300;
Coin txFeePerVbyte = feeService.getTxFeePerVbyte();
Address changeAddress = getFreshAddressEntry().getAddress();
checkNotNull(changeAddress, "changeAddress must not be null");
@ -253,7 +253,7 @@ 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 +
sendRequest.fee = txFeePerVbyte.multiply(txVsizeWithUnsignedInputs +
sigSizePerInput * numLegacyInputs +
sigSizePerInput * numSegwitInputs / 4);
@ -272,8 +272,8 @@ public class BtcWalletService extends WalletService {
numInputs = getNumInputs(resultTx);
numLegacyInputs = numInputs.first;
numSegwitInputs = numInputs.second;
txSizeWithUnsignedInputs = resultTx.getVsize();
long estimatedFeeAsLong = txFeePerByte.multiply(txSizeWithUnsignedInputs +
txVsizeWithUnsignedInputs = resultTx.getVsize();
long estimatedFeeAsLong = txFeePerVbyte.multiply(txVsizeWithUnsignedInputs +
sigSizePerInput * numLegacyInputs +
sigSizePerInput * numSegwitInputs / 4).value;
@ -340,8 +340,8 @@ public class BtcWalletService extends WalletService {
// estimated size of input sig
int sigSizePerInput = 106;
// typical size for a tx with 3 inputs
int txSizeWithUnsignedInputs = 300;
Coin txFeePerByte = feeService.getTxFeePerByte();
int txVsizeWithUnsignedInputs = 300;
Coin txFeePerVbyte = feeService.getTxFeePerVbyte();
Address changeAddress = getFreshAddressEntry().getAddress();
checkNotNull(changeAddress, "changeAddress must not be null");
@ -373,7 +373,7 @@ 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 +
sendRequest.fee = txFeePerVbyte.multiply(txVsizeWithUnsignedInputs +
sigSizePerInput * numLegacyInputs +
sigSizePerInput * numSegwitInputs / 4);
sendRequest.feePerKb = Coin.ZERO;
@ -391,8 +391,8 @@ public class BtcWalletService extends WalletService {
numInputs = getNumInputs(resultTx);
numLegacyInputs = numInputs.first;
numSegwitInputs = numInputs.second;
txSizeWithUnsignedInputs = resultTx.getVsize();
final long estimatedFeeAsLong = txFeePerByte.multiply(txSizeWithUnsignedInputs +
txVsizeWithUnsignedInputs = resultTx.getVsize();
final long estimatedFeeAsLong = txFeePerVbyte.multiply(txVsizeWithUnsignedInputs +
sigSizePerInput * numLegacyInputs +
sigSizePerInput * numSegwitInputs / 4).value;
// calculated fee must be inside of a tolerance range with tx fee
@ -486,9 +486,9 @@ public class BtcWalletService extends WalletService {
// estimated size of input sig
int sigSizePerInput = 106;
// typical size for a tx with 2 inputs
int txSizeWithUnsignedInputs = 203;
int txVsizeWithUnsignedInputs = 203;
// If useCustomTxFee we allow overriding the estimated fee from preferences
Coin txFeePerByte = useCustomTxFee ? getTxFeeForWithdrawalPerByte() : feeService.getTxFeePerByte();
Coin txFeePerVbyte = useCustomTxFee ? getTxFeeForWithdrawalPerVbyte() : feeService.getTxFeePerVbyte();
// In case there are no change outputs we force a change by adding min dust to the BTC input
Coin forcedChangeValue = Coin.ZERO;
@ -531,7 +531,7 @@ 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 +
sendRequest.fee = txFeePerVbyte.multiply(txVsizeWithUnsignedInputs +
sigSizePerInput * numLegacyInputs +
sigSizePerInput * numSegwitInputs / 4);
sendRequest.feePerKb = Coin.ZERO;
@ -556,8 +556,8 @@ public class BtcWalletService extends WalletService {
Tuple2<Integer, Integer> numInputs = getNumInputs(resultTx);
numLegacyInputs = numInputs.first;
numSegwitInputs = numInputs.second;
txSizeWithUnsignedInputs = resultTx.getVsize();
final long estimatedFeeAsLong = txFeePerByte.multiply(txSizeWithUnsignedInputs +
txVsizeWithUnsignedInputs = resultTx.getVsize();
final long estimatedFeeAsLong = txFeePerVbyte.multiply(txVsizeWithUnsignedInputs +
sigSizePerInput * numLegacyInputs +
sigSizePerInput * numSegwitInputs / 4).value;
// calculated fee must be inside of a tolerance range with tx fee
@ -565,7 +565,7 @@ public class BtcWalletService extends WalletService {
}
while (opReturnIsOnlyOutput ||
isFeeOutsideTolerance ||
resultTx.getFee().value < txFeePerByte.multiply(resultTx.getVsize()).value);
resultTx.getFee().value < txFeePerVbyte.multiply(resultTx.getVsize()).value);
// Sign all BTC inputs
signAllBtcInputs(preparedBsqTxInputs.size(), resultTx);
@ -857,7 +857,7 @@ public class BtcWalletService extends WalletService {
);
log.info("newTransaction no. of inputs " + newTransaction.getInputs().size());
log.info("newTransaction size in kB " + newTransaction.getVsize() / 1024);
log.info("newTransaction vsize in vkB " + newTransaction.getVsize() / 1024);
if (!newTransaction.getInputs().isEmpty()) {
Coin amount = Coin.valueOf(newTransaction.getInputs().stream()
@ -868,13 +868,13 @@ public class BtcWalletService extends WalletService {
try {
Coin fee;
int counter = 0;
int txSize = 0;
int txVsize = 0;
Transaction tx;
SendRequest sendRequest;
Coin txFeeForWithdrawalPerByte = getTxFeeForWithdrawalPerByte();
Coin txFeeForWithdrawalPerVbyte = getTxFeeForWithdrawalPerVbyte();
do {
counter++;
fee = txFeeForWithdrawalPerByte.multiply(txSize);
fee = txFeeForWithdrawalPerVbyte.multiply(txVsize);
newTransaction.clearOutputs();
newTransaction.addOutput(amount.subtract(fee), toAddress);
@ -887,7 +887,7 @@ public class BtcWalletService extends WalletService {
sendRequest.changeAddress = toAddress;
wallet.completeTx(sendRequest);
tx = sendRequest.tx;
txSize = tx.getVsize();
txVsize = tx.getVsize();
printTx("FeeEstimationTransaction", tx);
sendRequest.tx.getOutputs().forEach(o -> log.debug("Output value " + o.getValue().toFriendlyString()));
}
@ -986,16 +986,16 @@ public class BtcWalletService extends WalletService {
try {
Coin fee;
int counter = 0;
int txSize = 0;
int txVsize = 0;
Transaction tx;
Coin txFeeForWithdrawalPerByte = getTxFeeForWithdrawalPerByte();
Coin txFeeForWithdrawalPerVbyte = getTxFeeForWithdrawalPerVbyte();
do {
counter++;
fee = txFeeForWithdrawalPerByte.multiply(txSize);
fee = txFeeForWithdrawalPerVbyte.multiply(txVsize);
SendRequest sendRequest = getSendRequest(fromAddress, toAddress, amount, fee, aesKey, context);
wallet.completeTx(sendRequest);
tx = sendRequest.tx;
txSize = tx.getVsize();
txVsize = tx.getVsize();
printTx("FeeEstimationTransaction", tx);
}
while (feeEstimationNotSatisfied(counter, tx));
@ -1033,12 +1033,12 @@ public class BtcWalletService extends WalletService {
try {
Coin fee;
int counter = 0;
int txSize = 0;
int txVsize = 0;
Transaction tx;
Coin txFeeForWithdrawalPerByte = getTxFeeForWithdrawalPerByte();
Coin txFeeForWithdrawalPerVbyte = getTxFeeForWithdrawalPerVbyte();
do {
counter++;
fee = txFeeForWithdrawalPerByte.multiply(txSize);
fee = txFeeForWithdrawalPerVbyte.multiply(txVsize);
// We use a dummy address for the output
// 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.
@ -1046,7 +1046,7 @@ public class BtcWalletService extends WalletService {
SendRequest sendRequest = getSendRequestForMultipleAddresses(fromAddresses, dummyReceiver, amount, fee, null, aesKey);
wallet.completeTx(sendRequest);
tx = sendRequest.tx;
txSize = tx.getVsize();
txVsize = tx.getVsize();
printTx("FeeEstimationTransactionForMultipleAddresses", tx);
}
while (feeEstimationNotSatisfied(counter, tx));
@ -1062,13 +1062,13 @@ public class BtcWalletService extends WalletService {
}
private boolean feeEstimationNotSatisfied(int counter, Transaction tx) {
long targetFee = getTxFeeForWithdrawalPerByte().multiply(tx.getVsize()).value;
long targetFee = getTxFeeForWithdrawalPerVbyte().multiply(tx.getVsize()).value;
return counter < 10 &&
(tx.getFee().value < targetFee ||
tx.getFee().value - targetFee > 1000);
}
public int getEstimatedFeeTxSize(List<Coin> outputValues, Coin txFee)
public int getEstimatedFeeTxVsize(List<Coin> outputValues, Coin txFee)
throws InsufficientMoneyException, AddressFormatException {
Transaction transaction = new Transaction(params);
// In reality txs have a mix of segwit/legacy ouputs, but we don't care too much because the size of

View file

@ -475,10 +475,10 @@ public abstract class WalletService {
return getBalanceForAddress(getAddressFromOutput(output));
}
public Coin getTxFeeForWithdrawalPerByte() {
public Coin getTxFeeForWithdrawalPerVbyte() {
Coin fee = (preferences.isUseCustomWithdrawalTxFee()) ?
Coin.valueOf(preferences.getWithdrawalTxFeeInBytes()) :
feeService.getTxFeePerByte();
Coin.valueOf(preferences.getWithdrawalTxFeeInVbytes()) :
feeService.getTxFeePerVbyte();
log.info("tx fee = " + fee.toFriendlyString());
return fee;
}
@ -517,7 +517,7 @@ public abstract class WalletService {
throws InsufficientMoneyException, AddressFormatException {
SendRequest sendRequest = SendRequest.emptyWallet(Address.fromString(params, toAddress));
sendRequest.fee = Coin.ZERO;
sendRequest.feePerKb = getTxFeeForWithdrawalPerByte().multiply(1000);
sendRequest.feePerKb = getTxFeeForWithdrawalPerVbyte().multiply(1000);
sendRequest.aesKey = aesKey;
Wallet.SendResult sendResult = wallet.sendCoins(sendRequest);
printTx("empty btc wallet", sendResult.tx);

View file

@ -382,9 +382,9 @@ public class DaoFacade implements DaoSetupService {
return BlindVoteConsensus.getFee(daoStateService, daoStateService.getChainHeight());
}
public Tuple2<Coin, Integer> getBlindVoteMiningFeeAndTxSize(Coin stake)
public Tuple2<Coin, Integer> getBlindVoteMiningFeeAndTxVsize(Coin stake)
throws WalletException, InsufficientMoneyException, TransactionVerificationException {
return myBlindVoteListService.getMiningFeeAndTxSize(stake);
return myBlindVoteListService.getMiningFeeAndTxVsize(stake);
}
// Publish blindVote tx and broadcast blindVote to p2p network and store to blindVoteList.
@ -532,12 +532,12 @@ public class DaoFacade implements DaoSetupService {
lockupTxService.publishLockupTx(lockupAmount, lockTime, lockupReason, hash, resultHandler, exceptionHandler);
}
public Tuple2<Coin, Integer> getLockupTxMiningFeeAndTxSize(Coin lockupAmount,
int lockTime,
LockupReason lockupReason,
byte[] hash)
public Tuple2<Coin, Integer> getLockupTxMiningFeeAndTxVsize(Coin lockupAmount,
int lockTime,
LockupReason lockupReason,
byte[] hash)
throws InsufficientMoneyException, IOException, TransactionVerificationException, WalletException {
return lockupTxService.getMiningFeeAndTxSize(lockupAmount, lockTime, lockupReason, hash);
return lockupTxService.getMiningFeeAndTxVsize(lockupAmount, lockTime, lockupReason, hash);
}
public void publishUnlockTx(String lockupTxId, Consumer<String> resultHandler,
@ -545,9 +545,9 @@ public class DaoFacade implements DaoSetupService {
unlockTxService.publishUnlockTx(lockupTxId, resultHandler, exceptionHandler);
}
public Tuple2<Coin, Integer> getUnlockTxMiningFeeAndTxSize(String lockupTxId)
public Tuple2<Coin, Integer> getUnlockTxMiningFeeAndTxVsize(String lockupTxId)
throws InsufficientMoneyException, TransactionVerificationException, WalletException {
return unlockTxService.getMiningFeeAndTxSize(lockupTxId);
return unlockTxService.getMiningFeeAndTxVsize(lockupTxId);
}
public long getTotalLockupAmount() {

View file

@ -189,14 +189,14 @@ public class MyBlindVoteListService implements PersistedDataHost, DaoStateListen
// API
///////////////////////////////////////////////////////////////////////////////////////////
public Tuple2<Coin, Integer> getMiningFeeAndTxSize(Coin stake)
public Tuple2<Coin, Integer> getMiningFeeAndTxVsize(Coin stake)
throws InsufficientMoneyException, WalletException, TransactionVerificationException {
// We set dummy opReturn data
Coin blindVoteFee = BlindVoteConsensus.getFee(daoStateService, daoStateService.getChainHeight());
Transaction dummyTx = getBlindVoteTx(stake, blindVoteFee, new byte[22]);
Coin miningFee = dummyTx.getFee();
int txSize = dummyTx.getVsize();
return new Tuple2<>(miningFee, txSize);
int txVsize = dummyTx.getVsize();
return new Tuple2<>(miningFee, txVsize);
}
public void publishBlindVote(Coin stake, ResultHandler resultHandler, ExceptionHandler exceptionHandler) {

View file

@ -91,12 +91,12 @@ public class LockupTxService {
}
}
public Tuple2<Coin, Integer> getMiningFeeAndTxSize(Coin lockupAmount, int lockTime, LockupReason lockupReason, byte[] hash)
public Tuple2<Coin, Integer> getMiningFeeAndTxVsize(Coin lockupAmount, int lockTime, LockupReason lockupReason, byte[] hash)
throws InsufficientMoneyException, WalletException, TransactionVerificationException, IOException {
Transaction tx = getLockupTx(lockupAmount, lockTime, lockupReason, hash);
Coin miningFee = tx.getFee();
int txSize = tx.getVsize();
return new Tuple2<>(miningFee, txSize);
int txVsize = tx.getVsize();
return new Tuple2<>(miningFee, txVsize);
}
private Transaction getLockupTx(Coin lockupAmount, int lockTime, LockupReason lockupReason, byte[] hash)

View file

@ -89,12 +89,12 @@ public class UnlockTxService {
}
}
public Tuple2<Coin, Integer> getMiningFeeAndTxSize(String lockupTxId)
public Tuple2<Coin, Integer> getMiningFeeAndTxVsize(String lockupTxId)
throws InsufficientMoneyException, WalletException, TransactionVerificationException {
Transaction tx = getUnlockTx(lockupTxId);
Coin miningFee = tx.getFee();
int txSize = tx.getVsize();
return new Tuple2<>(miningFee, txSize);
int txVsize = tx.getVsize();
return new Tuple2<>(miningFee, txVsize);
}
private Transaction getUnlockTx(String lockupTxId)

View file

@ -163,7 +163,7 @@ public class CreateOfferService {
String bankId = PaymentAccountUtil.getBankId(paymentAccount);
List<String> acceptedBanks = PaymentAccountUtil.getAcceptedBanks(paymentAccount);
double sellerSecurityDeposit = getSellerSecurityDepositAsDouble(buyerSecurityDepositAsDouble);
Coin txFeeFromFeeService = getEstimatedFeeAndTxSize(amount, direction, buyerSecurityDepositAsDouble, sellerSecurityDeposit).first;
Coin txFeeFromFeeService = getEstimatedFeeAndTxVsize(amount, direction, buyerSecurityDepositAsDouble, sellerSecurityDeposit).first;
Coin txFeeToUse = txFee.isPositive() ? txFee : txFeeFromFeeService;
Coin makerFeeAsCoin = offerUtil.getMakerFee(amount);
boolean isCurrencyForMakerFeeBtc = offerUtil.isCurrencyForMakerFeeBtc(amount);
@ -233,15 +233,15 @@ public class CreateOfferService {
return offer;
}
public Tuple2<Coin, Integer> getEstimatedFeeAndTxSize(Coin amount,
OfferPayload.Direction direction,
double buyerSecurityDeposit,
double sellerSecurityDeposit) {
public Tuple2<Coin, Integer> getEstimatedFeeAndTxVsize(Coin amount,
OfferPayload.Direction direction,
double buyerSecurityDeposit,
double sellerSecurityDeposit) {
Coin reservedFundsForOffer = getReservedFundsForOffer(direction,
amount,
buyerSecurityDeposit,
sellerSecurityDeposit);
return txFeeEstimationService.getEstimatedFeeAndTxSizeForMaker(reservedFundsForOffer,
return txFeeEstimationService.getEstimatedFeeAndTxVsizeForMaker(reservedFundsForOffer,
offerUtil.getMakerFee(amount));
}

View file

@ -177,8 +177,8 @@ public class OfferUtil {
return CoinUtil.getMakerFee(isCurrencyForMakerFeeBtc, amount);
}
public Coin getTxFeeBySize(Coin txFeePerByteFromFeeService, int sizeInBytes) {
return txFeePerByteFromFeeService.multiply(getAverageTakerFeeTxSize(sizeInBytes));
public Coin getTxFeeByVsize(Coin txFeePerVbyteFromFeeService, int vsizeInVbytes) {
return txFeePerVbyteFromFeeService.multiply(getAverageTakerFeeTxVsize(vsizeInVbytes));
}
// We use the sum of the size of the trade fee and the deposit tx to get an average.
@ -186,8 +186,8 @@ public class OfferUtil {
// enough. 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.
public int getAverageTakerFeeTxSize(int txSize) {
return (txSize + 233) / 2;
public int getAverageTakerFeeTxVsize(int txVsize) {
return (txVsize + 233) / 2;
}
/**

View file

@ -78,8 +78,8 @@ public class TakeOfferModel implements Model {
private boolean useSavingsWallet;
// Use an average of a typical trade fee tx with 1 input, deposit tx and payout tx.
private final int feeTxSize = 192; // (175+233+169)/3
private Coin txFeePerByteFromFeeService;
private final int feeTxVsize = 192; // (175+233+169)/3
private Coin txFeePerVbyteFromFeeService;
@Getter
private Coin txFeeFromFeeService;
@Getter
@ -149,26 +149,26 @@ public class TakeOfferModel implements Model {
// 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 175 bytes (if one input). The trade txs has
// about 169-263 bytes. We use 192 as a average value.
// A typical trade fee tx has about 175 vbytes (if one input). The trade txs has
// about 169-263 vbytes. We use 192 as a average value.
// Fee calculations:
// Trade fee tx: 175 bytes (1 input)
// Deposit tx: 233 bytes (1 MS output+ OP_RETURN) - 263 bytes
// Trade fee tx: 175 vbytes (1 input)
// Deposit tx: 233 vbytes (1 MS output+ OP_RETURN) - 263 vbytes
// (1 MS output + OP_RETURN + change in case of smaller trade amount)
// Payout tx: 169 bytes
// Disputed payout tx: 139 bytes
// Payout tx: 169 vbytes
// Disputed payout tx: 139 vbytes
txFeePerByteFromFeeService = getTxFeePerByte();
txFeeFromFeeService = offerUtil.getTxFeeBySize(txFeePerByteFromFeeService, feeTxSize);
log.info("{} txFeePerByte = {}", feeService.getClass().getSimpleName(), txFeePerByteFromFeeService);
txFeePerVbyteFromFeeService = getTxFeePerVbyte();
txFeeFromFeeService = offerUtil.getTxFeeByVsize(txFeePerVbyteFromFeeService, feeTxVsize);
log.info("{} txFeePerVbyte = {}", feeService.getClass().getSimpleName(), txFeePerVbyteFromFeeService);
}
private Coin getTxFeePerByte() {
private Coin getTxFeePerVbyte() {
try {
CompletableFuture<Void> feeRequestFuture = CompletableFuture.runAsync(feeService::requestFees);
feeRequestFuture.get(); // Block until async fee request is complete.
return feeService.getTxFeePerByte();
return feeService.getTxFeePerVbyte();
} catch (InterruptedException | ExecutionException e) {
throw new IllegalStateException("Could not request fees from fee service.", e);
}
@ -283,7 +283,7 @@ public class TakeOfferModel implements Model {
this.totalAvailableBalance = null;
this.totalToPayAsCoin = null;
this.txFeeFromFeeService = null;
this.txFeePerByteFromFeeService = null;
this.txFeePerVbyteFromFeeService = null;
this.useSavingsWallet = true;
this.volume = null;
}
@ -299,8 +299,8 @@ public class TakeOfferModel implements Model {
", addressEntry=" + addressEntry + "\n" +
", amount=" + amount + "\n" +
", securityDeposit=" + securityDeposit + "\n" +
", feeTxSize=" + feeTxSize + "\n" +
", txFeePerByteFromFeeService=" + txFeePerByteFromFeeService + "\n" +
", feeTxVsize=" + feeTxVsize + "\n" +
", txFeePerVbyteFromFeeService=" + txFeePerVbyteFromFeeService + "\n" +
", txFeeFromFeeService=" + txFeeFromFeeService + "\n" +
", takerFee=" + takerFee + "\n" +
", totalToPayAsCoin=" + totalToPayAsCoin + "\n" +

View file

@ -59,7 +59,7 @@ public class FeeService {
// Static
///////////////////////////////////////////////////////////////////////////////////////////
// Miner fees are between 1-600 sat/byte. We try to stay on the safe side. BTC_DEFAULT_TX_FEE is only used if our
// Miner fees are between 1-600 sat/vbyte. We try to stay on the safe side. BTC_DEFAULT_TX_FEE is only used if our
// fee service would not deliver data.
private static final long BTC_DEFAULT_TX_FEE = 50;
private static final long MIN_PAUSE_BETWEEN_REQUESTS_IN_MIN = 2;
@ -93,10 +93,10 @@ public class FeeService {
private final FeeProvider feeProvider;
private final IntegerProperty feeUpdateCounter = new SimpleIntegerProperty(0);
private long txFeePerByte = BTC_DEFAULT_TX_FEE;
private long txFeePerVbyte = BTC_DEFAULT_TX_FEE;
private Map<String, Long> timeStampMap;
private long lastRequest;
private long minFeePerByte;
private long minFeePerVByte;
private long epochInSecondAtLastRequest;
@ -117,7 +117,7 @@ public class FeeService {
///////////////////////////////////////////////////////////////////////////////////////////
public void onAllServicesInitialized() {
minFeePerByte = Config.baseCurrencyNetwork().getDefaultMinFeePerByte();
minFeePerVByte = Config.baseCurrencyNetwork().getDefaultMinFeePerVbyte();
requestFees();
@ -149,15 +149,15 @@ public class FeeService {
timeStampMap = result.first;
epochInSecondAtLastRequest = timeStampMap.get("bitcoinFeesTs");
final Map<String, Long> map = result.second;
txFeePerByte = map.get("BTC");
txFeePerVbyte = map.get("BTC");
if (txFeePerByte < minFeePerByte) {
log.warn("The delivered fee per byte is smaller than the min. default fee of 5 sat/byte");
txFeePerByte = minFeePerByte;
if (txFeePerVbyte < minFeePerVByte) {
log.warn("The delivered fee per vbyte is smaller than the min. default fee of 5 sat/vbyte");
txFeePerVbyte = minFeePerVByte;
}
feeUpdateCounter.set(feeUpdateCounter.get() + 1);
log.info("BTC tx fee: txFeePerByte={}", txFeePerByte);
log.info("BTC tx fee: txFeePerVbyte={}", txFeePerVbyte);
if (resultHandler != null)
resultHandler.run();
});
@ -179,12 +179,12 @@ public class FeeService {
}
}
public Coin getTxFee(int sizeInBytes) {
return getTxFeePerByte().multiply(sizeInBytes);
public Coin getTxFee(int vsizeInVbytes) {
return getTxFeePerVbyte().multiply(vsizeInVbytes);
}
public Coin getTxFeePerByte() {
return Coin.valueOf(txFeePerByte);
public Coin getTxFeePerVbyte() {
return Coin.valueOf(txFeePerVbyte);
}
public ReadOnlyIntegerProperty feeUpdateCounterProperty() {

View file

@ -589,8 +589,8 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
requestPersistence();
}
public void setWithdrawalTxFeeInBytes(long withdrawalTxFeeInBytes) {
prefPayload.setWithdrawalTxFeeInBytes(withdrawalTxFeeInBytes);
public void setWithdrawalTxFeeInVbytes(long withdrawalTxFeeInVbytes) {
prefPayload.setWithdrawalTxFeeInVbytes(withdrawalTxFeeInVbytes);
requestPersistence();
}
@ -865,8 +865,8 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
return prefPayload.getBridgeAddresses();
}
public long getWithdrawalTxFeeInBytes() {
return Math.max(prefPayload.getWithdrawalTxFeeInBytes(), Config.baseCurrencyNetwork().getDefaultMinFeePerByte());
public long getWithdrawalTxFeeInVbytes() {
return Math.max(prefPayload.getWithdrawalTxFeeInVbytes(), Config.baseCurrencyNetwork().getDefaultMinFeePerVbyte());
}
public boolean isDaoFullNode() {
@ -976,7 +976,7 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
void setUseCustomWithdrawalTxFee(boolean useCustomWithdrawalTxFee);
void setWithdrawalTxFeeInBytes(long withdrawalTxFeeInBytes);
void setWithdrawalTxFeeInVbytes(long withdrawalTxFeeInVbytes);
void setSelectedPaymentAccountForCreateOffer(@Nullable PaymentAccount paymentAccount);
@ -1022,7 +1022,7 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
List<String> getBridgeAddresses();
long getWithdrawalTxFeeInBytes();
long getWithdrawalTxFeeInVbytes();
void setUseStandbyMode(boolean useStandbyMode);

View file

@ -67,7 +67,7 @@ public final class PreferencesPayload implements PersistableEnvelope {
private boolean showOwnOffersInOfferBook = true;
@Nullable
private TradeCurrency preferredTradeCurrency;
private long withdrawalTxFeeInBytes = 100;
private long withdrawalTxFeeInVbytes = 100;
private boolean useCustomWithdrawalTxFee = false;
private double maxPriceDistanceInPercent = 0.3;
@Nullable
@ -160,7 +160,7 @@ public final class PreferencesPayload implements PersistableEnvelope {
.setTacAccepted(tacAccepted)
.setUseTorForBitcoinJ(useTorForBitcoinJ)
.setShowOwnOffersInOfferBook(showOwnOffersInOfferBook)
.setWithdrawalTxFeeInBytes(withdrawalTxFeeInBytes)
.setWithdrawalTxFeeInVbytes(withdrawalTxFeeInVbytes)
.setUseCustomWithdrawalTxFee(useCustomWithdrawalTxFee)
.setMaxPriceDistanceInPercent(maxPriceDistanceInPercent)
.setTradeStatisticsTickUnitIndex(tradeStatisticsTickUnitIndex)
@ -241,7 +241,7 @@ public final class PreferencesPayload implements PersistableEnvelope {
proto.getUseTorForBitcoinJ(),
proto.getShowOwnOffersInOfferBook(),
proto.hasPreferredTradeCurrency() ? TradeCurrency.fromProto(proto.getPreferredTradeCurrency()) : null,
proto.getWithdrawalTxFeeInBytes(),
proto.getWithdrawalTxFeeInVbytes(),
proto.getUseCustomWithdrawalTxFee(),
proto.getMaxPriceDistanceInPercent(),
ProtoUtil.stringOrNullFromProto(proto.getOfferBookChartScreenCurrencyCode()),

View file

@ -52,9 +52,9 @@ public class CoinUtil {
return a.compareTo(b) >= 0 ? a : b;
}
public static double getFeePerByte(Coin miningFee, int txSize) {
public static double getFeePerVbyte(Coin miningFee, int txVsize) {
double value = miningFee != null ? miningFee.value : 0;
return MathUtils.roundDouble((value / (double) txSize), 2);
return MathUtils.roundDouble((value / (double) txVsize), 2);
}
/**

View file

@ -122,7 +122,7 @@ shared.noDateAvailable=No date available
shared.noDetailsAvailable=No details available
shared.notUsedYet=Not used yet
shared.date=Date
shared.sendFundsDetailsWithFee=Sending: {0}\nFrom address: {1}\nTo receiving address: {2}.\nRequired mining fee is: {3} ({4} satoshis/byte)\nTransaction size: {5} Kb\n\nThe recipient will receive: {6}\n\nAre you sure you want to withdraw this amount?
shared.sendFundsDetailsWithFee=Sending: {0}\nFrom address: {1}\nTo receiving address: {2}.\nRequired mining fee is: {3} ({4} satoshis/vbyte)\nTransaction vsize: {5} vKb\n\nThe recipient will receive: {6}\n\nAre you sure you want to withdraw this amount?
shared.sendFundsDetailsDust=Bisq detected that this transaction would create a change output which is below the minimum dust threshold (and therefore not allowed by Bitcoin consensus rules). Instead, this dust ({0} satoshi{1}) will be added to the mining fee.\n\n\n
shared.copyToClipboard=Copy to clipboard
shared.language=Language
@ -1192,10 +1192,10 @@ setting.preferences.autoConfirmRequiredConfirmations=Required confirmations
setting.preferences.autoConfirmMaxTradeSize=Max. trade amount (BTC)
setting.preferences.autoConfirmServiceAddresses=Monero Explorer URLs (uses Tor, except for localhost, LAN IP addresses, and *.local hostnames)
setting.preferences.deviationToLarge=Values higher than {0}% are not allowed.
setting.preferences.txFee=Withdrawal transaction fee (satoshis/byte)
setting.preferences.txFee=Withdrawal transaction fee (satoshis/vbyte)
setting.preferences.useCustomValue=Use custom value
setting.preferences.txFeeMin=Transaction fee must be at least {0} satoshis/byte
setting.preferences.txFeeTooLarge=Your input is above any reasonable value (>5000 satoshis/byte). Transaction fee is usually in the range of 50-400 satoshis/byte.
setting.preferences.txFeeMin=Transaction fee must be at least {0} satoshis/vbyte
setting.preferences.txFeeTooLarge=Your input is above any reasonable value (>5000 satoshis/vbyte). Transaction fee is usually in the range of 50-400 satoshis/vbyte.
setting.preferences.ignorePeers=Ignored peers [onion address:port]
setting.preferences.refererId=Referral ID
setting.preferences.ignoreDustThreshold=Min. non-dust output value
@ -1944,10 +1944,10 @@ dao.bond.reputation.hash=Hash
dao.bond.reputation.lockupButton=Lockup
dao.bond.reputation.lockup.headline=Confirm lockup transaction
dao.bond.reputation.lockup.details=Lockup amount: {0}\nUnlock time: {1} block(s) (≈{2})\n\n\
Mining fee: {3} ({4} Satoshis/byte)\nTransaction size: {5} Kb\n\nAre you sure you want to proceed?
Mining fee: {3} ({4} Satoshis/vbyte)\nTransaction vsize: {5} Kb\n\nAre you sure you want to proceed?
dao.bond.reputation.unlock.headline=Confirm unlock transaction
dao.bond.reputation.unlock.details=Unlock amount: {0}\nUnlock time: {1} block(s) (≈{2})\n\n\
Mining fee: {3} ({4} Satoshis/byte)\nTransaction size: {5} Kb\n\nAre you sure you want to proceed?
Mining fee: {3} ({4} Satoshis/vbyte)\nTransaction vsize: {5} Kb\n\nAre you sure you want to proceed?
dao.bond.allBonds.header=All bonds
@ -2262,7 +2262,7 @@ dao.wallet.send.setDestinationAddress=Fill in your destination address
dao.wallet.send.send=Send BSQ funds
dao.wallet.send.sendBtc=Send BTC funds
dao.wallet.send.sendFunds.headline=Confirm withdrawal request
dao.wallet.send.sendFunds.details=Sending: {0}\nTo receiving address: {1}.\nRequired transaction fee is: {2} ({3} satoshis/byte)\nTransaction size: {4} Kb\n\nThe recipient will receive: {5}\n\nAre you sure you want to withdraw that amount?
dao.wallet.send.sendFunds.details=Sending: {0}\nTo receiving address: {1}.\nRequired transaction fee is: {2} ({3} satoshis/vbyte)\nTransaction vsize: {4} vKb\n\nThe recipient will receive: {5}\n\nAre you sure you want to withdraw that amount?
dao.wallet.chainHeightSynced=Latest verified block: {0}
dao.wallet.chainHeightSyncing=Awaiting blocks... Verified {0} blocks out of {1}
dao.wallet.tx.type=Type
@ -2333,14 +2333,14 @@ dao.proposal.create.missingIssuanceFunds=You don''t have sufficient BTC funds fo
dao.feeTx.confirm=Confirm {0} transaction
dao.feeTx.confirm.details={0} fee: {1}\n\
Mining fee: {2} ({3} Satoshis/byte)\n\
Transaction size: {4} Kb\n\n\
Mining fee: {2} ({3} Satoshis/vbyte)\n\
Transaction vsize: {4} vKb\n\n\
Are you sure you want to publish the {5} transaction?
dao.feeTx.issuanceProposal.confirm.details={0} fee: {1}\n\
BTC needed for BSQ issuance: {2} ({3} Satoshis/BSQ)\n\
Mining fee: {4} ({5} Satoshis/byte)\n\
Transaction size: {6} Kb\n\n\
Mining fee: {4} ({5} Satoshis/vbyte)\n\
Transaction vsize: {6} vKb\n\n\
If your request is approved, you will receive the amount you requested net of the 2 BSQ proposal fee.\n\n\
Are you sure you want to publish the {7} transaction?
@ -2562,8 +2562,8 @@ disputeSummaryWindow.close.txDetails.buyer=Buyer receives {0} on address: {1}\n
disputeSummaryWindow.close.txDetails.seller=Seller receives {0} on address: {1}\n
disputeSummaryWindow.close.txDetails=Spending: {0}\n\
{1}{2}\
Transaction fee: {3} ({4} satoshis/byte)\n\
Transaction size: {5} Kb\n\n\
Transaction fee: {3} ({4} satoshis/vbyte)\n\
Transaction vsize: {5} vKb\n\n\
Are you sure you want to publish this transaction?
disputeSummaryWindow.close.noPayout.headline=Close without any payout
@ -2960,7 +2960,7 @@ systemTray.tooltip=Bisq: A decentralized bitcoin exchange network
####################################################################
guiUtil.miningFeeInfo=Please be sure that the mining fee used by your external wallet is \
at least {0} satoshis/byte. Otherwise the trade transactions may not be confirmed in time and the trade will end up in a dispute.
at least {0} satoshis/vbyte. Otherwise the trade transactions may not be confirmed in time and the trade will end up in a dispute.
guiUtil.accountExport.savedToPath=Trading accounts saved to path:\n{0}
guiUtil.accountExport.noAccountSetup=You don't have trading accounts set up for exporting.

View file

@ -36,106 +36,106 @@ import static org.mockito.Mockito.when;
public class TxFeeEstimationServiceTest {
@Test
public void testGetEstimatedTxSize_withDefaultTxSize() throws InsufficientMoneyException {
public void testGetEstimatedTxVsize_withDefaultTxVsize() throws InsufficientMoneyException {
List<Coin> outputValues = List.of(Coin.valueOf(2000), Coin.valueOf(3000));
int initialEstimatedTxSize;
Coin txFeePerByte;
int initialEstimatedTxVsize;
Coin txFeePerVbyte;
BtcWalletService btcWalletService = mock(BtcWalletService.class);
int result;
int realTxSize;
int realTxVsize;
Coin txFee;
initialEstimatedTxSize = 175;
txFeePerByte = Coin.valueOf(10);
realTxSize = 175;
initialEstimatedTxVsize = 175;
txFeePerVbyte = Coin.valueOf(10);
realTxVsize = 175;
txFee = txFeePerByte.multiply(initialEstimatedTxSize);
when(btcWalletService.getEstimatedFeeTxSize(outputValues, txFee)).thenReturn(realTxSize);
result = TxFeeEstimationService.getEstimatedTxSize(outputValues, initialEstimatedTxSize, txFeePerByte, btcWalletService);
txFee = txFeePerVbyte.multiply(initialEstimatedTxVsize);
when(btcWalletService.getEstimatedFeeTxVsize(outputValues, txFee)).thenReturn(realTxVsize);
result = TxFeeEstimationService.getEstimatedTxVsize(outputValues, initialEstimatedTxVsize, txFeePerVbyte, btcWalletService);
assertEquals(175, result);
}
// FIXME @Bernard could you have a look?
@Test
@Ignore
public void testGetEstimatedTxSize_withLargeTx() throws InsufficientMoneyException {
public void testGetEstimatedTxVsize_withLargeTx() throws InsufficientMoneyException {
List<Coin> outputValues = List.of(Coin.valueOf(2000), Coin.valueOf(3000));
int initialEstimatedTxSize;
Coin txFeePerByte;
int initialEstimatedTxVsize;
Coin txFeePerVbyte;
BtcWalletService btcWalletService = mock(BtcWalletService.class);
int result;
int realTxSize;
int realTxVsize;
Coin txFee;
initialEstimatedTxSize = 175;
txFeePerByte = Coin.valueOf(10);
realTxSize = 1750;
initialEstimatedTxVsize = 175;
txFeePerVbyte = Coin.valueOf(10);
realTxVsize = 1750;
txFee = txFeePerByte.multiply(initialEstimatedTxSize);
when(btcWalletService.getEstimatedFeeTxSize(outputValues, txFee)).thenReturn(realTxSize);
txFee = txFeePerVbyte.multiply(initialEstimatedTxVsize);
when(btcWalletService.getEstimatedFeeTxVsize(outputValues, txFee)).thenReturn(realTxVsize);
// 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);
// repeated calls to getEstimatedFeeTxVsize do not work (returns 0 at second call in loop which cause test to fail)
result = TxFeeEstimationService.getEstimatedTxVsize(outputValues, initialEstimatedTxVsize, txFeePerVbyte, btcWalletService);
assertEquals(1750, result);
}
// FIXME @Bernard could you have a look?
@Test
@Ignore
public void testGetEstimatedTxSize_withSmallTx() throws InsufficientMoneyException {
public void testGetEstimatedTxVsize_withSmallTx() throws InsufficientMoneyException {
List<Coin> outputValues = List.of(Coin.valueOf(2000), Coin.valueOf(3000));
int initialEstimatedTxSize;
Coin txFeePerByte;
int initialEstimatedTxVsize;
Coin txFeePerVbyte;
BtcWalletService btcWalletService = mock(BtcWalletService.class);
int result;
int realTxSize;
int realTxVsize;
Coin txFee;
initialEstimatedTxSize = 1750;
txFeePerByte = Coin.valueOf(10);
realTxSize = 175;
initialEstimatedTxVsize = 1750;
txFeePerVbyte = Coin.valueOf(10);
realTxVsize = 175;
txFee = txFeePerByte.multiply(initialEstimatedTxSize);
when(btcWalletService.getEstimatedFeeTxSize(outputValues, txFee)).thenReturn(realTxSize);
result = TxFeeEstimationService.getEstimatedTxSize(outputValues, initialEstimatedTxSize, txFeePerByte, btcWalletService);
txFee = txFeePerVbyte.multiply(initialEstimatedTxVsize);
when(btcWalletService.getEstimatedFeeTxVsize(outputValues, txFee)).thenReturn(realTxVsize);
result = TxFeeEstimationService.getEstimatedTxVsize(outputValues, initialEstimatedTxVsize, txFeePerVbyte, btcWalletService);
assertEquals(175, result);
}
@Test
public void testIsInTolerance() {
int estimatedSize;
int txSize;
int txVsize;
double tolerance;
boolean result;
estimatedSize = 100;
txSize = 100;
txVsize = 100;
tolerance = 0.0001;
result = TxFeeEstimationService.isInTolerance(estimatedSize, txSize, tolerance);
result = TxFeeEstimationService.isInTolerance(estimatedSize, txVsize, tolerance);
assertTrue(result);
estimatedSize = 100;
txSize = 200;
txVsize = 200;
tolerance = 0.2;
result = TxFeeEstimationService.isInTolerance(estimatedSize, txSize, tolerance);
result = TxFeeEstimationService.isInTolerance(estimatedSize, txVsize, tolerance);
assertFalse(result);
estimatedSize = 120;
txSize = 100;
txVsize = 100;
tolerance = 0.2;
result = TxFeeEstimationService.isInTolerance(estimatedSize, txSize, tolerance);
result = TxFeeEstimationService.isInTolerance(estimatedSize, txVsize, tolerance);
assertTrue(result);
estimatedSize = 200;
txSize = 100;
txVsize = 100;
tolerance = 1;
result = TxFeeEstimationService.isInTolerance(estimatedSize, txSize, tolerance);
result = TxFeeEstimationService.isInTolerance(estimatedSize, txVsize, tolerance);
assertTrue(result);
estimatedSize = 201;
txSize = 100;
txVsize = 100;
tolerance = 1;
result = TxFeeEstimationService.isInTolerance(estimatedSize, txSize, tolerance);
result = TxFeeEstimationService.isInTolerance(estimatedSize, txVsize, tolerance);
assertFalse(result);
}
}

View file

@ -34,9 +34,7 @@ import bisq.core.dao.state.model.blockchain.TxOutput;
import bisq.core.dao.state.model.governance.Role;
import bisq.core.dao.state.model.governance.RoleProposal;
import bisq.core.locale.Res;
import bisq.core.util.coin.ImmutableCoinFormatter;
import bisq.core.util.coin.BsqFormatter;
import bisq.core.util.coin.CoinFormatter;
import bisq.core.util.coin.CoinUtil;
import bisq.core.util.FormattingUtils;
@ -111,9 +109,9 @@ public class BondingViewUtils {
if (GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, walletsSetup)) {
if (!DevEnv.isDevMode()) {
try {
Tuple2<Coin, Integer> miningFeeAndTxSize = daoFacade.getLockupTxMiningFeeAndTxSize(lockupAmount, lockupTime, lockupReason, hash);
Coin miningFee = miningFeeAndTxSize.first;
int txSize = miningFeeAndTxSize.second;
Tuple2<Coin, Integer> miningFeeAndTxVsize = daoFacade.getLockupTxMiningFeeAndTxVsize(lockupAmount, lockupTime, lockupReason, hash);
Coin miningFee = miningFeeAndTxVsize.first;
int txVsize = miningFeeAndTxVsize.second;
String duration = FormattingUtils.formatDurationAsWords(lockupTime * 10 * 60 * 1000L, false, false);
new Popup().headLine(Res.get("dao.bond.reputation.lockup.headline"))
.confirmation(Res.get("dao.bond.reputation.lockup.details",
@ -121,8 +119,8 @@ public class BondingViewUtils {
lockupTime,
duration,
bsqFormatter.formatBTCWithCode(miningFee),
CoinUtil.getFeePerByte(miningFee, txSize),
txSize / 1000d
CoinUtil.getFeePerVbyte(miningFee, txVsize),
txVsize / 1000d
))
.actionButtonText(Res.get("shared.yes"))
.onAction(() -> publishLockupTx(lockupAmount, lockupTime, lockupReason, hash, resultHandler))
@ -169,9 +167,9 @@ public class BondingViewUtils {
try {
if (!DevEnv.isDevMode()) {
Tuple2<Coin, Integer> miningFeeAndTxSize = daoFacade.getUnlockTxMiningFeeAndTxSize(lockupTxId);
Coin miningFee = miningFeeAndTxSize.first;
int txSize = miningFeeAndTxSize.second;
Tuple2<Coin, Integer> miningFeeAndTxVsize = daoFacade.getUnlockTxMiningFeeAndTxVsize(lockupTxId);
Coin miningFee = miningFeeAndTxVsize.first;
int txVsize = miningFeeAndTxVsize.second;
String duration = FormattingUtils.formatDurationAsWords(lockTime * 10 * 60 * 1000L, false, false);
new Popup().headLine(Res.get("dao.bond.reputation.unlock.headline"))
.confirmation(Res.get("dao.bond.reputation.unlock.details",
@ -179,8 +177,8 @@ public class BondingViewUtils {
lockTime,
duration,
bsqFormatter.formatBTCWithCode(miningFee),
CoinUtil.getFeePerByte(miningFee, txSize),
txSize / 1000d
CoinUtil.getFeePerVbyte(miningFee, txVsize),
txVsize / 1000d
))
.actionButtonText(Res.get("shared.yes"))
.onAction(() -> publishUnlockTx(lockupTxId, resultHandler))

View file

@ -189,10 +189,10 @@ public class AssetFeeView extends ActivatableView<GridPane, Void> implements Bsq
try {
Transaction transaction = assetService.payFee(selectedAsset, listingFee.value);
Coin miningFee = transaction.getFee();
int txSize = transaction.getVsize();
int txVsize = transaction.getVsize();
if (!DevEnv.isDevMode()) {
GUIUtil.showBsqFeeInfoPopup(listingFee, miningFee, txSize, bsqFormatter, btcFormatter,
GUIUtil.showBsqFeeInfoPopup(listingFee, miningFee, txVsize, bsqFormatter, btcFormatter,
Res.get("dao.burnBsq.assetFee"), () -> doPublishFeeTx(transaction));
} else {
doPublishFeeTx(transaction);

View file

@ -172,10 +172,10 @@ 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.getVsize();
int txVsize = transaction.getVsize();
if (!DevEnv.isDevMode()) {
GUIUtil.showBsqFeeInfoPopup(amount, miningFee, txSize, bsqFormatter, btcFormatter,
GUIUtil.showBsqFeeInfoPopup(amount, miningFee, txVsize, bsqFormatter, btcFormatter,
Res.get("dao.proofOfBurn.header"), () -> doPublishFeeTx(transaction, preImageAsString));
} else {
doPublishFeeTx(transaction, preImageAsString);

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.getVsize();
int txVsize = transaction.getVsize();
Coin fee = daoFacade.getProposalFee(daoFacade.getChainHeight());
if (type.equals(ProposalType.BONDED_ROLE)) {
@ -298,13 +298,13 @@ public class MakeProposalView extends ActivatableView<GridPane, Void> implements
new Popup().warning(Res.get("dao.proposal.create.missingBsqFundsForBond",
bsqFormatter.formatCoinWithCode(missing)))
.actionButtonText(Res.get("dao.proposal.create.publish"))
.onAction(() -> showFeeInfoAndPublishMyProposal(proposal, transaction, miningFee, txSize, fee))
.onAction(() -> showFeeInfoAndPublishMyProposal(proposal, transaction, miningFee, txVsize, fee))
.show();
} else {
showFeeInfoAndPublishMyProposal(proposal, transaction, miningFee, txSize, fee);
showFeeInfoAndPublishMyProposal(proposal, transaction, miningFee, txVsize, fee);
}
} else {
showFeeInfoAndPublishMyProposal(proposal, transaction, miningFee, txSize, fee);
showFeeInfoAndPublishMyProposal(proposal, transaction, miningFee, txVsize, fee);
}
} catch (InsufficientMoneyException e) {
if (e instanceof InsufficientBsqException) {
@ -340,13 +340,13 @@ public class MakeProposalView extends ActivatableView<GridPane, Void> implements
}
}
private void showFeeInfoAndPublishMyProposal(Proposal proposal, Transaction transaction, Coin miningFee, int txSize, Coin fee) {
private void showFeeInfoAndPublishMyProposal(Proposal proposal, Transaction transaction, Coin miningFee, int txVsize, Coin fee) {
if (!DevEnv.isDevMode()) {
Coin btcForIssuance = null;
if (proposal instanceof IssuanceProposal) btcForIssuance = ((IssuanceProposal) proposal).getRequestedBsq();
GUIUtil.showBsqFeeInfoPopup(fee, miningFee, btcForIssuance, txSize, bsqFormatter, btcFormatter,
GUIUtil.showBsqFeeInfoPopup(fee, miningFee, btcForIssuance, txVsize, bsqFormatter, btcFormatter,
Res.get("dao.proposal"), () -> doPublishMyProposal(proposal, transaction));
} else {
doPublishMyProposal(proposal, transaction);

View file

@ -475,12 +475,12 @@ public class ProposalsView extends ActivatableView<GridPane, Void> implements Bs
Coin stake = ParsingUtils.parseToCoin(stakeInputTextField.getText(), bsqFormatter);
try {
// We create a dummy tx to get the miningFee for displaying it at the confirmation popup
Tuple2<Coin, Integer> miningFeeAndTxSize = daoFacade.getBlindVoteMiningFeeAndTxSize(stake);
Coin miningFee = miningFeeAndTxSize.first;
int txSize = miningFeeAndTxSize.second;
Tuple2<Coin, Integer> miningFeeAndTxVsize = daoFacade.getBlindVoteMiningFeeAndTxVsize(stake);
Coin miningFee = miningFeeAndTxVsize.first;
int txVsize = miningFeeAndTxVsize.second;
Coin blindVoteFee = daoFacade.getBlindVoteFeeForCycle();
if (!DevEnv.isDevMode()) {
GUIUtil.showBsqFeeInfoPopup(blindVoteFee, miningFee, txSize, bsqFormatter, btcFormatter,
GUIUtil.showBsqFeeInfoPopup(blindVoteFee, miningFee, txVsize, bsqFormatter, btcFormatter,
Res.get("dao.blindVote"), () -> publishBlindVote(stake));
} else {
publishBlindVote(stake);

View file

@ -243,12 +243,12 @@ 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.getVsize();
int txVsize = signedTx.getVsize();
showPublishTxPopup(receiverAmount,
txWithBtcFee,
TxType.TRANSFER_BSQ,
miningFee,
txSize,
txVsize,
receiversAddressInputTextField.getText(),
bsqFormatter,
btcFormatter,
@ -305,12 +305,12 @@ public class BsqSendView extends ActivatableView<GridPane, Void> implements BsqB
if (miningFee.getValue() >= receiverAmount.getValue())
GUIUtil.showWantToBurnBTCPopup(miningFee, receiverAmount, btcFormatter);
else {
int txSize = signedTx.getVsize();
int txVsize = signedTx.getVsize();
showPublishTxPopup(receiverAmount,
txWithBtcFee,
TxType.INVALID,
miningFee,
txSize, receiversBtcAddressInputTextField.getText(),
txVsize, receiversBtcAddressInputTextField.getText(),
btcFormatter,
btcFormatter,
() -> {
@ -348,7 +348,7 @@ public class BsqSendView extends ActivatableView<GridPane, Void> implements BsqB
Transaction txWithBtcFee,
TxType txType,
Coin miningFee,
int txSize, String address,
int txVsize, String address,
CoinFormatter amountFormatter, // can be BSQ or BTC formatter
CoinFormatter feeFormatter,
ResultHandler resultHandler) {
@ -357,8 +357,8 @@ public class BsqSendView extends ActivatableView<GridPane, Void> implements BsqB
amountFormatter.formatCoinWithCode(receiverAmount),
address,
feeFormatter.formatCoinWithCode(miningFee),
CoinUtil.getFeePerByte(miningFee, txSize),
txSize / 1000d,
CoinUtil.getFeePerVbyte(miningFee, txVsize),
txVsize / 1000d,
amountFormatter.formatCoinWithCode(receiverAmount)))
.actionButtonText(Res.get("shared.yes"))
.onAction(() -> {

View file

@ -356,20 +356,20 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
}
if (areInputsValid()) {
int txSize = feeEstimationTransaction.getVsize();
log.info("Fee for tx with size {}: {} " + Res.getBaseCurrencyCode() + "", txSize, fee.toPlainString());
int txVsize = feeEstimationTransaction.getVsize();
log.info("Fee for tx with size {}: {} " + Res.getBaseCurrencyCode() + "", txVsize, fee.toPlainString());
if (receiverAmount.isPositive()) {
double feePerByte = CoinUtil.getFeePerByte(fee, txSize);
double kb = txSize / 1000d;
double feePerVbyte = CoinUtil.getFeePerVbyte(fee, txVsize);
double vkb = txVsize / 1000d;
String messageText = Res.get("shared.sendFundsDetailsWithFee",
formatter.formatCoinWithCode(sendersAmount),
withdrawFromTextField.getText(),
withdrawToTextField.getText(),
formatter.formatCoinWithCode(fee),
feePerByte,
kb,
feePerVbyte,
vkb,
formatter.formatCoinWithCode(receiverAmount));
if (dust.isPositive()) {
messageText = Res.get("shared.sendFundsDetailsDust",

View file

@ -129,7 +129,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
protected double marketPriceMargin = 0;
private Coin txFeeFromFeeService = Coin.ZERO;
private boolean marketPriceAvailable;
private int feeTxSize = TxFeeEstimationService.TYPICAL_TX_WITH_1_INPUT_SIZE;
private int feeTxVsize = TxFeeEstimationService.TYPICAL_TX_WITH_1_INPUT_VSIZE;
protected boolean allowAmountUpdate = true;
private final TradeStatisticsManager tradeStatisticsManager;
@ -262,7 +262,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
// Set the default values (in rare cases if the fee request was not done yet we get the hard coded default values)
// But offer creation happens usually after that so we should have already the value from the estimation service.
txFeeFromFeeService = feeService.getTxFee(feeTxSize);
txFeeFromFeeService = feeService.getTxFee(feeTxVsize);
calculateVolume();
calculateTotalToPay();
@ -301,13 +301,13 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
}
// This works only if we have already funds in the wallet
public void updateEstimatedFeeAndTxSize() {
Tuple2<Coin, Integer> estimatedFeeAndTxSize = createOfferService.getEstimatedFeeAndTxSize(amount.get(),
public void updateEstimatedFeeAndTxVsize() {
Tuple2<Coin, Integer> estimatedFeeAndTxVsize = createOfferService.getEstimatedFeeAndTxVsize(amount.get(),
direction,
buyerSecurityDeposit.get(),
createOfferService.getSellerSecurityDepositAsDouble(buyerSecurityDeposit.get()));
txFeeFromFeeService = estimatedFeeAndTxSize.first;
feeTxSize = estimatedFeeAndTxSize.second;
txFeeFromFeeService = estimatedFeeAndTxVsize.first;
feeTxVsize = estimatedFeeAndTxVsize.second;
}
void onPlaceOffer(Offer offer, TransactionResultHandler resultHandler) {
@ -439,7 +439,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
void requestTxFee(@Nullable Runnable actionHandler) {
feeService.requestFees(() -> {
txFeeFromFeeService = feeService.getTxFee(feeTxSize);
txFeeFromFeeService = feeService.getTxFee(feeTxVsize);
calculateTotalToPay();
if (actionHandler != null)
actionHandler.run();

View file

@ -688,7 +688,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
}
void onShowPayFundsScreen(Runnable actionHandler) {
dataModel.updateEstimatedFeeAndTxSize();
dataModel.updateEstimatedFeeAndTxVsize();
dataModel.requestTxFee(actionHandler);
showPayFundsScreenDisplayed.set(true);
updateSpinnerInfo();

View file

@ -110,9 +110,9 @@ class TakeOfferDataModel extends OfferDataModel {
private boolean isTabSelected;
Price tradePrice;
// 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 int feeTxVsize = 192; // (175+233+169)/3
private boolean freezeFee;
private Coin txFeePerByteFromFeeService;
private Coin txFeePerVbyteFromFeeService;
///////////////////////////////////////////////////////////////////////////////////////////
@ -213,25 +213,25 @@ 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 175 bytes (if one input). The trade txs has about 169-263 bytes.
// A typical trade fee tx has about 175 vbytes (if one input). The trade txs has about 169-263 vbytes.
// We use 192 as average value.
// 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
// trade fee tx: 175 vbytes (1 input)
// deposit tx: 233 vbytes (1 MS output+ OP_RETURN) - 263 vbytes (1 MS output + OP_RETURN + change in case of smaller trade amount)
// payout tx: 169 vbytes
// disputed payout tx: 139 vbytes
// 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.
txFeePerByteFromFeeService = feeService.getTxFeePerByte();
txFeeFromFeeService = getTxFeeBySize(feeTxSize);
txFeePerVbyteFromFeeService = feeService.getTxFeePerVbyte();
txFeeFromFeeService = getTxFeeByVsize(feeTxVsize);
// We request to get the actual estimated fee
log.info("Start requestTxFee: txFeeFromFeeService={}", txFeeFromFeeService);
feeService.requestFees(() -> {
if (!freezeFee) {
txFeePerByteFromFeeService = feeService.getTxFeePerByte();
txFeeFromFeeService = getTxFeeBySize(feeTxSize);
txFeePerVbyteFromFeeService = feeService.getTxFeePerVbyte();
txFeeFromFeeService = getTxFeeByVsize(feeTxVsize);
calculateTotalToPay();
log.info("Completed requestTxFee: txFeeFromFeeService={}", txFeeFromFeeService);
} else {
@ -257,7 +257,7 @@ class TakeOfferDataModel extends OfferDataModel {
// We don't want that the fee gets updated anymore after we show the funding screen.
void onShowPayFundsScreen() {
estimateTxSize();
estimateTxVsize();
freezeFee = true;
calculateTotalToPay();
}
@ -338,8 +338,8 @@ class TakeOfferDataModel extends OfferDataModel {
// leading to a smaller tx and too high fees. Simply updating the fee estimation would lead to changed required funds
// and if funds get higher (if tx get larger) the user would get confused (adding small inputs would increase total required funds).
// So that would require more thoughts how to deal with all those cases.
public void estimateTxSize() {
int txSize = 0;
public void estimateTxVsize() {
int txVsize = 0;
if (btcWalletService.getBalance(Wallet.BalanceType.AVAILABLE).isPositive()) {
Coin fundsNeededForTrade = getFundsNeededForTrade();
if (isBuyOffer())
@ -347,28 +347,28 @@ class TakeOfferDataModel extends OfferDataModel {
// As taker we pay 3 times the fee and currently the fee is the same for all 3 txs (trade fee tx, deposit
// tx and payout tx).
// We should try to change that in future to have the deposit and payout tx with a fixed fee as the size is
// We should try to change that in future to have the deposit and payout tx with a fixed fee as the vsize is
// 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 175 bytes but the
// other 2 txs are different (233 and 169 bytes) and may 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 vbytes but the
// other 2 txs are different (233 and 169 vbytes) and may get a lower fee/vbyte 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 deposit tx with 233 bytes.
Tuple2<Coin, Integer> estimatedFeeAndTxSize = txFeeEstimationService.getEstimatedFeeAndTxSizeForTaker(fundsNeededForTrade,
// We make sure that the fee is sufficient to meet our intended fee/vbyte for the larger deposit tx with 233 vbytes.
Tuple2<Coin, Integer> estimatedFeeAndTxVsize = txFeeEstimationService.getEstimatedFeeAndTxVsizeForTaker(fundsNeededForTrade,
getTakerFee());
txFeeFromFeeService = estimatedFeeAndTxSize.first;
feeTxSize = estimatedFeeAndTxSize.second;
txFeeFromFeeService = estimatedFeeAndTxVsize.first;
feeTxVsize = estimatedFeeAndTxVsize.second;
} else {
feeTxSize = 233;
txFeeFromFeeService = txFeePerByteFromFeeService.multiply(feeTxSize);
feeTxVsize = 233;
txFeeFromFeeService = txFeePerVbyteFromFeeService.multiply(feeTxVsize);
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 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());
"In that case we use an estimated tx vsize of 233 vbytes.\n" +
"txFee based on estimated vsize of {} vbytes. feeTxVsize = {} vbytes. Actual tx vsize = {} vbytes. TxFee is {} ({} sat/vbyte)",
feeTxVsize, feeTxVsize, txVsize, txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerVbyte());
}
}
@ -526,16 +526,16 @@ class TakeOfferDataModel extends OfferDataModel {
btcWalletService.resetAddressEntriesForOpenOffer(offer.getId());
}
// We use the sum of the size of the trade fee and the deposit tx to get an average.
// We use the sum of the vsize of the trade fee and the deposit tx to get an average.
// Miners will take the trade fee tx if the total fee of both dependent txs are good enough.
// 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 + 233) / 2;
private int getAverageVsize(int txVsize) {
return (txVsize + 233) / 2;
}
private Coin getTxFeeBySize(int sizeInBytes) {
return txFeePerByteFromFeeService.multiply(getAverageSize(sizeInBytes));
private Coin getTxFeeByVsize(int vsizeInVbytes) {
return txFeePerVbyteFromFeeService.multiply(getAverageVsize(vsizeInVbytes));
}
/* private void setFeeFromFundingTx(Coin fee) {

View file

@ -672,11 +672,11 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
Coin sellerPayoutAmount = disputeResult.getSellerPayoutAmount();
String sellerPayoutAddressString = contract.getSellerPayoutAddressString();
Coin outputAmount = buyerPayoutAmount.add(sellerPayoutAmount);
Tuple2<Coin, Integer> feeTuple = txFeeEstimationService.getEstimatedFeeAndTxSize(outputAmount, feeService, btcWalletService);
Tuple2<Coin, Integer> feeTuple = txFeeEstimationService.getEstimatedFeeAndTxVsize(outputAmount, feeService, btcWalletService);
Coin fee = feeTuple.first;
Integer txSize = feeTuple.second;
double feePerByte = CoinUtil.getFeePerByte(fee, txSize);
double kb = txSize / 1000d;
Integer txVsize = feeTuple.second;
double feePerVbyte = CoinUtil.getFeePerVbyte(fee, txVsize);
double vkb = txVsize / 1000d;
Coin inputAmount = outputAmount.add(fee);
String buyerDetails = "";
if (buyerPayoutAmount.isPositive()) {
@ -698,8 +698,8 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
buyerDetails,
sellerDetails,
formatter.formatCoinWithCode(fee),
feePerByte,
kb))
feePerVbyte,
vkb))
.actionButtonText(Res.get("shared.yes"))
.onAction(() -> {
doPayout(buyerPayoutAmount,

View file

@ -205,9 +205,9 @@ public class BuyerStep4View extends TradeStepView {
validateWithdrawAddress();
} else if (Restrictions.isAboveDust(receiverAmount)) {
CoinFormatter formatter = model.btcFormatter;
int txSize = feeEstimationTransaction.getVsize();
double feePerByte = CoinUtil.getFeePerByte(fee, txSize);
double kb = txSize / 1000d;
int txVsize = feeEstimationTransaction.getVsize();
double feePerVbyte = CoinUtil.getFeePerVbyte(fee, txVsize);
double vkb = txVsize / 1000d;
String recAmount = formatter.formatCoinWithCode(receiverAmount);
new Popup().headLine(Res.get("portfolio.pending.step5_buyer.confirmWithdrawal"))
.confirmation(Res.get("shared.sendFundsDetailsWithFee",
@ -215,8 +215,8 @@ public class BuyerStep4View extends TradeStepView {
fromAddresses,
toAddresses,
formatter.formatCoinWithCode(fee),
feePerByte,
kb,
feePerVbyte,
vkb,
recAmount))
.actionButtonText(Res.get("shared.yes"))
.onAction(() -> doWithdrawal(amount, fee))

View file

@ -270,9 +270,9 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
preferences.setUseCustomWithdrawalTxFee(newValue);
transactionFeeInputTextField.setEditable(newValue);
if (!newValue) {
transactionFeeInputTextField.setText(String.valueOf(feeService.getTxFeePerByte().value));
transactionFeeInputTextField.setText(String.valueOf(feeService.getTxFeePerVbyte().value));
try {
preferences.setWithdrawalTxFeeInBytes(feeService.getTxFeePerByte().value);
preferences.setWithdrawalTxFeeInVbytes(feeService.getTxFeePerVbyte().value);
} catch (Exception e) {
e.printStackTrace();
}
@ -283,18 +283,18 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
transactionFeeFocusedListener = (o, oldValue, newValue) -> {
if (oldValue && !newValue) {
String estimatedFee = String.valueOf(feeService.getTxFeePerByte().value);
String estimatedFee = String.valueOf(feeService.getTxFeePerVbyte().value);
try {
int withdrawalTxFeePerByte = Integer.parseInt(transactionFeeInputTextField.getText());
final long minFeePerByte = Config.baseCurrencyNetwork().getDefaultMinFeePerByte();
if (withdrawalTxFeePerByte < minFeePerByte) {
new Popup().warning(Res.get("setting.preferences.txFeeMin", minFeePerByte)).show();
int withdrawalTxFeePerVbyte = Integer.parseInt(transactionFeeInputTextField.getText());
final long minFeePerVbyte = Config.baseCurrencyNetwork().getDefaultMinFeePerVbyte();
if (withdrawalTxFeePerVbyte < minFeePerVbyte) {
new Popup().warning(Res.get("setting.preferences.txFeeMin", minFeePerVbyte)).show();
transactionFeeInputTextField.setText(estimatedFee);
} else if (withdrawalTxFeePerByte > 5000) {
} else if (withdrawalTxFeePerVbyte > 5000) {
new Popup().warning(Res.get("setting.preferences.txFeeTooLarge")).show();
transactionFeeInputTextField.setText(estimatedFee);
} else {
preferences.setWithdrawalTxFeeInBytes(withdrawalTxFeePerByte);
preferences.setWithdrawalTxFeeInVbytes(withdrawalTxFeePerVbyte);
}
} catch (NumberFormatException t) {
log.error(t.toString());
@ -309,7 +309,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
}
}
};
transactionFeeChangeListener = (observable, oldValue, newValue) -> transactionFeeInputTextField.setText(String.valueOf(feeService.getTxFeePerByte().value));
transactionFeeChangeListener = (observable, oldValue, newValue) -> transactionFeeInputTextField.setText(String.valueOf(feeService.getTxFeePerVbyte().value));
// deviation
deviationInputTextField = addInputTextField(root, ++gridRow,
@ -795,11 +795,11 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
transactionFeeInputTextField.setEditable(useCustomWithdrawalTxFee);
if (!useCustomWithdrawalTxFee) {
transactionFeeInputTextField.setText(String.valueOf(feeService.getTxFeePerByte().value));
transactionFeeInputTextField.setText(String.valueOf(feeService.getTxFeePerVbyte().value));
feeService.feeUpdateCounterProperty().addListener(transactionFeeChangeListener);
}
transactionFeeInputTextField.setText(String.valueOf(getTxFeeForWithdrawalPerByte()));
transactionFeeInputTextField.setText(String.valueOf(getTxFeeForWithdrawalPerVbyte()));
ignoreTradersListInputTextField.setText(String.join(", ", preferences.getIgnoreTradersList()));
/* referralIdService.getOptionalReferralId().ifPresent(referralId -> referralIdInputTextField.setText(referralId));
referralIdInputTextField.setPromptText(Res.get("setting.preferences.refererId.prompt"));*/
@ -870,10 +870,10 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
ignoreDustThresholdInputTextField.textProperty().addListener(ignoreDustThresholdListener);
}
private Coin getTxFeeForWithdrawalPerByte() {
private Coin getTxFeeForWithdrawalPerVbyte() {
Coin fee = (preferences.isUseCustomWithdrawalTxFee()) ?
Coin.valueOf(preferences.getWithdrawalTxFeeInBytes()) :
feeService.getTxFeePerByte();
Coin.valueOf(preferences.getWithdrawalTxFeeInVbytes()) :
feeService.getTxFeePerVbyte();
log.info("tx fee = " + fee.toFriendlyString());
return fee;
}

View file

@ -194,7 +194,7 @@ public class GUIUtil {
public static void showFeeInfoBeforeExecute(Runnable runnable) {
String key = "miningFeeInfo";
if (!DevEnv.isDevMode() && DontShowAgainLookup.showAgain(key)) {
new Popup().attention(Res.get("guiUtil.miningFeeInfo", String.valueOf(GUIUtil.feeService.getTxFeePerByte().value)))
new Popup().attention(Res.get("guiUtil.miningFeeInfo", String.valueOf(GUIUtil.feeService.getTxFeePerVbyte().value)))
.onClose(runnable)
.useIUnderstandButton()
.show();
@ -911,7 +911,7 @@ public class GUIUtil {
public static void showBsqFeeInfoPopup(Coin fee,
Coin miningFee,
Coin btcForIssuance,
int txSize,
int txVsize,
BsqFormatter bsqFormatter,
CoinFormatter btcFormatter,
String type,
@ -925,16 +925,16 @@ public class GUIUtil {
bsqFormatter.formatBTCWithCode(btcForIssuance),
100,
btcFormatter.formatCoinWithCode(miningFee),
CoinUtil.getFeePerByte(miningFee, txSize),
txSize / 1000d,
CoinUtil.getFeePerVbyte(miningFee, txVsize),
txVsize / 1000d,
type);
} else {
confirmationMessage = Res.get("dao.feeTx.confirm.details",
StringUtils.capitalize(type),
bsqFormatter.formatCoinWithCode(fee),
btcFormatter.formatCoinWithCode(miningFee),
CoinUtil.getFeePerByte(miningFee, txSize),
txSize / 1000d,
CoinUtil.getFeePerVbyte(miningFee, txVsize),
txVsize / 1000d,
type);
}
new Popup().headLine(Res.get("dao.feeTx.confirm", type))
@ -945,10 +945,10 @@ public class GUIUtil {
.show();
}
public static void showBsqFeeInfoPopup(Coin fee, Coin miningFee, int txSize, BsqFormatter bsqFormatter,
public static void showBsqFeeInfoPopup(Coin fee, Coin miningFee, int txVsize, BsqFormatter bsqFormatter,
CoinFormatter btcFormatter, String type,
Runnable actionHandler) {
showBsqFeeInfoPopup(fee, miningFee, null, txSize, bsqFormatter, btcFormatter, type, actionHandler);
showBsqFeeInfoPopup(fee, miningFee, null, txVsize, bsqFormatter, btcFormatter, type, actionHandler);
}
public static void setFitToRowsForTableView(TableView<?> tableView,

View file

@ -26,7 +26,7 @@ import java.time.Duration;
*/
public abstract class FeeRateProvider extends PriceProvider<FeeRate> {
public static final long MIN_FEE_RATE = 10; // satoshi/byte
public static final long MIN_FEE_RATE = 10; // satoshi/vbyte
public static final long MAX_FEE_RATE = 1000;
public FeeRateProvider(Duration refreshInterval) {

View file

@ -95,7 +95,7 @@ abstract class MempoolFeeRateProvider extends FeeRateProvider {
.map(Map.Entry::getValue)
.findFirst()
.map(r -> {
log.info("Retrieved estimated mining fee of {} sat/byte from {}", r, getMempoolApiHostname());
log.info("Retrieved estimated mining fee of {} sat/vbyte from {}", r, getMempoolApiHostname());
return r;
})
.map(r -> Math.max(r, MIN_FEE_RATE))

View file

@ -1546,7 +1546,7 @@ message PreferencesPayload {
bool use_tor_for_bitcoin_j = 12;
bool show_own_offers_in_offer_book = 13;
TradeCurrency preferred_trade_currency = 14;
int64 withdrawal_tx_fee_in_bytes = 15;
int64 withdrawal_tx_fee_in_vbytes = 15;
bool use_custom_withdrawal_tx_fee = 16;
double max_price_distance_in_percent = 17;
string offer_book_chart_screen_currency_code = 18;