Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display the reason for auto-disabling an open offer. #6952

Merged
merged 2 commits into from Dec 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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