diff --git a/core/src/main/java/bisq/core/api/CoreOffersService.java b/core/src/main/java/bisq/core/api/CoreOffersService.java index ac2f55c29a..3299d2c07c 100644 --- a/core/src/main/java/bisq/core/api/CoreOffersService.java +++ b/core/src/main/java/bisq/core/api/CoreOffersService.java @@ -24,8 +24,8 @@ import bisq.core.offer.CreateOfferService; import bisq.core.offer.Offer; import bisq.core.offer.OfferBookService; import bisq.core.offer.OfferDirection; -import bisq.core.offer.OfferFilter; -import bisq.core.offer.OfferFilter.Result; +import bisq.core.offer.OfferFilterService; +import bisq.core.offer.OfferFilterService.Result; import bisq.core.offer.OfferUtil; import bisq.core.offer.OpenOffer; import bisq.core.offer.OpenOfferManager; @@ -65,7 +65,7 @@ import static java.util.Comparator.comparing; @Singleton @Slf4j -class CoreOffersService { +public class CoreOffersService { private final Supplier> priceComparator = () -> comparing(Offer::getPrice); private final Supplier> reversePriceComparator = () -> comparing(Offer::getPrice).reversed(); @@ -78,7 +78,7 @@ class CoreOffersService { private final CoreWalletsService coreWalletsService; private final CreateOfferService createOfferService; private final OfferBookService offerBookService; - private final OfferFilter offerFilter; + private final OfferFilterService offerFilter; private final OpenOfferManager openOfferManager; private final User user; private final XmrWalletService xmrWalletService; @@ -89,7 +89,7 @@ class CoreOffersService { CoreWalletsService coreWalletsService, CreateOfferService createOfferService, OfferBookService offerBookService, - OfferFilter offerFilter, + OfferFilterService offerFilter, OpenOfferManager openOfferManager, OfferUtil offerUtil, User user, diff --git a/core/src/main/java/bisq/core/offer/OfferFilter.java b/core/src/main/java/bisq/core/offer/OfferFilter.java deleted file mode 100644 index 466202ab57..0000000000 --- a/core/src/main/java/bisq/core/offer/OfferFilter.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * This file is part of Haveno. - * - * Haveno is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at - * your option) any later version. - * - * Haveno is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public - * License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Haveno. If not, see . - */ - -package bisq.core.offer; - -import bisq.core.account.witness.AccountAgeWitnessService; -import bisq.core.filter.FilterManager; -import bisq.core.locale.CurrencyUtil; -import bisq.core.payment.PaymentAccount; -import bisq.core.payment.PaymentAccountUtil; -import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator; -import bisq.core.trade.TradeUtils; -import bisq.core.user.Preferences; -import bisq.core.user.User; - -import bisq.common.app.Version; - -import org.bitcoinj.core.Coin; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import javafx.collections.SetChangeListener; - -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -@Singleton -public class OfferFilter { - private final User user; - private final Preferences preferences; - private final FilterManager filterManager; - private final AccountAgeWitnessService accountAgeWitnessService; - private final Map insufficientCounterpartyTradeLimitCache = new HashMap<>(); - private final Map myInsufficientTradeLimitCache = new HashMap<>(); - - @Inject - public OfferFilter(User user, - Preferences preferences, - FilterManager filterManager, - AccountAgeWitnessService accountAgeWitnessService) { - this.user = user; - this.preferences = preferences; - this.filterManager = filterManager; - this.accountAgeWitnessService = accountAgeWitnessService; - - if (user != null && user.getPaymentAccountsAsObservable() != null) { - // If our accounts have changed we reset our myInsufficientTradeLimitCache as it depends on account data - user.getPaymentAccountsAsObservable().addListener((SetChangeListener) c -> - myInsufficientTradeLimitCache.clear()); - } - } - - public enum Result { - VALID(true), - API_DISABLED, - HAS_NO_PAYMENT_ACCOUNT_VALID_FOR_OFFER, - HAS_NOT_SAME_PROTOCOL_VERSION, - IS_IGNORED, - IS_OFFER_BANNED, - IS_CURRENCY_BANNED, - IS_PAYMENT_METHOD_BANNED, - IS_NODE_ADDRESS_BANNED, - REQUIRE_UPDATE_TO_NEW_VERSION, - IS_INSUFFICIENT_COUNTERPARTY_TRADE_LIMIT, - IS_MY_INSUFFICIENT_TRADE_LIMIT, - SIGNATURE_NOT_VALIDATED; - - @Getter - private final boolean isValid; - - Result(boolean isValid) { - this.isValid = isValid; - } - - Result() { - this(false); - } - } - - public Result canTakeOffer(Offer offer, boolean isTakerApiUser) { - if (isTakerApiUser && filterManager.getFilter() != null && filterManager.getFilter().isDisableApi()) { - return Result.API_DISABLED; - } - if (!hasSameProtocolVersion(offer)) { - return Result.HAS_NOT_SAME_PROTOCOL_VERSION; - } - if (isIgnored(offer)) { - return Result.IS_IGNORED; - } - if (isOfferBanned(offer)) { - return Result.IS_OFFER_BANNED; - } - if (isCurrencyBanned(offer)) { - return Result.IS_CURRENCY_BANNED; - } - if (isPaymentMethodBanned(offer)) { - return Result.IS_PAYMENT_METHOD_BANNED; - } - if (isNodeAddressBanned(offer)) { - return Result.IS_NODE_ADDRESS_BANNED; - } - if (requireUpdateToNewVersion()) { - return Result.REQUIRE_UPDATE_TO_NEW_VERSION; - } - if (isInsufficientCounterpartyTradeLimit(offer)) { - return Result.IS_INSUFFICIENT_COUNTERPARTY_TRADE_LIMIT; - } - if (isMyInsufficientTradeLimit(offer)) { - return Result.IS_MY_INSUFFICIENT_TRADE_LIMIT; - } - if (!hasValidSignature(offer)) { - return Result.SIGNATURE_NOT_VALIDATED; // TODO (woodser): handle this wherever IS_MY_INSUFFICIENT_TRADE_LIMIT is handled - } - if (!isAnyPaymentAccountValidForOffer(offer)) { - return Result.HAS_NO_PAYMENT_ACCOUNT_VALID_FOR_OFFER; - } - - return Result.VALID; - } - - public boolean isAnyPaymentAccountValidForOffer(Offer offer) { - return user.getPaymentAccounts() != null && - PaymentAccountUtil.isAnyPaymentAccountValidForOffer(offer, user.getPaymentAccounts()); - } - - public boolean hasSameProtocolVersion(Offer offer) { - return offer.getProtocolVersion() == Version.TRADE_PROTOCOL_VERSION; - } - - public boolean isIgnored(Offer offer) { - return preferences.getIgnoreTradersList().stream() - .anyMatch(i -> i.equals(offer.getMakerNodeAddress().getFullAddress())); - } - - public boolean isOfferBanned(Offer offer) { - return filterManager.isOfferIdBanned(offer.getId()); - } - - public boolean isCurrencyBanned(Offer offer) { - return filterManager.isCurrencyBanned(offer.getCurrencyCode()); - } - - public boolean isPaymentMethodBanned(Offer offer) { - return filterManager.isPaymentMethodBanned(offer.getPaymentMethod()); - } - - public boolean isNodeAddressBanned(Offer offer) { - return filterManager.isNodeAddressBanned(offer.getMakerNodeAddress()); - } - - public boolean requireUpdateToNewVersion() { - return filterManager.requireUpdateToNewVersionForTrading(); - } - - // This call is a bit expensive so we cache results - public boolean isInsufficientCounterpartyTradeLimit(Offer offer) { - String offerId = offer.getId(); - if (insufficientCounterpartyTradeLimitCache.containsKey(offerId)) { - return insufficientCounterpartyTradeLimitCache.get(offerId); - } - - boolean result = CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()) && - !accountAgeWitnessService.verifyPeersTradeAmount(offer, offer.getAmount(), - errorMessage -> { - }); - insufficientCounterpartyTradeLimitCache.put(offerId, result); - return result; - } - - // This call is a bit expensive so we cache results - public boolean isMyInsufficientTradeLimit(Offer offer) { - String offerId = offer.getId(); - if (myInsufficientTradeLimitCache.containsKey(offerId)) { - return myInsufficientTradeLimitCache.get(offerId); - } - - Optional accountOptional = PaymentAccountUtil.getMostMaturePaymentAccountForOffer(offer, - user.getPaymentAccounts(), - accountAgeWitnessService); - long myTradeLimit = accountOptional - .map(paymentAccount -> accountAgeWitnessService.getMyTradeLimit(paymentAccount, - offer.getCurrencyCode(), offer.getMirroredDirection())) - .orElse(0L); - long offerMinAmount = offer.getMinAmount().value; - log.debug("isInsufficientTradeLimit accountOptional={}, myTradeLimit={}, offerMinAmount={}, ", - accountOptional.isPresent() ? accountOptional.get().getAccountName() : "null", - Coin.valueOf(myTradeLimit).toFriendlyString(), - Coin.valueOf(offerMinAmount).toFriendlyString()); - boolean result = CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()) && - accountOptional.isPresent() && - myTradeLimit < offerMinAmount; - myInsufficientTradeLimitCache.put(offerId, result); - return result; - } - - public boolean hasValidSignature(Offer offer) { - - // get arbitrator - Arbitrator arbitrator = user.getAcceptedArbitratorByAddress(offer.getOfferPayload().getArbitratorSigner()); - if (arbitrator == null) return false; // invalid arbitrator - - // validate arbitrator signature - return TradeUtils.isArbitratorSignatureValid(offer.getOfferPayload(), arbitrator); - } -} diff --git a/core/src/main/java/bisq/core/offer/OfferFilterService.java b/core/src/main/java/bisq/core/offer/OfferFilterService.java index b53b8bc2a3..518da981c7 100644 --- a/core/src/main/java/bisq/core/offer/OfferFilterService.java +++ b/core/src/main/java/bisq/core/offer/OfferFilterService.java @@ -21,10 +21,11 @@ import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.filter.FilterManager; import bisq.core.payment.PaymentAccount; import bisq.core.payment.PaymentAccountUtil; +import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator; +import bisq.core.trade.TradeUtils; import bisq.core.user.Preferences; import bisq.core.user.User; -import bisq.common.app.DevEnv; import bisq.common.app.Version; import org.bitcoinj.core.Coin; @@ -53,15 +54,15 @@ public class OfferFilterService { @Inject public OfferFilterService(User user, - Preferences preferences, - FilterManager filterManager, - AccountAgeWitnessService accountAgeWitnessService) { + Preferences preferences, + FilterManager filterManager, + AccountAgeWitnessService accountAgeWitnessService) { this.user = user; this.preferences = preferences; this.filterManager = filterManager; this.accountAgeWitnessService = accountAgeWitnessService; - if (user != null) { + if (user != null && user.getPaymentAccountsAsObservable() != null) { // If our accounts have changed we reset our myInsufficientTradeLimitCache as it depends on account data user.getPaymentAccountsAsObservable().addListener((SetChangeListener) c -> myInsufficientTradeLimitCache.clear()); @@ -81,7 +82,8 @@ public class OfferFilterService { REQUIRE_UPDATE_TO_NEW_VERSION, IS_INSUFFICIENT_COUNTERPARTY_TRADE_LIMIT, IS_MY_INSUFFICIENT_TRADE_LIMIT, - HIDE_BSQ_SWAPS_DUE_DAO_DEACTIVATED; + ARBITRATOR_NOT_VALIDATED, + SIGNATURE_NOT_VALIDATED; @Getter private final boolean isValid; @@ -99,9 +101,6 @@ public class OfferFilterService { if (isTakerApiUser && filterManager.getFilter() != null && filterManager.getFilter().isDisableApi()) { return Result.API_DISABLED; } - if (!isAnyPaymentAccountValidForOffer(offer)) { - return Result.HAS_NO_PAYMENT_ACCOUNT_VALID_FOR_OFFER; - } if (!hasSameProtocolVersion(offer)) { return Result.HAS_NOT_SAME_PROTOCOL_VERSION; } @@ -129,6 +128,15 @@ public class OfferFilterService { if (isMyInsufficientTradeLimit(offer)) { return Result.IS_MY_INSUFFICIENT_TRADE_LIMIT; } + if (!hasValidArbitrator(offer)) { + return Result.ARBITRATOR_NOT_VALIDATED; + } + if (!hasValidSignature(offer)) { + return Result.SIGNATURE_NOT_VALIDATED; + } + if (!isAnyPaymentAccountValidForOffer(offer)) { + return Result.HAS_NO_PAYMENT_ACCOUNT_VALID_FOR_OFFER; + } return Result.VALID; } @@ -207,4 +215,15 @@ public class OfferFilterService { myInsufficientTradeLimitCache.put(offerId, result); return result; } + + public boolean hasValidArbitrator(Offer offer) { + Arbitrator arbitrator = user.getAcceptedArbitratorByAddress(offer.getOfferPayload().getArbitratorSigner()); + return arbitrator != null; + } + + public boolean hasValidSignature(Offer offer) { + Arbitrator arbitrator = user.getAcceptedArbitratorByAddress(offer.getOfferPayload().getArbitratorSigner()); + if (arbitrator == null) return false; // invalid arbitrator + return TradeUtils.isArbitratorSignatureValid(offer.getOfferPayload(), arbitrator); + } } diff --git a/core/src/main/java/bisq/core/trade/TradeUtils.java b/core/src/main/java/bisq/core/trade/TradeUtils.java index 53f0873649..cec9f24ca2 100644 --- a/core/src/main/java/bisq/core/trade/TradeUtils.java +++ b/core/src/main/java/bisq/core/trade/TradeUtils.java @@ -90,9 +90,7 @@ public class TradeUtils { // verify arbitrator signature boolean isValid = true; try { - isValid = Sig.verify(arbitrator.getPubKeyRing().getSignaturePubKey(), // TODO (woodser): assign isValid - unsignedOfferAsJson, - signature); + isValid = Sig.verify(arbitrator.getPubKeyRing().getSignaturePubKey(), unsignedOfferAsJson, signature); } catch (Exception e) { isValid = false; } diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 6f08c32b14..5c1758e16b 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -415,6 +415,9 @@ offerbook.warning.requireUpdateToNewVersion=Your version of Haveno is not compat offerbook.warning.offerWasAlreadyUsedInTrade=You cannot take this offer because you already took it earlier. \ It could be that your previous take-offer attempt resulted in a failed trade. +offerbook.warning.arbitratorNotValidated=This offer cannot be taken because the arbitrator is invalid +offerbook.warning.signatureNotValidated=This offer cannot be taken because the arbitrator's signature is invalid + offerbook.info.sellAtMarketPrice=You will sell at market price (updated every minute). offerbook.info.buyAtMarketPrice=You will buy at market price (updated every minute). offerbook.info.sellBelowMarketPrice=You will get {0} less than the current market price (updated every minute). diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java index e1d070c064..9fe47b825b 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java @@ -685,8 +685,11 @@ abstract public class OfferBookView