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 check for account age to apply restrictions #2801

Merged
merged 15 commits into from
May 3, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
47 changes: 47 additions & 0 deletions core/src/main/java/bisq/core/offer/OfferRestrictions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* 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.payment.payload.PaymentMethod;

import org.bitcoinj.core.Coin;

public class OfferRestrictions {
public static Coin TOLERATED_SMALL_TRADE_AMOUNT = Coin.parseCoin("0.01");

public static boolean isOfferRisky(Offer offer) {
return offer != null &&
offer.isBuyOffer() &&
PaymentMethod.hasChargebackRisk(offer.getPaymentMethod()) &&
isMinTradeAmountRisky(offer);
}

public static boolean isSellOfferRisky(Offer offer) {
return offer != null &&
PaymentMethod.hasChargebackRisk(offer.getPaymentMethod()) &&
isMinTradeAmountRisky(offer);
}

public static boolean isMinTradeAmountRisky(Offer offer) {
return isAmountRisky(offer.getMinAmount());
}

public static boolean isAmountRisky(Coin amount) {
return amount.isGreaterThan(TOLERATED_SMALL_TRADE_AMOUNT);
}
}
50 changes: 50 additions & 0 deletions core/src/main/java/bisq/core/payment/AccountAgeRestrictions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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.payment;

import bisq.core.offer.Offer;
import bisq.core.trade.Trade;

import bisq.common.util.Utilities;

import java.util.Date;
import java.util.GregorianCalendar;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class AccountAgeRestrictions {
public static final long SAFE_ACCOUNT_AGE_DATE = Utilities.getUTCDate(2019, GregorianCalendar.MARCH, 15).getTime();

public static boolean isMakersAccountAgeImmature(AccountAgeWitnessService accountAgeWitnessService, Offer offer) {
long accountCreationDate = new Date().getTime() - accountAgeWitnessService.getMakersAccountAge(offer, new Date());
return accountCreationDate > SAFE_ACCOUNT_AGE_DATE;
}

public static boolean isTradePeersAccountAgeImmature(AccountAgeWitnessService accountAgeWitnessService, Trade trade) {
long accountCreationDate = new Date().getTime() - accountAgeWitnessService.getTradingPeersAccountAge(trade);
return accountCreationDate > SAFE_ACCOUNT_AGE_DATE;
}

public static boolean isMyAccountAgeImmature(AccountAgeWitnessService accountAgeWitnessService, PaymentAccount myPaymentAccount) {
long accountCreationDate = new Date().getTime() - accountAgeWitnessService.getMyAccountAge(myPaymentAccount.getPaymentAccountPayload());
log.error("isMyAccountAgeImmature {}", accountCreationDate > SAFE_ACCOUNT_AGE_DATE);

return accountCreationDate > SAFE_ACCOUNT_AGE_DATE;
}
}
40 changes: 18 additions & 22 deletions core/src/main/java/bisq/core/payment/PaymentAccountUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@

