remove btc fee service

This commit is contained in:
woodser 2022-12-09 13:20:23 +00:00
parent 3314eac881
commit 31dfdd7710
49 changed files with 66 additions and 1797 deletions

View file

@ -23,7 +23,6 @@ import bisq.core.api.model.MarketDepthInfo;
import bisq.core.api.model.MarketPriceInfo;
import bisq.core.api.model.PaymentAccountForm;
import bisq.core.api.model.PaymentAccountFormField;
import bisq.core.api.model.TxFeeRateInfo;
import bisq.core.app.AppStartupState;
import bisq.core.monetary.Price;
import bisq.core.offer.Offer;
@ -315,24 +314,6 @@ public class CoreApi {
walletsService.sendBtc(address, amount, txFeeRate, memo, callback);
}
public void getTxFeeRate(ResultHandler resultHandler) {
walletsService.getTxFeeRate(resultHandler);
}
public void setTxFeeRatePreference(long txFeeRate,
ResultHandler resultHandler) {
walletsService.setTxFeeRatePreference(txFeeRate, resultHandler);
}
public void unsetTxFeeRatePreference(ResultHandler resultHandler) {
walletsService.unsetTxFeeRatePreference(resultHandler);
}
public TxFeeRateInfo getMostRecentTxFeeRateInfo() {
return walletsService.getMostRecentTxFeeRateInfo();
}
public Transaction getTransaction(String txId) {
return walletsService.getTransaction(txId);
}

View file

