From 9b5be6fa8754b8710e24ef7c499e332657c99f0c Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Sat, 20 Oct 2018 13:53:35 -0500 Subject: [PATCH 01/15] Add BisqSetupCompleteListener interface --- core/src/main/java/bisq/core/app/BisqExecutable.java | 5 ++++- core/src/main/java/bisq/core/app/BisqHeadlessApp.java | 1 - core/src/main/java/bisq/core/app/BisqHeadlessAppMain.java | 5 +++++ .../java/bisq/core/app/misc/ExecutableForAppWithP2p.java | 5 +++++ desktop/src/main/java/bisq/desktop/app/BisqAppMain.java | 4 ++++ 5 files changed, 18 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/bisq/core/app/BisqExecutable.java b/core/src/main/java/bisq/core/app/BisqExecutable.java index 0950ffc270e..d6ffbcd8419 100644 --- a/core/src/main/java/bisq/core/app/BisqExecutable.java +++ b/core/src/main/java/bisq/core/app/BisqExecutable.java @@ -74,7 +74,7 @@ import static java.lang.String.join; @Slf4j -public abstract class BisqExecutable implements GracefulShutDownHandler { +public abstract class BisqExecutable implements GracefulShutDownHandler, BisqSetup.BisqSetupCompleteListener { static { Utilities.removeCryptographyRestrictions(); } @@ -244,9 +244,12 @@ protected void onApplicationStarted() { protected void startAppSetup() { BisqSetup bisqSetup = injector.getInstance(BisqSetup.class); + bisqSetup.addBisqSetupCompleteListener(this); bisqSetup.start(); } + public abstract void onSetupComplete(); + /////////////////////////////////////////////////////////////////////////////////////////// // GracefulShutDownHandler implementation diff --git a/core/src/main/java/bisq/core/app/BisqHeadlessApp.java b/core/src/main/java/bisq/core/app/BisqHeadlessApp.java index 4518da4ec94..acfcb380902 100644 --- a/core/src/main/java/bisq/core/app/BisqHeadlessApp.java +++ b/core/src/main/java/bisq/core/app/BisqHeadlessApp.java @@ -54,7 +54,6 @@ public BisqHeadlessApp() { public void startApplication() { try { bisqSetup = injector.getInstance(BisqSetup.class); - bisqSetup.addBisqSetupCompleteListener(this); corruptedDatabaseFilesHandler = injector.getInstance(CorruptedDatabaseFilesHandler.class); tradeManager = injector.getInstance(TradeManager.class); diff --git a/core/src/main/java/bisq/core/app/BisqHeadlessAppMain.java b/core/src/main/java/bisq/core/app/BisqHeadlessAppMain.java index 22a9543f81f..16b6acb1ab5 100644 --- a/core/src/main/java/bisq/core/app/BisqHeadlessAppMain.java +++ b/core/src/main/java/bisq/core/app/BisqHeadlessAppMain.java @@ -80,6 +80,11 @@ protected void onApplicationLaunched() { headlessApp.setGracefulShutDownHandler(this); } + @Override + public void onSetupComplete() { + log.info("onSetupComplete"); + } + /////////////////////////////////////////////////////////////////////////////////////////// // We continue with a series of synchronous execution tasks /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java b/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java index a1dbf242e2a..9042e385022 100644 --- a/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java +++ b/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java @@ -60,6 +60,11 @@ protected void configUserThread() { UserThread.setExecutor(Executors.newSingleThreadExecutor(threadFactory)); } + @Override + public void onSetupComplete() { + log.info("onSetupComplete"); + } + // We don't use the gracefulShutDown implementation of the super class as we have a limited set of modules @Override public void gracefulShutDown(ResultHandler resultHandler) { diff --git a/desktop/src/main/java/bisq/desktop/app/BisqAppMain.java b/desktop/src/main/java/bisq/desktop/app/BisqAppMain.java index 1d519806758..d81cafb0a59 100644 --- a/desktop/src/main/java/bisq/desktop/app/BisqAppMain.java +++ b/desktop/src/main/java/bisq/desktop/app/BisqAppMain.java @@ -54,6 +54,10 @@ public static void main(String[] args) throws Exception { } } + @Override + public void onSetupComplete() { + log.info("onSetupComplete"); + } /////////////////////////////////////////////////////////////////////////////////////////// // First synchronous execution tasks From e3e0552fc67765fd73aa4af78107bd4e5f81c322 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Sat, 20 Oct 2018 13:55:24 -0500 Subject: [PATCH 02/15] Add HTTP_API_HOST and HTTP_API_PORT options --- core/src/main/java/bisq/core/app/AppOptionKeys.java | 2 ++ .../src/main/java/bisq/core/app/BisqEnvironment.java | 12 ++++++++++-- core/src/main/java/bisq/core/app/BisqExecutable.java | 6 ++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/bisq/core/app/AppOptionKeys.java b/core/src/main/java/bisq/core/app/AppOptionKeys.java index 6c44f7d4f81..c9036c38865 100644 --- a/core/src/main/java/bisq/core/app/AppOptionKeys.java +++ b/core/src/main/java/bisq/core/app/AppOptionKeys.java @@ -29,4 +29,6 @@ public class AppOptionKeys { public static final String IGNORE_DEV_MSG_KEY = "ignoreDevMsg"; public static final String USE_DEV_PRIVILEGE_KEYS = "useDevPrivilegeKeys"; public static final String REFERRAL_ID = "referralId"; + public static final String HTTP_API_HOST = "httpApiHost"; + public static final String HTTP_API_PORT = "httpApiPort"; } diff --git a/core/src/main/java/bisq/core/app/BisqEnvironment.java b/core/src/main/java/bisq/core/app/BisqEnvironment.java index b75074c5b51..5703beea70c 100644 --- a/core/src/main/java/bisq/core/app/BisqEnvironment.java +++ b/core/src/main/java/bisq/core/app/BisqEnvironment.java @@ -193,8 +193,10 @@ private static String appDataDir(String userDataDir, String appName) { protected final String btcNodes, seedNodes, ignoreDevMsg, useDevPrivilegeKeys, useDevMode, useTorForBtc, rpcUser, rpcPassword, rpcPort, rpcBlockNotificationPort, dumpBlockchainData, fullDaoNode, myAddress, banList, dumpStatistics, maxMemory, socks5ProxyBtcAddress, - socks5ProxyHttpAddress, useAllProvidedNodes, numConnectionForBtc, genesisTxId, genesisBlockHeight, referralId, daoActivated; - + socks5ProxyHttpAddress, useAllProvidedNodes, numConnectionForBtc, genesisTxId, genesisBlockHeight, + referralId, daoActivated; + @Getter + protected final String httpApiHost, httpApiPort; public BisqEnvironment(OptionSet options) { this(new JOptCommandLinePropertySource(BISQ_COMMANDLINE_PROPERTY_SOURCE_NAME, checkNotNull( @@ -237,6 +239,12 @@ public BisqEnvironment(PropertySource commandLineProperties) { referralId = commandLineProperties.containsProperty(AppOptionKeys.REFERRAL_ID) ? (String) commandLineProperties.getProperty(AppOptionKeys.REFERRAL_ID) : ""; + httpApiHost = commandLineProperties.containsProperty(AppOptionKeys.HTTP_API_HOST) ? + (String) commandLineProperties.getProperty(AppOptionKeys.HTTP_API_HOST) : + "127.0.0.1"; + httpApiPort = commandLineProperties.containsProperty(AppOptionKeys.HTTP_API_PORT) ? + (String) commandLineProperties.getProperty(AppOptionKeys.HTTP_API_PORT) : + "8080"; useDevMode = commandLineProperties.containsProperty(CommonOptionKeys.USE_DEV_MODE) ? (String) commandLineProperties.getProperty(CommonOptionKeys.USE_DEV_MODE) : ""; diff --git a/core/src/main/java/bisq/core/app/BisqExecutable.java b/core/src/main/java/bisq/core/app/BisqExecutable.java index d6ffbcd8419..fccf55b21a6 100644 --- a/core/src/main/java/bisq/core/app/BisqExecutable.java +++ b/core/src/main/java/bisq/core/app/BisqExecutable.java @@ -368,6 +368,12 @@ protected void customizeOptionParsing(OptionParser parser) { parser.accepts(AppOptionKeys.REFERRAL_ID, description("Optional Referral ID (e.g. for API users or pro market makers)", "")) .withRequiredArg(); + parser.accepts(AppOptionKeys.HTTP_API_HOST, + description("Optional HTTP API host", "127.0.0.1")) + .withRequiredArg(); + parser.accepts(AppOptionKeys.HTTP_API_PORT, + description("Optional HTTP API port", "8080")) + .withRequiredArg(); parser.accepts(CommonOptionKeys.USE_DEV_MODE, description("Enables dev mode which is used for convenience for developer testing", false)) .withRequiredArg() From dc469e5ecc6e7c022e0faf7a06847b468131a576 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Sat, 20 Oct 2018 14:17:16 -0500 Subject: [PATCH 03/15] Refactor balanceModel - Remove BalanceModel and add Balances and BalanceUtil - Move setup code from BisqSetup to domains --- .../main/java/bisq/core/app/BisqFacade.java | 11 +- .../main/java/bisq/core/app/BisqSetup.java | 31 +---- .../main/java/bisq/core/btc/BalanceUtil.java | 82 +++++++++++++ .../src/main/java/bisq/core/btc/Balances.java | 108 ++++++++++++++++++ .../java/bisq/core/btc/BitcoinModule.java | 4 +- .../offer/placeoffer/tasks/ValidateOffer.java | 1 + .../presentation/BalancePresentation.java | 12 +- .../java/bisq/core/trade/TradeManager.java | 7 ++ .../main/funds/withdrawal/WithdrawalView.java | 2 +- 9 files changed, 218 insertions(+), 40 deletions(-) create mode 100644 core/src/main/java/bisq/core/btc/BalanceUtil.java create mode 100644 core/src/main/java/bisq/core/btc/Balances.java diff --git a/core/src/main/java/bisq/core/app/BisqFacade.java b/core/src/main/java/bisq/core/app/BisqFacade.java index 235c5f01179..4a7b6997112 100644 --- a/core/src/main/java/bisq/core/app/BisqFacade.java +++ b/core/src/main/java/bisq/core/app/BisqFacade.java @@ -17,7 +17,7 @@ package bisq.core.app; -import bisq.core.btc.model.BalanceModel; +import bisq.core.btc.Balances; import bisq.core.presentation.BalancePresentation; import bisq.common.app.Version; @@ -29,12 +29,12 @@ * E.g. useful for different APIs to access data of different domains of Bisq. */ public class BisqFacade { - private final BalanceModel balanceModel; + private final Balances balances; private final BalancePresentation balancePresentation; @Inject - public BisqFacade(BalanceModel balanceModel, BalancePresentation balancePresentation) { - this.balanceModel = balanceModel; + public BisqFacade(Balances balances, BalancePresentation balancePresentation) { + this.balances = balances; this.balancePresentation = balancePresentation; } @@ -43,8 +43,7 @@ public String getVersion() { } public long getAvailableBalance() { - balanceModel.updateBalance(); - return balanceModel.getAvailableBalance().get().getValue(); + return balances.getAvailableBalance().get().getValue(); } public String getAvailableBalanceAsString() { diff --git a/core/src/main/java/bisq/core/app/BisqSetup.java b/core/src/main/java/bisq/core/app/BisqSetup.java index e7511c6bd71..07daf67afdf 100644 --- a/core/src/main/java/bisq/core/app/BisqSetup.java +++ b/core/src/main/java/bisq/core/app/BisqSetup.java @@ -23,9 +23,8 @@ import bisq.core.alert.PrivateNotificationPayload; import bisq.core.arbitration.ArbitratorManager; import bisq.core.arbitration.DisputeManager; -import bisq.core.btc.listeners.BalanceListener; +import bisq.core.btc.Balances; import bisq.core.btc.model.AddressEntry; -import bisq.core.btc.model.BalanceModel; import bisq.core.btc.setup.WalletsSetup; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.WalletsManager; @@ -40,14 +39,12 @@ import bisq.core.notifications.alerts.TradeEvents; import bisq.core.notifications.alerts.market.MarketAlerts; import bisq.core.notifications.alerts.price.PriceAlert; -import bisq.core.offer.OpenOffer; import bisq.core.offer.OpenOfferManager; import bisq.core.payment.AccountAgeWitnessService; 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.core.trade.Trade; import bisq.core.trade.TradeManager; import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.user.Preferences; @@ -71,7 +68,6 @@ import bisq.common.util.Utilities; import org.bitcoinj.core.Coin; -import org.bitcoinj.core.Transaction; import javax.inject.Inject; @@ -123,7 +119,7 @@ public interface BisqSetupCompleteListener { private final WalletsManager walletsManager; private final WalletsSetup walletsSetup; private final BtcWalletService btcWalletService; - private final BalanceModel balanceModel; + private final Balances balances; private final PriceFeedService priceFeedService; private final ArbitratorManager arbitratorManager; private final P2PService p2PService; @@ -196,7 +192,7 @@ public BisqSetup(P2PNetworkSetup p2PNetworkSetup, WalletsManager walletsManager, WalletsSetup walletsSetup, BtcWalletService btcWalletService, - BalanceModel balanceModel, + Balances balances, PriceFeedService priceFeedService, ArbitratorManager arbitratorManager, P2PService p2PService, @@ -232,7 +228,7 @@ public BisqSetup(P2PNetworkSetup p2PNetworkSetup, this.walletsManager = walletsManager; this.walletsSetup = walletsSetup; this.btcWalletService = btcWalletService; - this.balanceModel = balanceModel; + this.balances = balances; this.priceFeedService = priceFeedService; this.arbitratorManager = arbitratorManager; this.p2PService = p2PService; @@ -578,29 +574,12 @@ private void initDomainServices() { disputeManager.onAllServicesInitialized(); tradeManager.onAllServicesInitialized(); - tradeManager.getTradableList().addListener((ListChangeListener) change -> balanceModel.updateBalance()); - tradeManager.getAddressEntriesForAvailableBalanceStream() - .filter(addressEntry -> addressEntry.getOfferId() != null) - .forEach(addressEntry -> { - log.debug("swapPendingOfferFundingEntries, offerId={}, OFFER_FUNDING", addressEntry.getOfferId()); - btcWalletService.swapTradeEntryToAvailableEntry(addressEntry.getOfferId(), AddressEntry.Context.OFFER_FUNDING); - }); - - btcWalletService.addBalanceListener(new BalanceListener() { - @Override - public void onBalanceChanged(Coin balance, Transaction tx) { - balanceModel.updateBalance(); - } - }); if (walletsSetup.downloadPercentageProperty().get() == 1) checkForLockedUpFunds(); - balanceModel.updateBalance(); - - openOfferManager.getObservableList().addListener((ListChangeListener) c -> balanceModel.updateBalance()); openOfferManager.onAllServicesInitialized(); - + balances.onAllServicesInitialized(); arbitratorManager.onAllServicesInitialized(); alertManager.alertMessageProperty().addListener((observable, oldValue, newValue) -> diff --git a/core/src/main/java/bisq/core/btc/BalanceUtil.java b/core/src/main/java/bisq/core/btc/BalanceUtil.java new file mode 100644 index 00000000000..24b0e79ecc1 --- /dev/null +++ b/core/src/main/java/bisq/core/btc/BalanceUtil.java @@ -0,0 +1,82 @@ +/* + * This file is part of Bisq. + * + * Bisq 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. + * + * Bisq 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 Bisq. If not, see . + */ + +package bisq.core.btc; + +import bisq.core.btc.model.AddressEntry; +import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.offer.OpenOffer; +import bisq.core.offer.OpenOfferManager; +import bisq.core.trade.Trade; +import bisq.core.trade.TradeManager; +import bisq.core.trade.closed.ClosedTradableManager; +import bisq.core.trade.failed.FailedTradesManager; + +import bisq.common.util.Tuple2; + +import javax.inject.Inject; + +import java.util.Objects; +import java.util.stream.Stream; + +public class BalanceUtil { + private final TradeManager tradeManager; + private final BtcWalletService btcWalletService; + private final OpenOfferManager openOfferManager; + private final ClosedTradableManager closedTradableManager; + private final FailedTradesManager failedTradesManager; + + @Inject + public BalanceUtil(TradeManager tradeManager, BtcWalletService btcWalletService, OpenOfferManager openOfferManager, + ClosedTradableManager closedTradableManager, FailedTradesManager failedTradesManager) { + this.tradeManager = tradeManager; + this.btcWalletService = btcWalletService; + this.openOfferManager = openOfferManager; + this.closedTradableManager = closedTradableManager; + this.failedTradesManager = failedTradesManager; + } + + public Stream getAddressEntriesForAvailableFunds() { + return tradeManager.getAddressEntriesForAvailableBalanceStream(); + } + + public Stream getAddressEntriesForReservedFunds() { + return getOpenOfferAndAddressEntriesForReservedFunds().map(tuple2 -> tuple2.second); + } + + public Stream> getOpenOfferAndAddressEntriesForReservedFunds() { + return openOfferManager.getObservableList().stream() + .map(openOffer -> btcWalletService.getAddressEntry(openOffer.getId(), AddressEntry.Context.RESERVED_FOR_TRADE) + .map(addressEntry -> new Tuple2<>(openOffer, addressEntry)) + .orElse(null)) + .filter(Objects::nonNull); + } + + public Stream getAddressEntriesForLockedFunds() { + return getTradesAndAddressEntriesForLockedFunds().map(tuple2 -> tuple2.second); + } + + public Stream> getTradesAndAddressEntriesForLockedFunds() { + Stream lockedTrades = Stream.concat(closedTradableManager.getLockedTradesStream(), failedTradesManager.getLockedTradesStream()); + lockedTrades = Stream.concat(lockedTrades, tradeManager.getLockedTradesStream()); + return lockedTrades + .map(trade -> btcWalletService.getAddressEntry(trade.getId(), AddressEntry.Context.MULTI_SIG) + .map(addressEntry -> new Tuple2<>(trade, addressEntry)) + .orElse(null)) + .filter(Objects::nonNull); + } +} diff --git a/core/src/main/java/bisq/core/btc/Balances.java b/core/src/main/java/bisq/core/btc/Balances.java new file mode 100644 index 00000000000..87f86f722b6 --- /dev/null +++ b/core/src/main/java/bisq/core/btc/Balances.java @@ -0,0 +1,108 @@ +/* + * This file is part of Bisq. + * + * Bisq 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. + * + * Bisq 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 Bisq. If not, see . + */ + +package bisq.core.btc; + +import bisq.core.btc.listeners.BalanceListener; +import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.offer.OpenOffer; +import bisq.core.offer.OpenOfferManager; +import bisq.core.trade.Trade; +import bisq.core.trade.TradeManager; +import bisq.core.trade.closed.ClosedTradableManager; +import bisq.core.trade.failed.FailedTradesManager; + +import bisq.common.UserThread; + +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.Transaction; + +import javax.inject.Inject; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; + +import javafx.collections.ListChangeListener; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class Balances { + private final BalanceUtil balanceUtil; + private final TradeManager tradeManager; + private final BtcWalletService btcWalletService; + private final OpenOfferManager openOfferManager; + + @Getter + private final ObjectProperty availableBalance = new SimpleObjectProperty<>(); + @Getter + private final ObjectProperty reservedBalance = new SimpleObjectProperty<>(); + @Getter + private final ObjectProperty lockedBalance = new SimpleObjectProperty<>(); + + @Inject + public Balances(BalanceUtil balanceUtil, TradeManager tradeManager, BtcWalletService btcWalletService, OpenOfferManager openOfferManager, + ClosedTradableManager closedTradableManager, FailedTradesManager failedTradesManager) { + this.balanceUtil = balanceUtil; + this.tradeManager = tradeManager; + this.btcWalletService = btcWalletService; + this.openOfferManager = openOfferManager; + } + + public void onAllServicesInitialized() { + openOfferManager.getObservableList().addListener((ListChangeListener) c -> updateBalance()); + tradeManager.getTradableList().addListener((ListChangeListener) change -> updateBalance()); + btcWalletService.addBalanceListener(new BalanceListener() { + @Override + public void onBalanceChanged(Coin balance, Transaction tx) { + updateBalance(); + } + }); + updateBalance(); + } + + private void updateBalance() { + // Need to delay a bit to get the balances correct + UserThread.execute(() -> { + updateAvailableBalance(); + updateReservedBalance(); + updateLockedBalance(); + }); + } + + private void updateAvailableBalance() { + Coin sum = Coin.valueOf(balanceUtil.getAddressEntriesForAvailableFunds() + .mapToLong(addressEntry -> btcWalletService.getBalanceForAddress(addressEntry.getAddress()).value) + .sum()); + availableBalance.set(sum); + } + + private void updateReservedBalance() { + Coin sum = Coin.valueOf(balanceUtil.getAddressEntriesForReservedFunds() + .mapToLong(addressEntry -> btcWalletService.getBalanceForAddress(addressEntry.getAddress()).value) + .sum()); + reservedBalance.set(sum); + } + + private void updateLockedBalance() { + Coin sum = Coin.valueOf(balanceUtil.getAddressEntriesForLockedFunds() + .mapToLong(addressEntry -> addressEntry.getCoinLockedInMultiSig().getValue()) + .sum()); + lockedBalance.set(sum); + } +} diff --git a/core/src/main/java/bisq/core/btc/BitcoinModule.java b/core/src/main/java/bisq/core/btc/BitcoinModule.java index b9e365a3bbd..f2ab467e975 100644 --- a/core/src/main/java/bisq/core/btc/BitcoinModule.java +++ b/core/src/main/java/bisq/core/btc/BitcoinModule.java @@ -19,7 +19,6 @@ import bisq.core.app.AppOptionKeys; import bisq.core.btc.model.AddressEntryList; -import bisq.core.btc.model.BalanceModel; import bisq.core.btc.nodes.BtcNodes; import bisq.core.btc.setup.RegTestHost; import bisq.core.btc.setup.WalletsSetup; @@ -78,7 +77,8 @@ protected void configure() { bind(BsqCoinSelector.class).in(Singleton.class); bind(NonBsqCoinSelector.class).in(Singleton.class); bind(BtcNodes.class).in(Singleton.class); - bind(BalanceModel.class).in(Singleton.class); + bind(Balances.class).in(Singleton.class); + bind(BalanceUtil.class).in(Singleton.class); bind(PriceNodeHttpClient.class).in(Singleton.class); diff --git a/core/src/main/java/bisq/core/offer/placeoffer/tasks/ValidateOffer.java b/core/src/main/java/bisq/core/offer/placeoffer/tasks/ValidateOffer.java index efe6040d369..8216c924f21 100644 --- a/core/src/main/java/bisq/core/offer/placeoffer/tasks/ValidateOffer.java +++ b/core/src/main/java/bisq/core/offer/placeoffer/tasks/ValidateOffer.java @@ -98,6 +98,7 @@ protected void run() { "maxTradePeriod must be positive. maxTradePeriod=" + offer.getMaxTradePeriod()); // TODO check upper and lower bounds for fiat // TODO check rest of new parameters + // TODO check for account age witness base tradeLimit is missing complete(); } catch (Exception e) { diff --git a/core/src/main/java/bisq/core/presentation/BalancePresentation.java b/core/src/main/java/bisq/core/presentation/BalancePresentation.java index 311a8570ea2..3b42d19ac6a 100644 --- a/core/src/main/java/bisq/core/presentation/BalancePresentation.java +++ b/core/src/main/java/bisq/core/presentation/BalancePresentation.java @@ -17,7 +17,7 @@ package bisq.core.presentation; -import bisq.core.btc.model.BalanceModel; +import bisq.core.btc.Balances; import bisq.core.util.BSFormatter; import javax.inject.Inject; @@ -26,7 +26,9 @@ import javafx.beans.property.StringProperty; import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +@Slf4j public class BalancePresentation { @Getter private final StringProperty availableBalance = new SimpleStringProperty(); @@ -36,8 +38,8 @@ public class BalancePresentation { private final StringProperty lockedBalance = new SimpleStringProperty(); @Inject - public BalancePresentation(BalanceModel balanceModel, BSFormatter formatter) { - balanceModel.getAvailableBalance().addListener((observable, oldValue, newValue) -> { + public BalancePresentation(Balances balances, BSFormatter formatter) { + balances.getAvailableBalance().addListener((observable, oldValue, newValue) -> { String value = formatter.formatCoinWithCode(newValue); // If we get full precision the BTC postfix breaks layout so we omit it if (value.length() > 11) @@ -45,10 +47,10 @@ public BalancePresentation(BalanceModel balanceModel, BSFormatter formatter) { availableBalance.set(value); }); - balanceModel.getReservedBalance().addListener((observable, oldValue, newValue) -> { + balances.getReservedBalance().addListener((observable, oldValue, newValue) -> { reservedBalance.set(formatter.formatCoinWithCode(newValue)); }); - balanceModel.getLockedBalance().addListener((observable, oldValue, newValue) -> { + balances.getLockedBalance().addListener((observable, oldValue, newValue) -> { lockedBalance.set(formatter.formatCoinWithCode(newValue)); }); } diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java index e4cb74d467b..def150cb681 100644 --- a/core/src/main/java/bisq/core/trade/TradeManager.java +++ b/core/src/main/java/bisq/core/trade/TradeManager.java @@ -239,6 +239,13 @@ public void onUpdatedDataReceived() { tradableList.getList().addListener((ListChangeListener) change -> onTradesChanged()); onTradesChanged(); + + getAddressEntriesForAvailableBalanceStream() + .filter(addressEntry -> addressEntry.getOfferId() != null) + .forEach(addressEntry -> { + log.debug("swapPendingOfferFundingEntries, offerId={}, OFFER_FUNDING", addressEntry.getOfferId()); + btcWalletService.swapTradeEntryToAvailableEntry(addressEntry.getOfferId(), AddressEntry.Context.OFFER_FUNDING); + }); } public void shutDown() { diff --git a/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalView.java b/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalView.java index f1f9b3b4ad8..ad6f7e9946b 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalView.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalView.java @@ -29,8 +29,8 @@ import bisq.core.btc.exceptions.AddressEntryException; import bisq.core.btc.exceptions.InsufficientFundsException; import bisq.core.btc.listeners.BalanceListener; -import bisq.core.btc.setup.WalletsSetup; import bisq.core.btc.model.AddressEntry; +import bisq.core.btc.setup.WalletsSetup; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.Restrictions; import bisq.core.locale.Res; From e6f9d770b7cf2d58dbd2a86c5253364053e16e06 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Sat, 20 Oct 2018 14:19:53 -0500 Subject: [PATCH 04/15] Add methods to OfferUtil --- .../main/java/bisq/core/offer/OfferUtil.java | 119 ++++++++++++++++++ .../payment/AccountAgeWitnessService.java | 6 +- 2 files changed, 123 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/bisq/core/offer/OfferUtil.java b/core/src/main/java/bisq/core/offer/OfferUtil.java index bc87e11f4af..19baf669659 100644 --- a/core/src/main/java/bisq/core/offer/OfferUtil.java +++ b/core/src/main/java/bisq/core/offer/OfferUtil.java @@ -20,23 +20,44 @@ import bisq.core.app.BisqEnvironment; import bisq.core.btc.wallet.BsqWalletService; import bisq.core.btc.wallet.Restrictions; +import bisq.core.filter.FilterManager; +import bisq.core.locale.Country; +import bisq.core.locale.CurrencyUtil; +import bisq.core.locale.Res; import bisq.core.monetary.Price; import bisq.core.monetary.Volume; +import bisq.core.payment.AccountAgeWitnessService; +import bisq.core.payment.BankAccount; +import bisq.core.payment.CountryBasedPaymentAccount; +import bisq.core.payment.F2FAccount; +import bisq.core.payment.PaymentAccount; +import bisq.core.payment.SameBankAccount; +import bisq.core.payment.SepaAccount; +import bisq.core.payment.SepaInstantAccount; +import bisq.core.payment.SpecificBanksAccount; import bisq.core.provider.fee.FeeService; +import bisq.core.trade.statistics.ReferralIdService; import bisq.core.user.Preferences; import bisq.core.util.CoinUtil; +import bisq.network.p2p.P2PService; + import bisq.common.util.MathUtils; import org.bitcoinj.core.Coin; import com.google.common.annotations.VisibleForTesting; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + import lombok.extern.slf4j.Slf4j; import javax.annotation.Nullable; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; /** * This class holds utility methods for the creation of an Offer. @@ -247,4 +268,102 @@ static Coin getAdjustedAmount(Coin amount, Price price, long maxTradeLimit, int adjustedAmount = Math.min(maxTradeLimit, adjustedAmount); return Coin.valueOf(adjustedAmount); } + + public static ArrayList getAcceptedCountryCodes(PaymentAccount paymentAccount) { + ArrayList acceptedCountryCodes = null; + if (paymentAccount instanceof SepaAccount) { + acceptedCountryCodes = new ArrayList<>(((SepaAccount) paymentAccount).getAcceptedCountryCodes()); + } else if (paymentAccount instanceof SepaInstantAccount) { + acceptedCountryCodes = new ArrayList<>(((SepaInstantAccount) paymentAccount).getAcceptedCountryCodes()); + } else if (paymentAccount instanceof CountryBasedPaymentAccount) { + acceptedCountryCodes = new ArrayList<>(); + Country country = ((CountryBasedPaymentAccount) paymentAccount).getCountry(); + if (country != null) + acceptedCountryCodes.add(country.code); + } + return acceptedCountryCodes; + } + + public static ArrayList getAcceptedBanks(PaymentAccount paymentAccount) { + ArrayList acceptedBanks = null; + if (paymentAccount instanceof SpecificBanksAccount) { + acceptedBanks = new ArrayList<>(((SpecificBanksAccount) paymentAccount).getAcceptedBanks()); + } else if (paymentAccount instanceof SameBankAccount) { + acceptedBanks = new ArrayList<>(); + acceptedBanks.add(((SameBankAccount) paymentAccount).getBankId()); + } + return acceptedBanks; + } + + public static String getBankId(PaymentAccount paymentAccount) { + return paymentAccount instanceof BankAccount ? ((BankAccount) paymentAccount).getBankId() : null; + } + + // That is optional and set to null if not supported (AltCoins, OKPay,...) + public static String getCountryCode(PaymentAccount paymentAccount) { + if (paymentAccount instanceof CountryBasedPaymentAccount) { + Country country = ((CountryBasedPaymentAccount) paymentAccount).getCountry(); + return country != null ? country.code : null; + } else { + return null; + } + } + + public static long getMaxTradeLimit(AccountAgeWitnessService accountAgeWitnessService, PaymentAccount paymentAccount, String currencyCode) { + if (paymentAccount != null) + return accountAgeWitnessService.getMyTradeLimit(paymentAccount, currencyCode); + else + return 0; + } + + public static long getMaxTradePeriod(PaymentAccount paymentAccount) { + return paymentAccount.getPaymentMethod().getMaxTradePeriod(); + } + + public static Map getExtraDataMap(AccountAgeWitnessService accountAgeWitnessService, + ReferralIdService referralIdService, + PaymentAccount paymentAccount, + String currencyCode) { + Map extraDataMap = null; + if (CurrencyUtil.isFiatCurrency(currencyCode)) { + extraDataMap = new HashMap<>(); + final String myWitnessHashAsHex = accountAgeWitnessService.getMyWitnessHashAsHex(paymentAccount.getPaymentAccountPayload()); + extraDataMap.put(OfferPayload.ACCOUNT_AGE_WITNESS_HASH, myWitnessHashAsHex); + } + + if (referralIdService.getOptionalReferralId().isPresent()) { + if (extraDataMap == null) + extraDataMap = new HashMap<>(); + extraDataMap.put(OfferPayload.REFERRAL_ID, referralIdService.getOptionalReferralId().get()); + } + + if (paymentAccount instanceof F2FAccount) { + if (extraDataMap == null) + extraDataMap = new HashMap<>(); + extraDataMap.put(OfferPayload.F2F_CITY, ((F2FAccount) paymentAccount).getCity()); + extraDataMap.put(OfferPayload.F2F_EXTRA_INFO, ((F2FAccount) paymentAccount).getExtraInfo()); + } + + return extraDataMap; + } + + public static void validateOfferData(FilterManager filterManager, + P2PService p2PService, + Coin buyerSecurityDepositAsCoin, + PaymentAccount paymentAccount, + String currencyCode, Coin makerFeeAsCoin) { + checkArgument(buyerSecurityDepositAsCoin.compareTo(Restrictions.getMaxBuyerSecurityDeposit()) <= 0, + "securityDeposit must be not exceed " + + Restrictions.getMaxBuyerSecurityDeposit().toFriendlyString()); + checkArgument(buyerSecurityDepositAsCoin.compareTo(Restrictions.getMinBuyerSecurityDeposit()) >= 0, + "securityDeposit must be not be less than " + + Restrictions.getMinBuyerSecurityDeposit().toFriendlyString()); + + checkArgument(!filterManager.isCurrencyBanned(currencyCode), + Res.get("offerbook.warning.currencyBanned")); + checkArgument(!filterManager.isPaymentMethodBanned(paymentAccount.getPaymentMethod()), + Res.get("offerbook.warning.paymentMethodBanned")); + checkNotNull(makerFeeAsCoin, "makerFee must not be null"); + checkNotNull(p2PService.getAddress(), "Address must not be null"); + } } diff --git a/core/src/main/java/bisq/core/payment/AccountAgeWitnessService.java b/core/src/main/java/bisq/core/payment/AccountAgeWitnessService.java index 38cac92d482..84adb747ae0 100644 --- a/core/src/main/java/bisq/core/payment/AccountAgeWitnessService.java +++ b/core/src/main/java/bisq/core/payment/AccountAgeWitnessService.java @@ -263,8 +263,10 @@ public long getMyAccountAge(PaymentAccountPayload paymentAccountPayload) { public long getMyTradeLimit(PaymentAccount paymentAccount, String currencyCode) { final Optional witnessOptional = Optional.of(getMyWitness(paymentAccount.getPaymentAccountPayload())); - return getTradeLimit(paymentAccount.getPaymentMethod() - .getMaxTradeLimitAsCoin(currencyCode), currencyCode, witnessOptional, new Date()); + return getTradeLimit(paymentAccount.getPaymentMethod().getMaxTradeLimitAsCoin(currencyCode), + currencyCode, + witnessOptional, + new Date()); } From 118c24766c83dd278eaed58d93e8ab942dd0618d Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Sat, 20 Oct 2018 14:21:36 -0500 Subject: [PATCH 05/15] Add TxFeeEstimation class --- .../java/bisq/core/offer/TxFeeEstimation.java | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 core/src/main/java/bisq/core/offer/TxFeeEstimation.java diff --git a/core/src/main/java/bisq/core/offer/TxFeeEstimation.java b/core/src/main/java/bisq/core/offer/TxFeeEstimation.java new file mode 100644 index 00000000000..dd42cb23f72 --- /dev/null +++ b/core/src/main/java/bisq/core/offer/TxFeeEstimation.java @@ -0,0 +1,148 @@ +/* + * This file is part of Bisq. + * + * Bisq 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. + * + * Bisq 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 Bisq. If not, see . + */ + +package bisq.core.offer; + +import bisq.core.btc.model.AddressEntry; +import bisq.core.btc.wallet.BsqWalletService; +import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.btc.wallet.Restrictions; +import bisq.core.btc.wallet.TradeWalletService; +import bisq.core.provider.fee.FeeService; +import bisq.core.user.Preferences; +import bisq.core.user.User; + +import org.bitcoinj.core.Address; +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.InsufficientMoneyException; +import org.bitcoinj.core.Transaction; + +import lombok.extern.slf4j.Slf4j; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Util class for getting a fee estimation. + */ +@Slf4j +public class TxFeeEstimation { + private final BtcWalletService btcWalletService; + private final BsqWalletService bsqWalletService; + private final Preferences preferences; + private final User user; + private final TradeWalletService tradeWalletService; + private final FeeService feeService; + + private int feeTxSize = 260; // size of typical tx with 1 input + private int feeTxSizeEstimationRecursionCounter; + private String offerId; + private OfferPayload.Direction direction; + private Coin amount; + private Coin buyerSecurityDeposit; + private double marketPriceMargin; + private boolean marketPriceAvailable; + + public TxFeeEstimation(BtcWalletService btcWalletService, + BsqWalletService bsqWalletService, + Preferences preferences, + User user, + TradeWalletService tradeWalletService, + FeeService feeService, + String offerId, + OfferPayload.Direction direction, + Coin amount, + Coin buyerSecurityDeposit, + double marketPriceMargin, + boolean marketPriceAvailable, + int feeTxSize) { + + + this.btcWalletService = btcWalletService; + this.bsqWalletService = bsqWalletService; + this.preferences = preferences; + this.user = user; + this.tradeWalletService = tradeWalletService; + this.feeService = feeService; + + this.offerId = offerId; + this.direction = direction; + this.amount = amount; + this.buyerSecurityDeposit = buyerSecurityDeposit; + this.marketPriceMargin = marketPriceMargin; + this.marketPriceAvailable = marketPriceAvailable; + this.feeTxSize = feeTxSize; + } + + public Coin getEstimatedFee() { + Coin sellerSecurityDeposit = Restrictions.getSellerSecurityDeposit(); + Coin txFeeFromFeeService = feeService.getTxFee(feeTxSize); + Address fundingAddress = btcWalletService.getFreshAddressEntry().getAddress(); + Address reservedForTradeAddress = btcWalletService.getOrCreateAddressEntry(offerId, AddressEntry.Context.RESERVED_FOR_TRADE).getAddress(); + Address changeAddress = btcWalletService.getFreshAddressEntry().getAddress(); + + Coin reservedFundsForOffer = OfferUtil.isBuyOffer(direction) ? buyerSecurityDeposit : sellerSecurityDeposit; + if (!OfferUtil.isBuyOffer(direction)) + reservedFundsForOffer = reservedFundsForOffer.add(amount); + + checkNotNull(user.getAcceptedArbitrators(), "user.getAcceptedArbitrators() must not be null"); + checkArgument(!user.getAcceptedArbitrators().isEmpty(), "user.getAcceptedArbitrators() must not be empty"); + String dummyArbitratorAddress = user.getAcceptedArbitrators().get(0).getBtcAddress(); + try { + log.info("We create a dummy tx to see if our estimated size is in the accepted range. feeTxSize={}," + + " txFee based on feeTxSize: {}, recommended txFee is {} sat/byte", + feeTxSize, txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerByte()); + Transaction tradeFeeTx = tradeWalletService.estimateBtcTradingFeeTxSize( + fundingAddress, + reservedForTradeAddress, + changeAddress, + reservedFundsForOffer, + true, + OfferUtil.getMakerFee(bsqWalletService, preferences, amount, marketPriceAvailable, marketPriceMargin), + txFeeFromFeeService, + dummyArbitratorAddress); + + final int txSize = tradeFeeTx.bitcoinSerialize().length; + // use feeTxSizeEstimationRecursionCounter to avoid risk for endless loop + if (txSize > feeTxSize * 1.2 && feeTxSizeEstimationRecursionCounter < 10) { + feeTxSizeEstimationRecursionCounter++; + log.info("txSize is {} bytes but feeTxSize used for txFee calculation was {} bytes. We try again with an " + + "adjusted txFee to reach the target tx fee.", txSize, feeTxSize); + feeTxSize = txSize; + txFeeFromFeeService = feeService.getTxFee(feeTxSize); + // lets try again with the adjusted txSize and fee. + getEstimatedFee(); + } else { + log.info("feeTxSize {} bytes", feeTxSize); + log.info("txFee based on estimated size: {}, recommended txFee is {} sat/byte", + txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerByte()); + } + } catch (InsufficientMoneyException e) { + // If we need to fund from an external wallet we can assume we only have 1 input (260 bytes). + log.warn("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 260 bytes."); + feeTxSize = 260; + txFeeFromFeeService = feeService.getTxFee(feeTxSize); + log.info("feeTxSize {} bytes", feeTxSize); + log.info("txFee based on estimated size: {}, recommended txFee is {} sat/byte", + txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerByte()); + } + + return txFeeFromFeeService; + } +} + From 6d1ae1e4186b2ccd72b3e4512c447b8a8cc77de2 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Sat, 20 Oct 2018 14:27:07 -0500 Subject: [PATCH 06/15] Use OfferUtil.getAcceptedBanks --- .../bisq/desktop/main/offer/MutableOfferDataModel.java | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java index 6f02681699d..d9d596ccf9a 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java @@ -42,10 +42,8 @@ import bisq.core.payment.F2FAccount; import bisq.core.payment.HalCashAccount; import bisq.core.payment.PaymentAccount; -import bisq.core.payment.SameBankAccount; import bisq.core.payment.SepaAccount; import bisq.core.payment.SepaInstantAccount; -import bisq.core.payment.SpecificBanksAccount; import bisq.core.provider.fee.FeeService; import bisq.core.provider.price.PriceFeedService; import bisq.core.trade.handlers.TransactionResultHandler; @@ -333,13 +331,7 @@ Offer createAndGetOffer() { acceptedCountryCodes.add(((CountryBasedPaymentAccount) paymentAccount).getCountry().code); } - ArrayList acceptedBanks = null; - if (paymentAccount instanceof SpecificBanksAccount) { - acceptedBanks = new ArrayList<>(((SpecificBanksAccount) paymentAccount).getAcceptedBanks()); - } else if (paymentAccount instanceof SameBankAccount) { - acceptedBanks = new ArrayList<>(); - acceptedBanks.add(((SameBankAccount) paymentAccount).getBankId()); - } + ArrayList acceptedBanks = OfferUtil.getAcceptedBanks(paymentAccount); String bankId = paymentAccount instanceof BankAccount ? ((BankAccount) paymentAccount).getBankId() : null; From ea9c2213d6336fa2082d0697ede738bd64dd5a37 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Sat, 20 Oct 2018 14:29:23 -0500 Subject: [PATCH 07/15] Use OfferUtil.getExtraDataMap --- .../main/offer/MutableOfferDataModel.java | 21 +------------------ 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java index d9d596ccf9a..b79889a05ed 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java @@ -39,7 +39,6 @@ import bisq.core.payment.AccountAgeWitnessService; import bisq.core.payment.BankAccount; import bisq.core.payment.CountryBasedPaymentAccount; -import bisq.core.payment.F2FAccount; import bisq.core.payment.HalCashAccount; import bisq.core.payment.PaymentAccount; import bisq.core.payment.SepaAccount; @@ -83,7 +82,6 @@ import java.util.ArrayList; import java.util.Date; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -352,25 +350,8 @@ Offer createAndGetOffer() { long lowerClosePrice = 0; long upperClosePrice = 0; String hashOfChallenge = null; - Map extraDataMap = null; - if (CurrencyUtil.isFiatCurrency(currencyCode)) { - extraDataMap = new HashMap<>(); - final String myWitnessHashAsHex = accountAgeWitnessService.getMyWitnessHashAsHex(paymentAccount.getPaymentAccountPayload()); - extraDataMap.put(OfferPayload.ACCOUNT_AGE_WITNESS_HASH, myWitnessHashAsHex); - } - - if (referralIdService.getOptionalReferralId().isPresent()) { - if (extraDataMap == null) - extraDataMap = new HashMap<>(); - extraDataMap.put(OfferPayload.REFERRAL_ID, referralIdService.getOptionalReferralId().get()); - } - if (paymentAccount instanceof F2FAccount) { - if (extraDataMap == null) - extraDataMap = new HashMap<>(); - extraDataMap.put(OfferPayload.F2F_CITY, ((F2FAccount) paymentAccount).getCity()); - extraDataMap.put(OfferPayload.F2F_EXTRA_INFO, ((F2FAccount) paymentAccount).getExtraInfo()); - } + Map extraDataMap = OfferUtil.getExtraDataMap(accountAgeWitnessService, referralIdService, paymentAccount, currencyCode); Coin buyerSecurityDepositAsCoin = buyerSecurityDeposit.get(); checkArgument(buyerSecurityDepositAsCoin.compareTo(Restrictions.getMaxBuyerSecurityDeposit()) <= 0, From 998dfa7a6f993da95315c4aeaf8418ba77ca1d80 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Sat, 20 Oct 2018 14:32:10 -0500 Subject: [PATCH 08/15] Use OfferUtil.getAcceptedCountryCodes --- .../desktop/main/offer/MutableOfferDataModel.java | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java index b79889a05ed..9321bdae996 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java @@ -41,8 +41,6 @@ import bisq.core.payment.CountryBasedPaymentAccount; import bisq.core.payment.HalCashAccount; import bisq.core.payment.PaymentAccount; -import bisq.core.payment.SepaAccount; -import bisq.core.payment.SepaInstantAccount; import bisq.core.provider.fee.FeeService; import bisq.core.provider.price.PriceFeedService; import bisq.core.trade.handlers.TransactionResultHandler; @@ -317,17 +315,7 @@ Offer createAndGetOffer() { long amount = this.amount.get() != null ? this.amount.get().getValue() : 0L; long minAmount = this.minAmount.get() != null ? this.minAmount.get().getValue() : 0L; - ArrayList acceptedCountryCodes = null; - if (paymentAccount instanceof SepaAccount) { - acceptedCountryCodes = new ArrayList<>(); - acceptedCountryCodes.addAll(((SepaAccount) paymentAccount).getAcceptedCountryCodes()); - } else if (paymentAccount instanceof SepaInstantAccount) { - acceptedCountryCodes = new ArrayList<>(); - acceptedCountryCodes.addAll(((SepaInstantAccount) paymentAccount).getAcceptedCountryCodes()); - } else if (paymentAccount instanceof CountryBasedPaymentAccount) { - acceptedCountryCodes = new ArrayList<>(); - acceptedCountryCodes.add(((CountryBasedPaymentAccount) paymentAccount).getCountry().code); - } + ArrayList acceptedCountryCodes = OfferUtil.getAcceptedCountryCodes(paymentAccount); ArrayList acceptedBanks = OfferUtil.getAcceptedBanks(paymentAccount); From 063db255cd9818802bc8f7abb49b8ff35bdaf32a Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Sat, 20 Oct 2018 14:34:21 -0500 Subject: [PATCH 09/15] Use OfferUtil.getBankId and getCountryCode --- .../java/bisq/desktop/main/offer/MutableOfferDataModel.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java index 9321bdae996..c5dcf1b7b85 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java @@ -37,8 +37,6 @@ import bisq.core.offer.OfferUtil; import bisq.core.offer.OpenOfferManager; import bisq.core.payment.AccountAgeWitnessService; -import bisq.core.payment.BankAccount; -import bisq.core.payment.CountryBasedPaymentAccount; import bisq.core.payment.HalCashAccount; import bisq.core.payment.PaymentAccount; import bisq.core.provider.fee.FeeService; @@ -319,10 +317,10 @@ Offer createAndGetOffer() { ArrayList acceptedBanks = OfferUtil.getAcceptedBanks(paymentAccount); - String bankId = paymentAccount instanceof BankAccount ? ((BankAccount) paymentAccount).getBankId() : null; + String bankId = OfferUtil.getBankId(paymentAccount); // That is optional and set to null if not supported (AltCoins, OKPay,...) - String countryCode = paymentAccount instanceof CountryBasedPaymentAccount ? ((CountryBasedPaymentAccount) paymentAccount).getCountry().code : null; + String countryCode = OfferUtil.getCountryCode(paymentAccount); checkNotNull(p2PService.getAddress(), "Address must not be null"); checkNotNull(getMakerFee(), "makerFee must not be null"); From eb89ed2a27ea209bd4f8ebef61f008dc219ca9db Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Sat, 20 Oct 2018 14:36:47 -0500 Subject: [PATCH 10/15] Add TODOs --- core/src/main/java/bisq/core/offer/OfferUtil.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/main/java/bisq/core/offer/OfferUtil.java b/core/src/main/java/bisq/core/offer/OfferUtil.java index 19baf669659..826dcb2ebab 100644 --- a/core/src/main/java/bisq/core/offer/OfferUtil.java +++ b/core/src/main/java/bisq/core/offer/OfferUtil.java @@ -309,6 +309,7 @@ public static String getCountryCode(PaymentAccount paymentAccount) { } } + // TODO remove public static long getMaxTradeLimit(AccountAgeWitnessService accountAgeWitnessService, PaymentAccount paymentAccount, String currencyCode) { if (paymentAccount != null) return accountAgeWitnessService.getMyTradeLimit(paymentAccount, currencyCode); @@ -316,6 +317,7 @@ public static long getMaxTradeLimit(AccountAgeWitnessService accountAgeWitnessSe return 0; } + // TODO remove public static long getMaxTradePeriod(PaymentAccount paymentAccount) { return paymentAccount.getPaymentMethod().getMaxTradePeriod(); } @@ -347,6 +349,7 @@ public static Map getExtraDataMap(AccountAgeWitnessService accou return extraDataMap; } + public static void validateOfferData(FilterManager filterManager, P2PService p2PService, Coin buyerSecurityDepositAsCoin, From 6ed70b9a291011446cd642dacd93f121be8cc1c0 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Sat, 20 Oct 2018 22:27:38 -0500 Subject: [PATCH 11/15] Fix ConcurrentModificationException --- .../main/java/bisq/core/btc/model/AddressEntryList.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/bisq/core/btc/model/AddressEntryList.java b/core/src/main/java/bisq/core/btc/model/AddressEntryList.java index 42a5bb0ee57..e3157ade487 100644 --- a/core/src/main/java/bisq/core/btc/model/AddressEntryList.java +++ b/core/src/main/java/bisq/core/btc/model/AddressEntryList.java @@ -77,11 +77,14 @@ public static AddressEntryList fromProto(PB.AddressEntryList proto) { @Override public Message toProtoMessage() { + // We clone here as we got ConcurrentModificationExceptions + ArrayList clone = new ArrayList<>(this.list); + List addressEntries = clone.stream() + .map(AddressEntry::toProtoMessage) + .collect(Collectors.toList()); return PB.PersistableEnvelope.newBuilder() .setAddressEntryList(PB.AddressEntryList.newBuilder() - .addAllAddressEntry(list.stream() - .map(AddressEntry::toProtoMessage) - .collect(Collectors.toList()))) + .addAllAddressEntry(addressEntries)) .build(); } From a74c656c3f0c2befcf373cabb74ab2c18c4bdb90 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Sat, 20 Oct 2018 23:02:06 -0500 Subject: [PATCH 12/15] Add TxFeeEstimation class - rewrite fee estimation for trade fee transactions - Remove preferences.getPayFeeInBtc call and use isPayFeeInBtc (delegate) instead --- .../core/btc/wallet/BtcWalletService.java | 19 ++ .../core/btc/wallet/TradeWalletService.java | 31 --- .../main/java/bisq/core/offer/OfferUtil.java | 2 +- .../java/bisq/core/offer/TxFeeEstimation.java | 199 +++++++++--------- .../main/java/bisq/core/user/Preferences.java | 4 - .../bisq/core/offer/TxFeeEstimationTest.java | 124 +++++++++++ .../main/offer/MutableOfferDataModel.java | 69 ++---- .../createoffer/CreateOfferDataModel.java | 39 +++- .../offer/takeoffer/TakeOfferDataModel.java | 169 ++++++--------- .../editoffer/EditOfferDataModel.java | 29 ++- 10 files changed, 374 insertions(+), 311 deletions(-) create mode 100644 core/src/test/java/bisq/core/offer/TxFeeEstimationTest.java diff --git a/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java b/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java index b58901d8d75..7c4f0828107 100644 --- a/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java @@ -17,6 +17,7 @@ package bisq.core.btc.wallet; +import bisq.core.app.BisqEnvironment; import bisq.core.btc.exceptions.AddressEntryException; import bisq.core.btc.exceptions.InsufficientFundsException; import bisq.core.btc.exceptions.TransactionVerificationException; @@ -946,6 +947,24 @@ private boolean feeEstimationNotSatisfied(int counter, Transaction tx) { tx.getFee().value - targetFee > 1000); } + public int getEstimatedFeeTxSize(List outputValues, Coin txFee) + throws InsufficientMoneyException, AddressFormatException { + Transaction transaction = new Transaction(params); + Address dummyAddress = wallet.currentReceiveKey().toAddress(BisqEnvironment.getParameters()); + outputValues.forEach(outputValue -> transaction.addOutput(outputValue, dummyAddress)); + + SendRequest sendRequest = SendRequest.forTx(transaction); + sendRequest.shuffleOutputs = false; + sendRequest.aesKey = aesKey; + sendRequest.coinSelector = new BtcCoinSelector(walletsSetup.getAddressesByContext(AddressEntry.Context.AVAILABLE)); + sendRequest.fee = txFee; + sendRequest.feePerKb = Coin.ZERO; + sendRequest.ensureMinRequiredFee = false; + sendRequest.changeAddress = dummyAddress; + wallet.completeTx(sendRequest); + return transaction.bitcoinSerialize().length; + } + /////////////////////////////////////////////////////////////////////////////////////////// // Withdrawal Send diff --git a/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java b/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java index cc41d021959..69a5162568a 100644 --- a/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java @@ -230,37 +230,6 @@ public Transaction createBtcTradingFeeTx(Address fundingAddress, } } - public Transaction estimateBtcTradingFeeTxSize(Address fundingAddress, - Address reservedForTradeAddress, - Address changeAddress, - Coin reservedFundsForOffer, - boolean useSavingsWallet, - Coin tradingFee, - Coin txFee, - String feeReceiverAddresses) - throws InsufficientMoneyException, AddressFormatException { - Transaction tradingFeeTx = new Transaction(params); - tradingFeeTx.addOutput(tradingFee, Address.fromBase58(params, feeReceiverAddresses)); - tradingFeeTx.addOutput(reservedFundsForOffer, reservedForTradeAddress); - - SendRequest sendRequest = SendRequest.forTx(tradingFeeTx); - sendRequest.shuffleOutputs = false; - sendRequest.aesKey = aesKey; - if (useSavingsWallet) - sendRequest.coinSelector = new BtcCoinSelector(walletsSetup.getAddressesByContext(AddressEntry.Context.AVAILABLE)); - else - sendRequest.coinSelector = new BtcCoinSelector(fundingAddress); - - sendRequest.fee = txFee; - sendRequest.feePerKb = Coin.ZERO; - sendRequest.ensureMinRequiredFee = false; - sendRequest.changeAddress = changeAddress; - checkNotNull(wallet, "Wallet must not be null"); - log.info("estimateBtcTradingFeeTxSize"); - wallet.completeTx(sendRequest); - return tradingFeeTx; - } - public Transaction completeBsqTradingFeeTx(Transaction preparedBsqTx, Address fundingAddress, Address reservedForTradeAddress, diff --git a/core/src/main/java/bisq/core/offer/OfferUtil.java b/core/src/main/java/bisq/core/offer/OfferUtil.java index 826dcb2ebab..06fff5e0ecc 100644 --- a/core/src/main/java/bisq/core/offer/OfferUtil.java +++ b/core/src/main/java/bisq/core/offer/OfferUtil.java @@ -142,7 +142,7 @@ public static Coin getMakerFee(boolean isCurrencyForMakerFeeBtc, @Nullable Coin * @return */ public static boolean isCurrencyForMakerFeeBtc(Preferences preferences, BsqWalletService bsqWalletService, Coin amount, boolean marketPriceAvailable, double marketPriceMargin) { - return preferences.getPayFeeInBtc() || + return preferences.isPayFeeInBtc() || !isBsqForFeeAvailable(bsqWalletService, amount, marketPriceAvailable, marketPriceMargin); } diff --git a/core/src/main/java/bisq/core/offer/TxFeeEstimation.java b/core/src/main/java/bisq/core/offer/TxFeeEstimation.java index dd42cb23f72..44323a22d66 100644 --- a/core/src/main/java/bisq/core/offer/TxFeeEstimation.java +++ b/core/src/main/java/bisq/core/offer/TxFeeEstimation.java @@ -17,132 +17,121 @@ package bisq.core.offer; -import bisq.core.btc.model.AddressEntry; -import bisq.core.btc.wallet.BsqWalletService; import bisq.core.btc.wallet.BtcWalletService; -import bisq.core.btc.wallet.Restrictions; -import bisq.core.btc.wallet.TradeWalletService; import bisq.core.provider.fee.FeeService; import bisq.core.user.Preferences; -import bisq.core.user.User; -import org.bitcoinj.core.Address; +import bisq.common.util.Tuple2; + import org.bitcoinj.core.Coin; import org.bitcoinj.core.InsufficientMoneyException; -import org.bitcoinj.core.Transaction; + +import com.google.common.annotations.VisibleForTesting; + +import java.util.List; import lombok.extern.slf4j.Slf4j; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; /** - * Util class for getting a fee estimation. + * Util class for getting the estimated tx fee for maker or taker fee tx. */ @Slf4j public class TxFeeEstimation { - private final BtcWalletService btcWalletService; - private final BsqWalletService bsqWalletService; - private final Preferences preferences; - private final User user; - private final TradeWalletService tradeWalletService; - private final FeeService feeService; - - private int feeTxSize = 260; // size of typical tx with 1 input - private int feeTxSizeEstimationRecursionCounter; - private String offerId; - private OfferPayload.Direction direction; - private Coin amount; - private Coin buyerSecurityDeposit; - private double marketPriceMargin; - private boolean marketPriceAvailable; - - public TxFeeEstimation(BtcWalletService btcWalletService, - BsqWalletService bsqWalletService, - Preferences preferences, - User user, - TradeWalletService tradeWalletService, - FeeService feeService, - String offerId, - OfferPayload.Direction direction, - Coin amount, - Coin buyerSecurityDeposit, - double marketPriceMargin, - boolean marketPriceAvailable, - int feeTxSize) { - - - this.btcWalletService = btcWalletService; - this.bsqWalletService = bsqWalletService; - this.preferences = preferences; - this.user = user; - this.tradeWalletService = tradeWalletService; - this.feeService = feeService; - - this.offerId = offerId; - this.direction = direction; - this.amount = amount; - this.buyerSecurityDeposit = buyerSecurityDeposit; - this.marketPriceMargin = marketPriceMargin; - this.marketPriceAvailable = marketPriceAvailable; - this.feeTxSize = feeTxSize; - } + private static int counter; + + public static Tuple2 getEstimatedFeeAndTxSizeForTaker(Coin reservedFundsForOffer, + Coin tradeFee, + FeeService feeService, + BtcWalletService btcWalletService, + Preferences preferences) { + Coin txFeePerByte = feeService.getTxFeePerByte(); + // We start with min taker fee size of 260 + int estimatedTxSize = 260; + try { + estimatedTxSize = getEstimatedTxSize(List.of(tradeFee, reservedFundsForOffer), estimatedTxSize, txFeePerByte, btcWalletService); + } catch (InsufficientMoneyException e) { + // if we cannot do the estimation we use the payout tx size + estimatedTxSize = 380; + 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); + } - public Coin getEstimatedFee() { - Coin sellerSecurityDeposit = Restrictions.getSellerSecurityDeposit(); - Coin txFeeFromFeeService = feeService.getTxFee(feeTxSize); - Address fundingAddress = btcWalletService.getFreshAddressEntry().getAddress(); - Address reservedForTradeAddress = btcWalletService.getOrCreateAddressEntry(offerId, AddressEntry.Context.RESERVED_FOR_TRADE).getAddress(); - Address changeAddress = btcWalletService.getFreshAddressEntry().getAddress(); + if (!preferences.isPayFeeInBtc()) { + // If we pay the fee in BSQ we have one input more which adds about 150 bytes + estimatedTxSize += 150; + } - Coin reservedFundsForOffer = OfferUtil.isBuyOffer(direction) ? buyerSecurityDeposit : sellerSecurityDeposit; - if (!OfferUtil.isBuyOffer(direction)) - reservedFundsForOffer = reservedFundsForOffer.add(amount); + int averageSize = (estimatedTxSize + 320) / 2; + // We use at least the size of the payout tx to not underpay at payout. + int minSize = Math.max(380, averageSize); + Coin txFee = txFeePerByte.multiply(minSize); + log.info("Fee estimation resulted in a tx size of {} bytes.\n" + + "We use an average between the taker fee tx and the deposit tx (320 bytes) which results in {} bytes.\n" + + "The payout tx has 380 bytes so we use that as our min value which is {} bytes.\n" + + "The tx fee of {}", estimatedTxSize, averageSize, minSize, txFee.toFriendlyString()); + return new Tuple2<>(txFee, minSize); + } - checkNotNull(user.getAcceptedArbitrators(), "user.getAcceptedArbitrators() must not be null"); - checkArgument(!user.getAcceptedArbitrators().isEmpty(), "user.getAcceptedArbitrators() must not be empty"); - String dummyArbitratorAddress = user.getAcceptedArbitrators().get(0).getBtcAddress(); + public static Tuple2 getEstimatedFeeAndTxSizeForMaker(Coin reservedFundsForOffer, + Coin tradeFee, + FeeService feeService, + BtcWalletService btcWalletService, + Preferences preferences) { + Coin txFeePerByte = feeService.getTxFeePerByte(); + // We start with min maker fee size of 260 + int estimatedTxSize = 260; try { - log.info("We create a dummy tx to see if our estimated size is in the accepted range. feeTxSize={}," + - " txFee based on feeTxSize: {}, recommended txFee is {} sat/byte", - feeTxSize, txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerByte()); - Transaction tradeFeeTx = tradeWalletService.estimateBtcTradingFeeTxSize( - fundingAddress, - reservedForTradeAddress, - changeAddress, - reservedFundsForOffer, - true, - OfferUtil.getMakerFee(bsqWalletService, preferences, amount, marketPriceAvailable, marketPriceMargin), - txFeeFromFeeService, - dummyArbitratorAddress); - - final int txSize = tradeFeeTx.bitcoinSerialize().length; - // use feeTxSizeEstimationRecursionCounter to avoid risk for endless loop - if (txSize > feeTxSize * 1.2 && feeTxSizeEstimationRecursionCounter < 10) { - feeTxSizeEstimationRecursionCounter++; - log.info("txSize is {} bytes but feeTxSize used for txFee calculation was {} bytes. We try again with an " + - "adjusted txFee to reach the target tx fee.", txSize, feeTxSize); - feeTxSize = txSize; - txFeeFromFeeService = feeService.getTxFee(feeTxSize); - // lets try again with the adjusted txSize and fee. - getEstimatedFee(); - } else { - log.info("feeTxSize {} bytes", feeTxSize); - log.info("txFee based on estimated size: {}, recommended txFee is {} sat/byte", - txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerByte()); - } + estimatedTxSize = getEstimatedTxSize(List.of(tradeFee, reservedFundsForOffer), estimatedTxSize, txFeePerByte, btcWalletService); } catch (InsufficientMoneyException e) { - // If we need to fund from an external wallet we can assume we only have 1 input (260 bytes). - log.warn("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 260 bytes."); - feeTxSize = 260; - txFeeFromFeeService = feeService.getTxFee(feeTxSize); - log.info("feeTxSize {} bytes", feeTxSize); - log.info("txFee based on estimated size: {}, recommended txFee is {} sat/byte", - txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerByte()); + 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); } - return txFeeFromFeeService; + if (!preferences.isPayFeeInBtc()) { + // If we pay the fee in BSQ we have one input more which adds about 150 bytes + estimatedTxSize += 150; + } + + Coin txFee = txFeePerByte.multiply(estimatedTxSize); + log.info("Fee estimation resulted in a tx size of {} bytes and a tx fee of {}", estimatedTxSize, txFee.toFriendlyString()); + return new Tuple2<>(txFee, estimatedTxSize); + } + + @VisibleForTesting + static int getEstimatedTxSize(List outputValues, + int initialEstimatedTxSize, + Coin txFeePerByte, + BtcWalletService btcWalletService) + throws InsufficientMoneyException { + boolean isInTolerance; + int estimatedTxSize = initialEstimatedTxSize; + int realTxSize; + do { + Coin txFee = txFeePerByte.multiply(estimatedTxSize); + realTxSize = btcWalletService.getEstimatedFeeTxSize(outputValues, txFee); + isInTolerance = isInTolerance(estimatedTxSize, realTxSize, 0.2); + if (!isInTolerance) { + estimatedTxSize = realTxSize; + } + counter++; + } + while (!isInTolerance && counter < 10); + if (!isInTolerance) { + log.warn("We could not find a tx which satisfies our tolerance requirement of 20%. " + + "realTxSize={}, estimatedTxSize={}", + realTxSize, estimatedTxSize); + } + return estimatedTxSize; } -} + @VisibleForTesting + static boolean isInTolerance(int estimatedSize, int txSize, double tolerance) { + checkArgument(estimatedSize > 0, "estimatedSize must be positive"); + checkArgument(txSize > 0, "txSize must be positive"); + checkArgument(tolerance > 0, "tolerance must be positive"); + double deviation = Math.abs(1 - ((double) estimatedSize / (double) txSize)); + return deviation <= tolerance; + } +} diff --git a/core/src/main/java/bisq/core/user/Preferences.java b/core/src/main/java/bisq/core/user/Preferences.java index 173b258b3b9..cbeb620ac5a 100644 --- a/core/src/main/java/bisq/core/user/Preferences.java +++ b/core/src/main/java/bisq/core/user/Preferences.java @@ -673,10 +673,6 @@ public Coin getBuyerSecurityDepositAsCoin() { return Coin.valueOf(prefPayload.getBuyerSecurityDepositAsLong()); } - public boolean getPayFeeInBtc() { - return prefPayload.isPayFeeInBtc(); - } - @Override @Nullable public List getBridgeAddresses() { diff --git a/core/src/test/java/bisq/core/offer/TxFeeEstimationTest.java b/core/src/test/java/bisq/core/offer/TxFeeEstimationTest.java new file mode 100644 index 00000000000..b9d4db6d2eb --- /dev/null +++ b/core/src/test/java/bisq/core/offer/TxFeeEstimationTest.java @@ -0,0 +1,124 @@ +/* + * This file is part of Bisq. + * + * Bisq 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. + * + * Bisq 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 Bisq. If not, see . + */ + +package bisq.core.offer; + +import bisq.core.btc.wallet.BtcWalletService; + +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.InsufficientMoneyException; + +import java.util.List; + +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import org.junit.Test; +import org.junit.runner.RunWith; + +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; + +@RunWith(PowerMockRunner.class) +@PrepareForTest(BtcWalletService.class) +@PowerMockIgnore({"com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*"}) +public class TxFeeEstimationTest { + + @Test + public void testGetEstimatedTxSize() throws InsufficientMoneyException { + List outputValues = List.of(Coin.valueOf(2000), Coin.valueOf(3000)); + int initialEstimatedTxSize; + Coin txFeePerByte; + BtcWalletService btcWalletService = mock(BtcWalletService.class); + int result; + int realTxSize; + Coin txFee; + + + initialEstimatedTxSize = 260; + txFeePerByte = Coin.valueOf(10); + realTxSize = 260; + + txFee = txFeePerByte.multiply(initialEstimatedTxSize); + when(btcWalletService.getEstimatedFeeTxSize(outputValues, txFee)).thenReturn(realTxSize); + result = TxFeeEstimation.getEstimatedTxSize(outputValues, initialEstimatedTxSize, txFeePerByte, btcWalletService); + assertEquals(260, result); + + + // TODO check how to use the mocking framework for repeated calls + // The btcWalletService.getEstimatedFeeTxSize returns 0 at repeated calls in the while loop.... + /* initialEstimatedTxSize = 260; + txFeePerByte = Coin.valueOf(10); + realTxSize = 2600; + + txFee = txFeePerByte.multiply(initialEstimatedTxSize); + when(btcWalletService.getEstimatedFeeTxSize(outputValues, txFee)).thenReturn(realTxSize); + result = TxFeeEstimation.getEstimatedTxSize(outputValues, initialEstimatedTxSize, txFeePerByte, btcWalletService); + assertEquals(2600, result); + + initialEstimatedTxSize = 2600; + txFeePerByte = Coin.valueOf(10); + realTxSize = 260; + + txFee = txFeePerByte.multiply(initialEstimatedTxSize); + when(btcWalletService.getEstimatedFeeTxSize(outputValues, txFee)).thenReturn(realTxSize); + result = TxFeeEstimation.getEstimatedTxSize(outputValues, initialEstimatedTxSize, txFeePerByte, btcWalletService); + assertEquals(260, result);*/ + } + + @Test + public void testIsInTolerance() { + int estimatedSize; + int txSize; + double tolerance; + boolean result; + + estimatedSize = 100; + txSize = 100; + tolerance = 0.0001; + result = TxFeeEstimation.isInTolerance(estimatedSize, txSize, tolerance); + assertTrue(result); + + estimatedSize = 100; + txSize = 200; + tolerance = 0.2; + result = TxFeeEstimation.isInTolerance(estimatedSize, txSize, tolerance); + assertFalse(result); + + estimatedSize = 120; + txSize = 100; + tolerance = 0.2; + result = TxFeeEstimation.isInTolerance(estimatedSize, txSize, tolerance); + assertTrue(result); + + estimatedSize = 200; + txSize = 100; + tolerance = 1; + result = TxFeeEstimation.isInTolerance(estimatedSize, txSize, tolerance); + assertTrue(result); + + estimatedSize = 201; + txSize = 100; + tolerance = 1; + result = TxFeeEstimation.isInTolerance(estimatedSize, txSize, tolerance); + assertFalse(result); + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java index c5dcf1b7b85..85d60c29da7 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java @@ -25,7 +25,6 @@ import bisq.core.btc.wallet.BsqWalletService; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.Restrictions; -import bisq.core.btc.wallet.TradeWalletService; import bisq.core.filter.FilterManager; import bisq.core.locale.CurrencyUtil; import bisq.core.locale.Res; @@ -36,6 +35,7 @@ import bisq.core.offer.OfferPayload; import bisq.core.offer.OfferUtil; import bisq.core.offer.OpenOfferManager; +import bisq.core.offer.TxFeeEstimation; import bisq.core.payment.AccountAgeWitnessService; import bisq.core.payment.HalCashAccount; import bisq.core.payment.PaymentAccount; @@ -51,11 +51,10 @@ import bisq.common.app.Version; import bisq.common.crypto.KeyRing; +import bisq.common.util.Tuple2; import bisq.common.util.Utilities; -import org.bitcoinj.core.Address; import org.bitcoinj.core.Coin; -import org.bitcoinj.core.InsufficientMoneyException; import org.bitcoinj.core.Transaction; import com.google.inject.Inject; @@ -98,7 +97,6 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs final String shortOfferId; private final FilterManager filterManager; private final AccountAgeWitnessService accountAgeWitnessService; - private final TradeWalletService tradeWalletService; private final FeeService feeService; private final ReferralIdService referralIdService; private final BSFormatter formatter; @@ -130,7 +128,6 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs protected Coin txFeeFromFeeService; protected boolean marketPriceAvailable; protected int feeTxSize = 260; // size of typical tx with 1 input - protected int feeTxSizeEstimationRecursionCounter; protected boolean allowAmountUpdate = true; @@ -142,8 +139,9 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs public MutableOfferDataModel(OpenOfferManager openOfferManager, BtcWalletService btcWalletService, BsqWalletService bsqWalletService, Preferences preferences, User user, KeyRing keyRing, P2PService p2PService, PriceFeedService priceFeedService, FilterManager filterManager, - AccountAgeWitnessService accountAgeWitnessService, TradeWalletService tradeWalletService, - FeeService feeService, ReferralIdService referralIdService, BSFormatter formatter) { + AccountAgeWitnessService accountAgeWitnessService, FeeService feeService, + ReferralIdService referralIdService, + BSFormatter formatter) { super(btcWalletService); this.openOfferManager = openOfferManager; @@ -155,7 +153,6 @@ public MutableOfferDataModel(OpenOfferManager openOfferManager, BtcWalletService this.priceFeedService = priceFeedService; this.filterManager = filterManager; this.accountAgeWitnessService = accountAgeWitnessService; - this.tradeWalletService = tradeWalletService; this.feeService = feeService; this.referralIdService = referralIdService; this.formatter = formatter; @@ -397,57 +394,17 @@ Offer createAndGetOffer() { // This works only if we have already funds in the wallet public void estimateTxSize() { - txFeeFromFeeService = feeService.getTxFee(feeTxSize); - Address fundingAddress = btcWalletService.getFreshAddressEntry().getAddress(); - Address reservedForTradeAddress = btcWalletService.getOrCreateAddressEntry(offerId, AddressEntry.Context.RESERVED_FOR_TRADE).getAddress(); - Address changeAddress = btcWalletService.getFreshAddressEntry().getAddress(); - Coin reservedFundsForOffer = getSecurityDeposit(); if (!isBuyOffer()) reservedFundsForOffer = reservedFundsForOffer.add(amount.get()); - checkNotNull(user.getAcceptedArbitrators(), "user.getAcceptedArbitrators() must not be null"); - checkArgument(!user.getAcceptedArbitrators().isEmpty(), "user.getAcceptedArbitrators() must not be empty"); - String dummyArbitratorAddress = user.getAcceptedArbitrators().get(0).getBtcAddress(); - try { - log.info("We create a dummy tx to see if our estimated size is in the accepted range. feeTxSize={}," + - " txFee based on feeTxSize: {}, recommended txFee is {} sat/byte", - feeTxSize, txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerByte()); - Transaction tradeFeeTx = tradeWalletService.estimateBtcTradingFeeTxSize( - fundingAddress, - reservedForTradeAddress, - changeAddress, - reservedFundsForOffer, - true, - getMakerFee(), - txFeeFromFeeService, - dummyArbitratorAddress); - - final int txSize = tradeFeeTx.bitcoinSerialize().length; - // use feeTxSizeEstimationRecursionCounter to avoid risk for endless loop - if (txSize > feeTxSize * 1.2 && feeTxSizeEstimationRecursionCounter < 10) { - feeTxSizeEstimationRecursionCounter++; - log.info("txSize is {} bytes but feeTxSize used for txFee calculation was {} bytes. We try again with an " + - "adjusted txFee to reach the target tx fee.", txSize, feeTxSize); - feeTxSize = txSize; - txFeeFromFeeService = feeService.getTxFee(feeTxSize); - // lets try again with the adjusted txSize and fee. - estimateTxSize(); - } else { - log.info("feeTxSize {} bytes", feeTxSize); - log.info("txFee based on estimated size: {}, recommended txFee is {} sat/byte", - txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerByte()); - } - } catch (InsufficientMoneyException e) { - // If we need to fund from an external wallet we can assume we only have 1 input (260 bytes). - log.warn("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 260 bytes."); - feeTxSize = 260; - txFeeFromFeeService = feeService.getTxFee(feeTxSize); - log.info("feeTxSize {} bytes", feeTxSize); - log.info("txFee based on estimated size: {}, recommended txFee is {} sat/byte", - txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerByte()); - } + Tuple2 estimatedFeeAndTxSize = TxFeeEstimation.getEstimatedFeeAndTxSizeForMaker(reservedFundsForOffer, + getMakerFee(), + feeService, + btcWalletService, + preferences); + txFeeFromFeeService = estimatedFeeAndTxSize.first; + feeTxSize = estimatedFeeAndTxSize.second; } void onPlaceOffer(Offer offer, TransactionResultHandler resultHandler) { @@ -606,7 +563,7 @@ public double getMarketPriceMargin() { } boolean isMakerFeeValid() { - return preferences.getPayFeeInBtc() || isBsqForFeeAvailable(); + return preferences.isPayFeeInBtc() || isBsqForFeeAvailable(); } long getMaxTradeLimit() { diff --git a/desktop/src/main/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModel.java index e2250863431..cd9562515b0 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModel.java @@ -3,16 +3,20 @@ * * Bisq 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 + * the Free Software Foundation, +either version 3 of the License, +or (at * your option) any later version. * - * Bisq is distributed in the hope that it will be useful, but WITHOUT + * Bisq 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 Bisq. If not, see . + * along with Bisq. If not, +see . */ package bisq.desktop.main.offer.createoffer; @@ -21,7 +25,6 @@ import bisq.core.btc.wallet.BsqWalletService; import bisq.core.btc.wallet.BtcWalletService; -import bisq.core.btc.wallet.TradeWalletService; import bisq.core.filter.FilterManager; import bisq.core.offer.OpenOfferManager; import bisq.core.payment.AccountAgeWitnessService; @@ -46,7 +49,31 @@ class CreateOfferDataModel extends MutableOfferDataModel { @Inject - public CreateOfferDataModel(OpenOfferManager openOfferManager, BtcWalletService btcWalletService, BsqWalletService bsqWalletService, Preferences preferences, User user, KeyRing keyRing, P2PService p2PService, PriceFeedService priceFeedService, FilterManager filterManager, AccountAgeWitnessService accountAgeWitnessService, TradeWalletService tradeWalletService, FeeService feeService, ReferralIdService referralIdService, BSFormatter formatter) { - super(openOfferManager, btcWalletService, bsqWalletService, preferences, user, keyRing, p2PService, priceFeedService, filterManager, accountAgeWitnessService, tradeWalletService, feeService, referralIdService, formatter); + public CreateOfferDataModel(OpenOfferManager openOfferManager, + BtcWalletService btcWalletService, + BsqWalletService bsqWalletService, + Preferences preferences, + User user, + KeyRing keyRing, + P2PService p2PService, + PriceFeedService priceFeedService, + FilterManager filterManager, + AccountAgeWitnessService accountAgeWitnessService, + FeeService feeService, + ReferralIdService referralIdService, + BSFormatter formatter) { + super(openOfferManager, + btcWalletService, + bsqWalletService, + preferences, + user, + keyRing, + p2PService, + priceFeedService, + filterManager, + accountAgeWitnessService, + feeService, + referralIdService, + formatter); } } diff --git a/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java index 1e5bccee9b1..844da065321 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java @@ -27,7 +27,6 @@ import bisq.core.btc.wallet.BsqWalletService; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.Restrictions; -import bisq.core.btc.wallet.TradeWalletService; import bisq.core.filter.FilterManager; import bisq.core.locale.CurrencyUtil; import bisq.core.locale.Res; @@ -36,6 +35,7 @@ import bisq.core.offer.Offer; import bisq.core.offer.OfferPayload; import bisq.core.offer.OfferUtil; +import bisq.core.offer.TxFeeEstimation; import bisq.core.payment.AccountAgeWitnessService; import bisq.core.payment.HalCashAccount; import bisq.core.payment.PaymentAccount; @@ -49,9 +49,9 @@ import bisq.core.user.User; import bisq.core.util.CoinUtil; -import org.bitcoinj.core.Address; +import bisq.common.util.Tuple2; + import org.bitcoinj.core.Coin; -import org.bitcoinj.core.InsufficientMoneyException; import org.bitcoinj.core.Transaction; import org.bitcoinj.wallet.Wallet; @@ -65,6 +65,8 @@ import java.util.List; +import org.jetbrains.annotations.NotNull; + import javax.annotation.Nullable; import static com.google.common.base.Preconditions.checkArgument; @@ -83,12 +85,10 @@ class TakeOfferDataModel extends OfferDataModel { private final FilterManager filterManager; private final Preferences preferences; private final PriceFeedService priceFeedService; - private final TradeWalletService tradeWalletService; private final AccountAgeWitnessService accountAgeWitnessService; private Coin txFeeFromFeeService; private Coin securityDeposit; - // Coin feeFromFundingTx = Coin.NEGATIVE_SATOSHI; private Offer offer; @@ -101,9 +101,8 @@ class TakeOfferDataModel extends OfferDataModel { private PaymentAccount paymentAccount; private boolean isTabSelected; Price tradePrice; - // 260 kb is size of typical trade fee tx with 1 input but trade tx (deposit and payout) are larger so we adjust to 320 - private int feeTxSize = 320; - private int feeTxSizeEstimationRecursionCounter; + // We take payout tx as the default value if we cannot do the fee estimation + private int feeTxSize = 380; private boolean freezeFee; private Coin txFeePerByteFromFeeService; @@ -117,7 +116,7 @@ class TakeOfferDataModel extends OfferDataModel { TakeOfferDataModel(TradeManager tradeManager, BtcWalletService btcWalletService, BsqWalletService bsqWalletService, User user, FeeService feeService, FilterManager filterManager, - Preferences preferences, PriceFeedService priceFeedService, TradeWalletService tradeWalletService, + Preferences preferences, PriceFeedService priceFeedService, AccountAgeWitnessService accountAgeWitnessService) { super(btcWalletService); @@ -128,10 +127,7 @@ class TakeOfferDataModel extends OfferDataModel { this.filterManager = filterManager; this.preferences = preferences; this.priceFeedService = priceFeedService; - this.tradeWalletService = tradeWalletService; this.accountAgeWitnessService = accountAgeWitnessService; - - // isMainNet.set(preferences.getBaseCryptoNetwork() == BitcoinNetwork.BTC_MAINNET); } @Override @@ -194,7 +190,7 @@ void initWithData(Offer offer) { // and batched txs would add more complexity to the trade protocol. // A typical trade fee tx has about 260 bytes (if one input). The trade txs has about 336-414 bytes. - // We use 320 as a average value. + // We use 380 as a average value. // trade fee tx: 260 bytes (1 input) // deposit tx: 336 bytes (1 MS output+ OP_RETURN) - 414 bytes (1 MS output + OP_RETURN + change in case of smaller trade amount) @@ -204,14 +200,15 @@ void initWithData(Offer offer) { // 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); + txFeeFromFeeService = txFeePerByteFromFeeService.multiply(feeTxSize); // We request to get the actual estimated fee log.info("Start requestTxFee: txFeeFromFeeService={}", txFeeFromFeeService); feeService.requestFees(() -> { if (!freezeFee) { txFeePerByteFromFeeService = feeService.getTxFeePerByte(); - txFeeFromFeeService = getTxFeeBySize(feeTxSize); + txFeeFromFeeService = txFeePerByteFromFeeService.multiply(feeTxSize); + calculateTotalToPay(); log.info("Completed requestTxFee: txFeeFromFeeService={}", txFeeFromFeeService); } else { @@ -287,7 +284,7 @@ void onTakeOffer(TradeResultHandler tradeResultHandler) { checkNotNull(txFeeFromFeeService, "txFeeFromFeeService must not be null"); checkNotNull(getTakerFee(), "takerFee must not be null"); - Coin fundsNeededForTrade = getSecurityDeposit().add(txFeeFromFeeService).add(txFeeFromFeeService); + Coin fundsNeededForTrade = getFundsNeededForTrade(); if (isBuyOffer()) fundsNeededForTrade = fundsNeededForTrade.add(amount.get()); @@ -329,83 +326,47 @@ void onTakeOffer(TradeResultHandler tradeResultHandler) { // 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() { - Address fundingAddress = btcWalletService.getFreshAddressEntry().getAddress(); int txSize = 0; if (btcWalletService.getBalance(Wallet.BalanceType.AVAILABLE).isPositive()) { - txFeeFromFeeService = getTxFeeBySize(feeTxSize); - - Address reservedForTradeAddress = btcWalletService.getOrCreateAddressEntry(offer.getId(), AddressEntry.Context.RESERVED_FOR_TRADE).getAddress(); - Address changeAddress = btcWalletService.getFreshAddressEntry().getAddress(); - - Coin reservedFundsForOffer = getSecurityDeposit().add(txFeeFromFeeService).add(txFeeFromFeeService); + Coin fundsNeededForTrade = getFundsNeededForTrade(); if (isBuyOffer()) - reservedFundsForOffer = reservedFundsForOffer.add(amount.get()); - - checkNotNull(user.getAcceptedArbitrators(), "user.getAcceptedArbitrators() must not be null"); - checkArgument(!user.getAcceptedArbitrators().isEmpty(), "user.getAcceptedArbitrators() must not be empty"); - String dummyArbitratorAddress = user.getAcceptedArbitrators().get(0).getBtcAddress(); - try { - log.debug("We create a dummy tx to see if our estimated size is in the accepted range. feeTxSize={}," + - " txFee based on feeTxSize: {}, recommended txFee is {} sat/byte", - feeTxSize, txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerByte()); - Transaction tradeFeeTx = tradeWalletService.estimateBtcTradingFeeTxSize( - fundingAddress, - reservedForTradeAddress, - changeAddress, - reservedFundsForOffer, - true, - getTakerFee(), - txFeeFromFeeService, - dummyArbitratorAddress); - - txSize = tradeFeeTx.bitcoinSerialize().length; - // use feeTxSizeEstimationRecursionCounter to avoid risk for endless loop - // We use the tx size for the trade fee tx as target for the fees. - // The deposit and payout txs are determined +/- 1 output but the trade fee tx can have either 1 or many inputs - // so we need to make sure the trade fee tx gets the correct fee to not get stuck. - // We use a 20% tolerance frm out default 320 byte size (typical for deposit and payout) and only if we get a - // larger size we increase the fee. Worst case is that we overpay for the other follow up txs, but better than - // use a too low fee and get stuck. - if (txSize > feeTxSize * 1.2 && feeTxSizeEstimationRecursionCounter < 10) { - feeTxSizeEstimationRecursionCounter++; - log.info("txSize is {} bytes but feeTxSize used for txFee calculation was {} bytes. We try again with an " + - "adjusted txFee to reach the target tx fee.", txSize, feeTxSize); - - feeTxSize = txSize; - txFeeFromFeeService = getTxFeeBySize(txSize); - - // lets try again with the adjusted txSize and fee. - estimateTxSize(); - } else { - // We are done with estimation iterations - if (feeTxSizeEstimationRecursionCounter < 10) - log.info("Fee estimation completed:\n" + - "txFee based on estimated size of {} bytes. Average tx size = {} bytes. Actual tx size = {} bytes. TxFee is {} ({} sat/byte)", - feeTxSize, getAverageSize(feeTxSize), txSize, txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerByte()); - else - log.warn("We could not estimate the fee as the feeTxSizeEstimationRecursionCounter exceeded our limit of 10 recursions.\n" + - "txFee based on estimated size of {} bytes. Average tx size = {} bytes. Actual tx size = {} bytes. " + - "TxFee is {} ({} sat/byte)", - feeTxSize, getAverageSize(feeTxSize), txSize, txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerByte()); - } - } catch (InsufficientMoneyException e) { - log.info("We cannot complete the fee estimation because there are not enough funds in the wallet.\n" + - "This is expected if the user has not sufficient funds yet.\n" + - "In that case we use the latest estimated tx size or the default if none has been calculated yet.\n" + - "txFee based on estimated size of {} bytes. Average tx size = {} bytes. Actual tx size = {} bytes. TxFee is {} ({} sat/byte)", - feeTxSize, getAverageSize(feeTxSize), txSize, txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerByte()); - } + fundsNeededForTrade = fundsNeededForTrade.add(amount.get()); + + // 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 + // there more deterministic. + // The trade fee tx can be in the worst case very large if there are many inputs so if we take that tx alone + // for the fee estimation we would overpay a lot. + // On the other side if we have the best case of a 1 input tx fee tx then it is only 260 bytes but the + // other 2 txs are larger (320 and 380 bytes) and would get a lower fee/byte as intended. + // We apply following model to not overpay too much but be on the safe side as well. + // We sum the taker fee tx and the deposit tx together as it can be assumed that both be in the same block and + // as they are dependent txs the miner will pick both if the fee in total is good enough. + // We make sure that the fee is sufficient to meet our intended fee/byte for the larger payout tx with 380 bytes. + Tuple2 estimatedFeeAndTxSize = TxFeeEstimation.getEstimatedFeeAndTxSizeForTaker(fundsNeededForTrade, + getTakerFee(), + feeService, + btcWalletService, + preferences); + txFeeFromFeeService = estimatedFeeAndTxSize.first; + feeTxSize = estimatedFeeAndTxSize.second; } else { - feeTxSize = 320; - txFeeFromFeeService = getTxFeeBySize(feeTxSize); + feeTxSize = 380; + txFeeFromFeeService = txFeePerByteFromFeeService.multiply(feeTxSize); log.info("We cannot do the fee estimation because there are no funds in the wallet.\nThis is expected " + "if the user has not funded his wallet yet.\n" + - "In that case we use an estimated tx size of 320 bytes.\n" + - "txFee based on estimated size of {} bytes. Average tx size = {} bytes. Actual tx size = {} bytes. TxFee is {} ({} sat/byte)", - feeTxSize, getAverageSize(feeTxSize), txSize, txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerByte()); + "In that case we use an estimated tx size of 380 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()); } } + @NotNull + private Coin getFundsNeededForTrade() { + return getSecurityDeposit().add(getTxFeeForDepositTx()).add(getTxFeeForPayoutTx()); + } + public void onPaymentAccountSelected(PaymentAccount paymentAccount) { if (paymentAccount != null) { this.paymentAccount = paymentAccount; @@ -450,11 +411,11 @@ boolean hasAcceptedArbitrators() { } boolean isCurrencyForTakerFeeBtc() { - return preferences.getPayFeeInBtc() || !isBsqForFeeAvailable(); + return preferences.isPayFeeInBtc() || !isBsqForFeeAvailable(); } boolean isTakerFeeValid() { - return preferences.getPayFeeInBtc() || isBsqForFeeAvailable(); + return preferences.isPayFeeInBtc() || isBsqForFeeAvailable(); } boolean isBsqForFeeAvailable() { @@ -557,23 +518,6 @@ public void swapTradeToSavings() { btcWalletService.resetAddressEntriesForOpenOffer(offer.getId()); } - // We use the sum of the size 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 + 320) / 2; - } - - private Coin getTxFeeBySize(int sizeInBytes) { - return txFeePerByteFromFeeService.multiply(getAverageSize(sizeInBytes)); - } - - /* private void setFeeFromFundingTx(Coin fee) { - feeFromFundingTx = fee; - isFeeFromFundingTxSufficient.set(feeFromFundingTx.compareTo(FeePolicy.getMinRequiredFeeForFundingTx()) >= 0); - }*/ - boolean isMinAmountLessOrEqualAmount() { //noinspection SimplifiableIfStatement if (offer != null && amount.get() != null) @@ -620,10 +564,25 @@ public String getCurrencyNameAndCode() { } public Coin getTotalTxFee() { + Coin totalTxTees = txFeeFromFeeService.add(getTxFeeForDepositTx()).add(getTxFeeForPayoutTx()); if (isCurrencyForTakerFeeBtc()) - return txFeeFromFeeService.multiply(3); + return totalTxTees; else - return txFeeFromFeeService.multiply(3).subtract(getTakerFee() != null ? getTakerFee() : Coin.ZERO); + return totalTxTees.subtract(getTakerFee() != null ? getTakerFee() : Coin.ZERO); + } + + private Coin getTxFeeForDepositTx() { + // Unfortunately we cannot change that to the correct fees as it would break backward compatibility + // We still might find a way with offer version or app version checks so lets keep that commented out + // code as that shows how it should be. + return txFeeFromFeeService; //feeService.getTxFee(320); + } + + private Coin getTxFeeForPayoutTx() { + // Unfortunately we cannot change that to the correct fees as it would break backward compatibility + // We still might find a way with offer version or app version checks so lets keep that commented out + // code as that shows how it should be. + return txFeeFromFeeService; //feeService.getTxFee(380); } public AddressEntry getAddressEntry() { diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModel.java index f0090d191c8..f1d27dd98c4 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModel.java @@ -22,7 +22,6 @@ import bisq.core.btc.wallet.BsqWalletService; import bisq.core.btc.wallet.BtcWalletService; -import bisq.core.btc.wallet.TradeWalletService; import bisq.core.filter.FilterManager; import bisq.core.locale.CurrencyUtil; import bisq.core.locale.TradeCurrency; @@ -55,8 +54,32 @@ class EditOfferDataModel extends MutableOfferDataModel { private OpenOffer.State initialState; @Inject - EditOfferDataModel(OpenOfferManager openOfferManager, BtcWalletService btcWalletService, BsqWalletService bsqWalletService, Preferences preferences, User user, KeyRing keyRing, P2PService p2PService, PriceFeedService priceFeedService, FilterManager filterManager, AccountAgeWitnessService accountAgeWitnessService, TradeWalletService tradeWalletService, FeeService feeService, ReferralIdService referralIdService, BSFormatter formatter, CorePersistenceProtoResolver corePersistenceProtoResolver) { - super(openOfferManager, btcWalletService, bsqWalletService, preferences, user, keyRing, p2PService, priceFeedService, filterManager, accountAgeWitnessService, tradeWalletService, feeService, referralIdService, formatter); + EditOfferDataModel(OpenOfferManager openOfferManager, BtcWalletService btcWalletService, + BsqWalletService bsqWalletService, + Preferences preferences, + User user, + KeyRing keyRing, + P2PService p2PService, + PriceFeedService priceFeedService, + FilterManager filterManager, + AccountAgeWitnessService accountAgeWitnessService, + FeeService feeService, + ReferralIdService referralIdService, + BSFormatter formatter, + CorePersistenceProtoResolver corePersistenceProtoResolver) { + super(openOfferManager, + btcWalletService, + bsqWalletService, + preferences, + user, + keyRing, + p2PService, + priceFeedService, + filterManager, + accountAgeWitnessService, + feeService, + referralIdService, + formatter); this.corePersistenceProtoResolver = corePersistenceProtoResolver; } From 495f6228a691df6e5602b1bd85124a3c6a712123 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Sat, 20 Oct 2018 23:04:39 -0500 Subject: [PATCH 13/15] Fix arguments --- .../main/offer/createoffer/CreateOfferViewModelTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferViewModelTest.java b/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferViewModelTest.java index 8ee590cf89f..e0e173b9ebd 100644 --- a/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferViewModelTest.java +++ b/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferViewModelTest.java @@ -103,7 +103,9 @@ public void setUp() { when(securityDepositValidator.validate(any())).thenReturn(new InputValidator.ValidationResult(false)); when(accountAgeWitnessService.getMyTradeLimit(any(), any())).thenReturn(100000000L); - CreateOfferDataModel dataModel = new CreateOfferDataModel(null, btcWalletService, bsqWalletService, empty, user, null, null, priceFeedService, null, accountAgeWitnessService, null, feeService, null, bsFormatter); + CreateOfferDataModel dataModel = new CreateOfferDataModel(null, btcWalletService, + bsqWalletService, empty, user, null, null, priceFeedService, + null, accountAgeWitnessService, feeService, null, bsFormatter); dataModel.initWithData(OfferPayload.Direction.BUY, new CryptoCurrency("BTC", "bitcoin")); dataModel.activate(); From 612a2b1707b24d86c5facba8ae4b97f5dd6e3eba Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Sat, 20 Oct 2018 23:30:05 -0500 Subject: [PATCH 14/15] Add getFundsNeededForMaker and getFundsNeededForTaker methods --- .../main/java/bisq/core/offer/OfferUtil.java | 42 ++++++++++++++----- .../main/offer/MutableOfferDataModel.java | 9 ++-- .../offer/takeoffer/TakeOfferDataModel.java | 6 +-- 3 files changed, 36 insertions(+), 21 deletions(-) diff --git a/core/src/main/java/bisq/core/offer/OfferUtil.java b/core/src/main/java/bisq/core/offer/OfferUtil.java index 06fff5e0ecc..b4d00697b5b 100644 --- a/core/src/main/java/bisq/core/offer/OfferUtil.java +++ b/core/src/main/java/bisq/core/offer/OfferUtil.java @@ -176,9 +176,8 @@ public static Volume getAdjustedVolumeForHalCash(Volume volumeByAmount) { } /** - * - * @param volumeByAmount The volume generated from an amount - * @param factor The factor used for rounding. E.g. 1 means rounded to units of 1 EUR, 10 means rounded to 10 EUR... + * @param volumeByAmount The volume generated from an amount + * @param factor The factor used for rounding. E.g. 1 means rounded to units of 1 EUR, 10 means rounded to 10 EUR... * @return The adjusted Fiat volume */ @VisibleForTesting @@ -195,9 +194,9 @@ static Volume getAdjustedFiatVolume(Volume volumeByAmount, int factor) { * Calculate the possibly adjusted amount for {@code amount}, taking into account the * {@code price} and {@code maxTradeLimit} and {@code factor}. * - * @param amount Bitcoin amount which is a candidate for getting rounded. - * @param price Price used in relation ot that amount. - * @param maxTradeLimit The max. trade limit of the users account, in satoshis. + * @param amount Bitcoin amount which is a candidate for getting rounded. + * @param price Price used in relation ot that amount. + * @param maxTradeLimit The max. trade limit of the users account, in satoshis. * @return The adjusted amount */ public static Coin getRoundedFiatAmount(Coin amount, Price price, long maxTradeLimit) { @@ -212,11 +211,11 @@ public static Coin getAdjustedAmountForHalCash(Coin amount, Price price, long ma * Calculate the possibly adjusted amount for {@code amount}, taking into account the * {@code price} and {@code maxTradeLimit} and {@code factor}. * - * @param amount Bitcoin amount which is a candidate for getting rounded. - * @param price Price used in relation ot that amount. - * @param maxTradeLimit The max. trade limit of the users account, in satoshis. - * @param factor The factor used for rounding. E.g. 1 means rounded to units of - * 1 EUR, 10 means rounded to 10 EUR, etc. + * @param amount Bitcoin amount which is a candidate for getting rounded. + * @param price Price used in relation ot that amount. + * @param maxTradeLimit The max. trade limit of the users account, in satoshis. + * @param factor The factor used for rounding. E.g. 1 means rounded to units of + * 1 EUR, 10 means rounded to 10 EUR, etc. * @return The adjusted amount */ @VisibleForTesting @@ -369,4 +368,25 @@ public static void validateOfferData(FilterManager filterManager, checkNotNull(makerFeeAsCoin, "makerFee must not be null"); checkNotNull(p2PService.getAddress(), "Address must not be null"); } + + public static Coin getFundsNeededForMaker(Coin tradeAmount, Coin buyerSecurityDeposit, OfferPayload.Direction direction) { + boolean buyOffer = isBuyOffer(direction); + Coin needed = buyOffer ? buyerSecurityDeposit : Restrictions.getSellerSecurityDeposit(); + if (!buyOffer) + needed = needed.add(tradeAmount); + + return needed; + } + + public static Coin getFundsNeededForTaker(Coin tradeAmount, Coin txFeeForDepositTx, Coin txFeeForPayoutTx, Offer offer) { + boolean buyOffer = isBuyOffer(offer.getDirection()); + Coin needed = buyOffer ? offer.getSellerSecurityDeposit() : offer.getBuyerSecurityDeposit(); + + if (buyOffer) + needed = needed.add(tradeAmount); + + needed = needed.add(txFeeForDepositTx).add(txFeeForPayoutTx); + + return needed; + } } diff --git a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java index 85d60c29da7..a2faf404ac9 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java @@ -394,11 +394,8 @@ Offer createAndGetOffer() { // This works only if we have already funds in the wallet public void estimateTxSize() { - Coin reservedFundsForOffer = getSecurityDeposit(); - if (!isBuyOffer()) - reservedFundsForOffer = reservedFundsForOffer.add(amount.get()); - - Tuple2 estimatedFeeAndTxSize = TxFeeEstimation.getEstimatedFeeAndTxSizeForMaker(reservedFundsForOffer, + Coin fundsNeededForMaker = OfferUtil.getFundsNeededForMaker(amount.get(), buyerSecurityDeposit.get(), direction); + Tuple2 estimatedFeeAndTxSize = TxFeeEstimation.getEstimatedFeeAndTxSizeForMaker(fundsNeededForMaker, getMakerFee(), feeService, btcWalletService, @@ -643,7 +640,7 @@ Coin getSecurityDeposit() { } public boolean isBuyOffer() { - return OfferUtil.isBuyOffer(getDirection()); + return OfferUtil.isBuyOffer(direction); } public Coin getTxFee() { diff --git a/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java index 844da065321..f4ac32e6dcb 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java @@ -328,9 +328,7 @@ void onTakeOffer(TradeResultHandler tradeResultHandler) { public void estimateTxSize() { int txSize = 0; if (btcWalletService.getBalance(Wallet.BalanceType.AVAILABLE).isPositive()) { - Coin fundsNeededForTrade = getFundsNeededForTrade(); - if (isBuyOffer()) - fundsNeededForTrade = fundsNeededForTrade.add(amount.get()); + Coin fundsNeededForTaker = OfferUtil.getFundsNeededForTaker(amount.get(), getTxFeeForDepositTx(), getTxFeeForPayoutTx(), offer); // 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). @@ -344,7 +342,7 @@ public void estimateTxSize() { // We sum the taker fee tx and the deposit tx together as it can be assumed that both be in the same block and // as they are dependent txs the miner will pick both if the fee in total is good enough. // We make sure that the fee is sufficient to meet our intended fee/byte for the larger payout tx with 380 bytes. - Tuple2 estimatedFeeAndTxSize = TxFeeEstimation.getEstimatedFeeAndTxSizeForTaker(fundsNeededForTrade, + Tuple2 estimatedFeeAndTxSize = TxFeeEstimation.getEstimatedFeeAndTxSizeForTaker(fundsNeededForTaker, getTakerFee(), feeService, btcWalletService, From 9837ded1b8529825166cc0e27d6d355efc47ada9 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Sun, 21 Oct 2018 01:01:09 -0500 Subject: [PATCH 15/15] Add TakerUtil --- .../main/java/bisq/core/offer/OfferUtil.java | 27 +------ .../main/java/bisq/core/offer/TakerUtil.java | 71 +++++++++++++++++++ .../payment/AccountAgeWitnessService.java | 5 +- .../main/offer/MutableOfferDataModel.java | 2 +- .../offer/takeoffer/TakeOfferDataModel.java | 11 ++- 5 files changed, 87 insertions(+), 29 deletions(-) create mode 100644 core/src/main/java/bisq/core/offer/TakerUtil.java diff --git a/core/src/main/java/bisq/core/offer/OfferUtil.java b/core/src/main/java/bisq/core/offer/OfferUtil.java index b4d00697b5b..cc279dc98d1 100644 --- a/core/src/main/java/bisq/core/offer/OfferUtil.java +++ b/core/src/main/java/bisq/core/offer/OfferUtil.java @@ -308,19 +308,6 @@ public static String getCountryCode(PaymentAccount paymentAccount) { } } - // TODO remove - public static long getMaxTradeLimit(AccountAgeWitnessService accountAgeWitnessService, PaymentAccount paymentAccount, String currencyCode) { - if (paymentAccount != null) - return accountAgeWitnessService.getMyTradeLimit(paymentAccount, currencyCode); - else - return 0; - } - - // TODO remove - public static long getMaxTradePeriod(PaymentAccount paymentAccount) { - return paymentAccount.getPaymentMethod().getMaxTradePeriod(); - } - public static Map getExtraDataMap(AccountAgeWitnessService accountAgeWitnessService, ReferralIdService referralIdService, PaymentAccount paymentAccount, @@ -369,7 +356,7 @@ public static void validateOfferData(FilterManager filterManager, checkNotNull(p2PService.getAddress(), "Address must not be null"); } - public static Coin getFundsNeededForMaker(Coin tradeAmount, Coin buyerSecurityDeposit, OfferPayload.Direction direction) { + public static Coin getFundsNeededForOffer(Coin tradeAmount, Coin buyerSecurityDeposit, OfferPayload.Direction direction) { boolean buyOffer = isBuyOffer(direction); Coin needed = buyOffer ? buyerSecurityDeposit : Restrictions.getSellerSecurityDeposit(); if (!buyOffer) @@ -377,16 +364,4 @@ public static Coin getFundsNeededForMaker(Coin tradeAmount, Coin buyerSecurityDe return needed; } - - public static Coin getFundsNeededForTaker(Coin tradeAmount, Coin txFeeForDepositTx, Coin txFeeForPayoutTx, Offer offer) { - boolean buyOffer = isBuyOffer(offer.getDirection()); - Coin needed = buyOffer ? offer.getSellerSecurityDeposit() : offer.getBuyerSecurityDeposit(); - - if (buyOffer) - needed = needed.add(tradeAmount); - - needed = needed.add(txFeeForDepositTx).add(txFeeForPayoutTx); - - return needed; - } } diff --git a/core/src/main/java/bisq/core/offer/TakerUtil.java b/core/src/main/java/bisq/core/offer/TakerUtil.java new file mode 100644 index 00000000000..6da4f7f9b1b --- /dev/null +++ b/core/src/main/java/bisq/core/offer/TakerUtil.java @@ -0,0 +1,71 @@ +/* + * This file is part of Bisq. + * + * Bisq 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. + * + * Bisq 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 Bisq. If not, see . + */ + +package bisq.core.offer; + +import bisq.core.app.BisqEnvironment; +import bisq.core.btc.wallet.BsqWalletService; +import bisq.core.provider.fee.FeeService; +import bisq.core.user.Preferences; +import bisq.core.util.CoinUtil; + +import org.bitcoinj.core.Coin; + +import javax.annotation.Nullable; + +public class TakerUtil { + public static Coin getFundsNeededForTakeOffer(Coin tradeAmount, Coin txFeeForDepositTx, Coin txFeeForPayoutTx, Offer offer) { + boolean buyOffer = OfferUtil.isBuyOffer(offer.getDirection()); + Coin needed = buyOffer ? offer.getSellerSecurityDeposit() : offer.getBuyerSecurityDeposit(); + + if (buyOffer) + needed = needed.add(tradeAmount); + + needed = needed.add(txFeeForDepositTx).add(txFeeForPayoutTx); + + return needed; + } + + @Nullable + public static Coin getTakerFee(Coin amount, Preferences preferences, BsqWalletService bsqWalletService) { + boolean currencyForTakerFeeBtc = isCurrencyForTakerFeeBtc(amount, preferences, bsqWalletService); + return getTakerFee(currencyForTakerFeeBtc, amount); + } + + @Nullable + public static Coin getTakerFee(boolean isCurrencyForTakerFeeBtc, Coin amount) { + if (amount != null) { + // TODO write unit test for that + Coin feePerBtc = CoinUtil.getFeePerBtc(FeeService.getTakerFeePerBtc(isCurrencyForTakerFeeBtc), amount); + return CoinUtil.maxCoin(feePerBtc, FeeService.getMinTakerFee(isCurrencyForTakerFeeBtc)); + } else { + return null; + } + } + + public static boolean isCurrencyForTakerFeeBtc(Coin amount, Preferences preferences, BsqWalletService bsqWalletService) { + return preferences.isPayFeeInBtc() || !isBsqForFeeAvailable(amount, bsqWalletService); + } + + public static boolean isBsqForFeeAvailable(Coin amount, BsqWalletService bsqWalletService) { + Coin takerFee = getTakerFee(false, amount); + return BisqEnvironment.isBaseCurrencySupportingBsq() && + takerFee != null && + bsqWalletService.getAvailableBalance() != null && + !bsqWalletService.getAvailableBalance().subtract(takerFee).isNegative(); + } +} diff --git a/core/src/main/java/bisq/core/payment/AccountAgeWitnessService.java b/core/src/main/java/bisq/core/payment/AccountAgeWitnessService.java index 84adb747ae0..77f299123cd 100644 --- a/core/src/main/java/bisq/core/payment/AccountAgeWitnessService.java +++ b/core/src/main/java/bisq/core/payment/AccountAgeWitnessService.java @@ -262,7 +262,10 @@ public long getMyAccountAge(PaymentAccountPayload paymentAccountPayload) { } public long getMyTradeLimit(PaymentAccount paymentAccount, String currencyCode) { - final Optional witnessOptional = Optional.of(getMyWitness(paymentAccount.getPaymentAccountPayload())); + if (paymentAccount == null) + return 0; + + Optional witnessOptional = Optional.of(getMyWitness(paymentAccount.getPaymentAccountPayload())); return getTradeLimit(paymentAccount.getPaymentMethod().getMaxTradeLimitAsCoin(currencyCode), currencyCode, witnessOptional, diff --git a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java index a2faf404ac9..9457bad0c2c 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java @@ -394,7 +394,7 @@ Offer createAndGetOffer() { // This works only if we have already funds in the wallet public void estimateTxSize() { - Coin fundsNeededForMaker = OfferUtil.getFundsNeededForMaker(amount.get(), buyerSecurityDeposit.get(), direction); + Coin fundsNeededForMaker = OfferUtil.getFundsNeededForOffer(amount.get(), buyerSecurityDeposit.get(), direction); Tuple2 estimatedFeeAndTxSize = TxFeeEstimation.getEstimatedFeeAndTxSizeForMaker(fundsNeededForMaker, getMakerFee(), feeService, diff --git a/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java index f4ac32e6dcb..db4894b4c54 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java @@ -35,6 +35,7 @@ import bisq.core.offer.Offer; import bisq.core.offer.OfferPayload; import bisq.core.offer.OfferUtil; +import bisq.core.offer.TakerUtil; import bisq.core.offer.TxFeeEstimation; import bisq.core.payment.AccountAgeWitnessService; import bisq.core.payment.HalCashAccount; @@ -328,7 +329,7 @@ void onTakeOffer(TradeResultHandler tradeResultHandler) { public void estimateTxSize() { int txSize = 0; if (btcWalletService.getBalance(Wallet.BalanceType.AVAILABLE).isPositive()) { - Coin fundsNeededForTaker = OfferUtil.getFundsNeededForTaker(amount.get(), getTxFeeForDepositTx(), getTxFeeForPayoutTx(), offer); + Coin fundsNeededForTaker = TakerUtil.getFundsNeededForTakeOffer(amount.get(), getTxFeeForDepositTx(), getTxFeeForPayoutTx(), offer); // 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). @@ -409,6 +410,8 @@ boolean hasAcceptedArbitrators() { } boolean isCurrencyForTakerFeeBtc() { + // TODO do more testing before applying TakerUtil + //return TakerUtil.isCurrencyForTakerFeeBtc(amount.get(), preferences, bsqWalletService); return preferences.isPayFeeInBtc() || !isBsqForFeeAvailable(); } @@ -417,6 +420,9 @@ boolean isTakerFeeValid() { } boolean isBsqForFeeAvailable() { + // TODO do more testing before applying TakerUtil + //return TakerUtil.isBsqForFeeAvailable(amount.get(), bsqWalletService); + final Coin takerFee = getTakerFee(false); return BisqEnvironment.isBaseCurrencySupportingBsq() && takerFee != null && @@ -496,6 +502,9 @@ private boolean isBuyOffer() { @Nullable Coin getTakerFee(boolean isCurrencyForTakerFeeBtc) { + // TODO do more testing before applying TakerUtil + // return TakerUtil.getTakerFee(isCurrencyForTakerFeeBtc, this.amount.get()); + Coin amount = this.amount.get(); if (amount != null) { // TODO write unit test for that