diff --git a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java index 1aeed551502..3d6ac7a5de0 100644 --- a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java +++ b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java @@ -85,7 +85,6 @@ @Slf4j public class AccountAgeWitnessService { private static final Date RELEASE = Utilities.getUTCDate(2017, GregorianCalendar.NOVEMBER, 11); - public static final Date FULL_ACTIVATION = Utilities.getUTCDate(2018, GregorianCalendar.FEBRUARY, 15); private static final long SAFE_ACCOUNT_AGE_DATE = Utilities.getUTCDate(2019, GregorianCalendar.MARCH, 1).getTime(); public enum AccountAge { @@ -208,10 +207,11 @@ private void republishAllFiatAccounts() { user.getPaymentAccounts().stream() .filter(e -> !(e instanceof AssetAccount)) .forEach(e -> { - // We delay with a random interval of 20-60 sec to ensure to be better connected and don't stress the - // P2P network with publishing all at once at startup time. + // We delay with a random interval of 20-60 sec to ensure to be better connected and don't + // stress the P2P network with publishing all at once at startup time. final int delayInSec = 20 + new Random().nextInt(40); - UserThread.runAfter(() -> p2PService.addPersistableNetworkPayload(getMyWitness(e.getPaymentAccountPayload()), true), delayInSec); + UserThread.runAfter(() -> p2PService.addPersistableNetworkPayload(getMyWitness( + e.getPaymentAccountPayload()), true), delayInSec); }); } @@ -238,7 +238,8 @@ public byte[] getPeerAccountAgeWitnessHash(Trade trade) { } byte[] getAccountInputDataWithSalt(PaymentAccountPayload paymentAccountPayload) { - return Utilities.concatenateByteArrays(paymentAccountPayload.getAgeWitnessInputData(), paymentAccountPayload.getSalt()); + return Utilities.concatenateByteArrays(paymentAccountPayload.getAgeWitnessInputData(), + paymentAccountPayload.getSalt()); } @VisibleForTesting @@ -281,7 +282,8 @@ private Optional getWitnessByHash(byte[] hash) { if (!containsKey) log.debug("hash not found in accountAgeWitnessMap"); - return accountAgeWitnessMap.containsKey(hashAsByteArray) ? Optional.of(accountAgeWitnessMap.get(hashAsByteArray)) : Optional.empty(); + return accountAgeWitnessMap.containsKey(hashAsByteArray) ? + Optional.of(accountAgeWitnessMap.get(hashAsByteArray)) : Optional.empty(); } private Optional getWitnessByHashAsHex(String hashAsHex) { @@ -359,65 +361,57 @@ private AccountAge getAccountAgeCategory(long accountAge) { } } - // Checks trade limit based on time since signing of AccountAgeWitness + // Get trade limit based on a time schedule + // Buying of BTC with a payment method that has chargeback risk will use a low trade limit schedule + // All selling and all other fiat payment methods use the normal trade limit schedule + // Non fiat always has max limit + // Account types that can get signed will use time since signing, other methods use time since account age creation + // when measuring account age private long getTradeLimit(Coin maxTradeLimit, String currencyCode, AccountAgeWitness accountAgeWitness, AccountAge accountAgeCategory, OfferPayload.Direction direction, PaymentMethod paymentMethod) { - if (CurrencyUtil.isFiatCurrency(currencyCode)) { - double factor; - boolean isRisky = PaymentMethod.hasChargebackRisk(paymentMethod, currencyCode); - if (!isRisky || direction == OfferPayload.Direction.SELL) { - // Get age of witness rather than time since signing for non risky payment methods and for selling - accountAgeCategory = getAccountAgeCategory(getAccountAge(accountAgeWitness, new Date())); - } - long limit = OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT.value; - if (direction == OfferPayload.Direction.BUY && isRisky) { - // Used only for bying of BTC with risky payment methods - switch (accountAgeCategory) { - case TWO_MONTHS_OR_MORE: - factor = 1; - break; - case ONE_TO_TWO_MONTHS: - factor = 0.5; - break; - case LESS_ONE_MONTH: - case UNVERIFIED: - default: - factor = 0; - } - } else { - // Used by non risky payment methods and for selling BTC with risky methods - switch (accountAgeCategory) { - case TWO_MONTHS_OR_MORE: - factor = 1; - break; - case ONE_TO_TWO_MONTHS: - factor = 0.5; - break; - case LESS_ONE_MONTH: - case UNVERIFIED: - factor = 0.25; - break; - default: - factor = 0; - } - } - if (factor > 0) { - limit = MathUtils.roundDoubleToLong((double) maxTradeLimit.value * factor); - } - - log.debug("accountAgeCategory={}, limit={}, factor={}, accountAgeWitnessHash={}", - accountAgeCategory, - Coin.valueOf(limit).toFriendlyString(), - factor, - Utilities.bytesAsHexString(accountAgeWitness.getHash())); - return limit; - } else { + if (!CurrencyUtil.isFiatCurrency(currencyCode)) { return maxTradeLimit.value; } + + long limit = OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT.value; + var factor = PaymentMethod.hasChargebackRisk(paymentMethod, currencyCode) ? + signedTypeFactor(accountAgeCategory, direction) : normalFactor(); + if (factor > 0) { + limit = MathUtils.roundDoubleToLong((double) maxTradeLimit.value * factor); + } + + log.debug("limit={}, factor={}, accountAgeWitnessHash={}", + Coin.valueOf(limit).toFriendlyString(), + factor, + Utilities.bytesAsHexString(accountAgeWitness.getHash())); + return limit; + } + + private double signedTypeFactor(AccountAge accountAgeCategory, + OfferPayload.Direction direction) { + return direction == OfferPayload.Direction.BUY ? signedBuyFactor(accountAgeCategory) : + normalFactor(); + } + + private double signedBuyFactor(AccountAge accountAgeCategory) { + switch (accountAgeCategory) { + case TWO_MONTHS_OR_MORE: + return 1; + case ONE_TO_TWO_MONTHS: + return 0.5; + case LESS_ONE_MONTH: + case UNVERIFIED: + default: + } + return 0; + } + + private double normalFactor() { + return 1; } /////////////////////////////////////////////////////////////////////////////////////////// @@ -444,7 +438,8 @@ private boolean hasTradeLimitException(AccountAgeWitness accountAgeWitness) { /////////////////////////////////////////////////////////////////////////////////////////// public AccountAgeWitness getMyWitness(PaymentAccountPayload paymentAccountPayload) { - final Optional accountAgeWitnessOptional = findWitness(paymentAccountPayload, keyRing.getPubKeyRing()); + final Optional accountAgeWitnessOptional = + findWitness(paymentAccountPayload, keyRing.getPubKeyRing()); return accountAgeWitnessOptional.orElseGet(() -> getNewWitness(paymentAccountPayload, keyRing.getPubKeyRing())); } @@ -460,8 +455,7 @@ public long getMyAccountAge(PaymentAccountPayload paymentAccountPayload) { return getAccountAge(getMyWitness(paymentAccountPayload), new Date()); } - public long getMyTradeLimit(PaymentAccount paymentAccount, String currencyCode, OfferPayload.Direction - direction) { + public long getMyTradeLimit(PaymentAccount paymentAccount, String currencyCode, OfferPayload.Direction direction) { if (paymentAccount == null) return 0; @@ -492,7 +486,8 @@ public boolean verifyAccountAgeWitness(Trade trade, byte[] nonce, byte[] signature, ErrorMessageHandler errorMessageHandler) { - final Optional accountAgeWitnessOptional = findWitness(peersPaymentAccountPayload, peersPubKeyRing); + final Optional accountAgeWitnessOptional = + findWitness(peersPaymentAccountPayload, peersPubKeyRing); // If we don't find a stored witness data we create a new dummy object which makes is easier to reuse the // below validation methods. This peersWitness object is not used beside for validation. Some of the // validation calls are pointless in the case we create a new Witness ourselves but the verifyPeersTradeLimit @@ -513,8 +508,10 @@ public boolean verifyAccountAgeWitness(Trade trade, if (!verifyPeersCurrentDate(peersCurrentDate, errorMessageHandler)) return false; - final byte[] peersAccountInputDataWithSalt = Utilities.concatenateByteArrays(peersPaymentAccountPayload.getAgeWitnessInputData(), peersPaymentAccountPayload.getSalt()); - byte[] hash = Hash.getSha256Ripemd160hash(Utilities.concatenateByteArrays(peersAccountInputDataWithSalt, peersPubKeyRing.getSignaturePubKeyBytes())); + final byte[] peersAccountInputDataWithSalt = Utilities.concatenateByteArrays( + peersPaymentAccountPayload.getAgeWitnessInputData(), peersPaymentAccountPayload.getSalt()); + byte[] hash = Hash.getSha256Ripemd160hash(Utilities.concatenateByteArrays(peersAccountInputDataWithSalt, + peersPubKeyRing.getSignaturePubKeyBytes())); // Check if the hash in the witness data matches the hash derived from the data provided by the peer final byte[] peersWitnessHash = peersWitness.getHash(); @@ -591,7 +588,8 @@ private boolean verifyPeersTradeLimit(Offer offer, ErrorMessageHandler errorMessageHandler) { checkNotNull(offer); final String currencyCode = offer.getCurrencyCode(); - final Coin defaultMaxTradeLimit = PaymentMethod.getPaymentMethodById(offer.getOfferPayload().getPaymentMethodId()).getMaxTradeLimitAsCoin(currencyCode); + final Coin defaultMaxTradeLimit = PaymentMethod.getPaymentMethodById( + offer.getOfferPayload().getPaymentMethodId()).getMaxTradeLimitAsCoin(currencyCode); long peersCurrentTradeLimit = defaultMaxTradeLimit.value; if (!hasTradeLimitException(peersWitness)) { final long accountSignAge = getWitnessSignAge(peersWitness, peersCurrentDate); @@ -654,8 +652,8 @@ public String arbitratorSignOrphanWitness(AccountAgeWitness accountAgeWitness, .findAny() .orElse(null); checkNotNull(signedWitness); - return signedWitnessService.signAndPublishAccountAgeWitness(accountAgeWitness, key, signedWitness.getWitnessOwnerPubKey(), - time); + return signedWitnessService.signAndPublishAccountAgeWitness(accountAgeWitness, key, + signedWitness.getWitnessOwnerPubKey(), time); } public String arbitratorSignOrphanPubKey(ECKey key, @@ -676,7 +674,8 @@ public Optional traderSignAndPublishPeersAccountAgeWitness(Trade Coin tradeAmount = trade.getTradeAmount(); checkNotNull(trade.getProcessModel().getTradingPeer().getPubKeyRing(), "Peer must have a keyring"); PublicKey peersPubKey = trade.getProcessModel().getTradingPeer().getPubKeyRing().getSignaturePubKey(); - checkNotNull(peersWitness, "Not able to find peers witness, unable to sign for trade {}", trade.toString()); + checkNotNull(peersWitness, "Not able to find peers witness, unable to sign for trade {}", + trade.toString()); checkNotNull(tradeAmount, "Trade amount must not be null"); checkNotNull(peersPubKey, "Peers pub key must not be null"); @@ -717,7 +716,8 @@ private boolean isNotFiltered(Dispute dispute) { filterManager.isPaymentMethodBanned( PaymentMethod.getPaymentMethodById(dispute.getContract().getPaymentMethodId())) || filterManager.arePeersPaymentAccountDataBanned(dispute.getContract().getBuyerPaymentAccountPayload()) || - filterManager.arePeersPaymentAccountDataBanned(dispute.getContract().getSellerPaymentAccountPayload()) || + filterManager.arePeersPaymentAccountDataBanned( + dispute.getContract().getSellerPaymentAccountPayload()) || filterManager.isWitnessSignerPubKeyBanned( Utils.HEX.encode(dispute.getContract().getBuyerPubKeyRing().getSignaturePubKeyBytes())) || filterManager.isWitnessSignerPubKeyBanned(