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

Add protection tools #5053

Merged
merged 10 commits into from
Jan 7, 2021
1 change: 1 addition & 0 deletions core/src/main/java/bisq/core/api/CoreTradesService.java
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ void takeOffer(Offer offer,
offer,
paymentAccountId,
useSavingsWallet,
true,
resultHandler::accept,
errorMessage -> {
log.error(errorMessage);
Expand Down
24 changes: 17 additions & 7 deletions core/src/main/java/bisq/core/filter/Filter.java
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload {

// added at v1.5.5
private final Set<String> nodeAddressesBannedFromNetwork;
private final boolean disableApi;

// After we have created the signature from the filter data we clone it and apply the signature
static Filter cloneWithSig(Filter filter, String signatureAsBase64) {
Expand Down Expand Up @@ -122,7 +123,8 @@ static Filter cloneWithSig(Filter filter, String signatureAsBase64) {
filter.getBannedPrivilegedDevPubKeys(),
filter.isDisableAutoConf(),
filter.getBannedAutoConfExplorers(),
filter.getNodeAddressesBannedFromNetwork());
filter.getNodeAddressesBannedFromNetwork(),
filter.isDisableApi());
}

// Used for signature verification as we created the sig without the signatureAsBase64 field we set it to null again
Expand Down Expand Up @@ -152,7 +154,8 @@ static Filter cloneWithoutSig(Filter filter) {
filter.getBannedPrivilegedDevPubKeys(),
filter.isDisableAutoConf(),
filter.getBannedAutoConfExplorers(),
filter.getNodeAddressesBannedFromNetwork());
filter.getNodeAddressesBannedFromNetwork(),
filter.isDisableApi());
}

public Filter(List<String> bannedOfferIds,
Expand All @@ -177,7 +180,8 @@ public Filter(List<String> bannedOfferIds,
List<String> bannedPrivilegedDevPubKeys,
boolean disableAutoConf,
List<String> bannedAutoConfExplorers,
Set<String> nodeAddressesBannedFromNetwork) {
Set<String> nodeAddressesBannedFromNetwork,
boolean disableApi) {
this(bannedOfferIds,
nodeAddressesBannedFromTrading,
bannedPaymentAccounts,
Expand All @@ -203,7 +207,8 @@ public Filter(List<String> bannedOfferIds,
bannedPrivilegedDevPubKeys,
disableAutoConf,
bannedAutoConfExplorers,
nodeAddressesBannedFromNetwork);
nodeAddressesBannedFromNetwork,
disableApi);
}


Expand Down Expand Up @@ -237,7 +242,8 @@ public Filter(List<String> bannedOfferIds,
List<String> bannedPrivilegedDevPubKeys,
boolean disableAutoConf,
List<String> bannedAutoConfExplorers,
Set<String> nodeAddressesBannedFromNetwork) {
Set<String> nodeAddressesBannedFromNetwork,
boolean disableApi) {
this.bannedOfferIds = bannedOfferIds;
this.nodeAddressesBannedFromTrading = nodeAddressesBannedFromTrading;
this.bannedPaymentAccounts = bannedPaymentAccounts;
Expand All @@ -264,6 +270,7 @@ public Filter(List<String> bannedOfferIds,
this.disableAutoConf = disableAutoConf;
this.bannedAutoConfExplorers = bannedAutoConfExplorers;
this.nodeAddressesBannedFromNetwork = nodeAddressesBannedFromNetwork;
this.disableApi = disableApi;

// ownerPubKeyBytes can be null when called from tests
if (ownerPubKeyBytes != null) {
Expand Down Expand Up @@ -302,7 +309,8 @@ public protobuf.StoragePayload toProtoMessage() {
.addAllBannedPrivilegedDevPubKeys(bannedPrivilegedDevPubKeys)
.setDisableAutoConf(disableAutoConf)
.addAllBannedAutoConfExplorers(bannedAutoConfExplorers)
.addAllNodeAddressesBannedFromNetwork(nodeAddressesBannedFromNetwork);
.addAllNodeAddressesBannedFromNetwork(nodeAddressesBannedFromNetwork)
.setDisableApi(disableApi);

Optional.ofNullable(signatureAsBase64).ifPresent(builder::setSignatureAsBase64);
Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData);
Expand Down Expand Up @@ -341,7 +349,8 @@ public static Filter fromProto(protobuf.Filter proto) {
ProtoUtil.protocolStringListToList(proto.getBannedPrivilegedDevPubKeysList()),
proto.getDisableAutoConf(),
ProtoUtil.protocolStringListToList(proto.getBannedAutoConfExplorersList()),
ProtoUtil.protocolStringListToSet(proto.getNodeAddressesBannedFromNetworkList())
ProtoUtil.protocolStringListToSet(proto.getNodeAddressesBannedFromNetworkList()),
proto.getDisableApi()
);
}

Expand Down Expand Up @@ -385,6 +394,7 @@ public String toString() {
",\n ownerPubKey=" + ownerPubKey +
",\n disableAutoConf=" + disableAutoConf +
",\n nodeAddressesBannedFromNetwork=" + nodeAddressesBannedFromNetwork +
",\n disableApi=" + disableApi +
"\n}";
}
}
4 changes: 3 additions & 1 deletion core/src/main/java/bisq/core/offer/AvailabilityResult.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,7 @@ public enum AvailabilityResult {
NO_MEDIATORS,
USER_IGNORED,
MISSING_MANDATORY_CAPABILITY,
NO_REFUND_AGENTS
NO_REFUND_AGENTS,
UNCONF_TX_LIMIT_HIT,
MAKER_DENIED_API_USER
}
209 changes: 209 additions & 0 deletions core/src/main/java/bisq/core/offer/OfferFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
/*
* 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.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.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) {
// 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;

@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 (!isAnyPaymentAccountValidForOffer(offer)) {
return Result.HAS_NO_PAYMENT_ACCOUNT_VALID_FOR_OFFER;
}
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;
}

return Result.VALID;
}

public boolean isAnyPaymentAccountValidForOffer(Offer offer) {
return user.getPaymentAccounts() != null &&
PaymentAccountUtil.isAnyTakerPaymentAccountValidForOffer(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;
}
}
Loading