Skip to content

Commit

Permalink
Merge pull request #6952 from jmacxx/fee_validation_reason_codes
Browse files Browse the repository at this point in the history
Display the reason for auto-disabling an open offer.
  • Loading branch information
alejandrogarcia83 authored Dec 31, 2023
2 parents 8451d7d + 265ca17 commit e5ed806
Show file tree
Hide file tree
Showing 20 changed files with 283 additions and 187 deletions.
1 change: 1 addition & 0 deletions core/src/main/java/bisq/core/app/BisqHeadlessApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
Expand Down
8 changes: 7 additions & 1 deletion core/src/main/java/bisq/core/app/BisqSetup.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<Boolean> displayTorNetworkSettingsHandler;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -482,6 +487,7 @@ private void initDomainServices() {
daoWarnMessageHandler,
filterWarningHandler,
chainNotSyncedHandler,
offerDisabledHandler,
voteResultExceptionHandler,
revolutAccountsUpdateHandler,
amazonGiftCardAccountsUpdateHandler,
Expand Down
3 changes: 2 additions & 1 deletion core/src/main/java/bisq/core/app/DomainInitialisation.java
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ public void initDomainServices(Consumer<String> rejectedTxErrorMessageHandler,
Consumer<String> daoWarnMessageHandler,
Consumer<String> filterWarningHandler,
Consumer<String> chainNotSyncedHandler,
Consumer<String> offerDisabledHandler,
Consumer<VoteResultException> voteResultExceptionHandler,
Consumer<List<RevolutAccount>> revolutAccountsUpdateHandler,
Consumer<List<AmazonGiftCardAccount>> amazonGiftCardAccountsUpdateHandler,
Expand Down Expand Up @@ -280,7 +281,7 @@ public void initDomainServices(Consumer<String> rejectedTxErrorMessageHandler,
disputeMsgEvents.onAllServicesInitialized();
priceAlert.onAllServicesInitialized();
marketAlerts.onAllServicesInitialized();
triggerPriceService.onAllServicesInitialized();
triggerPriceService.onAllServicesInitialized(offerDisabledHandler);
mempoolService.onAllServicesInitialized();

mailboxMessageService.onAllServicesInitialized();
Expand Down
9 changes: 7 additions & 2 deletions core/src/main/java/bisq/core/offer/OpenOffer.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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");
Expand Down
2 changes: 2 additions & 0 deletions core/src/main/java/bisq/core/offer/OpenOfferManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down
66 changes: 47 additions & 19 deletions core/src/main/java/bisq/core/offer/bisq_v1/TriggerPriceService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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 {
Expand All @@ -60,6 +67,8 @@ public class TriggerPriceService {
private final MempoolService mempoolService;
private final PriceFeedService priceFeedService;
private final Map<String, Set<OpenOffer>> openOffersByCurrency = new HashMap<>();
private Consumer<String> offerDisabledHandler;
public final IntegerProperty updateCounter = new SimpleIntegerProperty(0);

@Inject
public TriggerPriceService(P2PService p2PService,
Expand All @@ -72,7 +81,8 @@ public TriggerPriceService(P2PService p2PService,
this.priceFeedService = priceFeedService;
}

public void onAllServicesInitialized() {
public void onAllServicesInitialized(Consumer<String> offerDisabledHandler) {
this.offerDisabledHandler = offerDisabledHandler;
if (p2PService.isBootstrapped()) {
onBootstrapComplete();
} else {
Expand All @@ -97,19 +107,24 @@ 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)
.filter(marketPrice -> openOffersByCurrency.containsKey(marketPrice.getCurrencyCode()))
.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);
}
});
});
}

Expand Down Expand Up @@ -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<? extends OpenOffer> openOffers) {
openOffers.forEach(openOffer -> {
String currencyCode = openOffer.getOffer().getCurrencyCode();
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

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;
}
}
}
Loading

0 comments on commit e5ed806

Please sign in to comment.