Further speedups of TradeStatistics3.isValid

Use a Map to speed up 'PaymentMethod.getPaymentMethod', called from
'isValid', instead of scanning the payment method list every invocation.
Make the list immutable to ensure the map never goes stale, which is OK
since no code modified it outside PaymentMethod's static initialisation.

Also speed up the global accessor 'TradeLimits.getMaxTradeLimit', by
caching the DAO param value in a volatile field, cleared upon each new
block arrival.

Furthermore, speed up 'TradeLimits.getFirstMonthRiskBasedTradeLimit' by
simplifying the rounding logic to avoid a pass through the (rather slow)
BigDecimal type.
This commit is contained in:
Steven Barclay 2023-05-04 16:54:47 +01:00
parent 4bc1d299e5
commit 277b170e3c
No known key found for this signature in database
GPG key ID: 9FED6BF1176D500B
2 changed files with 31 additions and 25 deletions

View file

@ -19,9 +19,9 @@ package bisq.core.payment;
import bisq.core.dao.governance.param.Param; import bisq.core.dao.governance.param.Param;
import bisq.core.dao.governance.period.PeriodService; import bisq.core.dao.governance.period.PeriodService;
import bisq.core.dao.state.DaoStateListener;
import bisq.core.dao.state.DaoStateService; import bisq.core.dao.state.DaoStateService;
import bisq.core.dao.state.model.blockchain.Block;
import bisq.common.util.MathUtils;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
@ -37,7 +37,7 @@ import javax.annotation.Nullable;
@Slf4j @Slf4j
@Singleton @Singleton
public class TradeLimits { public class TradeLimits implements DaoStateListener {
@Nullable @Nullable
@Getter @Getter
private static TradeLimits INSTANCE; private static TradeLimits INSTANCE;
@ -45,10 +45,14 @@ public class TradeLimits {
private final DaoStateService daoStateService; private final DaoStateService daoStateService;
private final PeriodService periodService; private final PeriodService periodService;
private volatile Coin cachedMaxTradeLimit;
@Inject @Inject
public TradeLimits(DaoStateService daoStateService, PeriodService periodService) { public TradeLimits(DaoStateService daoStateService, PeriodService periodService) {
this.daoStateService = daoStateService; this.daoStateService = daoStateService;
this.periodService = periodService; this.periodService = periodService;
daoStateService.addDaoStateListener(this);
INSTANCE = this; INSTANCE = this;
} }
@ -58,6 +62,10 @@ public class TradeLimits {
// guice. // guice.
} }
@Override
public void onParseBlockCompleteAfterBatchProcessing(Block block) {
cachedMaxTradeLimit = null;
}
/** /**
* The default trade limits defined as statics in PaymentMethod are only used until the DAO * The default trade limits defined as statics in PaymentMethod are only used until the DAO
@ -67,7 +75,11 @@ public class TradeLimits {
* @return the maximum trade limit set by the DAO. * @return the maximum trade limit set by the DAO.
*/ */
public Coin getMaxTradeLimit() { public Coin getMaxTradeLimit() {
return daoStateService.getParamValueAsCoin(Param.MAX_TRADE_LIMIT, periodService.getChainHeight()); Coin limit = cachedMaxTradeLimit;
if (limit == null) {
cachedMaxTradeLimit = limit = daoStateService.getParamValueAsCoin(Param.MAX_TRADE_LIMIT, periodService.getChainHeight());
}
return limit;
} }
// We possibly rounded value for the first month gets multiplied by 4 to get the trade limit after the account // We possibly rounded value for the first month gets multiplied by 4 to get the trade limit after the account
@ -98,8 +110,6 @@ public class TradeLimits {
long smallestLimit = maxLimit / (4 * riskFactor); // e.g. 100000000 / 32 = 3125000 long smallestLimit = maxLimit / (4 * riskFactor); // e.g. 100000000 / 32 = 3125000
// We want to avoid more than 4 decimal places (100000000 / 32 = 3125000 or 1 BTC / 32 = 0.03125 BTC). // We want to avoid more than 4 decimal places (100000000 / 32 = 3125000 or 1 BTC / 32 = 0.03125 BTC).
// We want rounding to 0.0313 BTC // We want rounding to 0.0313 BTC
double decimalForm = MathUtils.scaleDownByPowerOf10((double) smallestLimit, 8); return ((smallestLimit + 5000L) / 10000L) * 10000L;
double rounded = MathUtils.roundDouble(decimalForm, 4);
return MathUtils.roundDoubleToLong(MathUtils.scaleUpByPowerOf10(rounded, 8));
} }
} }

