From 0ad71fc6806e0726b6894533928fce5b60e623fe Mon Sep 17 00:00:00 2001 From: jmacxx <47253594+jmacxx@users.noreply.github.com> Date: Tue, 28 Nov 2023 10:54:45 -0600 Subject: [PATCH 1/2] Fee validation refactor. --- .../java/bisq/core/app/BisqHeadlessApp.java | 1 + .../main/java/bisq/core/app/BisqSetup.java | 8 +- .../bisq/core/app/DomainInitialisation.java | 3 +- .../main/java/bisq/core/offer/OpenOffer.java | 9 +- .../bisq/core/offer/OpenOfferManager.java | 2 + .../offer/bisq_v1/TriggerPriceService.java | 66 ++++++--- .../provider/mempool/FeeValidationStatus.java | 54 ++++++++ .../core/provider/mempool/MempoolService.java | 12 +- .../core/provider/mempool/TxValidator.java | 125 +++++++++--------- .../resources/i18n/displayStrings.properties | 27 +++- .../provider/mempool/TxValidatorTest.java | 18 +-- .../java/bisq/desktop/main/MainViewModel.java | 6 + .../bisq_v1/takeoffer/TakeOfferDataModel.java | 16 +-- .../bisq_v1/takeoffer/TakeOfferView.java | 10 +- .../bisq_v1/takeoffer/TakeOfferViewModel.java | 13 +- .../openoffer/OpenOfferListItem.java | 7 - .../openoffer/OpenOffersDataModel.java | 12 +- .../portfolio/openoffer/OpenOffersView.java | 13 +- .../pendingtrades/PendingTradesView.java | 13 +- .../pendingtrades/PendingTradesViewModel.java | 11 +- 20 files changed, 262 insertions(+), 164 deletions(-) create mode 100644 core/src/main/java/bisq/core/provider/mempool/FeeValidationStatus.java diff --git a/core/src/main/java/bisq/core/app/BisqHeadlessApp.java b/core/src/main/java/bisq/core/app/BisqHeadlessApp.java index 80c908ab004..2ca0f18641e 100644 --- a/core/src/main/java/bisq/core/app/BisqHeadlessApp.java +++ b/core/src/main/java/bisq/core/app/BisqHeadlessApp.java @@ -79,6 +79,7 @@ protected void setupHandlers() { bisqSetup.setSpvFileCorruptedHandler(msg -> log.error("onSpvFileCorruptedHandler: msg={}", msg)); bisqSetup.setChainFileLockedExceptionHandler(msg -> log.error("onChainFileLockedExceptionHandler: msg={}", msg)); bisqSetup.setDiskSpaceWarningHandler(msg -> log.error("onDiskSpaceWarningHandler: msg={}", msg)); + bisqSetup.setOfferDisabledHandler(msg -> log.error("onOfferDisabledHandler: msg={}", msg)); bisqSetup.setChainNotSyncedHandler(msg -> log.error("onChainNotSyncedHandler: msg={}", msg)); bisqSetup.setLockedUpFundsHandler(msg -> log.info("onLockedUpFundsHandler: msg={}", msg)); bisqSetup.setShowFirstPopupIfResyncSPVRequestedHandler(() -> log.info("onShowFirstPopupIfResyncSPVRequestedHandler")); diff --git a/core/src/main/java/bisq/core/app/BisqSetup.java b/core/src/main/java/bisq/core/app/BisqSetup.java index 11eaf06007d..e92596ccdd1 100644 --- a/core/src/main/java/bisq/core/app/BisqSetup.java +++ b/core/src/main/java/bisq/core/app/BisqSetup.java @@ -164,7 +164,7 @@ default void onRequestWalletPassword() { filterWarningHandler, displaySecurityRecommendationHandler, displayLocalhostHandler, wrongOSArchitectureHandler, displaySignedByArbitratorHandler, displaySignedByPeerHandler, displayPeerLimitLiftedHandler, displayPeerSignerHandler, - rejectedTxErrorMessageHandler, diskSpaceWarningHandler, chainNotSyncedHandler; + rejectedTxErrorMessageHandler, diskSpaceWarningHandler, offerDisabledHandler, chainNotSyncedHandler; @Setter @Nullable private Consumer displayTorNetworkSettingsHandler; @@ -294,6 +294,11 @@ public void displayAlertIfPresent(Alert alert, boolean openNewVersionPopup) { } } + public void displayOfferDisabledMessage(String message) { + if (offerDisabledHandler != null) { + offerDisabledHandler.accept(message); + } + } /////////////////////////////////////////////////////////////////////////////////////////// // Main startup tasks @@ -482,6 +487,7 @@ private void initDomainServices() { daoWarnMessageHandler, filterWarningHandler, chainNotSyncedHandler, + offerDisabledHandler, voteResultExceptionHandler, revolutAccountsUpdateHandler, amazonGiftCardAccountsUpdateHandler, diff --git a/core/src/main/java/bisq/core/app/DomainInitialisation.java b/core/src/main/java/bisq/core/app/DomainInitialisation.java index cb19c89d398..e2228a1a1a4 100644 --- a/core/src/main/java/bisq/core/app/DomainInitialisation.java +++ b/core/src/main/java/bisq/core/app/DomainInitialisation.java @@ -205,6 +205,7 @@ public void initDomainServices(Consumer rejectedTxErrorMessageHandler, Consumer daoWarnMessageHandler, Consumer filterWarningHandler, Consumer chainNotSyncedHandler, + Consumer offerDisabledHandler, Consumer voteResultExceptionHandler, Consumer> revolutAccountsUpdateHandler, Consumer> amazonGiftCardAccountsUpdateHandler, @@ -280,7 +281,7 @@ public void initDomainServices(Consumer rejectedTxErrorMessageHandler, disputeMsgEvents.onAllServicesInitialized(); priceAlert.onAllServicesInitialized(); marketAlerts.onAllServicesInitialized(); - triggerPriceService.onAllServicesInitialized(); + triggerPriceService.onAllServicesInitialized(offerDisabledHandler); mempoolService.onAllServicesInitialized(); mailboxMessageService.onAllServicesInitialized(); diff --git a/core/src/main/java/bisq/core/offer/OpenOffer.java b/core/src/main/java/bisq/core/offer/OpenOffer.java index 088a3e8f042..d3979354b93 100644 --- a/core/src/main/java/bisq/core/offer/OpenOffer.java +++ b/core/src/main/java/bisq/core/offer/OpenOffer.java @@ -18,6 +18,7 @@ package bisq.core.offer; import bisq.core.offer.bsq_swap.BsqSwapOfferPayload; +import bisq.core.provider.mempool.FeeValidationStatus; import bisq.core.trade.model.Tradable; import bisq.network.p2p.NodeAddress; @@ -38,7 +39,7 @@ import static com.google.common.base.Preconditions.checkArgument; -@EqualsAndHashCode(exclude = {"bsqSwapOfferHasMissingFunds", "state", "timeoutTimer", "mempoolStatus"}) +@EqualsAndHashCode(exclude = {"bsqSwapOfferHasMissingFunds", "state", "timeoutTimer", "feeValidationStatus"}) @Slf4j public final class OpenOffer implements Tradable { // Timeout for offer reservation during takeoffer process. If deposit tx is not completed in that time we reset the offer to AVAILABLE state. @@ -78,7 +79,7 @@ public enum State { private final long triggerPrice; @Getter @Setter - transient private long mempoolStatus = -1; + transient private FeeValidationStatus feeValidationStatus = FeeValidationStatus.NOT_CHECKED_YET; transient private Timer timeoutTimer; // Added at BsqSwap release. We do not persist that field @@ -191,6 +192,10 @@ public boolean isCanceled() { return state == State.CANCELED; } + public boolean triggerInfoShouldBeShown() { + return triggerPrice > 0 || feeValidationStatus.fail(); + } + public BsqSwapOfferPayload getBsqSwapOfferPayload() { checkArgument(getOffer().getBsqSwapOfferPayload().isPresent(), "getBsqSwapOfferPayload must be called only when BsqSwapOfferPayload is the expected payload"); diff --git a/core/src/main/java/bisq/core/offer/OpenOfferManager.java b/core/src/main/java/bisq/core/offer/OpenOfferManager.java index 7ea84ff0b3b..56d87602c38 100644 --- a/core/src/main/java/bisq/core/offer/OpenOfferManager.java +++ b/core/src/main/java/bisq/core/offer/OpenOfferManager.java @@ -36,6 +36,7 @@ import bisq.core.offer.bisq_v1.OfferPayload; import bisq.core.offer.placeoffer.bisq_v1.PlaceOfferModel; import bisq.core.offer.placeoffer.bisq_v1.PlaceOfferProtocol; +import bisq.core.provider.mempool.FeeValidationStatus; import bisq.core.provider.price.PriceFeedService; import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; import bisq.core.support.dispute.mediation.mediator.MediatorManager; @@ -497,6 +498,7 @@ public void activateOpenOffer(OpenOffer openOffer, offerBookService.activateOffer(offer, () -> { openOffer.setState(OpenOffer.State.AVAILABLE); + openOffer.setFeeValidationStatus(FeeValidationStatus.NOT_CHECKED_YET); requestPersistence(); log.debug("activateOpenOffer, offerId={}", offer.getId()); resultHandler.handleResult(); diff --git a/core/src/main/java/bisq/core/offer/bisq_v1/TriggerPriceService.java b/core/src/main/java/bisq/core/offer/bisq_v1/TriggerPriceService.java index 09e49a37950..6b74514c2d0 100644 --- a/core/src/main/java/bisq/core/offer/bisq_v1/TriggerPriceService.java +++ b/core/src/main/java/bisq/core/offer/bisq_v1/TriggerPriceService.java @@ -18,12 +18,14 @@ package bisq.core.offer.bisq_v1; import bisq.core.locale.CurrencyUtil; +import bisq.core.locale.Res; import bisq.core.monetary.Altcoin; import bisq.core.monetary.Price; import bisq.core.offer.Offer; import bisq.core.offer.OfferDirection; import bisq.core.offer.OpenOffer; import bisq.core.offer.OpenOfferManager; +import bisq.core.provider.mempool.FeeValidationStatus; import bisq.core.provider.mempool.MempoolService; import bisq.core.provider.price.MarketPrice; import bisq.core.provider.price.PriceFeedService; @@ -38,6 +40,9 @@ import javax.inject.Inject; import javax.inject.Singleton; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.SimpleIntegerProperty; + import javafx.collections.ListChangeListener; import java.util.HashMap; @@ -46,12 +51,14 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.function.Consumer; import lombok.extern.slf4j.Slf4j; import static bisq.common.util.MathUtils.roundDoubleToLong; import static bisq.common.util.MathUtils.scaleUpByPowerOf10; + @Slf4j @Singleton public class TriggerPriceService { @@ -60,6 +67,8 @@ public class TriggerPriceService { private final MempoolService mempoolService; private final PriceFeedService priceFeedService; private final Map> openOffersByCurrency = new HashMap<>(); + private Consumer offerDisabledHandler; + public final IntegerProperty updateCounter = new SimpleIntegerProperty(0); @Inject public TriggerPriceService(P2PService p2PService, @@ -72,7 +81,8 @@ public TriggerPriceService(P2PService p2PService, this.priceFeedService = priceFeedService; } - public void onAllServicesInitialized() { + public void onAllServicesInitialized(Consumer offerDisabledHandler) { + this.offerDisabledHandler = offerDisabledHandler; if (p2PService.isBootstrapped()) { onBootstrapComplete(); } else { @@ -97,11 +107,11 @@ private void onBootstrapComplete() { }); onAddedOpenOffers(openOfferManager.getObservableList()); - priceFeedService.updateCounterProperty().addListener((observable, oldValue, newValue) -> onPriceFeedChanged()); - onPriceFeedChanged(); + priceFeedService.updateCounterProperty().addListener((observable, oldValue, newValue) -> onPriceFeedChanged(false)); + onPriceFeedChanged(true); } - private void onPriceFeedChanged() { + private void onPriceFeedChanged(boolean bootstrapping) { openOffersByCurrency.keySet().stream() .map(priceFeedService::getMarketPrice) .filter(Objects::nonNull) @@ -109,7 +119,12 @@ private void onPriceFeedChanged() { .forEach(marketPrice -> { openOffersByCurrency.get(marketPrice.getCurrencyCode()).stream() .filter(openOffer -> !openOffer.isDeactivated()) - .forEach(openOffer -> checkPriceThreshold(marketPrice, openOffer)); + .forEach(openOffer -> { + checkPriceThreshold(marketPrice, openOffer); + if (!bootstrapping) { + maybeCheckOfferFee(openOffer); + } + }); }); } @@ -161,29 +176,42 @@ private void checkPriceThreshold(MarketPrice marketPrice, OpenOffer openOffer) { marketPrice.getPrice(), MathUtils.scaleDownByPowerOf10(triggerPrice, smallestUnitExponent) ); + deactivateOpenOffer(openOffer, Res.get("openOffer.triggered", openOffer.getOffer().getShortId())); + } + } - openOfferManager.deactivateOpenOffer(openOffer, () -> { - }, errorMessage -> { - }); - } else if (openOffer.getState() == OpenOffer.State.AVAILABLE) { - // check the mempool if it has not been done before + private void maybeCheckOfferFee(OpenOffer openOffer) { + Offer offer = openOffer.getOffer(); + if (offer.isBsqSwapOffer()) { + return; + } + + if (openOffer.getState() == OpenOffer.State.AVAILABLE) { + // check the offer fee if it has not been done before OfferPayload offerPayload = offer.getOfferPayload().orElseThrow(); - if (openOffer.getMempoolStatus() < 0 && + if (openOffer.getFeeValidationStatus() == FeeValidationStatus.NOT_CHECKED_YET && mempoolService.canRequestBeMade(offerPayload)) { mempoolService.validateOfferMakerTx(offerPayload, (txValidator -> { - openOffer.setMempoolStatus(txValidator.isFail() ? 0 : 1); + openOffer.setFeeValidationStatus(txValidator.getStatus()); + if (openOffer.getFeeValidationStatus().fail()) { + deactivateOpenOffer(openOffer, Res.get("openOffer.deactivated.feeValidationIssue", + openOffer.getOffer().getShortId(), openOffer.getFeeValidationStatus())); + } })); } - // if the mempool indicated failure then deactivate the open offer - if (openOffer.getMempoolStatus() == 0) { - log.info("Deactivating open offer {} due to mempool validation", offer.getShortId()); - openOfferManager.deactivateOpenOffer(openOffer, () -> { - }, errorMessage -> { - }); - } } } + private void deactivateOpenOffer(OpenOffer openOffer, String message) { + openOfferManager.deactivateOpenOffer(openOffer, () -> { }, errorMessage -> { }); + log.info(message); + if (offerDisabledHandler != null) { + offerDisabledHandler.accept(message); // shows notification on screen + } + // tells the UI layer (Open Offers View) to update its contents + updateCounter.set(updateCounter.get() + 1); + } + private void onAddedOpenOffers(List openOffers) { openOffers.forEach(openOffer -> { String currencyCode = openOffer.getOffer().getCurrencyCode(); diff --git a/core/src/main/java/bisq/core/provider/mempool/FeeValidationStatus.java b/core/src/main/java/bisq/core/provider/mempool/FeeValidationStatus.java new file mode 100644 index 00000000000..2de91a6f471 --- /dev/null +++ b/core/src/main/java/bisq/core/provider/mempool/FeeValidationStatus.java @@ -0,0 +1,54 @@ +/* + * 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.provider.mempool; + +import bisq.core.locale.Res; + +public enum FeeValidationStatus { + NOT_CHECKED_YET("fee.validation.notCheckedYet"), + ACK_FEE_OK("fee.validation.ack.feeCheckedOk"), + ACK_BSQ_TX_IS_NEW("fee.validation.ack.bsqTxIsNew"), + ACK_CHECK_BYPASSED("fee.validation.ack.checkBypassed"), + NACK_BTC_TX_NOT_FOUND("fee.validation.error.btcTxNotFound"), + NACK_BSQ_FEE_NOT_FOUND("fee.validation.error.bsqTxNotFound"), + NACK_MAKER_FEE_TOO_LOW("fee.validation.error.makerFeeTooLow"), + NACK_TAKER_FEE_TOO_LOW("fee.validation.error.takerFeeTooLow"), + NACK_UNKNOWN_FEE_RECEIVER("fee.validation.error.unknownReceiver"), + NACK_JSON_ERROR("fee.validation.error.json"); + + private final String descriptionKey; + + FeeValidationStatus(String descriptionKey) { + this.descriptionKey = descriptionKey; + } + + public boolean pass() { + return this == ACK_FEE_OK || this == ACK_BSQ_TX_IS_NEW || this == ACK_CHECK_BYPASSED; + } + public boolean fail() { + return this != NOT_CHECKED_YET && !pass(); + } + + public String toString() { + try { + return Res.get(descriptionKey); + } catch (Exception ex) { + return descriptionKey; + } + } +} diff --git a/core/src/main/java/bisq/core/provider/mempool/MempoolService.java b/core/src/main/java/bisq/core/provider/mempool/MempoolService.java index 5bbc16dab80..5a2cb8524ff 100644 --- a/core/src/main/java/bisq/core/provider/mempool/MempoolService.java +++ b/core/src/main/java/bisq/core/provider/mempool/MempoolService.java @@ -104,7 +104,7 @@ public void validateOfferMakerTx(OfferPayload offerPayload, Consumer resultHandler) { if (txValidator.getIsFeeCurrencyBtc() != null && txValidator.getIsFeeCurrencyBtc()) { if (!isServiceSupported()) { - UserThread.runAfter(() -> resultHandler.accept(txValidator.endResult("mempool request not supported, bypassing", true)), 1); + UserThread.runAfter(() -> resultHandler.accept(txValidator.endResult(FeeValidationStatus.ACK_CHECK_BYPASSED)), 1); return; } MempoolRequest mempoolRequest = new MempoolRequest(preferences, socks5ProxyProvider); @@ -123,7 +123,7 @@ public void validateOfferTakerTx(Trade trade, Consumer resultHandle public void validateOfferTakerTx(TxValidator txValidator, Consumer resultHandler) { if (txValidator.getIsFeeCurrencyBtc() != null && txValidator.getIsFeeCurrencyBtc()) { if (!isServiceSupported()) { - UserThread.runAfter(() -> resultHandler.accept(txValidator.endResult("mempool request not supported, bypassing", true)), 1); + UserThread.runAfter(() -> resultHandler.accept(txValidator.endResult(FeeValidationStatus.ACK_CHECK_BYPASSED)), 1); return; } MempoolRequest mempoolRequest = new MempoolRequest(preferences, socks5ProxyProvider); @@ -137,7 +137,7 @@ public void validateOfferTakerTx(TxValidator txValidator, Consumer public void checkTxIsConfirmed(String txId, Consumer resultHandler) { TxValidator txValidator = new TxValidator(daoStateService, txId, filterManager); if (!isServiceSupported()) { - UserThread.runAfter(() -> resultHandler.accept(txValidator.endResult("mempool request not supported, bypassing", true)), 1); + UserThread.runAfter(() -> resultHandler.accept(txValidator.endResult(FeeValidationStatus.ACK_CHECK_BYPASSED)), 1); return; } MempoolRequest mempoolRequest = new MempoolRequest(preferences, socks5ProxyProvider); @@ -191,7 +191,7 @@ public void onFailure(Throwable throwable) { validateOfferMakerTx(theRequest, txValidator, resultHandler); } else { // exhausted all providers, let user know of failure - resultHandler.accept(txValidator.endResult("Tx not found", false)); + resultHandler.accept(txValidator.endResult(FeeValidationStatus.NACK_BTC_TX_NOT_FOUND)); } }); } @@ -221,7 +221,7 @@ public void onFailure(Throwable throwable) { validateOfferTakerTx(theRequest, txValidator, resultHandler); } else { // exhausted all providers, let user know of failure - resultHandler.accept(txValidator.endResult("Tx not found", false)); + resultHandler.accept(txValidator.endResult(FeeValidationStatus.NACK_BTC_TX_NOT_FOUND)); } }); } @@ -248,7 +248,7 @@ public void onFailure(Throwable throwable) { log.warn("onFailure - {}", throwable.toString()); UserThread.execute(() -> { outstandingRequests--; - resultHandler.accept(txValidator.endResult("Tx not found", false)); + resultHandler.accept(txValidator.endResult(FeeValidationStatus.NACK_BTC_TX_NOT_FOUND)); }); } diff --git a/core/src/main/java/bisq/core/provider/mempool/TxValidator.java b/core/src/main/java/bisq/core/provider/mempool/TxValidator.java index 9c95c2e14f3..d7f43431c4b 100644 --- a/core/src/main/java/bisq/core/provider/mempool/TxValidator.java +++ b/core/src/main/java/bisq/core/provider/mempool/TxValidator.java @@ -32,7 +32,6 @@ import com.google.gson.JsonObject; import com.google.gson.JsonSyntaxException; -import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -43,7 +42,6 @@ import org.jetbrains.annotations.Nullable; import static bisq.core.util.coin.CoinUtil.maxCoin; -import static com.google.common.base.Preconditions.checkNotNull; @Slf4j @Getter @@ -54,7 +52,7 @@ public class TxValidator { private final DaoStateService daoStateService; private final FilterManager filterManager; private long feePaymentBlockHeight; // applicable to maker and taker fees - private final List errorList; + private FeeValidationStatus status; private final String txId; private Coin amount; @Nullable @@ -76,8 +74,8 @@ public TxValidator(DaoStateService daoStateService, this.feePaymentBlockHeight = feePaymentBlockHeight; this.chainHeight = (long) daoStateService.getChainHeight(); this.filterManager = filterManager; - this.errorList = new ArrayList<>(); this.jsonTxt = ""; + this.status = FeeValidationStatus.NOT_CHECKED_YET; } public TxValidator(DaoStateService daoStateService, String txId, FilterManager filterManager) { @@ -85,8 +83,8 @@ public TxValidator(DaoStateService daoStateService, String txId, FilterManager f this.txId = txId; this.chainHeight = (long) daoStateService.getChainHeight(); this.filterManager = filterManager; - this.errorList = new ArrayList<>(); this.jsonTxt = ""; + this.status = FeeValidationStatus.NOT_CHECKED_YET; } @@ -96,19 +94,18 @@ public TxValidator(DaoStateService daoStateService, String txId, FilterManager f public TxValidator parseJsonValidateMakerFeeTx(String jsonTxt, List btcFeeReceivers) { this.jsonTxt = jsonTxt; - boolean status = initialSanityChecks(txId, jsonTxt); + FeeValidationStatus status = initialSanityChecks(txId, jsonTxt); try { - if (status) { - if (checkNotNull(isFeeCurrencyBtc)) { - status = checkFeeAddressBTC(jsonTxt, btcFeeReceivers) - && checkFeeAmountBTC(jsonTxt, amount, true, getBlockHeightForFeeCalculation(jsonTxt)); + if (status.pass()) { + status = checkFeeAddressBTC(jsonTxt, btcFeeReceivers); + if (status.pass()) { + status = checkFeeAmountBTC(jsonTxt, amount, true, getBlockHeightForFeeCalculation(jsonTxt)); } } } catch (JsonSyntaxException e) { String s = "The maker fee tx JSON validation failed with reason: " + e.toString(); log.info(s); - errorList.add(s); - status = false; + status = FeeValidationStatus.NACK_JSON_ERROR; } return endResult("Maker tx validation", status); } @@ -122,11 +119,10 @@ public TxValidator validateBsqFeeTx(boolean isMaker) { // still unconfirmed after 8 hours grace period we assume there may be SPV wallet issue. // see github.com/bisq-network/bisq/issues/6603 statusStr = String.format("BSQ tx %s not found, age=%d: FAIL.", txId, txAge); - log.warn(statusStr); - return endResult(statusStr, false); + return endResult(statusStr, FeeValidationStatus.NACK_BSQ_FEE_NOT_FOUND); } else { log.info("DAO does not yet have the tx {} (age={}), bypassing check of burnt BSQ amount.", txId, txAge); - return endResult(statusStr, true); + return endResult(statusStr, FeeValidationStatus.ACK_BSQ_TX_IS_NEW); } } else { return endResult(statusStr, checkFeeAmountBSQ(tx.get(), amount, isMaker, feePaymentBlockHeight)); @@ -135,28 +131,24 @@ public TxValidator validateBsqFeeTx(boolean isMaker) { public TxValidator parseJsonValidateTakerFeeTx(String jsonTxt, List btcFeeReceivers) { this.jsonTxt = jsonTxt; - boolean status = initialSanityChecks(txId, jsonTxt); + FeeValidationStatus status = initialSanityChecks(txId, jsonTxt); try { - if (status) { - if (isFeeCurrencyBtc == null) { - isFeeCurrencyBtc = checkFeeAddressBTC(jsonTxt, btcFeeReceivers); - } - if (isFeeCurrencyBtc) { - status = checkFeeAddressBTC(jsonTxt, btcFeeReceivers) - && checkFeeAmountBTC(jsonTxt, amount, false, getBlockHeightForFeeCalculation(jsonTxt)); + if (status.pass()) { + status = checkFeeAddressBTC(jsonTxt, btcFeeReceivers); + if (status.pass()) { + status = checkFeeAmountBTC(jsonTxt, amount, false, getBlockHeightForFeeCalculation(jsonTxt)); } } } catch (JsonSyntaxException e) { String s = "The taker fee tx JSON validation failed with reason: " + e.toString(); log.info(s); - errorList.add(s); - status = false; + status = FeeValidationStatus.NACK_JSON_ERROR; } return endResult("Taker tx validation", status); } public long parseJsonValidateTx() { - if (!initialSanityChecks(txId, jsonTxt)) { + if (!initialSanityChecks(txId, jsonTxt).pass()) { return -1; } return getTxConfirms(jsonTxt, chainHeight); @@ -164,31 +156,30 @@ public long parseJsonValidateTx() { /////////////////////////////////////////////////////////////////////////////////////////// - private boolean checkFeeAddressBTC(String jsonTxt, List btcFeeReceivers) { + private FeeValidationStatus checkFeeAddressBTC(String jsonTxt, List btcFeeReceivers) { try { JsonArray jsonVout = getVinAndVout(jsonTxt).second; JsonObject jsonVout0 = jsonVout.get(0).getAsJsonObject(); JsonElement jsonFeeAddress = jsonVout0.get("scriptpubkey_address"); log.debug("fee address: {}", jsonFeeAddress.getAsString()); if (btcFeeReceivers.contains(jsonFeeAddress.getAsString())) { - return true; + return FeeValidationStatus.ACK_FEE_OK; } else if (getBlockHeightForFeeCalculation(jsonTxt) < BLOCK_TOLERANCE) { log.info("Leniency rule, unrecognised fee receiver but its a really old offer so let it pass, {}", jsonFeeAddress.getAsString()); - return true; + return FeeValidationStatus.ACK_FEE_OK; } else { String error = "fee address: " + jsonFeeAddress.getAsString() + " was not a known BTC fee receiver"; - errorList.add(error); log.info(error); log.info("Known BTC fee receivers: {}", btcFeeReceivers.toString()); + return FeeValidationStatus.NACK_UNKNOWN_FEE_RECEIVER; } } catch (JsonSyntaxException e) { - errorList.add(e.toString()); log.warn(e.toString()); } - return false; + return FeeValidationStatus.NACK_JSON_ERROR; } - private boolean checkFeeAmountBTC(String jsonTxt, Coin tradeAmount, boolean isMaker, long blockHeight) { + private FeeValidationStatus checkFeeAmountBTC(String jsonTxt, Coin tradeAmount, boolean isMaker, long blockHeight) { JsonArray jsonVin = getVinAndVout(jsonTxt).first; JsonArray jsonVout = getVinAndVout(jsonTxt).second; JsonObject jsonVin0 = jsonVin.get(0).getAsJsonObject(); @@ -212,19 +203,19 @@ private boolean checkFeeAmountBTC(String jsonTxt, Coin tradeAmount, boolean isMa feeValueAsCoin + " sats"; if (expectedFeeAsLong == feeValue) { log.debug("The fee matched. " + description); - return true; + return FeeValidationStatus.ACK_FEE_OK; } if (expectedFeeAsLong < feeValue) { log.info("The fee was more than what we expected: " + description); - return true; + return FeeValidationStatus.ACK_FEE_OK; } double leniencyCalc = feeValue / (double) expectedFeeAsLong; if (leniencyCalc > FEE_TOLERANCE) { log.info("Leniency rule: the fee was low, but above {} of what was expected {} {}", FEE_TOLERANCE, leniencyCalc, description); - return true; + return FeeValidationStatus.ACK_FEE_OK; } Optional result = maybeCheckAgainstFeeFromFilter(tradeAmount, @@ -234,22 +225,22 @@ private boolean checkFeeAmountBTC(String jsonTxt, Coin tradeAmount, boolean isMa true, description); if (result.isPresent()) { - return result.get(); + return result.get() ? FeeValidationStatus.ACK_FEE_OK : + isMaker ? FeeValidationStatus.NACK_MAKER_FEE_TOO_LOW : FeeValidationStatus.NACK_TAKER_FEE_TOO_LOW; } Param defaultFeeParam = isMaker ? Param.DEFAULT_MAKER_FEE_BTC : Param.DEFAULT_TAKER_FEE_BTC; if (feeExistsUsingDifferentDaoParam(tradeAmount, feeValueAsCoin, defaultFeeParam, minFeeParam)) { log.info("Leniency rule: the fee matches a different DAO parameter {}", description); - return true; + return FeeValidationStatus.ACK_FEE_OK; } String feeUnderpaidMessage = "UNDERPAID. " + description; - errorList.add(feeUnderpaidMessage); log.info(feeUnderpaidMessage); - return false; + return isMaker ? FeeValidationStatus.NACK_MAKER_FEE_TOO_LOW : FeeValidationStatus.NACK_TAKER_FEE_TOO_LOW; } - private boolean checkFeeAmountBSQ(Tx bsqTx, Coin tradeAmount, boolean isMaker, long blockHeight) { + private FeeValidationStatus checkFeeAmountBSQ(Tx bsqTx, Coin tradeAmount, boolean isMaker, long blockHeight) { Param minFeeParam = isMaker ? Param.MIN_MAKER_FEE_BSQ : Param.MIN_TAKER_FEE_BSQ; long expectedFeeAsLong = calculateFee(tradeAmount, isMaker ? getMakerFeeRateBsq(blockHeight) : getTakerFeeRateBsq(blockHeight), @@ -262,18 +253,18 @@ private boolean checkFeeAmountBSQ(Tx bsqTx, Coin tradeAmount, boolean isMaker, l if (expectedFeeAsLong == feeValue) { log.debug("The fee matched. " + description); - return true; + return FeeValidationStatus.ACK_FEE_OK; } if (expectedFeeAsLong < feeValue) { log.info("The fee was more than what we expected. " + description + " Tx:" + bsqTx.getId()); - return true; + return FeeValidationStatus.ACK_FEE_OK; } double leniencyCalc = feeValue / (double) expectedFeeAsLong; if (leniencyCalc > FEE_TOLERANCE) { log.info("Leniency rule: the fee was low, but above {} of what was expected {} {}", FEE_TOLERANCE, leniencyCalc, description); - return true; + return FeeValidationStatus.ACK_FEE_OK; } Coin feeValueAsCoin = Coin.valueOf(feeValue); @@ -284,18 +275,18 @@ private boolean checkFeeAmountBSQ(Tx bsqTx, Coin tradeAmount, boolean isMaker, l false, description); if (maybeTestFeeFromFilter.isPresent()) { - return maybeTestFeeFromFilter.get(); + return maybeTestFeeFromFilter.get() ? FeeValidationStatus.ACK_FEE_OK : + isMaker ? FeeValidationStatus.NACK_MAKER_FEE_TOO_LOW : FeeValidationStatus.NACK_TAKER_FEE_TOO_LOW; } Param defaultFeeParam = isMaker ? Param.DEFAULT_MAKER_FEE_BSQ : Param.DEFAULT_TAKER_FEE_BSQ; if (feeExistsUsingDifferentDaoParam(tradeAmount, Coin.valueOf(feeValue), defaultFeeParam, minFeeParam)) { log.info("Leniency rule: the fee matches a different DAO parameter {}", description); - return true; + return FeeValidationStatus.ACK_FEE_OK; } - errorList.add(description); log.info(description); - return false; + return isMaker ? FeeValidationStatus.NACK_MAKER_FEE_TOO_LOW : FeeValidationStatus.NACK_TAKER_FEE_TOO_LOW; } private static Tuple2 getVinAndVout(String jsonTxt) throws JsonSyntaxException { @@ -313,26 +304,26 @@ private static Tuple2 getVinAndVout(String jsonTxt) throws return new Tuple2<>(jsonVin, jsonVout); } - private static boolean initialSanityChecks(String txId, String jsonTxt) { + private static FeeValidationStatus initialSanityChecks(String txId, String jsonTxt) { // there should always be "status" container element at the top level if (jsonTxt == null || jsonTxt.length() == 0) { - return false; + return FeeValidationStatus.NACK_JSON_ERROR; } JsonObject json = new Gson().fromJson(jsonTxt, JsonObject.class); if (json.get("status") == null) { - return false; + return FeeValidationStatus.NACK_JSON_ERROR; } // there should always be "txid" string element at the top level if (json.get("txid") == null) { - return false; + return FeeValidationStatus.NACK_JSON_ERROR; } // txid should match what we requested if (!txId.equals(json.get("txid").getAsString())) { - return false; + return FeeValidationStatus.NACK_JSON_ERROR; } JsonObject jsonStatus = json.get("status").getAsJsonObject(); JsonElement jsonConfirmed = jsonStatus.get("confirmed"); - return (jsonConfirmed != null); + return (jsonConfirmed != null ? FeeValidationStatus.ACK_FEE_OK : FeeValidationStatus.NACK_JSON_ERROR); // the json is valid and it contains a "confirmed" field then tx is known to mempool.space // we don't care if it is confirmed or not, just that it exists. } @@ -472,27 +463,29 @@ private boolean feeExistsUsingDifferentDaoParam(Coin tradeAmount, return actualFeeValue.equals(calculateFee(tradeAmount, defaultRate, minFeeParam)); } - public TxValidator endResult(String title, boolean status) { - log.info("{} : {}", title, status ? "SUCCESS" : "FAIL"); - if (!status) { - errorList.add(title); + public TxValidator endResult(FeeValidationStatus status) { + return endResult(status.toString(), status); + } + + private TxValidator endResult(String title, FeeValidationStatus status) { + this.status = status; + if (status.pass()) { + log.info("{} : {}", title, status); + } else { + log.warn("{} : {}", title, status); } return this; } - public boolean isFail() { - return errorList.size() > 0; + public FeeValidationStatus getStatus() { + return status; } public boolean getResult() { - return errorList.size() == 0; - } - - public String errorSummary() { - return errorList.toString().substring(0, Math.min(85, errorList.toString().length())); + return status.pass(); } public String toString() { - return errorList.toString(); + return status.toString(); } } diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 599a6ea2b14..498db4d179d 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -633,8 +633,9 @@ takeOffer.tac=With taking this offer I agree to the trade conditions as defined openOffer.header.triggerPrice=Trigger price openOffer.header.makerFeeTxId=Maker fee openOffer.triggerPrice=Trigger price {0} -openOffer.triggered=The offer has been deactivated because the market price reached your trigger price.\n\ +openOffer.triggered=Offer {0} has been deactivated because the market price reached your trigger price.\n\ Please edit the offer to define a new trigger price +openOffer.deactivated.feeValidationIssue=Offer \"{0}\" is deactivated, due to: {1} openOffer.bsqSwap.missingFunds=Open BSQ swap offer is disabled because there are not sufficient funds in the wallet @@ -3284,7 +3285,7 @@ popup.warning.lockedUpFunds=You have locked up funds from a failed trade.\n\ Trade ID: {2}.\n\n\ Please open a support ticket by selecting the trade in the open trades screen and pressing \"alt + o\" or \"option + o\"." -popup.warning.makerTxInvalid=This offer is not valid. Please choose a different offer.\n\n +popup.warning.makerTxInvalid=This offer is not valid. Please choose a different offer.\n\nReason: {0} takeOffer.cancelButton=Cancel take-offer takeOffer.warningButton=Ignore and continue anyway @@ -3676,6 +3677,28 @@ seed.restore.error=An error occurred when restoring the wallets with seed words. seed.restore.openOffers.warn=You have open offers which will be removed if you restore from seed words.\n\ Are you sure that you want to continue? +# dynamic values are not recognized by IntelliJ +# suppress inspection "UnusedProperty" +fee.validation.notCheckedYet=Fee has not been checked +# suppress inspection "UnusedProperty" +fee.validation.ack.feeCheckedOk=Fee checked OK +# suppress inspection "UnusedProperty" +fee.validation.ack.bsqTxIsNew=BSQ tx new/unconfirmed, fee assumed OK +# suppress inspection "UnusedProperty" +fee.validation.ack.checkBypassed=Fee check bypassed +# suppress inspection "UnusedProperty" +fee.validation.error.btcTxNotFound=BTC fee tx not found +# suppress inspection "UnusedProperty" +fee.validation.error.bsqTxNotFound=BSQ fee tx not found +# suppress inspection "UnusedProperty" +fee.validation.error.makerFeeTooLow=Maker Fee underpaid +# suppress inspection "UnusedProperty" +fee.validation.error.takerFeeTooLow=Taker Fee underpaid +# suppress inspection "UnusedProperty" +fee.validation.error.unknownReceiver=Fee receiver not a recognised address +# suppress inspection "UnusedProperty" +fee.validation.error.json=JSON parsing failure + #################################################################### # Payment methods diff --git a/core/src/test/java/bisq/core/provider/mempool/TxValidatorTest.java b/core/src/test/java/bisq/core/provider/mempool/TxValidatorTest.java index b96b3f49ad1..c648c704526 100644 --- a/core/src/test/java/bisq/core/provider/mempool/TxValidatorTest.java +++ b/core/src/test/java/bisq/core/provider/mempool/TxValidatorTest.java @@ -81,18 +81,18 @@ public void testMakerTx() { log.info("checking issue from user 2022-10-07"); offerData = "1322804,5bec4007de1cb8cf18a5fa859d80d66031b8c78cfd99674e09ffd65cf23b50fc,9630000,137,0,757500"; - mempoolData = "{\"txid\":\"5bec4007de1cb8cf18a5fa859d80d66031b8c78cfd99674e09ffd65cf23b50fc\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":0,\"prevout\":{\"value\":8921}},{\"vout\":1,\"prevout\":{\"value\":12155000}},{\"vout\":1,\"prevout\":{\"value\":2967000}}],\"vout\":[{\"scriptpubkey_address\":\"bc1qtyl6dququ2amxtsh4f3kx5rk9f5w9cuscz7ugm\",\"value\":8784},{\"scriptpubkey_address\":\"bc1qwj0jktuyjwj2ecwp9wgcrztxhve0hwn7n5lnxg\",\"value\":12519000},{\"scriptpubkey_address\":\"bc1qn3rd52mzkp6mgduz5wxprjw4rk9xpft6kga2mk\",\"value\":2600037}],\"size\":551,\"weight\":1229,\"fee\":3100,\"status\":{\"confirmed\":true,\"block_height\":757528,\"block_hash\":\"00000000000000000006b4426a0d2688a7e933e563e4d4fd80f572d935b12ae9\",\"block_time\":1665150690}}"; - assertTrue(createTxValidator(offerData).parseJsonValidateMakerFeeTx(mempoolData, btcFeeReceivers).getResult()); + //mempoolData = "{\"txid\":\"5bec4007de1cb8cf18a5fa859d80d66031b8c78cfd99674e09ffd65cf23b50fc\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":0,\"prevout\":{\"value\":8921}},{\"vout\":1,\"prevout\":{\"value\":12155000}},{\"vout\":1,\"prevout\":{\"value\":2967000}}],\"vout\":[{\"scriptpubkey_address\":\"bc1qtyl6dququ2amxtsh4f3kx5rk9f5w9cuscz7ugm\",\"value\":8784},{\"scriptpubkey_address\":\"bc1qwj0jktuyjwj2ecwp9wgcrztxhve0hwn7n5lnxg\",\"value\":12519000},{\"scriptpubkey_address\":\"bc1qn3rd52mzkp6mgduz5wxprjw4rk9xpft6kga2mk\",\"value\":2600037}],\"size\":551,\"weight\":1229,\"fee\":3100,\"status\":{\"confirmed\":true,\"block_height\":757528,\"block_hash\":\"00000000000000000006b4426a0d2688a7e933e563e4d4fd80f572d935b12ae9\",\"block_time\":1665150690}}"; + assertTrue(createTxValidator(offerData).validateBsqFeeTx(true).getResult()); log.info("expected: paid the correct amount of BSQ fees"); offerData = "msimscqb,0636bafb14890edfb95465e66e2b1e15915f7fb595f9b653b9129c15ef4c1c4b,1000000,10,0,662390"; - mempoolData = "{\"txid\":\"0636bafb14890edfb95465e66e2b1e15915f7fb595f9b653b9129c15ef4c1c4b\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":0,\"prevout\":{\"value\":7899}},{\"vout\":2,\"prevout\":{\"value\":54877439}}],\"vout\":[{\"scriptpubkey_address\":\"1FCUu7hqKCSsGhVJaLbGEoCWdZRJRNqq8w\",\"value\":7889},{\"scriptpubkey_address\":\"bc1qkj5l4wxl00ufdx6ygcnrck9fz5u927gkwqcgey\",\"value\":1600000},{\"scriptpubkey_address\":\"bc1qkw4a8u9l5w9fhdh3ue9v7e7celk4jyudzg5gk5\",\"value\":53276799}],\"size\":405,\"weight\":1287,\"fee\":650,\"status\":{\"confirmed\":true,\"block_height\":663140}}"; - assertTrue(createTxValidator(offerData).parseJsonValidateMakerFeeTx(mempoolData, btcFeeReceivers).getResult()); + //mempoolData = "{\"txid\":\"0636bafb14890edfb95465e66e2b1e15915f7fb595f9b653b9129c15ef4c1c4b\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":0,\"prevout\":{\"value\":7899}},{\"vout\":2,\"prevout\":{\"value\":54877439}}],\"vout\":[{\"scriptpubkey_address\":\"1FCUu7hqKCSsGhVJaLbGEoCWdZRJRNqq8w\",\"value\":7889},{\"scriptpubkey_address\":\"bc1qkj5l4wxl00ufdx6ygcnrck9fz5u927gkwqcgey\",\"value\":1600000},{\"scriptpubkey_address\":\"bc1qkw4a8u9l5w9fhdh3ue9v7e7celk4jyudzg5gk5\",\"value\":53276799}],\"size\":405,\"weight\":1287,\"fee\":650,\"status\":{\"confirmed\":true,\"block_height\":663140}}"; + assertTrue(createTxValidator(offerData).validateBsqFeeTx(true).getResult()); log.info("expected: paid the correct amount of BSQ fees with two UTXOs"); offerData = "qmmtead,94b2589f3270caa0df63437707d4442cae34498ee5b0285090deed9c0ce8584d,800000,11,0,705301"; - mempoolData = "{\"txid\":\"94b2589f3270caa0df63437707d4442cae34498ee5b0285090deed9c0ce8584d\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":0,\"prevout\":{\"value\":577}},{\"vout\":0,\"prevout\":{\"value\":19989}},{\"vout\":2,\"prevout\":{\"value\":3008189}}],\"vout\":[{\"scriptpubkey_address\":\"bc1q48p2nvqf3tepjy7x33c5sfx3tp89e8c05z46cs\",\"value\":20555},{\"scriptpubkey_address\":\"bc1q9h69k8l0vy2yv3c72lw2cgn95sd7hlwjjzul05\",\"value\":920000},{\"scriptpubkey_address\":\"bc1qxmwscy2krw7zzfryw5g8868dexfy6pnq9yx3rv\",\"value\":2085750}],\"size\":550,\"weight\":1228,\"fee\":2450,\"status\":{\"confirmed\":true,\"block_height\":705301}}"; - assertTrue(createTxValidator(offerData).parseJsonValidateMakerFeeTx(mempoolData, btcFeeReceivers).getResult()); + //mempoolData = "{\"txid\":\"94b2589f3270caa0df63437707d4442cae34498ee5b0285090deed9c0ce8584d\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":0,\"prevout\":{\"value\":577}},{\"vout\":0,\"prevout\":{\"value\":19989}},{\"vout\":2,\"prevout\":{\"value\":3008189}}],\"vout\":[{\"scriptpubkey_address\":\"bc1q48p2nvqf3tepjy7x33c5sfx3tp89e8c05z46cs\",\"value\":20555},{\"scriptpubkey_address\":\"bc1q9h69k8l0vy2yv3c72lw2cgn95sd7hlwjjzul05\",\"value\":920000},{\"scriptpubkey_address\":\"bc1qxmwscy2krw7zzfryw5g8868dexfy6pnq9yx3rv\",\"value\":2085750}],\"size\":550,\"weight\":1228,\"fee\":2450,\"status\":{\"confirmed\":true,\"block_height\":705301}}"; + assertTrue(createTxValidator(offerData).validateBsqFeeTx(true).getResult()); log.info("expected: UNDERPAID expected 1.01 BSQ, actual fee paid 0.40 BSQ (USED 4.00 RATE INSTEAD OF 10.06 RATE"); offerData = "48067552,3b6009da764b71d79a4df8e2d8960b6919cae2e9bdccd5ef281e261fa9cd31b3,10000000,40,0,667656"; @@ -102,13 +102,11 @@ public void testMakerTx() { log.info("expected: LENIENCY Expected fee: 0.61 BSQ, actual fee paid: 0.35 BSQ (USED 5.75 RATE INSTEAD OF 10.06 RATE"); offerData = "am7DzIv,4cdea8872a7d96210f378e0221dc1aae8ee9abb282582afa7546890fb39b7189,6100000,35,0,668195"; //mempoolData = "{\"txid\":\"4cdea8872a7d96210f378e0221dc1aae8ee9abb282582afa7546890fb39b7189\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":0,\"prevout\":{\"value\":23893}},{\"vout\":1,\"prevout\":{\"value\":1440000}},{\"vout\":2,\"prevout\":{\"value\":16390881}}],\"vout\":[{\"scriptpubkey_address\":\"1Kmrzq3WGCQsZw5kroEphuk1KgsEr65yB7\",\"value\":23858},{\"scriptpubkey_address\":\"bc1qyw5qql9m7rkse9mhcun225nrjpwycszsa5dpjg\",\"value\":7015000},{\"scriptpubkey_address\":\"bc1q90y3p6mg0pe3rvvzfeudq4mfxafgpc9rulruff\",\"value\":10774186}],\"size\":554,\"weight\":1559,\"fee\":41730,\"status\":{\"confirmed\":true,\"block_height\":668198}}"; - assertTrue(createTxValidator(offerData).validateBsqFeeTx(true).getResult()); log.info("expected: LENIENCY expected 0.11 BSQ, actual fee paid 0.08 BSQ (USED 5.75 RATE INSTEAD OF 7.53"); offerData = "F1dzaFNQ,f72e263947c9dee6fbe7093fc85be34a149ef5bcfdd49b59b9cc3322fea8967b,1440000,8,0,670822, bsq paid too little"; //mempoolData = "{\"txid\":\"f72e263947c9dee6fbe7093fc85be34a149ef5bcfdd49b59b9cc3322fea8967b\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":0,\"prevout\":{\"value\":15163}},{\"vout\":2,\"prevout\":{\"value\":6100000}}],\"vout\":[{\"scriptpubkey_address\":\"1MEsc2m4MSomNJWSr1p6fhnUQMyA3DRGrN\",\"value\":15155},{\"scriptpubkey_address\":\"bc1qztgwe9ry9a9puchjuscqdnv4v9lsm2ut0jtfec\",\"value\":2040000},{\"scriptpubkey_address\":\"bc1q0nstwxc0vqkj4x000xt328mfjapvlsd56nn70h\",\"value\":4048308}],\"size\":406,\"weight\":1291,\"fee\":11700,\"status\":{\"confirmed\":true,\"block_height\":670823}}"; - assertTrue(createTxValidator(offerData).validateBsqFeeTx(true).getResult()); } @@ -144,7 +142,7 @@ public void testTakerTx() { log.info("========== test case: The fee matched what we expected"); offerData = "89284,e1269aad63b3d894f5133ad658960971ef5c0fce6a13ad10544dc50fa3360588,900000,47,0,666473"; mempoolData = "{\"txid\":\"e1269aad63b3d894f5133ad658960971ef5c0fce6a13ad10544dc50fa3360588\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":0,\"prevout\":{\"value\":72738}},{\"vout\":0,\"prevout\":{\"value\":1600000}}],\"vout\":[{\"scriptpubkey_address\":\"17Kh5Ype9yNomqRrqu2k1mdV5c6FcKfGwQ\",\"value\":72691},{\"scriptpubkey_address\":\"bc1qdr9zcw7gf2sehxkux4fmqujm5uguhaqz7l9lca\",\"value\":629016},{\"scriptpubkey_address\":\"bc1qgqrrqv8q6l5d3t52fe28ghuhz4xqrsyxlwn03z\",\"value\":956523}],\"size\":404,\"weight\":1286,\"fee\":14508,\"status\":{\"confirmed\":true,\"block_height\":672388}}"; - assertTrue(createTxValidator(offerData).parseJsonValidateTakerFeeTx(mempoolData, btcFeeReceivers).getResult()); + assertTrue(createTxValidator(offerData).validateBsqFeeTx(false).getResult()); log.info("========== test case for UNDERPAID: Expected fee: 7.04 BSQ, actual fee paid: 1.01 BSQ"); offerData = "VOxRS,e99ea06aefc824fd45031447f7a0b56efb8117a09f9b8982e2c4da480a3a0e91,10000000,101,0,669129"; @@ -182,8 +180,6 @@ public void testTakerTx() { offerData = "3UTXOLOWFEE,c7dddc267a366fa1d87840eeb0dcd89918a886ccb9aabee80f667635a5d4e262,200000000,101,0,733715"; mempoolData = "{\"txid\":\"c7dddc267a366fa1d87840eeb0dcd89918a886ccb9aabee80f667635a5d4e262\",\"version\":1,\"locktime\":0,\"vin\":[{\"vout\":0,\"prevout\":{\"value\":9833}},{\"vout\":0,\"prevout\":{\"value\":1362}},{vout\":0,\"prevout\":{\"value\":1362}},{\"vout\":2,\"prevout\":{\"value\":573360131}}],\"vout\":[{\"scriptpubkey_address\":\"bc1qvwpm87kmrlgave9srxk6nfwleehll0kxetu5j0\",\"value\":10795},{\"scriptpubkey_address\":\"bc1qz5n83ppfpdznnzff4e7tjep5c6f6jce9mqnrzh\",\"value\":230004780},{\"scriptpubkey_address\":\"bc1qcfyjajhuv55fyu6g5ug664r57u9a7qg55cgt5p\",\"value\":343370849}],\"size\":699,\"weight\":1500,\"fee\":2390,\"status\":{\"confirmed\":true,\"block_height\":733715}}"; assertFalse(createTxValidator(offerData).validateBsqFeeTx(false).getResult()); - - } @Test diff --git a/desktop/src/main/java/bisq/desktop/main/MainViewModel.java b/desktop/src/main/java/bisq/desktop/main/MainViewModel.java index 85c80162219..c522fac7528 100644 --- a/desktop/src/main/java/bisq/desktop/main/MainViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/MainViewModel.java @@ -400,6 +400,12 @@ private void setupHandlers() { new Popup().warning(msg).show(); } }); + bisqSetup.setOfferDisabledHandler(msg -> { + new Notification().headLine("Offer Disabled") + .notification(msg) + .autoClose() + .show(); + }); bisqSetup.setChainNotSyncedHandler(msg -> { if (PopupManager.isNoPopupDisplayed()) { new Popup().warning(msg).show(); diff --git a/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/takeoffer/TakeOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/takeoffer/TakeOfferDataModel.java index 78863fa806b..dca76841feb 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/takeoffer/TakeOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/takeoffer/TakeOfferDataModel.java @@ -43,6 +43,7 @@ import bisq.core.payment.PaymentAccountUtil; import bisq.core.payment.payload.PaymentMethod; import bisq.core.provider.fee.FeeService; +import bisq.core.provider.mempool.FeeValidationStatus; import bisq.core.provider.mempool.MempoolService; import bisq.core.provider.price.PriceFeedService; import bisq.core.trade.TradeManager; @@ -63,10 +64,8 @@ import com.google.inject.Inject; -import javafx.beans.property.IntegerProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyObjectProperty; -import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.ObservableList; @@ -123,9 +122,7 @@ class TakeOfferDataModel extends OfferDataModel { private boolean freezeFee; private Coin txFeePerVbyteFromFeeService; @Getter - protected final IntegerProperty mempoolStatus = new SimpleIntegerProperty(); - @Getter - protected String mempoolStatusText; + protected final ObjectProperty feeValidationStatus = new SimpleObjectProperty<>(); /////////////////////////////////////////////////////////////////////////////////////////// @@ -251,13 +248,12 @@ void initWithData(Offer offer) { log.info("Completed requestTxFee: txFeeFromFeeService={}", txFeeFromFeeService); } - mempoolStatus.setValue(-1); + feeValidationStatus.setValue(FeeValidationStatus.NOT_CHECKED_YET); OfferPayload offerPayload = offer.getOfferPayload().orElseThrow(); mempoolService.validateOfferMakerTx(offerPayload, (txValidator -> { - mempoolStatus.setValue(txValidator.isFail() ? 0 : 1); - if (txValidator.isFail()) { - mempoolStatusText = txValidator.toString(); - log.info("Mempool check of OfferFeePaymentTxId returned errors: [{}]", mempoolStatusText); + feeValidationStatus.setValue(txValidator.getStatus()); + if (txValidator.getStatus().fail()) { + log.info("Offer Fee check returned errors: [{}]", txValidator.getStatus()); } })); diff --git a/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/takeoffer/TakeOfferView.java b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/takeoffer/TakeOfferView.java index 64d01d82448..a8ca9c0d8cd 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/takeoffer/TakeOfferView.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/takeoffer/TakeOfferView.java @@ -58,6 +58,7 @@ import bisq.core.payment.FasterPaymentsAccount; import bisq.core.payment.PaymentAccount; import bisq.core.payment.payload.PaymentMethod; +import bisq.core.provider.mempool.FeeValidationStatus; import bisq.core.user.DontShowAgainLookup; import bisq.core.user.Preferences; import bisq.core.util.FormattingUtils; @@ -904,14 +905,13 @@ private void addButtons() { private void nextStepCheckMakerTx() { // the tx validation check has had plenty of time to complete, but if for some reason it has not returned // we continue anyway since the check is not crucial. - // note, it would be great if there was a real tri-state boolean we could use here, instead of -1, 0, and 1 - int result = model.dataModel.mempoolStatus.get(); - if (result == 0) { - new Popup().warning(Res.get("popup.warning.makerTxInvalid") + model.dataModel.getMempoolStatusText()) + FeeValidationStatus result = model.dataModel.feeValidationStatus.get(); + if (result.fail()) { + new Popup().warning(Res.get("popup.warning.makerTxInvalid", result)) .onClose(() -> cancelButton1.fire()) .show(); } else { - if (result == -1) { + if (result == FeeValidationStatus.NOT_CHECKED_YET) { log.warn("Fee check has not returned a result yet. We optimistically assume all is ok and continue."); } showNextStepAfterAmountIsSet(); diff --git a/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/takeoffer/TakeOfferViewModel.java b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/takeoffer/TakeOfferViewModel.java index d09202689c9..a561b22ef70 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/takeoffer/TakeOfferViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/bisq_v1/takeoffer/TakeOfferViewModel.java @@ -40,6 +40,7 @@ import bisq.core.payment.PaymentAccount; import bisq.core.payment.payload.PaymentMethod; import bisq.core.provider.fee.FeeService; +import bisq.core.provider.mempool.FeeValidationStatus; import bisq.core.trade.model.bisq_v1.Trade; import bisq.core.user.Preferences; import bisq.core.util.FormattingUtils; @@ -130,7 +131,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel im private ChangeListener tradeErrorListener; private ChangeListener offerStateListener; private ChangeListener offerErrorListener; - private ChangeListener getMempoolStatusListener; + private ChangeListener feeValidationStatusChangeListener; private ConnectionListener connectionListener; // private Subscription isFeeSufficientSubscription; private Runnable takeOfferSucceededHandler; @@ -481,7 +482,7 @@ private void updateButtonDisableState() { boolean inputDataValid = isBtcInputValid(amount.get()).isValid && dataModel.isMinAmountLessOrEqualAmount() && !dataModel.isAmountLargerThanOfferAmount() - && dataModel.mempoolStatus.get() >= 0 // TODO do we want to block in case response is slow (tor can be slow)? + && dataModel.feeValidationStatus.get() != FeeValidationStatus.NOT_CHECKED_YET && isOfferAvailable.get() && !dataModel.wouldCreateDustForMaker(); isNextButtonDisabled.set(!inputDataValid); @@ -524,8 +525,8 @@ private void createListeners() { tradeErrorListener = (ov, oldValue, newValue) -> applyTradeErrorMessage(newValue); offerStateListener = (ov, oldValue, newValue) -> applyOfferState(newValue); - getMempoolStatusListener = (observable, oldValue, newValue) -> { - if (newValue.longValue() >= 0) { + feeValidationStatusChangeListener = (observable, oldValue, newValue) -> { + if (newValue != FeeValidationStatus.NOT_CHECKED_YET) { updateButtonDisableState(); } }; @@ -575,7 +576,7 @@ private void addListeners() { dataModel.getAmount().addListener(amountAsCoinListener); dataModel.getIsBtcWalletFunded().addListener(isWalletFundedListener); - dataModel.getMempoolStatus().addListener(getMempoolStatusListener); + dataModel.getFeeValidationStatus().addListener(feeValidationStatusChangeListener); p2PService.getNetworkNode().addConnectionListener(connectionListener); /* isFeeSufficientSubscription = EasyBind.subscribe(dataModel.isFeeFromFundingTxSufficient, newValue -> { updateButtonDisableState(); @@ -588,7 +589,7 @@ private void removeListeners() { // Binding with Bindings.createObjectBinding does not work because of bi-directional binding dataModel.getAmount().removeListener(amountAsCoinListener); - dataModel.getMempoolStatus().removeListener(getMempoolStatusListener); + dataModel.getFeeValidationStatus().removeListener(feeValidationStatusChangeListener); dataModel.getIsBtcWalletFunded().removeListener(isWalletFundedListener); if (offer != null) { diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOfferListItem.java b/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOfferListItem.java index 99274f8272a..c0d011c0db6 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOfferListItem.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOfferListItem.java @@ -44,19 +44,16 @@ class OpenOfferListItem implements FilterableListItem { @Getter private final OpenOffer openOffer; - private final PriceUtil priceUtil; private final CoinFormatter btcFormatter; private final BsqFormatter bsqFormatter; private final OpenOfferManager openOfferManager; OpenOfferListItem(OpenOffer openOffer, - PriceUtil priceUtil, CoinFormatter btcFormatter, BsqFormatter bsqFormatter, OpenOfferManager openOfferManager) { this.openOffer = openOffer; - this.priceUtil = priceUtil; this.btcFormatter = btcFormatter; this.bsqFormatter = bsqFormatter; this.openOfferManager = openOfferManager; @@ -113,10 +110,6 @@ public String getDirectionLabel() { return DisplayUtils.getDirectionWithCode(direction, getOffer().getCurrencyCode()); } - public boolean hasMakerFee() { - return getOffer().getMakerFee().isPositive(); - } - public String getMakerFeeAsString() { Offer offer = getOffer(); return offer.isCurrencyForMakerFeeBtc() ? diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersDataModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersDataModel.java index 30e3c814a69..1fa352c9c27 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersDataModel.java @@ -50,6 +50,7 @@ class OpenOffersDataModel extends ActivatableDataModel { private final OpenOfferManager openOfferManager; private final OpenBsqSwapOfferService openBsqSwapOfferService; private final PriceFeedService priceFeedService; + private final TriggerPriceService triggerPriceService; private final PriceUtil priceUtil; private final CoinFormatter btcFormatter; private final BsqFormatter bsqFormatter; @@ -57,29 +58,34 @@ class OpenOffersDataModel extends ActivatableDataModel { private final ObservableList list = FXCollections.observableArrayList(); private final ListChangeListener tradesListChangeListener; private final ChangeListener currenciesUpdateFlagPropertyListener; + private final ChangeListener triggerServiceListener; @Inject public OpenOffersDataModel(OpenOfferManager openOfferManager, OpenBsqSwapOfferService openBsqSwapOfferService, PriceFeedService priceFeedService, + TriggerPriceService triggerPriceService, PriceUtil priceUtil, @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter, BsqFormatter bsqFormatter) { this.openOfferManager = openOfferManager; this.openBsqSwapOfferService = openBsqSwapOfferService; this.priceFeedService = priceFeedService; + this.triggerPriceService = triggerPriceService; this.priceUtil = priceUtil; this.btcFormatter = btcFormatter; this.bsqFormatter = bsqFormatter; tradesListChangeListener = change -> applyList(); currenciesUpdateFlagPropertyListener = (observable, oldValue, newValue) -> applyList(); + triggerServiceListener = (observable, oldValue, newValue) -> applyList(); } @Override protected void activate() { openOfferManager.getObservableList().addListener(tradesListChangeListener); priceFeedService.updateCounterProperty().addListener(currenciesUpdateFlagPropertyListener); + triggerPriceService.updateCounter.addListener(triggerServiceListener); applyList(); } @@ -87,6 +93,7 @@ protected void activate() { protected void deactivate() { openOfferManager.getObservableList().removeListener(tradesListChangeListener); priceFeedService.updateCounterProperty().removeListener(currenciesUpdateFlagPropertyListener); + triggerPriceService.updateCounter.removeListener(triggerServiceListener); } void onActivateOpenOffer(OpenOffer openOffer, @@ -123,7 +130,7 @@ private void applyList() { list.addAll( openOfferManager.getObservableList().stream() - .map(item -> new OpenOfferListItem(item, priceUtil, btcFormatter, bsqFormatter, openOfferManager)) + .map(item -> new OpenOfferListItem(item, btcFormatter, bsqFormatter, openOfferManager)) .collect(Collectors.toList()) ); @@ -132,7 +139,6 @@ private void applyList() { } boolean wasTriggered(OpenOffer openOffer) { - return TriggerPriceService.wasTriggered(priceFeedService.getMarketPrice(openOffer.getOffer().getCurrencyCode()), - openOffer); + return TriggerPriceService.wasTriggered(priceFeedService.getMarketPrice(openOffer.getOffer().getCurrencyCode()), openOffer); } } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersView.java b/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersView.java index 1b75ed1fdf9..ae109634794 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersView.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersView.java @@ -313,7 +313,7 @@ protected void activate() { GUIUtil.exportCSV("openOffers.csv", headerConverter, contentConverter, - new OpenOfferListItem(null, null, null, null, null), + new OpenOfferListItem(null, null, null, null), sortedList, (Stage) root.getScene().getWindow()); }); @@ -334,8 +334,7 @@ private void updateMakerFeeTxIdColumnVisibility() { private void updateTriggerColumnVisibility() { triggerIconColumn.setVisible(model.dataModel.getList().stream() - .mapToLong(item -> item.getOpenOffer().getTriggerPrice()) - .sum() > 0); + .anyMatch(item -> item.getOpenOffer().triggerInfoShouldBeShown())); } @Override @@ -965,11 +964,15 @@ public void updateItem(final OpenOfferListItem item, boolean empty) { if (button == null) { button = getRegularIconButton(MaterialDesignIcon.SHIELD_HALF_FULL); boolean triggerPriceSet = item.getOpenOffer().getTriggerPrice() > 0; - button.setVisible(triggerPriceSet); + button.setVisible(triggerPriceSet || item.getOpenOffer().getFeeValidationStatus().fail()); if (model.dataModel.wasTriggered(item.getOpenOffer())) { button.getGraphic().getStyleClass().add("warning"); - button.setTooltip(new Tooltip(Res.get("openOffer.triggered"))); + button.setTooltip(new Tooltip(Res.get("openOffer.triggered", item.getOpenOffer().getShortId()))); + } else if (item.getOpenOffer().getFeeValidationStatus().fail()) { + button.getGraphic().getStyleClass().add("warning"); + button.setTooltip(new Tooltip(Res.get("openOffer.deactivated.feeValidationIssue", + item.getOpenOffer().getShortId(), item.getOpenOffer().getFeeValidationStatus().toString()))); } else { button.getGraphic().getStyleClass().remove("warning"); button.setTooltip(new Tooltip(Res.get("openOffer.triggerPrice", item.getTriggerPriceAsString()))); diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesView.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesView.java index 978d2f10f36..732b112492e 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesView.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesView.java @@ -37,6 +37,7 @@ import bisq.core.alert.PrivateNotificationManager; import bisq.core.locale.Res; import bisq.core.offer.bisq_v1.OfferPayload; +import bisq.core.provider.mempool.FeeValidationStatus; import bisq.core.support.dispute.mediation.MediationResultState; import bisq.core.support.messages.ChatMessage; import bisq.core.support.traderchat.TradeChatSession; @@ -151,7 +152,6 @@ public interface ChatCallback { private final Map> listenerByTrade = new HashMap<>(); private ChangeListener disputeStateListener; private ChangeListener mediationResultStateListener; - private ChangeListener getMempoolStatusListener; @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") private final PeerInfoIconMap avatarMap = new PeerInfoIconMap(); @@ -270,15 +270,6 @@ public void initialize() { }; tradesListChangeListener = c -> onListChanged(); - - getMempoolStatusListener = (observable, oldValue, newValue) -> { - // -1 status is unknown - // 0 status is FAIL - // 1 status is PASS - if (newValue.longValue() >= 0) { - log.info("Taker fee validation returned {}", newValue.longValue()); - } - }; } @Override @@ -342,7 +333,6 @@ else if (root.getChildren().size() == 3) list.addListener(tradesListChangeListener); updateNewChatMessagesByTradeMap(); - model.getMempoolStatus().addListener(getMempoolStatusListener); } @Override @@ -355,7 +345,6 @@ protected void deactivate() { removeSelectedSubView(); model.dataModel.list.removeListener(tradesListChangeListener); - model.getMempoolStatus().removeListener(getMempoolStatusListener); if (scene != null) scene.removeEventHandler(KeyEvent.KEY_RELEASED, keyEventEventHandler); diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java index 176dd14c4f5..9773bf9d4cf 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java @@ -31,6 +31,7 @@ import bisq.core.offer.Offer; import bisq.core.offer.OfferUtil; import bisq.core.provider.fee.FeeService; +import bisq.core.provider.mempool.FeeValidationStatus; import bisq.core.provider.mempool.MempoolService; import bisq.core.trade.ClosedTradableManager; import bisq.core.trade.bisq_v1.TradeUtil; @@ -58,10 +59,8 @@ import org.fxmisc.easybind.EasyBind; import org.fxmisc.easybind.Subscription; -import javafx.beans.property.IntegerProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyObjectProperty; -import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleObjectProperty; import java.util.Date; @@ -121,8 +120,6 @@ enum SellerState implements State { private final ObjectProperty messageStateProperty = new SimpleObjectProperty<>(MessageState.UNDEFINED); private Subscription tradeStateSubscription; private Subscription messageStateSubscription; - @Getter - protected final IntegerProperty mempoolStatus = new SimpleIntegerProperty(); /////////////////////////////////////////////////////////////////////////////////////////// @@ -210,12 +207,10 @@ private void onMessageStateChanged(MessageState messageState) { } public void checkTakerFeeTx(Trade trade) { - mempoolStatus.setValue(-1); UserThread.runAfter(() -> { mempoolService.validateOfferTakerTx(trade, (txValidator -> { - mempoolStatus.setValue(txValidator.isFail() ? 0 : 1); - if (txValidator.isFail()) { - String errorMessage = "Validation of Taker Tx returned: " + txValidator.toString(); + if (txValidator.getStatus().fail()) { + String errorMessage = txValidator.getStatus().toString(); log.warn(errorMessage); // prompt user to open mediation if (trade.getDisputeState() == Trade.DisputeState.NO_DISPUTE) { From 265ca17e327fc659e433474bdc4b5eb0468bc956 Mon Sep 17 00:00:00 2001 From: jmacxx <47253594+jmacxx@users.noreply.github.com> Date: Thu, 7 Dec 2023 13:01:25 -0600 Subject: [PATCH 2/2] Check live Tx as part of BSQ fee validation process. --- .../core/provider/mempool/MempoolService.java | 42 +++++++++---------- .../core/provider/mempool/TxValidator.java | 6 +-- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/core/src/main/java/bisq/core/provider/mempool/MempoolService.java b/core/src/main/java/bisq/core/provider/mempool/MempoolService.java index 5a2cb8524ff..c3e9ad53651 100644 --- a/core/src/main/java/bisq/core/provider/mempool/MempoolService.java +++ b/core/src/main/java/bisq/core/provider/mempool/MempoolService.java @@ -102,17 +102,12 @@ public void validateOfferMakerTx(OfferPayload offerPayload, Consumer resultHandler) { - if (txValidator.getIsFeeCurrencyBtc() != null && txValidator.getIsFeeCurrencyBtc()) { - if (!isServiceSupported()) { - UserThread.runAfter(() -> resultHandler.accept(txValidator.endResult(FeeValidationStatus.ACK_CHECK_BYPASSED)), 1); - return; - } - MempoolRequest mempoolRequest = new MempoolRequest(preferences, socks5ProxyProvider); - validateOfferMakerTx(mempoolRequest, txValidator, resultHandler); - } else { - // using BSQ for fees - UserThread.runAfter(() -> resultHandler.accept(txValidator.validateBsqFeeTx(true)), 1); + if (!isServiceSupported()) { + UserThread.runAfter(() -> resultHandler.accept(txValidator.endResult(FeeValidationStatus.ACK_CHECK_BYPASSED)), 1); + return; } + MempoolRequest mempoolRequest = new MempoolRequest(preferences, socks5ProxyProvider); + validateOfferMakerTx(mempoolRequest, txValidator, resultHandler); } public void validateOfferTakerTx(Trade trade, Consumer resultHandler) { @@ -121,17 +116,12 @@ public void validateOfferTakerTx(Trade trade, Consumer resultHandle } public void validateOfferTakerTx(TxValidator txValidator, Consumer resultHandler) { - if (txValidator.getIsFeeCurrencyBtc() != null && txValidator.getIsFeeCurrencyBtc()) { - if (!isServiceSupported()) { - UserThread.runAfter(() -> resultHandler.accept(txValidator.endResult(FeeValidationStatus.ACK_CHECK_BYPASSED)), 1); - return; - } - MempoolRequest mempoolRequest = new MempoolRequest(preferences, socks5ProxyProvider); - validateOfferTakerTx(mempoolRequest, txValidator, resultHandler); - } else { - // using BSQ for fees - resultHandler.accept(txValidator.validateBsqFeeTx(false)); + if (!isServiceSupported()) { + UserThread.runAfter(() -> resultHandler.accept(txValidator.endResult(FeeValidationStatus.ACK_CHECK_BYPASSED)), 1); + return; } + MempoolRequest mempoolRequest = new MempoolRequest(preferences, socks5ProxyProvider); + validateOfferTakerTx(mempoolRequest, txValidator, resultHandler); } public void checkTxIsConfirmed(String txId, Consumer resultHandler) { @@ -178,7 +168,11 @@ private FutureCallback callbackForMakerTxValidation(MempoolRequest theRe public void onSuccess(@Nullable String jsonTxt) { UserThread.execute(() -> { outstandingRequests--; - resultHandler.accept(txValidator.parseJsonValidateMakerFeeTx(jsonTxt, getAllBtcFeeReceivers())); + if (txValidator.getIsFeeCurrencyBtc() != null && txValidator.getIsFeeCurrencyBtc()) { + resultHandler.accept(txValidator.parseJsonValidateMakerFeeTx(jsonTxt, getAllBtcFeeReceivers())); + } else { + resultHandler.accept(txValidator.validateBsqFeeTx(true)); + } }); } @@ -208,7 +202,11 @@ private FutureCallback callbackForTakerTxValidation(MempoolRequest theRe public void onSuccess(@Nullable String jsonTxt) { UserThread.execute(() -> { outstandingRequests--; - resultHandler.accept(txValidator.parseJsonValidateTakerFeeTx(jsonTxt, getAllBtcFeeReceivers())); + if (txValidator.getIsFeeCurrencyBtc() != null && txValidator.getIsFeeCurrencyBtc()) { + resultHandler.accept(txValidator.parseJsonValidateTakerFeeTx(jsonTxt, getAllBtcFeeReceivers())); + } else { + resultHandler.accept(txValidator.validateBsqFeeTx(false)); + } }); } diff --git a/core/src/main/java/bisq/core/provider/mempool/TxValidator.java b/core/src/main/java/bisq/core/provider/mempool/TxValidator.java index d7f43431c4b..b5f34f35b23 100644 --- a/core/src/main/java/bisq/core/provider/mempool/TxValidator.java +++ b/core/src/main/java/bisq/core/provider/mempool/TxValidator.java @@ -107,12 +107,12 @@ public TxValidator parseJsonValidateMakerFeeTx(String jsonTxt, List btcF log.info(s); status = FeeValidationStatus.NACK_JSON_ERROR; } - return endResult("Maker tx validation", status); + return endResult("Maker tx validation (BTC)", status); } public TxValidator validateBsqFeeTx(boolean isMaker) { Optional tx = daoStateService.getTx(txId); - String statusStr = (isMaker ? "Maker" : "Taker") + " tx validation"; + String statusStr = (isMaker ? "Maker" : "Taker") + " tx validation (BSQ)"; if (tx.isEmpty()) { long txAge = this.chainHeight - this.feePaymentBlockHeight; if (txAge > 48) { @@ -144,7 +144,7 @@ public TxValidator parseJsonValidateTakerFeeTx(String jsonTxt, List btcF log.info(s); status = FeeValidationStatus.NACK_JSON_ERROR; } - return endResult("Taker tx validation", status); + return endResult("Taker tx validation (BTC)", status); } public long parseJsonValidateTx() {