@ -103,12 +103,10 @@ class CoreTradesService {
var useSavingsWallet = true;
// synchronize access to take offer model // TODO (woodser): to avoid synchronizing, don't use stateful model
Coin txFeeFromFeeService; // TODO (woodser): remove this and other unused fields
Coin takerFee;
Coin fundsNeededForTrade;
synchronized (takeOfferModel) {
takeOfferModel.initModel(offer, paymentAccount, useSavingsWallet);
txFeeFromFeeService = takeOfferModel.getTxFeeFromFeeService();
takerFee = takeOfferModel.getTakerFee();
fundsNeededForTrade = takeOfferModel.getFundsNeededForTrade();
log.info("Initiating take {} offer, {}", offer.isBuyOffer() ? "buy" : "sell", takeOfferModel);
@ -116,7 +114,6 @@ class CoreTradesService {
// take offer
tradeManager.onTakeOffer(offer.getAmount(),
txFeeFromFeeService,
takerFee,
fundsNeededForTrade,
offer,
@ -127,6 +124,7 @@ class CoreTradesService {
errorMessageHandler
);
} catch (Exception e) {
e.printStackTrace();
errorMessageHandler.handleErrorMessage(e.getMessage());
}
}

View file

@ -20,7 +20,6 @@ package bisq.core.api;
import bisq.core.api.model.AddressBalanceInfo;
import bisq.core.api.model.BalancesInfo;
import bisq.core.api.model.BtcBalanceInfo;
import bisq.core.api.model.TxFeeRateInfo;
import bisq.core.api.model.XmrBalanceInfo;
import bisq.core.app.AppStartupState;
import bisq.core.btc.Balances;
@ -31,15 +30,12 @@ import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.WalletsManager;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.provider.fee.FeeService;
import bisq.core.user.Preferences;
import bisq.core.util.FormattingUtils;
import bisq.core.util.coin.CoinFormatter;
import bisq.common.Timer;
import bisq.common.UserThread;
import bisq.common.handlers.ResultHandler;
import bisq.common.util.Utilities;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
@ -57,10 +53,6 @@ import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import org.bouncycastle.crypto.params.KeyParameter;
@ -97,8 +89,6 @@ class CoreWalletsService {
private final BtcWalletService btcWalletService;
private final XmrWalletService xmrWalletService;
private final CoinFormatter btcFormatter;
private final FeeService feeService;
private final Preferences preferences;
@Nullable
private Timer lockTimer;
@ -106,8 +96,6 @@ class CoreWalletsService {
@Nullable
private KeyParameter tempAesKey;
private final ListeningExecutorService executor = Utilities.getSingleThreadListeningExecutor("CoreWalletsService");
@Inject
public CoreWalletsService(AppStartupState appStartupState,
CoreContext coreContext,
@ -118,7 +106,6 @@ class CoreWalletsService {
BtcWalletService btcWalletService,
XmrWalletService xmrWalletService,
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter,
FeeService feeService,
Preferences preferences) {
this.appStartupState = appStartupState;
this.coreContext = coreContext;
@ -129,8 +116,6 @@ class CoreWalletsService {
this.btcWalletService = btcWalletService;
this.xmrWalletService = xmrWalletService;
this.btcFormatter = btcFormatter;
this.feeService = feeService;
this.preferences = preferences;
}
@Nullable
@ -311,58 +296,6 @@ class CoreWalletsService {
}
}
void getTxFeeRate(ResultHandler resultHandler) {
try {
@SuppressWarnings({"unchecked", "Convert2MethodRef"})
ListenableFuture<Void> future =
(ListenableFuture<Void>) executor.submit(() -> feeService.requestFees());
Futures.addCallback(future, new FutureCallback<>() {
@Override
public void onSuccess(@Nullable Void ignored) {
resultHandler.handleResult();
}
@Override
public void onFailure(Throwable t) {
log.error("", t);
throw new IllegalStateException("could not request fees from fee service", t);
}
}, MoreExecutors.directExecutor());
} catch (Exception ex) {
log.error("", ex);
throw new IllegalStateException("could not request fees from fee service", ex);
}
}
void setTxFeeRatePreference(long txFeeRate,
ResultHandler resultHandler) {
long minFeePerVbyte = feeService.getMinFeePerVByte();
if (txFeeRate < minFeePerVbyte)
throw new IllegalStateException(
format("tx fee rate preference must be >= %d sats/byte", minFeePerVbyte));
preferences.setUseCustomWithdrawalTxFee(true);
Coin satsPerByte = Coin.valueOf(txFeeRate);
preferences.setWithdrawalTxFeeInVbytes(satsPerByte.value);
getTxFeeRate(resultHandler);
}
void unsetTxFeeRatePreference(ResultHandler resultHandler) {
preferences.setUseCustomWithdrawalTxFee(false);
getTxFeeRate(resultHandler);
}
TxFeeRateInfo getMostRecentTxFeeRateInfo() {
return new TxFeeRateInfo(
preferences.isUseCustomWithdrawalTxFee(),
preferences.getWithdrawalTxFeeInVbytes(),
feeService.getMinFeePerVByte(),
feeService.getTxFeePerVbyte().value,
feeService.getLastRequest());
}
Transaction getTransaction(String txId) {
if (txId.length() != 64)
throw new IllegalArgumentException(format("%s is not a transaction id", txId));

View file

@ -1,81 +0,0 @@
/*
* This file is part of Haveno.
*
* Haveno is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Haveno is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.api.model;
import bisq.common.Payload;
import lombok.EqualsAndHashCode;
import lombok.Getter;
@EqualsAndHashCode
@Getter
public class TxFeeRateInfo implements Payload {
private final boolean useCustomTxFeeRate;
private final long customTxFeeRate;
private final long minFeeServiceRate;
private final long feeServiceRate;
private final long lastFeeServiceRequestTs;
public TxFeeRateInfo(boolean useCustomTxFeeRate,
long customTxFeeRate,
long minFeeServiceRate,
long feeServiceRate,
long lastFeeServiceRequestTs) {
this.useCustomTxFeeRate = useCustomTxFeeRate;
this.customTxFeeRate = customTxFeeRate;
this.minFeeServiceRate = minFeeServiceRate;
this.feeServiceRate = feeServiceRate;
this.lastFeeServiceRequestTs = lastFeeServiceRequestTs;
}
//////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
//////////////////////////////////////////////////////////////////////////////////////
@Override
public bisq.proto.grpc.TxFeeRateInfo toProtoMessage() {
return bisq.proto.grpc.TxFeeRateInfo.newBuilder()
.setUseCustomTxFeeRate(useCustomTxFeeRate)
.setCustomTxFeeRate(customTxFeeRate)
.setMinFeeServiceRate(minFeeServiceRate)
.setFeeServiceRate(feeServiceRate)
.setLastFeeServiceRequestTs(lastFeeServiceRequestTs)
.build();
}
@SuppressWarnings("unused")
public static TxFeeRateInfo fromProto(bisq.proto.grpc.TxFeeRateInfo proto) {
return new TxFeeRateInfo(proto.getUseCustomTxFeeRate(),
proto.getCustomTxFeeRate(),
proto.getMinFeeServiceRate(),
proto.getFeeServiceRate(),
proto.getLastFeeServiceRequestTs());
}
@Override
public String toString() {
return "TxFeeRateInfo{" + "\n" +
" useCustomTxFeeRate=" + useCustomTxFeeRate + "\n" +
", customTxFeeRate=" + customTxFeeRate + " sats/byte" + "\n" +
", minFeeServiceRate=" + minFeeServiceRate + " sats/byte" + "\n" +
", feeServiceRate=" + feeServiceRate + " sats/byte" + "\n" +
", lastFeeServiceRequestTs=" + lastFeeServiceRequestTs + "\n" +
'}';
}
}

View file

@ -1,212 +0,0 @@
/*
* This file is part of Haveno.
*
* Haveno is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Haveno is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.api.model;
import bisq.common.Payload;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Transaction;
import java.util.Map;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import static java.util.Objects.requireNonNull;
@EqualsAndHashCode
@Getter
public class TxInfo implements Payload {
// The client cannot see an instance of an org.bitcoinj.core.Transaction. We use the
// lighter weight TxInfo proto wrapper instead, containing just enough fields to
// view some transaction details. A block explorer or bitcoin-core client can be
// used to see more detail.
private final String txId;
private final long inputSum;
private final long outputSum;
private final long fee;
private final int size;
private final boolean isPending;
private final String memo;
public TxInfo(TxInfoBuilder builder) {
this.txId = builder.txId;
this.inputSum = builder.inputSum;
this.outputSum = builder.outputSum;
this.fee = builder.fee;
this.size = builder.size;
this.isPending = builder.isPending;
this.memo = builder.memo;
}
public static TxInfo toTxInfo(Transaction transaction) {
if (transaction == null)
throw new IllegalStateException("server created a null transaction");
if (transaction.getFee() != null)
return new TxInfoBuilder()
.withTxId(transaction.getTxId().toString())
.withInputSum(transaction.getInputSum().value)
.withOutputSum(transaction.getOutputSum().value)
.withFee(transaction.getFee().value)
.withSize(transaction.getMessageSize())
.withIsPending(transaction.isPending())
.withMemo(transaction.getMemo())
.build();
else
return new TxInfoBuilder()
.withTxId(transaction.getTxId().toString())
.withInputSum(transaction.getInputSum().value)
.withOutputSum(transaction.getOutputSum().value)
// Do not set fee == null.
.withSize(transaction.getMessageSize())
.withIsPending(transaction.isPending())
.withMemo(transaction.getMemo())
.build();
}
//////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
//////////////////////////////////////////////////////////////////////////////////////
@Override
public bisq.proto.grpc.TxInfo toProtoMessage() {
return bisq.proto.grpc.TxInfo.newBuilder()
.setTxId(txId)
.setInputSum(inputSum)
.setOutputSum(outputSum)
.setFee(fee)
.setSize(size)
.setIsPending(isPending)
.setMemo(memo == null ? "" : memo)
.build();
}
@SuppressWarnings("unused")
public static TxInfo fromProto(bisq.proto.grpc.TxInfo proto) {
return new TxInfoBuilder()
.withTxId(proto.getTxId())
.withInputSum(proto.getInputSum())
.withOutputSum(proto.getOutputSum())
.withFee(proto.getFee())
.withSize(proto.getSize())
.withIsPending(proto.getIsPending())
.withMemo(proto.getMemo())
.build();
}
public static class TxInfoBuilder {
private String txId;
private long inputSum;
private long outputSum;
private long fee;
private int size;
private boolean isPending;
private String memo;
public TxInfoBuilder withTxId(String txId) {
this.txId = txId;
return this;
}
public TxInfoBuilder withInputSum(long inputSum) {
this.inputSum = inputSum;
return this;
}
public TxInfoBuilder withOutputSum(long outputSum) {
this.outputSum = outputSum;
return this;
}
public TxInfoBuilder withFee(long fee) {
this.fee = fee;
return this;
}
public TxInfoBuilder withSize(int size) {
this.size = size;
return this;
}
public TxInfoBuilder withIsPending(boolean isPending) {
this.isPending = isPending;
return this;
}
public TxInfoBuilder withMemo(String memo) {
this.memo = memo;
return this;
}
public TxInfo build() {
return new TxInfo(this);
}
}
@Override
public String toString() {
return "TxInfo{" + "\n" +
" txId='" + txId + '\'' + "\n" +
", inputSum=" + inputSum + "\n" +
", outputSum=" + outputSum + "\n" +
", fee=" + fee + "\n" +
", size=" + size + "\n" +
", isPending=" + isPending + "\n" +
", memo='" + memo + '\'' + "\n" +
'}';
}
public static String getTransactionDetailString(Transaction tx) {
if (tx == null)
throw new IllegalArgumentException("Cannot print details for null transaction");
StringBuilder builder = new StringBuilder("Transaction " + requireNonNull(tx).getTxId() + ":").append("\n");
builder.append("\tisPending: ").append(tx.isPending()).append("\n");
builder.append("\tfee: ").append(tx.getFee()).append("\n");
builder.append("\tweight: ").append(tx.getWeight()).append("\n");
builder.append("\tVsize: ").append(tx.getVsize()).append("\n");
builder.append("\tinputSum: ").append(tx.getInputSum()).append("\n");
builder.append("\toutputSum: ").append(tx.getOutputSum()).append("\n");
Map<Sha256Hash, Integer> appearsInHashes = tx.getAppearsInHashes();
if (appearsInHashes != null)
builder.append("\tappearsInHashes: yes, count: ").append(appearsInHashes.size()).append("\n");
else
builder.append("\tappearsInHashes: ").append("no").append("\n");
builder.append("\tanyOutputSpent: ").append(tx.isAnyOutputSpent()).append("\n");
builder.append("\tupdateTime: ").append(tx.getUpdateTime()).append("\n");
builder.append("\tincludedInBestChainAt: ").append(tx.getIncludedInBestChainAt()).append("\n");
builder.append("\thasWitnesses: ").append(tx.hasWitnesses()).append("\n");
builder.append("\tlockTime: ").append(tx.getLockTime()).append("\n");
builder.append("\tversion: ").append(tx.getVersion()).append("\n");
builder.append("\thasConfidence: ").append(tx.hasConfidence()).append("\n");
builder.append("\tsigOpCount: ").append(tx.getSigOpCount()).append("\n");
builder.append("\tisTimeLocked: ").append(tx.isTimeLocked()).append("\n");
builder.append("\thasRelativeLockTime: ").append(tx.hasRelativeLockTime()).append("\n");
builder.append("\tisOptInFullRBF: ").append(tx.isOptInFullRBF()).append("\n");
builder.append("\tpurpose: ").append(tx.getPurpose()).append("\n");
builder.append("\texchangeRate: ").append(tx.getExchangeRate()).append("\n");
builder.append("\tmemo: ").append(tx.getMemo()).append("\n");
return builder.toString();
}
}

View file

@ -33,7 +33,6 @@ import bisq.core.offer.OpenOfferManager;
import bisq.core.offer.TriggerPriceService;
import bisq.core.payment.AmazonGiftCardAccount;
import bisq.core.payment.RevolutAccount;
import bisq.core.provider.fee.FeeService;
import bisq.core.provider.mempool.MempoolService;
import bisq.core.provider.price.PriceFeedService;
import bisq.core.support.dispute.arbitration.ArbitrationManager;
@ -88,7 +87,6 @@ public class DomainInitialisation {
private final RefundAgentManager refundAgentManager;
private final PrivateNotificationManager privateNotificationManager;
private final P2PService p2PService;
private final FeeService feeService;
private final TradeStatisticsManager tradeStatisticsManager;
private final AccountAgeWitnessService accountAgeWitnessService;
private final SignedWitnessService signedWitnessService;
@ -122,7 +120,6 @@ public class DomainInitialisation {
RefundAgentManager refundAgentManager,
PrivateNotificationManager privateNotificationManager,
P2PService p2PService,
FeeService feeService,
TradeStatisticsManager tradeStatisticsManager,
AccountAgeWitnessService accountAgeWitnessService,
SignedWitnessService signedWitnessService,
@ -154,7 +151,6 @@ public class DomainInitialisation {
this.refundAgentManager = refundAgentManager;
this.privateNotificationManager = privateNotificationManager;
this.p2PService = p2PService;
this.feeService = feeService;
this.tradeStatisticsManager = tradeStatisticsManager;
this.accountAgeWitnessService = accountAgeWitnessService;
this.signedWitnessService = signedWitnessService;
@ -207,9 +203,6 @@ public class DomainInitialisation {
p2PService.onAllServicesInitialized();
feeService.onAllServicesInitialized();
tradeStatisticsManager.onAllServicesInitialized();
accountAgeWitnessService.onAllServicesInitialized();

View file

@ -25,7 +25,6 @@ import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.WalletsManager;
import bisq.core.locale.Res;
import bisq.core.offer.OpenOfferManager;
import bisq.core.provider.fee.FeeService;
import bisq.core.trade.TradeManager;
import bisq.core.user.Preferences;
import bisq.core.util.FormattingUtils;
@ -69,7 +68,6 @@ public class WalletAppSetup {
private final WalletsManager walletsManager;
private final WalletsSetup walletsSetup;
private final CoreMoneroConnectionsService connectionService;
private final FeeService feeService;
private final Config config;
private final Preferences preferences;
@ -94,14 +92,12 @@ public class WalletAppSetup {
WalletsManager walletsManager,
WalletsSetup walletsSetup,
CoreMoneroConnectionsService connectionService,
FeeService feeService,
Config config,
Preferences preferences) {
this.coreContext = coreContext;
this.walletsManager = walletsManager;
this.walletsSetup = walletsSetup;
this.connectionService = connectionService;
this.feeService = feeService;
this.config = config;
this.preferences = preferences;
this.useTorForBTC.set(preferences.getUseTorForBitcoinJ());
@ -121,9 +117,8 @@ public class WalletAppSetup {
ObjectProperty<Throwable> walletServiceException = new SimpleObjectProperty<>();
btcInfoBinding = EasyBind.combine(connectionService.downloadPercentageProperty(), // TODO (woodser): update to XMR
connectionService.chainHeightProperty(),
feeService.feeUpdateCounterProperty(),
walletServiceException,
(downloadPercentage, chainHeight, feeUpdate, exception) -> {
(downloadPercentage, chainHeight, exception) -> {
String result;
if (exception == null) {
double percentage = (double) downloadPercentage;

View file

@ -29,7 +29,6 @@ import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.provider.ProvidersRepository;
import bisq.core.provider.fee.FeeProvider;
import bisq.core.provider.fee.FeeService;
import bisq.core.provider.price.PriceFeedService;
import bisq.common.app.AppModule;
@ -99,8 +98,6 @@ public class BitcoinModule extends AppModule {
bind(ProvidersRepository.class).in(Singleton.class);
bind(FeeProvider.class).in(Singleton.class);
bind(PriceFeedService.class).in(Singleton.class);
bind(FeeService.class).in(Singleton.class);
bind(TxFeeEstimationService.class).in(Singleton.class);
}
}

View file

@ -1,205 +0,0 @@
/*
* This file is part of Haveno.
*
* Haveno is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Haveno is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.btc;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.provider.fee.FeeService;
import bisq.core.user.Preferences;
import bisq.common.util.Tuple2;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.InsufficientMoneyException;
import javax.inject.Inject;
import com.google.common.annotations.VisibleForTesting;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkArgument;
/**
* Util class for getting the estimated tx fee for maker or taker fee tx.
*/
@Slf4j
public class TxFeeEstimationService {
// 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_VSIZE = 175;
private static int DEPOSIT_TX_VSIZE = 233;
private static int MAX_ITERATIONS = 10;
private final FeeService feeService;
private final BtcWalletService btcWalletService;
private final Preferences preferences;
@Inject
public TxFeeEstimationService(FeeService feeService,
BtcWalletService btcWalletService,
Preferences preferences) {
this.feeService = feeService;
this.btcWalletService = btcWalletService;
this.preferences = preferences;
}
public Tuple2<Coin, Integer> getEstimatedFeeAndTxVsizeForTaker(Coin fundsNeededForTrade, Coin tradeFee) {
return getEstimatedFeeAndTxVsize(true,
fundsNeededForTrade,
tradeFee,
feeService,
btcWalletService,
preferences);
}
public Tuple2<Coin, Integer> getEstimatedFeeAndTxVsizeForMaker(Coin reservedFundsForOffer,
Coin tradeFee) {
return getEstimatedFeeAndTxVsize(false,
reservedFundsForOffer,
tradeFee,
feeService,
btcWalletService,
preferences);
}
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 {
estimatedTxVsize = getEstimatedTxVsize(List.of(tradeFee, amount), estimatedTxVsize, txFeePerVbyte, btcWalletService);
} catch (InsufficientMoneyException e) {
if (isTaker) {
// 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 vsize of {} vbytes.", estimatedTxVsize);
}
Coin txFee;
int vsize;
if (isTaker) {
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 {
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, vsize);
}
public Tuple2<Coin, Integer> getEstimatedFeeAndTxVsize(Coin amount,
BtcWalletService btcWalletService) {
Coin txFeePerVbyte = btcWalletService.getTxFeeForWithdrawalPerVbyte();
// We start with min taker fee vsize of 175
int estimatedTxVsize = TYPICAL_TX_WITH_1_INPUT_VSIZE;
try {
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 vsize of {} vbytes.", estimatedTxVsize);
}
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, estimatedTxVsize);
}
// 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 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 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 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 getEstimatedTxVsize(List<Coin> outputValues,
int initialEstimatedTxVsize,
Coin txFeePerVbyte,
BtcWalletService btcWalletService)
throws InsufficientMoneyException {
boolean isInTolerance;
int estimatedTxVsize = initialEstimatedTxVsize;
int realTxVsize;
int counter = 0;
do {
Coin txFee = txFeePerVbyte.multiply(estimatedTxVsize);
realTxVsize = btcWalletService.getEstimatedFeeTxVsize(outputValues, txFee);
isInTolerance = isInTolerance(estimatedTxVsize, realTxVsize, 0.2);
if (!isInTolerance) {
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%. " +
"realTxVsize={}, estimatedTxVsize={}",
realTxVsize, estimatedTxVsize);
}
return estimatedTxVsize;
}
@VisibleForTesting
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) estimatedVsize / (double) txVsize));
return deviation <= tolerance;
}
}

View file

@ -25,7 +25,6 @@ import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.model.AddressEntryList;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.http.MemPoolSpaceTxBroadcaster;
import bisq.core.provider.fee.FeeService;
import bisq.core.user.Preferences;
import bisq.common.handlers.ErrorMessageHandler;
@ -88,11 +87,9 @@ public class BtcWalletService extends WalletService {
@Inject
public BtcWalletService(WalletsSetup walletsSetup,
AddressEntryList addressEntryList,
Preferences preferences,
FeeService feeService) {
Preferences preferences) {
super(walletsSetup,
preferences,
feeService);
preferences);
this.addressEntryList = addressEntryList;
@ -574,6 +571,10 @@ public class BtcWalletService extends WalletService {
// Withdrawal Fee calculation
///////////////////////////////////////////////////////////////////////////////////////////
public Coin getTxFeeForWithdrawalPerVbyte() {
throw new RuntimeException("BTC fee estimation removed");
}
public Transaction getFeeEstimationTransaction(String fromAddress,
String toAddress,
Coin amount,

View file

@ -24,7 +24,6 @@ import bisq.core.btc.listeners.BalanceListener;
import bisq.core.btc.listeners.TxConfidenceListener;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.http.MemPoolSpaceTxBroadcaster;
import bisq.core.provider.fee.FeeService;
import bisq.core.user.Preferences;
import bisq.common.config.Config;
@ -117,7 +116,6 @@ import monero.wallet.model.MoneroTxWallet;
public abstract class WalletService {
protected final WalletsSetup walletsSetup;
protected final Preferences preferences;
protected final FeeService feeService;
protected final NetworkParameters params;
private final BisqWalletListener walletEventListener = new BisqWalletListener();
private final CopyOnWriteArraySet<AddressConfidenceListener> addressConfidenceListeners = new CopyOnWriteArraySet<>();
@ -140,11 +138,9 @@ public abstract class WalletService {
@Inject
WalletService(WalletsSetup walletsSetup,
Preferences preferences,
FeeService feeService) {
Preferences preferences) {
this.walletsSetup = walletsSetup;
this.preferences = preferences;
this.feeService = feeService;
params = walletsSetup.getParams();
@ -519,14 +515,6 @@ public abstract class WalletService {
return getBalanceForAddress(getAddressFromOutput(output));
}
public Coin getTxFeeForWithdrawalPerVbyte() {
Coin fee = (preferences.isUseCustomWithdrawalTxFee()) ?
Coin.valueOf(preferences.getWithdrawalTxFeeInVbytes()) :
feeService.getTxFeePerVbyte();
log.info("tx fee = " + fee.toFriendlyString());
return fee;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Tx outputs
@ -578,7 +566,6 @@ public abstract class WalletService {
throws InsufficientMoneyException, AddressFormatException {
SendRequest sendRequest = SendRequest.emptyWallet(Address.fromString(params, toAddress));
sendRequest.fee = Coin.ZERO;
sendRequest.feePerKb = getTxFeeForWithdrawalPerVbyte().multiply(1000);
sendRequest.aesKey = aesKey;
Wallet.SendResult sendResult = wallet.sendCoins(sendRequest);
printTx("empty btc wallet", sendResult.tx);

View file

@ -17,7 +17,6 @@
package bisq.core.offer;
import bisq.core.btc.TxFeeEstimationService;
import bisq.core.btc.wallet.Restrictions;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.locale.CurrencyUtil;
@ -38,7 +37,6 @@ import bisq.network.p2p.P2PService;
import bisq.common.app.Version;
import bisq.common.crypto.PubKeyRingProvider;
import bisq.common.util.Tuple2;
import bisq.common.util.Utilities;
import org.bitcoinj.core.Coin;
@ -59,7 +57,6 @@ import static bisq.core.payment.payload.PaymentMethod.HAL_CASH_ID;
@Singleton
public class CreateOfferService {
private final OfferUtil offerUtil;
private final TxFeeEstimationService txFeeEstimationService;
private final PriceFeedService priceFeedService;
private final P2PService p2PService;
private final PubKeyRingProvider pubKeyRingProvider;
@ -75,7 +72,6 @@ public class CreateOfferService {
@Inject
public CreateOfferService(OfferUtil offerUtil,
TxFeeEstimationService txFeeEstimationService,
PriceFeedService priceFeedService,
P2PService p2PService,
PubKeyRingProvider pubKeyRingProvider,
@ -84,7 +80,6 @@ public class CreateOfferService {
TradeStatisticsManager tradeStatisticsManager,
ArbitratorManager arbitratorManager) {
this.offerUtil = offerUtil;
this.txFeeEstimationService = txFeeEstimationService;
this.priceFeedService = priceFeedService;
this.p2PService = p2PService;
this.pubKeyRingProvider = pubKeyRingProvider;
@ -227,18 +222,6 @@ public class CreateOfferService {
return offer;
}
public Tuple2<Coin, Integer> getEstimatedFeeAndTxVsize(Coin amount,
OfferDirection direction,
double buyerSecurityDeposit,
double sellerSecurityDeposit) {
Coin reservedFundsForOffer = getReservedFundsForOffer(direction,
amount,
buyerSecurityDeposit,
sellerSecurityDeposit);
return txFeeEstimationService.getEstimatedFeeAndTxVsizeForMaker(reservedFundsForOffer,
offerUtil.getMakerFee(amount));
}
public Coin getReservedFundsForOffer(OfferDirection direction,
Coin amount,
double buyerSecurityDeposit,

View file

@ -27,9 +27,9 @@ import bisq.core.monetary.Volume;
import bisq.core.payment.CashByMailAccount;
import bisq.core.payment.F2FAccount;
import bisq.core.payment.PaymentAccount;
import bisq.core.provider.fee.FeeService;
import bisq.core.provider.price.MarketPrice;
import bisq.core.provider.price.PriceFeedService;
import bisq.core.trade.HavenoUtils;
import bisq.core.trade.statistics.ReferralIdService;
import bisq.core.user.AutoConfirmSettings;
import bisq.core.user.Preferences;
@ -191,8 +191,8 @@ public class OfferUtil {
@Nullable
public Coin getTakerFee(@Nullable Coin amount) {
if (amount != null) {
Coin feePerBtc = CoinUtil.getFeePerBtc(FeeService.getTakerFeePerBtc(), amount);
return CoinUtil.maxCoin(feePerBtc, FeeService.getMinTakerFee());
Coin feePerBtc = CoinUtil.getFeePerBtc(HavenoUtils.getTakerFeePerBtc(), amount);
return CoinUtil.maxCoin(feePerBtc, HavenoUtils.getMinTakerFee());
} else {
return null;
}

View file

@ -27,7 +27,6 @@ import bisq.core.offer.Offer;
import bisq.core.offer.OfferUtil;
import bisq.core.payment.PaymentAccount;
import bisq.core.payment.payload.PaymentMethod;
import bisq.core.provider.fee.FeeService;
import bisq.core.provider.price.PriceFeedService;
import bisq.common.taskrunner.Model;
@ -37,8 +36,6 @@ import org.bitcoinj.core.Coin;
import javax.inject.Inject;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@ -60,7 +57,6 @@ public class TakeOfferModel implements Model {
// Immutable
private final AccountAgeWitnessService accountAgeWitnessService;
private final XmrWalletService xmrWalletService;
private final FeeService feeService;
private final OfferUtil offerUtil;
private final PriceFeedService priceFeedService;
@ -75,11 +71,6 @@ public class TakeOfferModel implements Model {
private Coin securityDeposit;
private boolean useSavingsWallet;
// Use an average of a typical trade fee tx with 1 input, deposit tx and payout tx.
private final int feeTxVsize = 192; // (175+233+169)/3
private Coin txFeePerVbyteFromFeeService;
@Getter
private Coin txFeeFromFeeService;
@Getter
private Coin takerFee;
@Getter
@ -98,12 +89,10 @@ public class TakeOfferModel implements Model {
@Inject
public TakeOfferModel(AccountAgeWitnessService accountAgeWitnessService,
XmrWalletService xmrWalletService,
FeeService feeService,
OfferUtil offerUtil,
PriceFeedService priceFeedService) {
this.accountAgeWitnessService = accountAgeWitnessService;
this.xmrWalletService = xmrWalletService;
this.feeService = feeService;
this.offerUtil = offerUtil;
this.priceFeedService = priceFeedService;
}
@ -124,7 +113,6 @@ public class TakeOfferModel implements Model {
: offer.getSellerSecurityDeposit();
this.takerFee = offerUtil.getTakerFee(amount);
calculateTxFees();
calculateVolume();
calculateTotalToPay();
offer.resetState();
@ -137,46 +125,12 @@ public class TakeOfferModel implements Model {
// empty
}
private void calculateTxFees() {
// Taker pays 3 times the tx fee (taker fee, deposit, payout) because the mining
// fee might be different when maker created the offer and reserved his funds.
// Taker creates at least taker fee and deposit tx at nearly the same moment.
// Just the payout will be later and still could lead to issues if the required
// fee changed a lot in the meantime. using RBF and/or 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 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 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
txFeePerVbyteFromFeeService = getTxFeePerVbyte();
txFeeFromFeeService = offerUtil.getTxFeeByVsize(txFeePerVbyteFromFeeService, feeTxVsize);
log.info("{} txFeePerVbyte = {}", feeService.getClass().getSimpleName(), txFeePerVbyteFromFeeService);
}
private Coin getTxFeePerVbyte() {
try {
CompletableFuture<Void> feeRequestFuture = CompletableFuture.runAsync(feeService::requestFees);
feeRequestFuture.get(); // Block until async fee request is complete.
return feeService.getTxFeePerVbyte();
} catch (InterruptedException | ExecutionException e) {
throw new IllegalStateException("Could not request fees from fee service.", e);
}
}
private void calculateTotalToPay() {
// Taker pays 2 times the tx fee because the mining fee might be different when
// maker created the offer and reserved his funds, so that would not work well
// with dynamic fees. The mining fee for the takeOfferFee tx is deducted from
// the createOfferFee and not visible to the trader.
Coin feeAndSecDeposit = getTotalTxFee().add(securityDeposit).add(takerFee);
Coin feeAndSecDeposit = securityDeposit.add(takerFee);
totalToPayAsCoin = offer.isBuyOffer()
? feeAndSecDeposit.add(amount)
@ -212,35 +166,10 @@ public class TakeOfferModel implements Model {
offer.getMirroredDirection());
}
public Coin getTotalTxFee() {
return txFeeFromFeeService.add(getTxFeeForDepositTx()).add(getTxFeeForPayoutTx());
}
@NotNull
public Coin getFundsNeededForTrade() {
// If taking a buy offer, taker needs to reserve the offer.amt too.
return securityDeposit
.add(getTxFeeForDepositTx())
.add(getTxFeeForPayoutTx())
.add(offer.isBuyOffer() ? amount : ZERO);
}
private Coin getTxFeeForDepositTx() {
// TODO fix with new trade protocol!
// 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;
}
private Coin getTxFeeForPayoutTx() {
// TODO fix with new trade protocol!
// 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;
return securityDeposit.add(offer.isBuyOffer() ? amount : ZERO);
}
private void validateModelInputs() {
@ -264,8 +193,6 @@ public class TakeOfferModel implements Model {
this.takerFee = null;
this.totalAvailableBalance = null;
this.totalToPayAsCoin = null;
this.txFeeFromFeeService = null;
this.txFeePerVbyteFromFeeService = null;
this.useSavingsWallet = true;
this.volume = null;
}
@ -281,9 +208,6 @@ public class TakeOfferModel implements Model {
", addressEntry=" + addressEntry + "\n" +
", amount=" + amount + "\n" +
", securityDeposit=" + securityDeposit + "\n" +
", feeTxVsize=" + feeTxVsize + "\n" +
", txFeePerVbyteFromFeeService=" + txFeePerVbyteFromFeeService + "\n" +
", txFeeFromFeeService=" + txFeeFromFeeService + "\n" +
", takerFee=" + takerFee + "\n" +
", totalToPayAsCoin=" + totalToPayAsCoin + "\n" +
", missingCoin=" + missingCoin + "\n" +

View file

@ -1,191 +0,0 @@
/*
* This file is part of Haveno.
*
* Haveno is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Haveno is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.provider.fee;
import bisq.common.UserThread;
import bisq.common.config.Config;
import bisq.common.handlers.FaultHandler;
import bisq.common.util.Tuple2;
import org.bitcoinj.utils.MonetaryFormat;
import org.bitcoinj.core.Coin;
import com.google.inject.Inject;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import java.time.Instant;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import bisq.core.util.ParsingUtils;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
public class FeeService {
///////////////////////////////////////////////////////////////////////////////////////////
// Static
///////////////////////////////////////////////////////////////////////////////////////////
// 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;
private static final MonetaryFormat btcCoinFormat = Config.baseCurrencyNetworkParameters().getMonetaryFormat();
public static Coin getMakerFeePerBtc() {
return ParsingUtils.parseToCoin("0.001", btcCoinFormat);
}
public static Coin getMinMakerFee() {
return ParsingUtils.parseToCoin("0.00005", btcCoinFormat);
}
public static Coin getTakerFeePerBtc() {
return ParsingUtils.parseToCoin("0.003", btcCoinFormat);
}
public static Coin getMinTakerFee() {
return ParsingUtils.parseToCoin("0.00005", btcCoinFormat);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Class fields
///////////////////////////////////////////////////////////////////////////////////////////
private final FeeProvider feeProvider;
private final IntegerProperty feeUpdateCounter = new SimpleIntegerProperty(0);
private long txFeePerVbyte = BTC_DEFAULT_TX_FEE;
private Map<String, Long> timeStampMap;
@Getter
private long lastRequest;
@Getter
private long minFeePerVByte;
private long epochInSecondAtLastRequest;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public FeeService(FeeProvider feeProvider) {
this.feeProvider = feeProvider;
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
public void onAllServicesInitialized() {
minFeePerVByte = Config.baseCurrencyNetwork().getDefaultMinFeePerVbyte();
requestFees();
// We update all 5 min.
UserThread.runPeriodically(this::requestFees, 5, TimeUnit.MINUTES);
}
public void requestFees() {
requestFees(null, null);
}
public void requestFees(Runnable resultHandler) {
requestFees(resultHandler, null);
}
public void requestFees(@Nullable Runnable resultHandler, @Nullable FaultHandler faultHandler) {
long now = Instant.now().getEpochSecond();
// We all requests only each 2 minutes
if (now - lastRequest > MIN_PAUSE_BETWEEN_REQUESTS_IN_MIN * 60) {
lastRequest = now;
FeeRequest feeRequest = new FeeRequest();
SettableFuture<Tuple2<Map<String, Long>, Map<String, Long>>> future = feeRequest.getFees(feeProvider);
Futures.addCallback(future, new FutureCallback<Tuple2<Map<String, Long>, Map<String, Long>>>() {
@Override
public void onSuccess(@Nullable Tuple2<Map<String, Long>, Map<String, Long>> result) {
UserThread.execute(() -> {
checkNotNull(result, "Result must not be null at getFees");
timeStampMap = result.first;
epochInSecondAtLastRequest = timeStampMap.get(Config.BTC_FEES_TS);
final Map<String, Long> map = result.second;
txFeePerVbyte = map.get(Config.BTC_TX_FEE);
minFeePerVByte = map.get(Config.BTC_MIN_TX_FEE);
if (txFeePerVbyte < minFeePerVByte) {
log.warn("The delivered fee of {} sat/vbyte is smaller than the min. default fee of {} sat/vbyte", txFeePerVbyte, minFeePerVByte);
txFeePerVbyte = minFeePerVByte;
}
feeUpdateCounter.set(feeUpdateCounter.get() + 1);
log.info("BTC tx fee: txFeePerVbyte={} minFeePerVbyte={}", txFeePerVbyte, minFeePerVByte);
if (resultHandler != null)
resultHandler.run();
});
}
@Override
public void onFailure(@NotNull Throwable throwable) {
log.warn("Could not load fees. feeProvider={}, error={}", feeProvider.toString(), throwable.toString());
if (faultHandler != null)
UserThread.execute(() -> faultHandler.handleFault("Could not load fees", throwable));
}
}, MoreExecutors.directExecutor());
} else {
log.debug("We got a requestFees called again before min pause of {} minutes has passed.", MIN_PAUSE_BETWEEN_REQUESTS_IN_MIN);
UserThread.execute(() -> {
if (resultHandler != null)
resultHandler.run();
});
}
}
public Coin getTxFee(int vsizeInVbytes) {
return getTxFeePerVbyte().multiply(vsizeInVbytes);
}
public Coin getTxFeePerVbyte() {
return Coin.valueOf(txFeePerVbyte);
}
public ReadOnlyIntegerProperty feeUpdateCounterProperty() {
return feeUpdateCounter;
}
public boolean isFeeAvailable() {
return feeUpdateCounter.get() > 0;
}
}

View file

@ -27,6 +27,7 @@ import bisq.core.trade.messages.InitTradeRequest;
import bisq.core.trade.messages.PaymentReceivedMessage;
import bisq.core.trade.messages.PaymentSentMessage;
import bisq.core.util.JsonUtil;
import bisq.core.util.ParsingUtils;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
@ -39,7 +40,7 @@ import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.bitcoinj.core.Coin;
import org.bitcoinj.utils.MonetaryFormat;
import com.google.common.base.CaseFormat;
import com.google.common.base.Charsets;
@ -95,12 +96,29 @@ public class HavenoUtils {
public static long xmrToCentineros(double xmr) {
return atomicUnitsToCentineros(xmrToAtomicUnits(xmr));
}
private static final MonetaryFormat xmrCoinFormat = Config.baseCurrencyNetworkParameters().getMonetaryFormat();
public static Coin getMakerFeePerBtc() {
return ParsingUtils.parseToCoin("0.001", xmrCoinFormat);
}
public static Coin getMinMakerFee() {
return ParsingUtils.parseToCoin("0.00005", xmrCoinFormat);
}
public static Coin getTakerFeePerBtc() {
return ParsingUtils.parseToCoin("0.003", xmrCoinFormat);
}
public static Coin getMinTakerFee() {
return ParsingUtils.parseToCoin("0.00005", xmrCoinFormat);
}
/**
* Get address to collect trade fees.
*
* TODO: move to config constants?
*
* @return the address which collects trade fees
*/
public static String getTradeFeeAddress() {

View file

@ -29,7 +29,6 @@ import bisq.core.offer.OpenOffer;
import bisq.core.offer.OpenOfferManager;
import bisq.core.offer.SignedOffer;
import bisq.core.offer.availability.OfferAvailabilityModel;
import bisq.core.provider.fee.FeeService;
import bisq.core.provider.price.PriceFeedService;
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
@ -458,8 +457,8 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
}
// compute expected taker fee
Coin feePerBtc = CoinUtil.getFeePerBtc(FeeService.getTakerFeePerBtc(), Coin.valueOf(offer.getOfferPayload().getAmount()));
Coin takerFee = CoinUtil.maxCoin(feePerBtc, FeeService.getMinTakerFee());
Coin feePerBtc = CoinUtil.getFeePerBtc(HavenoUtils.getTakerFeePerBtc(), Coin.valueOf(offer.getOfferPayload().getAmount()));
Coin takerFee = CoinUtil.maxCoin(feePerBtc, HavenoUtils.getMinTakerFee());
// create arbitrator trade
trade = new ArbitratorTrade(offer,
@ -691,7 +690,6 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
// First we check if offer is still available then we create the trade with the protocol
public void onTakeOffer(Coin amount,
Coin txFee,
Coin takerFee,
Coin fundsNeededForTrade,
Offer offer,

View file

@ -29,7 +29,6 @@ import bisq.core.locale.GlobalSettings;
import bisq.core.locale.TradeCurrency;
import bisq.core.payment.PaymentAccount;
import bisq.core.payment.PaymentAccountUtil;
import bisq.core.provider.fee.FeeService;
import bisq.core.xmr.MoneroNodeSettings;
import bisq.network.p2p.network.BridgeAddressProvider;
@ -162,7 +161,6 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
private final PersistenceManager<PreferencesPayload> persistenceManager;
private final Config config;
private final FeeService feeService;
private final LocalBitcoinNode localBitcoinNode;
private final String btcNodesFromOptions;
@Getter
@ -175,13 +173,11 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
@Inject
public Preferences(PersistenceManager<PreferencesPayload> persistenceManager,
Config config,
FeeService feeService,
LocalBitcoinNode localBitcoinNode,
@Named(Config.BTC_NODES) String btcNodesFromOptions) {
this.persistenceManager = persistenceManager;
this.config = config;
this.feeService = feeService;
this.localBitcoinNode = localBitcoinNode;
this.btcNodesFromOptions = btcNodesFromOptions;
@ -837,11 +833,6 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
return prefPayload.getBridgeAddresses();
}
public long getWithdrawalTxFeeInVbytes() {
return Math.max(prefPayload.getWithdrawalTxFeeInVbytes(),
feeService.getMinFeePerVByte());
}
public List<String> getDefaultXmrTxProofServices() {
if (config.useLocalhostForP2P) {
return XMR_TX_PROOF_SERVICES_CLEAR_NET;

View file

@ -20,8 +20,7 @@ package bisq.core.util.coin;
import bisq.core.btc.wallet.Restrictions;
import bisq.core.monetary.Price;
import bisq.core.monetary.Volume;
import bisq.core.provider.fee.FeeService;
import bisq.core.trade.HavenoUtils;
import bisq.common.util.MathUtils;
import org.bitcoinj.core.Coin;
@ -98,8 +97,8 @@ public class CoinUtil {
@Nullable
public static Coin getMakerFee(@Nullable Coin amount) {
if (amount != null) {
Coin feePerBtc = getFeePerBtc(FeeService.getMakerFeePerBtc(), amount);
return maxCoin(feePerBtc, FeeService.getMinMakerFee());
Coin feePerBtc = getFeePerBtc(HavenoUtils.getMakerFeePerBtc(), amount);
return maxCoin(feePerBtc, HavenoUtils.getMinMakerFee());
} else {
return null;
}

View file

@ -1,141 +0,0 @@
/*
* This file is part of Haveno.
*
* Haveno is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Haveno is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.btc;
import bisq.core.btc.wallet.BtcWalletService;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.InsufficientMoneyException;
import java.util.List;
import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class TxFeeEstimationServiceTest {
@Test
public void testGetEstimatedTxVsize_withDefaultTxVsize() throws InsufficientMoneyException {
List<Coin> outputValues = List.of(Coin.valueOf(2000), Coin.valueOf(3000));
int initialEstimatedTxVsize;
Coin txFeePerVbyte;
BtcWalletService btcWalletService = mock(BtcWalletService.class);
int result;
int realTxVsize;
Coin txFee;
initialEstimatedTxVsize = 175;
txFeePerVbyte = Coin.valueOf(10);
realTxVsize = 175;
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 testGetEstimatedTxVsize_withLargeTx() throws InsufficientMoneyException {
List<Coin> outputValues = List.of(Coin.valueOf(2000), Coin.valueOf(3000));
int initialEstimatedTxVsize;
Coin txFeePerVbyte;
BtcWalletService btcWalletService = mock(BtcWalletService.class);
int result;
int realTxVsize;
Coin txFee;
initialEstimatedTxVsize = 175;
txFeePerVbyte = Coin.valueOf(10);
realTxVsize = 1750;
txFee = txFeePerVbyte.multiply(initialEstimatedTxVsize);
when(btcWalletService.getEstimatedFeeTxVsize(outputValues, txFee)).thenReturn(realTxVsize);
// 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 testGetEstimatedTxVsize_withSmallTx() throws InsufficientMoneyException {
List<Coin> outputValues = List.of(Coin.valueOf(2000), Coin.valueOf(3000));
int initialEstimatedTxVsize;
Coin txFeePerVbyte;
BtcWalletService btcWalletService = mock(BtcWalletService.class);
int result;
int realTxVsize;
Coin txFee;
initialEstimatedTxVsize = 1750;
txFeePerVbyte = Coin.valueOf(10);
realTxVsize = 175;
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 txVsize;
double tolerance;
boolean result;
estimatedSize = 100;
txVsize = 100;
tolerance = 0.0001;
result = TxFeeEstimationService.isInTolerance(estimatedSize, txVsize, tolerance);
assertTrue(result);
estimatedSize = 100;
txVsize = 200;
tolerance = 0.2;
result = TxFeeEstimationService.isInTolerance(estimatedSize, txVsize, tolerance);
assertFalse(result);
estimatedSize = 120;
txVsize = 100;
tolerance = 0.2;
result = TxFeeEstimationService.isInTolerance(estimatedSize, txVsize, tolerance);
assertTrue(result);
estimatedSize = 200;
txVsize = 100;
tolerance = 1;
result = TxFeeEstimationService.isInTolerance(estimatedSize, txVsize, tolerance);
assertTrue(result);
estimatedSize = 201;
txVsize = 100;
tolerance = 1;
result = TxFeeEstimationService.isInTolerance(estimatedSize, txVsize, tolerance);
assertFalse(result);
}
}

View file

@ -61,7 +61,7 @@ public class PreferencesTest {
Config config = new Config();
LocalBitcoinNode localBitcoinNode = new LocalBitcoinNode(config);
preferences = new Preferences(
persistenceManager, config, null, localBitcoinNode, null);
persistenceManager, config, localBitcoinNode, null);
}
@Test