View file

@ -26,11 +26,13 @@ import bisq.common.proto.persistable.PersistablePayload;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
import java.util.ArrayList; import java.util.Comparator;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
@ -196,8 +198,7 @@ public final class PaymentMethod implements PersistablePayload, Comparable<Payme
// The limit and duration assignment must not be changed as that could break old offers (if amount would be higher // The limit and duration assignment must not be changed as that could break old offers (if amount would be higher
// than new trade limit) and violate the maker expectation when he created the offer (duration). // than new trade limit) and violate the maker expectation when he created the offer (duration).
@Getter private static final List<PaymentMethod> PAYMENT_METHODS = Stream.of(
private final static List<PaymentMethod> paymentMethods = new ArrayList<>(Arrays.asList(
// EUR // EUR
SEPA = new PaymentMethod(SEPA_ID, 6 * DAY, DEFAULT_TRADE_LIMIT_HIGH_RISK), SEPA = new PaymentMethod(SEPA_ID, 6 * DAY, DEFAULT_TRADE_LIMIT_HIGH_RISK),
SEPA_INSTANT = new PaymentMethod(SEPA_INSTANT_ID, DAY, DEFAULT_TRADE_LIMIT_HIGH_RISK), SEPA_INSTANT = new PaymentMethod(SEPA_INSTANT_ID, DAY, DEFAULT_TRADE_LIMIT_HIGH_RISK),
@ -277,18 +278,15 @@ public final class PaymentMethod implements PersistablePayload, Comparable<Payme
BLOCK_CHAINS_INSTANT = new PaymentMethod(BLOCK_CHAINS_INSTANT_ID, TimeUnit.HOURS.toMillis(1), DEFAULT_TRADE_LIMIT_VERY_LOW_RISK), BLOCK_CHAINS_INSTANT = new PaymentMethod(BLOCK_CHAINS_INSTANT_ID, TimeUnit.HOURS.toMillis(1), DEFAULT_TRADE_LIMIT_VERY_LOW_RISK),
// BsqSwap // BsqSwap
BSQ_SWAP = new PaymentMethod(BSQ_SWAP_ID, 1, DEFAULT_TRADE_LIMIT_VERY_LOW_RISK) BSQ_SWAP = new PaymentMethod(BSQ_SWAP_ID, 1, DEFAULT_TRADE_LIMIT_VERY_LOW_RISK)
)); ).sorted(Comparator.comparing(
m -> m.id.equals(CLEAR_X_CHANGE_ID) ? "ZELLE" : m.id)
).collect(Collectors.toUnmodifiableList());
static { private static final Map<String, PaymentMethod> PAYMENT_METHOD_MAP = PAYMENT_METHODS.stream()
paymentMethods.sort((o1, o2) -> { .collect(Collectors.toUnmodifiableMap(m -> m.id, m -> m));
String id1 = o1.getId();
if (id1.equals(CLEAR_X_CHANGE_ID)) public static List<PaymentMethod> getPaymentMethods() {
id1 = "ZELLE"; return PAYMENT_METHODS;
String id2 = o2.getId();
if (id2.equals(CLEAR_X_CHANGE_ID))
id2 = "ZELLE";
return id1.compareTo(id2);
});
} }
public static PaymentMethod getDummyPaymentMethod(String id) { public static PaymentMethod getDummyPaymentMethod(String id) {
@ -371,9 +369,7 @@ public final class PaymentMethod implements PersistablePayload, Comparable<Payme
// We look up only our active payment methods not retired ones. // We look up only our active payment methods not retired ones.
public static Optional<PaymentMethod> getActivePaymentMethod(String id) { public static Optional<PaymentMethod> getActivePaymentMethod(String id) {
return paymentMethods.stream() return Optional.ofNullable(PAYMENT_METHOD_MAP.get(id));
.filter(e -> e.getId().equals(id))
.findFirst();
} }
public Coin getMaxTradeLimitAsCoin(String currencyCode) { public Coin getMaxTradeLimitAsCoin(String currencyCode) {