import bisq.core.locale.Country;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferRestrictions;
import bisq.core.payment.payload.PaymentMethod;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.Set;
Expand All @@ -40,49 +40,45 @@
public class PaymentAccountUtil {

public static boolean isRiskyBuyOfferWithImmatureAccountAge(Offer offer, AccountAgeWitnessService accountAgeWitnessService) {
long accountCreationDate = new Date().getTime() - accountAgeWitnessService.getMakersAccountAge(offer, new Date());
return offer.isBuyOffer() &&
PaymentMethod.hasChargebackRisk(offer.getPaymentMethod()) &&
accountCreationDate > AccountAgeWitnessService.SAFE_ACCOUNT_AGE_DATE;
return OfferRestrictions.isOfferRisky(offer) &&
AccountAgeRestrictions.isMakersAccountAgeImmature(accountAgeWitnessService, offer);
}

public static boolean isSellOfferAndAnyTakerPaymentAccountForOfferMature(Offer offer,
Collection<PaymentAccount> takerPaymentAccounts,
AccountAgeWitnessService accountAgeWitnessService) {
if (offer.isBuyOffer() || !PaymentMethod.hasChargebackRisk(offer.getPaymentMethod()))
if (!OfferRestrictions.isSellOfferRisky(offer)) {
return true;
}

for (PaymentAccount takerPaymentAccount : takerPaymentAccounts) {
if (isTakerPaymentAccountForOfferMature(offer, takerPaymentAccount, accountAgeWitnessService))
if (isTakerAccountForOfferMature(offer, takerPaymentAccount, accountAgeWitnessService))
return true;
}
return false;
}

private static boolean isTakerPaymentAccountForOfferMature(Offer offer,
PaymentAccount takerPaymentAccount,
AccountAgeWitnessService accountAgeWitnessService) {
long accountCreationDate = new Date().getTime() - accountAgeWitnessService.getMyAccountAge(takerPaymentAccount.getPaymentAccountPayload());
return isTakerPaymentAccountValidForOffer(offer, takerPaymentAccount) &&
accountCreationDate <= AccountAgeWitnessService.SAFE_ACCOUNT_AGE_DATE;
private static boolean isTakerAccountForOfferMature(Offer offer,
PaymentAccount takerPaymentAccount,
AccountAgeWitnessService accountAgeWitnessService) {
return !OfferRestrictions.isMinTradeAmountRisky(offer) ||
(isTakerPaymentAccountValidForOffer(offer, takerPaymentAccount) &&
!AccountAgeRestrictions.isMyAccountAgeImmature(accountAgeWitnessService, takerPaymentAccount));
}

public static boolean hasMakerAnyMatureAccountForBuyOffer(Collection<PaymentAccount> makerPaymentAccounts,
AccountAgeWitnessService accountAgeWitnessService) {
for (PaymentAccount makerPaymentAccount : makerPaymentAccounts) {
if (hasMakerMatureAccountForBuyOffer(makerPaymentAccount, accountAgeWitnessService))
if (hasMyMatureAccountForBuyOffer(makerPaymentAccount, accountAgeWitnessService))
return true;
}
return false;
}

private static boolean hasMakerMatureAccountForBuyOffer(PaymentAccount makerPaymentAccount,
AccountAgeWitnessService accountAgeWitnessService) {
if (!PaymentMethod.hasChargebackRisk(makerPaymentAccount.getPaymentMethod()))
return true;

long accountCreationDate = new Date().getTime() - accountAgeWitnessService.getMyAccountAge(makerPaymentAccount.getPaymentAccountPayload());
return accountCreationDate <= AccountAgeWitnessService.SAFE_ACCOUNT_AGE_DATE;
private static boolean hasMyMatureAccountForBuyOffer(PaymentAccount myPaymentAccount,
AccountAgeWitnessService accountAgeWitnessService) {
return !PaymentMethod.hasChargebackRisk(myPaymentAccount.getPaymentMethod()) ||
!AccountAgeRestrictions.isMyAccountAgeImmature(accountAgeWitnessService, myPaymentAccount);
}

public static boolean isAnyTakerPaymentAccountValidForOffer(Offer offer, Collection<PaymentAccount> takerPaymentAccounts) {
Expand All @@ -99,7 +95,7 @@ public static ObservableList<PaymentAccount> getPossiblePaymentAccounts(Offer of
ObservableList<PaymentAccount> result = FXCollections.observableArrayList();
result.addAll(paymentAccounts.stream()
.filter(paymentAccount -> isTakerPaymentAccountValidForOffer(offer, paymentAccount))
.filter(paymentAccount -> isTakerPaymentAccountForOfferMature(offer, paymentAccount, accountAgeWitnessService))
.filter(paymentAccount -> isTakerAccountForOfferMature(offer, paymentAccount, accountAgeWitnessService))
.collect(Collectors.toList()));
return result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,13 @@
package bisq.core.trade.protocol.tasks.seller;

import bisq.core.offer.Offer;
import bisq.core.payment.AccountAgeWitnessService;
import bisq.core.payment.payload.PaymentMethod;
import bisq.core.offer.OfferRestrictions;
import bisq.core.payment.AccountAgeRestrictions;
import bisq.core.trade.Trade;
import bisq.core.trade.protocol.tasks.TradeTask;

import bisq.common.taskrunner.TaskRunner;

import java.util.Date;

import lombok.extern.slf4j.Slf4j;

@Slf4j
Expand All @@ -43,15 +41,10 @@ protected void run() {
runInterceptHook();

Offer offer = trade.getOffer();
if (offer != null && PaymentMethod.hasChargebackRisk(offer.getPaymentMethod())) {
AccountAgeWitnessService accountAgeWitnessService = processModel.getAccountAgeWitnessService();
long accountCreationDate = new Date().getTime() - accountAgeWitnessService.getTradingPeersAccountAge(trade);
if (accountCreationDate <= AccountAgeWitnessService.SAFE_ACCOUNT_AGE_DATE) {
complete();
} else {
failed("Trade process failed because the buyer's payment account was created after March 15th 2019 and the payment method is considered " +
"risky regarding chargeback risk.");
}
if (OfferRestrictions.isOfferRisky(offer) &&
AccountAgeRestrictions.isTradePeersAccountAgeImmature(processModel.getAccountAgeWitnessService(), trade)) {
failed("Trade process failed because the buyer's payment account was created after March 15th 2019 and the payment method is considered " +
"risky regarding chargeback risk.");
} else {
complete();
}
Expand Down
20 changes: 11 additions & 9 deletions core/src/main/resources/i18n/displayStrings.properties
Original file line number Diff line number Diff line change
Expand Up @@ -343,16 +343,18 @@ offerbook.withdrawFundsHint=You can withdraw the funds you paid in from the {0}
offerbook.warning.noTradingAccountForCurrency.headline=No trading account for selected currency
offerbook.warning.noTradingAccountForCurrency.msg=You don't have a trading account for the selected currency.\nDo you want to create an offer with one of your existing trading accounts?
offerbook.warning.noMatchingAccount.headline=No matching trading account.

offerbook.warning.noMatchingAccount.msg=To take this offer, you will need to set up a payment account using this payment method.\n\nWould you like to do this now?
offerbook.warning.riskyBuyOfferWithImmatureAccountAge=This offer cannot be taken because the maker's payment account \
was created after March 15th 2019, and the payment method is considered risky for bank chargebacks. We needed to deploy this restriction as a \
short-term measure for enhanced security.\n\n\
The next software release will provide more robust protection tools so that offers with this risk profile can be traded again.
offerbook.warning.sellOfferAndAnyTakerPaymentAccountForOfferMature=This offer cannot be taken because your payment account \
was created after March 15th 2019 and the payment method is considered risky for bank chargebacks. We needed to deploy this restriction as a \
short-term measure for enhanced security.\n\n\
The next software release will provide more robust protection tools so that offers with this risk profile can be traded again.

offerbook.warning.riskyBuyOfferWithImmatureAccountAge=This offer cannot be taken because of security restrictions:\n\
- The maker''s payment account was created after March 15th 2019\n\
- The min. trade amount is above 0.01 BTC\n\
- The payment method for that offer is considered risky for bank chargebacks\n\n{0}
offerbook.warning.sellOfferAndAnyTakerPaymentAccountForOfferMature=This offer cannot be taken because of security restrictions:\n\
- Your payment account was created after March 15th 2019\n\
- The min. trade amount is above 0.01 BTC\n\
- The payment method for that offer is considered risky for bank chargebacks\n\n{0}\
offerbook.warning.newVersionAnnouncement=We needed to deploy this restriction as a short-term measure for enhanced security.\n\n\
The next software release will provide more robust protection tools so that offers with this risk profile can be traded again.

offerbook.warning.wrongTradeProtocol=That offer requires a different protocol version as the one used in your version of the software.\n\nPlease check if you have the latest version installed, otherwise the user who created the offer has used an older version.\n\nUsers cannot trade with an incompatible trade protocol version.
offerbook.warning.userIgnored=You have added that user's onion address to your ignore list.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
import bisq.core.payment.HalCashAccount;
import bisq.core.payment.PaymentAccount;
import bisq.core.payment.PaymentAccountUtil;
import bisq.core.payment.payload.PaymentMethod;
import bisq.core.provider.fee.FeeService;
import bisq.core.provider.price.PriceFeedService;
import bisq.core.trade.handlers.TransactionResultHandler;
Expand Down Expand Up @@ -445,12 +444,6 @@ void onPaymentAccountSelected(PaymentAccount paymentAccount) {
}
}

private boolean isPaymentAccountMatureForBuyOffer(PaymentAccount paymentAccount) {
return direction == OfferPayload.Direction.SELL ||
!PaymentMethod.hasChargebackRisk(paymentAccount.getPaymentMethod()) ||
new Date().getTime() - accountAgeWitnessService.getMyAccountAge(paymentAccount.getPaymentAccountPayload()) <= AccountAgeWitnessService.SAFE_ACCOUNT_AGE_DATE;
}

private void setTradeCurrencyFromPaymentAccount(PaymentAccount paymentAccount) {
if (!paymentAccount.getTradeCurrencies().contains(tradeCurrency)) {
if (paymentAccount.getSelectedTradeCurrency() != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -451,9 +451,11 @@ private void onShowInfo(Offer offer,
FiatAccountsView.class,
"navigation.account");
} else if (isRiskyBuyOfferWithImmatureAccountAge) {
new Popup<>().warning(Res.get("offerbook.warning.riskyBuyOfferWithImmatureAccountAge")).show();
new Popup<>().warning(Res.get("offerbook.warning.riskyBuyOfferWithImmatureAccountAge",
Res.get("offerbook.warning.newVersionAnnouncement"))).show();
} else if (!isSellOfferAndAnyTakerPaymentAccountForOfferMature) {
new Popup<>().warning(Res.get("offerbook.warning.sellOfferAndAnyTakerPaymentAccountForOfferMature")).show();
new Popup<>().warning(Res.get("offerbook.warning.sellOfferAndAnyTakerPaymentAccountForOfferMature",
Res.get("offerbook.warning.newVersionAnnouncement"))).show();
} else if (!hasSameProtocolVersion) {
new Popup<>().warning(Res.get("offerbook.warning.wrongTradeProtocol")).show();
} else if (isIgnored) {
Expand Down