mirror of
https://github.com/boldsuck/haveno.git
synced 2024-12-22 20:19:21 +00:00
offer book view checks for invalid signer or signature
merge OfferFilter into OfferFilterService
This commit is contained in:
parent
b95c689190
commit
a51aeeb484
6 changed files with 42 additions and 244 deletions
|
@ -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<Comparator<Offer>> priceComparator = () -> comparing(Offer::getPrice);
|
||||
private final Supplier<Comparator<Offer>> 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,
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<String, Boolean> insufficientCounterpartyTradeLimitCache = new HashMap<>();
|
||||
private final Map<String, Boolean> 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<PaymentAccount>) 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<PaymentAccount> 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);
|
||||
}
|
||||
}
|
|
@ -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<PaymentAccount>) 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -685,8 +685,11 @@ abstract public class OfferBookView<R extends GridPane, M extends OfferBookViewM
|
|||
"isInsufficientTradeLimit case.");
|
||||
}
|
||||
break;
|
||||
case HIDE_BSQ_SWAPS_DUE_DAO_DEACTIVATED:
|
||||
new Popup().warning(Res.get("offerbook.warning.hideBsqSwapsDueDaoDeactivated")).show();
|
||||
case ARBITRATOR_NOT_VALIDATED:
|
||||
new Popup().warning(Res.get("offerbook.warning.arbitratorNotValidated")).show();
|
||||
break;
|
||||
case SIGNATURE_NOT_VALIDATED:
|
||||
new Popup().warning(Res.get("offerbook.warning.signatureNotValidated")).show();
|
||||
break;
|
||||
case VALID:
|
||||
default:
|
||||
|
|
Loading…
Reference in a new issue