diff --git a/core/src/main/java/bisq/core/app/BisqSetup.java b/core/src/main/java/bisq/core/app/BisqSetup.java index 0c1d4a638c3..36ae441eb08 100644 --- a/core/src/main/java/bisq/core/app/BisqSetup.java +++ b/core/src/main/java/bisq/core/app/BisqSetup.java @@ -31,7 +31,6 @@ import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.WalletsManager; import bisq.core.dao.DaoSetup; -import bisq.core.dao.governance.asset.AssetService; import bisq.core.dao.governance.voteresult.VoteResultException; import bisq.core.dao.governance.voteresult.VoteResultService; import bisq.core.dao.state.unconfirmed.UnconfirmedBsqChangeOutputListService; @@ -58,7 +57,6 @@ import bisq.core.support.traderchat.TraderChatManager; import bisq.core.trade.TradeManager; import bisq.core.trade.TradeTxException; -import bisq.core.trade.statistics.AssetTradeActivityCheck; import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.user.Preferences; import bisq.core.user.User; @@ -183,8 +181,6 @@ default void onRequestWalletPassword() { private final PriceAlert priceAlert; private final MarketAlerts marketAlerts; private final VoteResultService voteResultService; - private final AssetTradeActivityCheck tradeActivityCheck; - private final AssetService assetService; private final TorSetup torSetup; private final TradeLimits tradeLimits; private final CoinFormatter formatter; @@ -275,8 +271,6 @@ public BisqSetup(P2PNetworkSetup p2PNetworkSetup, PriceAlert priceAlert, MarketAlerts marketAlerts, VoteResultService voteResultService, - AssetTradeActivityCheck tradeActivityCheck, - AssetService assetService, TorSetup torSetup, TradeLimits tradeLimits, @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter, @@ -322,8 +316,6 @@ public BisqSetup(P2PNetworkSetup p2PNetworkSetup, this.priceAlert = priceAlert; this.marketAlerts = marketAlerts; this.voteResultService = voteResultService; - this.tradeActivityCheck = tradeActivityCheck; - this.assetService = assetService; this.torSetup = torSetup; this.tradeLimits = tradeLimits; this.formatter = formatter; @@ -784,9 +776,6 @@ private void initDomainServices() { } tradeStatisticsManager.onAllServicesInitialized(); - tradeActivityCheck.onAllServicesInitialized(); - - assetService.onAllServicesInitialized(); accountAgeWitnessService.onAllServicesInitialized(); signedWitnessService.onAllServicesInitialized(); diff --git a/core/src/main/java/bisq/core/dao/governance/asset/AssetService.java b/core/src/main/java/bisq/core/dao/governance/asset/AssetService.java index 86080746814..bd33705004b 100644 --- a/core/src/main/java/bisq/core/dao/governance/asset/AssetService.java +++ b/core/src/main/java/bisq/core/dao/governance/asset/AssetService.java @@ -36,12 +36,9 @@ import bisq.core.dao.state.model.governance.EvaluatedProposal; import bisq.core.dao.state.model.governance.RemoveAssetProposal; import bisq.core.locale.CurrencyUtil; -import bisq.core.trade.statistics.TradeStatistics2; import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.util.coin.BsqFormatter; -import bisq.common.Timer; -import bisq.common.UserThread; import bisq.common.app.DevEnv; import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ResultHandler; @@ -52,30 +49,25 @@ import javax.inject.Inject; -import javafx.beans.property.IntegerProperty; -import javafx.beans.property.SimpleIntegerProperty; - -import javafx.collections.SetChangeListener; - import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; -import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; import java.util.stream.Stream; -import lombok.Getter; +import lombok.Value; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; +import javax.annotation.Nullable; + import static com.google.common.base.Preconditions.checkArgument; @Slf4j @@ -89,14 +81,10 @@ public class AssetService implements DaoSetupService, DaoStateListener { private final DaoStateService daoStateService; private final BsqFormatter bsqFormatter; - @Getter - private IntegerProperty updateFlag = new SimpleIntegerProperty(0); - @Getter - private final List statefulAssets = new ArrayList<>(); - private Map> tradeStatsByTickerSymbol; + // Only accessed via getter which fills the list on demand + private final List lazyLoadedStatefulAssets = new ArrayList<>(); private long bsqFeePerDay; private long minVolumeInBtc; - private Timer timer; /////////////////////////////////////////////////////////////////////////////////////////// @@ -118,21 +106,6 @@ public AssetService(BsqWalletService bsqWalletService, this.bsqFormatter = bsqFormatter; } - public void onAllServicesInitialized() { - tradeStatsByTickerSymbol = getTradeStatsByTickerSymbol(); - tradeStatisticsManager.getObservableTradeStatisticsSet().addListener((SetChangeListener) change -> { - // At startup if a user has downloaded the app long after the release he might receive a lots of trade statistic - // objects from the seed node. We don't want to trigger the expensive getTradeStatsByTickerSymbol call in - // between so we delay 20 sec. to be sure to call it after the data has been processed. - // To use a listener would be better but that requires bigger effort at the p2p lib side. - if (timer == null) - timer = UserThread.runAfter(() -> { - tradeStatsByTickerSymbol = getTradeStatsByTickerSymbol(); - updateList(); - timer = null; - }, 20); - }); - } /////////////////////////////////////////////////////////////////////////////////////////// // DaoSetupService @@ -144,52 +117,138 @@ public void addListeners() { } @Override + @SuppressWarnings({"EmptyMethod"}) public void start() { - statefulAssets.clear(); - statefulAssets.addAll(CurrencyUtil.getSortedAssetStream() - .filter(asset -> !asset.getTickerSymbol().equals("BSQ")) - .map(StatefulAsset::new) - .collect(Collectors.toList())); } - private void updateList() { - if (tradeStatsByTickerSymbol == null) - return; - - statefulAssets.forEach(statefulAsset -> { - AssetState assetState; - if (wasAssetRemovedByVoting(statefulAsset.getTickerSymbol())) { - assetState = AssetState.REMOVED_BY_VOTING; - } else { - statefulAsset.setFeePayments(getFeePayments(statefulAsset)); - - long lookBackPeriodInDays = getLookBackPeriodInDays(statefulAsset); - statefulAsset.setLookBackPeriodInDays(lookBackPeriodInDays); - long tradeVolume = getTradeVolume(statefulAsset, lookBackPeriodInDays); - statefulAsset.setTradeVolume(tradeVolume); - if (isInTrialPeriod(statefulAsset)) { - assetState = AssetState.IN_TRIAL_PERIOD; - } else if (tradeVolume >= minVolumeInBtc) { - assetState = AssetState.ACTIVELY_TRADED; - } else { - assetState = AssetState.DE_LISTED; - } - } - statefulAsset.setAssetState(assetState); - }); - updateFlag.set(updateFlag.get() + 1); + /////////////////////////////////////////////////////////////////////////////////////////// + // DaoStateListener + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public void onParseBlockCompleteAfterBatchProcessing(Block block) { + int chainHeight = daoStateService.getChainHeight(); + bsqFeePerDay = daoStateService.getParamValueAsCoin(Param.ASSET_LISTING_FEE_PER_DAY, chainHeight).value; + minVolumeInBtc = daoStateService.getParamValueAsCoin(Param.ASSET_MIN_VOLUME, chainHeight).value; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + public List getStatefulAssets() { + if (lazyLoadedStatefulAssets.isEmpty()) { + lazyLoadedStatefulAssets.addAll(CurrencyUtil.getSortedAssetStream() + .filter(asset -> !asset.getTickerSymbol().equals("BSQ")) + .map(StatefulAsset::new) + .collect(Collectors.toList())); + } + return lazyLoadedStatefulAssets; } - private Map> getTradeStatsByTickerSymbol() { - Map> map = new HashMap<>(); + // Call takes bout 22 ms. Should be only called on demand (e.g. view is showing the data) + public void updateAssetStates() { + // For performance optimisation we map the trade stats to a temporary lookup map and convert it to a custom + // TradeAmountDateTuple object holding only the data we need. + Map> lookupMap = new HashMap<>(); tradeStatisticsManager.getObservableTradeStatisticsSet().stream() .filter(e -> CurrencyUtil.isCryptoCurrency(e.getBaseCurrency())) .forEach(e -> { - map.putIfAbsent(e.getBaseCurrency(), new ArrayList<>()); - map.get(e.getBaseCurrency()).add(e); + lookupMap.putIfAbsent(e.getBaseCurrency(), new ArrayList<>()); + lookupMap.get(e.getBaseCurrency()).add(new TradeAmountDateTuple(e.getTradeAmount().getValue(), e.getTradeDate().getTime())); + }); + + getStatefulAssets().stream() + .filter(e -> AssetState.REMOVED_BY_VOTING != e.getAssetState()) // if once set to REMOVED_BY_VOTING we ignore it for further processing + .forEach(statefulAsset -> { + AssetState assetState; + String tickerSymbol = statefulAsset.getTickerSymbol(); + if (wasAssetRemovedByVoting(tickerSymbol)) { + assetState = AssetState.REMOVED_BY_VOTING; + } else { + statefulAsset.setFeePayments(getFeePayments(statefulAsset)); + long lookBackPeriodInDays = getLookBackPeriodInDays(statefulAsset); + statefulAsset.setLookBackPeriodInDays(lookBackPeriodInDays); + long lookupDate = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(lookBackPeriodInDays); + long tradeVolume = getTradeVolume(lookupDate, lookupMap.get(tickerSymbol)); + statefulAsset.setTradeVolume(tradeVolume); + if (isInTrialPeriod(statefulAsset)) { + assetState = AssetState.IN_TRIAL_PERIOD; + } else if (tradeVolume >= minVolumeInBtc) { + assetState = AssetState.ACTIVELY_TRADED; + } else { + assetState = AssetState.DE_LISTED; + } + } + statefulAsset.setAssetState(assetState); }); - return map; + + lookupMap.clear(); + } + + public boolean isActive(String tickerSymbol) { + return DevEnv.isDaoActivated() ? findAsset(tickerSymbol).map(StatefulAsset::isActive).orElse(false) : true; + } + + public Transaction payFee(StatefulAsset statefulAsset, + long listingFee) throws InsufficientMoneyException, TxException { + checkArgument(!statefulAsset.wasRemovedByVoting(), "Asset must not have been removed"); + checkArgument(listingFee >= getFeePerDay().value, "Fee must not be less then listing fee for 1 day."); + checkArgument(listingFee % 100 == 0, "Fee must be a multiple of 1 BSQ (100 satoshi)."); + try { + // We create a prepared Bsq Tx for the listing fee. + Transaction preparedBurnFeeTx = bsqWalletService.getPreparedBurnFeeTxForAssetListing(Coin.valueOf(listingFee)); + byte[] hash = AssetConsensus.getHash(statefulAsset); + byte[] opReturnData = AssetConsensus.getOpReturnData(hash); + // We add the BTC inputs for the miner fee. + Transaction txWithBtcFee = btcWalletService.completePreparedBurnBsqTx(preparedBurnFeeTx, opReturnData); + // We sign the BSQ inputs of the final tx. + Transaction transaction = bsqWalletService.signTx(txWithBtcFee); + log.info("Asset listing fee tx: " + transaction); + return transaction; + } catch (WalletException | TransactionVerificationException e) { + throw new TxException(e); + } + } + + public Coin getFeePerDay() { + return AssetConsensus.getFeePerDay(daoStateService, daoStateService.getChainHeight()); + } + + public void publishTransaction(Transaction transaction, ResultHandler resultHandler, + ErrorMessageHandler errorMessageHandler) { + walletsManager.publishAndCommitBsqTx(transaction, TxType.ASSET_LISTING_FEE, new TxBroadcaster.Callback() { + @Override + public void onSuccess(Transaction transaction) { + log.info("Asset listing fee tx has been published. TxId={}", transaction.getHashAsString()); + resultHandler.handleResult(); + } + + @Override + public void onFailure(TxBroadcastException exception) { + errorMessageHandler.handleErrorMessage(exception.getMessage()); + } + }); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Private + /////////////////////////////////////////////////////////////////////////////////////////// + + // Get the trade volume from lookupDate until current date + private long getTradeVolume(long lookupDate, @Nullable List tradeAmountDateTupleList) { + if (tradeAmountDateTupleList == null) { + // Was never traded + return 0; + } + + return tradeAmountDateTupleList.stream() + .filter(e -> e.getTradeDate() > lookupDate) + .mapToLong(TradeAmountDateTuple::getTradeAmount) + .sum(); } private boolean isInTrialPeriod(StatefulAsset statefulAsset) { @@ -205,15 +264,6 @@ private boolean isInTrialPeriod(StatefulAsset statefulAsset) { return false; } - private long getTradeVolume(StatefulAsset statefulAsset, long lookBackPeriodInDays) { - String tickerSymbol = statefulAsset.getTickerSymbol(); - if (tradeStatsByTickerSymbol.containsKey(tickerSymbol)) { - List tradeStatisticsForAsset = tradeStatsByTickerSymbol.get(tickerSymbol); - return getTradeVolume(tradeStatisticsForAsset, lookBackPeriodInDays); - } else { - return 0; - } - } @NotNull private Long getLookBackPeriodInDays(StatefulAsset statefulAsset) { @@ -229,21 +279,6 @@ private Long getLookBackPeriodInDays(StatefulAsset statefulAsset) { .orElse(DEFAULT_LOOK_BACK_PERIOD); } - private long getTradeVolume(List tradeStatisticsForAsset, long lookBackPeriodInDays) { - // We cannot use blocks as the block height is not in the TradeStatistics2 object and the lookup for all the - // deposit txs would be too expensive. - long lookBackPeriodInMs = TimeUnit.DAYS.toMillis(lookBackPeriodInDays); - AtomicLong accumulatedTradeAmount = new AtomicLong(0); - long now = new Date().getTime(); - tradeStatisticsForAsset.forEach(stat -> { - long timePassed = now - stat.getTradeDate().getTime(); - if (timePassed < lookBackPeriodInMs) { - accumulatedTradeAmount.addAndGet(stat.getTradeAmount().value); - } - }); - return accumulatedTradeAmount.get(); - } - private List getFeePayments(StatefulAsset statefulAsset) { return getFeeTxs(statefulAsset).stream() .map(tx -> { @@ -267,38 +302,10 @@ private List getFeeTxs(StatefulAsset statefulAsset) { .collect(Collectors.toList()); } - - /////////////////////////////////////////////////////////////////////////////////////////// - // DaoStateListener - /////////////////////////////////////////////////////////////////////////////////////////// - - @Override - public void onParseBlockCompleteAfterBatchProcessing(Block block) { - int chainHeight = daoStateService.getChainHeight(); - bsqFeePerDay = daoStateService.getParamValueAsCoin(Param.ASSET_LISTING_FEE_PER_DAY, chainHeight).value; - minVolumeInBtc = daoStateService.getParamValueAsCoin(Param.ASSET_MIN_VOLUME, chainHeight).value; - updateList(); - - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // API - /////////////////////////////////////////////////////////////////////////////////////////// - - public boolean isActive(String tickerSymbol) { - return DevEnv.isDaoActivated() ? findAsset(tickerSymbol).map(StatefulAsset::isActive).orElse(false) : true; - } - private Optional findAsset(String tickerSymbol) { - return statefulAssets.stream().filter(e -> e.getTickerSymbol().equals(tickerSymbol)).findAny(); + return getStatefulAssets().stream().filter(e -> e.getTickerSymbol().equals(tickerSymbol)).findAny(); } - - /////////////////////////////////////////////////////////////////////////////////////////// - // Private - /////////////////////////////////////////////////////////////////////////////////////////// - private boolean wasAssetRemovedByVoting(String tickerSymbol) { boolean isRemoved = getAcceptedRemoveAssetProposalStream() .anyMatch(proposal -> proposal.getTickerSymbol().equals(tickerSymbol)); @@ -315,44 +322,14 @@ private Stream getAcceptedRemoveAssetProposalStream() { .map(e -> ((RemoveAssetProposal) e.getProposal())); } + @Value + private static final class TradeAmountDateTuple { + private final long tradeAmount; + private final long tradeDate; - public Transaction payFee(StatefulAsset statefulAsset, long listingFee) throws InsufficientMoneyException, TxException { - checkArgument(!statefulAsset.wasRemovedByVoting(), "Asset must not have been removed"); - checkArgument(listingFee >= getFeePerDay().value, "Fee must not be less then listing fee for 1 day."); - checkArgument(listingFee % 100 == 0, "Fee must be a multiple of 1 BSQ (100 satoshi)."); - try { - // We create a prepared Bsq Tx for the listing fee. - Transaction preparedBurnFeeTx = bsqWalletService.getPreparedBurnFeeTxForAssetListing(Coin.valueOf(listingFee)); - byte[] hash = AssetConsensus.getHash(statefulAsset); - byte[] opReturnData = AssetConsensus.getOpReturnData(hash); - // We add the BTC inputs for the miner fee. - Transaction txWithBtcFee = btcWalletService.completePreparedBurnBsqTx(preparedBurnFeeTx, opReturnData); - // We sign the BSQ inputs of the final tx. - Transaction transaction = bsqWalletService.signTx(txWithBtcFee); - log.info("Asset listing fee tx: " + transaction); - return transaction; - } catch (WalletException | TransactionVerificationException e) { - throw new TxException(e); + TradeAmountDateTuple(long tradeAmount, long tradeDate) { + this.tradeAmount = tradeAmount; + this.tradeDate = tradeDate; } } - - public Coin getFeePerDay() { - return AssetConsensus.getFeePerDay(daoStateService, daoStateService.getChainHeight()); - } - - public void publishTransaction(Transaction transaction, ResultHandler resultHandler, - ErrorMessageHandler errorMessageHandler) { - walletsManager.publishAndCommitBsqTx(transaction, TxType.ASSET_LISTING_FEE, new TxBroadcaster.Callback() { - @Override - public void onSuccess(Transaction transaction) { - log.info("Asset listing fee tx has been published. TxId={}", transaction.getHashAsString()); - resultHandler.handleResult(); - } - - @Override - public void onFailure(TxBroadcastException exception) { - errorMessageHandler.handleErrorMessage(exception.getMessage()); - } - }); - } } diff --git a/core/src/main/java/bisq/core/trade/TradeModule.java b/core/src/main/java/bisq/core/trade/TradeModule.java index bdf5ea78e1f..a190a19f20b 100644 --- a/core/src/main/java/bisq/core/trade/TradeModule.java +++ b/core/src/main/java/bisq/core/trade/TradeModule.java @@ -23,7 +23,6 @@ import bisq.core.account.witness.AccountAgeWitnessStorageService; import bisq.core.trade.closed.ClosedTradableManager; import bisq.core.trade.failed.FailedTradesManager; -import bisq.core.trade.statistics.AssetTradeActivityCheck; import bisq.core.trade.statistics.ReferralIdService; import bisq.core.trade.statistics.TradeStatistics2StorageService; import bisq.core.trade.statistics.TradeStatisticsManager; @@ -56,7 +55,6 @@ protected void configure() { bind(SignedWitnessService.class).in(Singleton.class); bind(SignedWitnessStorageService.class).in(Singleton.class); bind(ReferralIdService.class).in(Singleton.class); - bind(AssetTradeActivityCheck.class).in(Singleton.class); bindConstant().annotatedWith(named(DUMP_STATISTICS)).to(config.dumpStatistics); bindConstant().annotatedWith(named(DUMP_DELAYED_PAYOUT_TXS)).to(config.dumpDelayedPayoutTxs); bindConstant().annotatedWith(named(ALLOW_FAULTY_DELAYED_TXS)).to(config.allowFaultyDelayedTxs); diff --git a/core/src/main/java/bisq/core/trade/statistics/AssetTradeActivityCheck.java b/core/src/main/java/bisq/core/trade/statistics/AssetTradeActivityCheck.java deleted file mode 100644 index 25caffeccb7..00000000000 --- a/core/src/main/java/bisq/core/trade/statistics/AssetTradeActivityCheck.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * 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 . - */ - -package bisq.core.trade.statistics; - -import bisq.core.dao.governance.asset.AssetService; -import bisq.core.filter.FilterManager; -import bisq.core.locale.CryptoCurrency; -import bisq.core.locale.CurrencyUtil; - -import bisq.common.util.Tuple2; - -import org.bitcoinj.core.Coin; - -import javax.inject.Inject; - -import com.google.common.base.Joiner; - -import java.time.Duration; - -import java.text.SimpleDateFormat; - -import java.util.Comparator; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -import lombok.extern.slf4j.Slf4j; - -/** - * This can be removed once the AssetService is activated with the DAO. - * At the moment it is only used for printing out trade statistics. - */ -@Slf4j -public class AssetTradeActivityCheck { - private final AssetService assetService; - private final TradeStatisticsManager tradeStatisticsManager; - private final FilterManager filterManager; - - @Inject - public AssetTradeActivityCheck(AssetService assetService, TradeStatisticsManager tradeStatisticsManager, FilterManager filterManager) { - this.assetService = assetService; - this.tradeStatisticsManager = tradeStatisticsManager; - this.filterManager = filterManager; - } - - public void onAllServicesInitialized() { - Date compareDate = new Date(new Date().getTime() - Duration.ofDays(120).toMillis()); - long minTradeAmount = Coin.parseCoin("0.01").value; - long minNumOfTrades = 3; - - Map> tradeStatMap = new HashMap<>(); - tradeStatisticsManager.getObservableTradeStatisticsSet().stream() - .filter(e -> !e.getBaseCurrency().equals("BTC")) - .filter(e -> CurrencyUtil.isCryptoCurrency(e.getBaseCurrency())) - .filter(e -> e.getTradeDate().getTime() > compareDate.getTime()) - .forEach(e -> { - tradeStatMap.putIfAbsent(e.getBaseCurrency(), new Tuple2<>(0L, 0)); - Tuple2 tuple2 = tradeStatMap.get(e.getBaseCurrency()); - long accumulatedTradeAmount = tuple2.first + e.getTradeAmount().getValue(); - int numTrades = tuple2.second + 1; - tradeStatMap.put(e.getBaseCurrency(), new Tuple2<>(accumulatedTradeAmount, numTrades)); - }); - StringBuilder newAssets = new StringBuilder("\nNew assets (in warming up phase):"); - StringBuilder sufficientlyTraded = new StringBuilder("\nSufficiently traded assets:"); - StringBuilder insufficientlyTraded = new StringBuilder("\nInsufficiently traded assets:"); - StringBuilder notTraded = new StringBuilder("\nNot traded assets:"); - List whiteListedSortedCryptoCurrencies = CurrencyUtil.getActiveSortedCryptoCurrencies(assetService, filterManager); - Set assetsToRemove = new HashSet<>(whiteListedSortedCryptoCurrencies); - whiteListedSortedCryptoCurrencies.forEach(e -> { - String code = e.getCode(); - String nameAndCode = CurrencyUtil.getNameAndCode(code); - long tradeAmount = 0; - int numTrades = 0; - boolean isInTradeStatMap = tradeStatMap.containsKey(code); - if (isInTradeStatMap) { - Tuple2 tuple = tradeStatMap.get(code); - tradeAmount = tuple.first; - numTrades = tuple.second; - } - - if (isWarmingUp(code)) { - assetsToRemove.remove(e); - newAssets.append("\n") - .append(nameAndCode) - .append(": Trade amount: ") - .append(Coin.valueOf(tradeAmount).toFriendlyString()) - .append(", number of trades: ") - .append(numTrades); - } - - if (!isWarmingUp(code) /*&& !hasPaidBSQFee(code)*/) { - if (isInTradeStatMap) { - if (tradeAmount >= minTradeAmount || numTrades >= minNumOfTrades) { - assetsToRemove.remove(e); - sufficientlyTraded.append("\n") - .append(nameAndCode) - .append(": Trade amount: ") - .append(Coin.valueOf(tradeAmount).toFriendlyString()) - .append(", number of trades: ") - .append(numTrades); - } else { - insufficientlyTraded.append("\n") - .append(nameAndCode) - .append(": Trade amount: ") - .append(Coin.valueOf(tradeAmount).toFriendlyString()) - .append(", number of trades: ") - .append(numTrades); - } - } else { - notTraded.append("\n").append(nameAndCode); - } - } - }); - List assetsToRemoveList = assetsToRemove.stream() - .sorted(Comparator.comparing(CryptoCurrency::getCode)) - .collect(Collectors.toList()); - - String result = "Date for checking trade activity: " + new SimpleDateFormat("yyyy-MM-dd'T'").format(compareDate) + - "\n\nAssets to remove (" + assetsToRemoveList.size() + "):\n" + Joiner.on("\n").join(assetsToRemoveList) + - "\n\n" + insufficientlyTraded.toString() + - "\n\n" + notTraded.toString() + - "\n\n" + newAssets.toString() + - "\n\n" + sufficientlyTraded.toString(); - // Utilities.copyToClipboard(result); - log.trace(result); - } - - private boolean isWarmingUp(String code) { - Set newlyAdded = new HashSet<>(); - - // v0.8.0 Aug 22 2018 - // none added - - // v0.9.0 (3.12.2018) - newlyAdded.add("ACM"); - newlyAdded.add("BLUR"); - newlyAdded.add("CHA"); - newlyAdded.add("CROAT"); - newlyAdded.add("DRGL"); - newlyAdded.add("ETHS"); - newlyAdded.add("KEK"); - newlyAdded.add("MASK"); - newlyAdded.add("MBGL"); - newlyAdded.add("MOX"); - newlyAdded.add("MUE"); - newlyAdded.add("NEOS"); - newlyAdded.add("PZDC"); - newlyAdded.add("QMCoin"); - newlyAdded.add("RADS"); - newlyAdded.add("RYO"); - newlyAdded.add("SUB1X"); - newlyAdded.add("MAI"); - newlyAdded.add("TRTL"); - newlyAdded.add("ZER"); - newlyAdded.add("XRC"); - - // v0.9.2 (Jan 8 2019) - newlyAdded.add("AEON"); - newlyAdded.add("BEAM"); - newlyAdded.add("BTM"); - newlyAdded.add("DXO"); - newlyAdded.add("FRTY"); - newlyAdded.add("GMCN"); - newlyAdded.add("GRIN"); - newlyAdded.add("ZEN"); - newlyAdded.add("IDA"); - newlyAdded.add("IRD"); - newlyAdded.add("NOR"); - newlyAdded.add("PINK"); - newlyAdded.add("QBS"); - newlyAdded.add("RMX"); - newlyAdded.add("SCP"); - newlyAdded.add("SPACE"); - newlyAdded.add("UCC"); - newlyAdded.add("WEB"); - newlyAdded.add("WRKZ"); - - // v0.9.3 - nothing added, was hotfix - - // v0.9.4 (Feb 18 2019) - newlyAdded.add("ADE"); - newlyAdded.add("ASK"); - newlyAdded.add("AEUR"); - newlyAdded.add("AUS"); - newlyAdded.add("CASH2"); - newlyAdded.add("DARX"); - newlyAdded.add("CRDS"); - newlyAdded.add("CRCL"); - newlyAdded.add("DAI"); - newlyAdded.add("ONION"); - newlyAdded.add("FJC"); - newlyAdded.add("LYTX"); - newlyAdded.add("LTZ"); - newlyAdded.add("MILE"); - newlyAdded.add("PRSN"); - newlyAdded.add("TUSD"); - newlyAdded.add("USDC"); - newlyAdded.add("VXV"); - newlyAdded.add("ZEL"); - - return newlyAdded.contains(code); - } -} diff --git a/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/assetfee/AssetFeeView.java b/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/assetfee/AssetFeeView.java index 10876b3fc82..d873ed60d79 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/assetfee/AssetFeeView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/assetfee/AssetFeeView.java @@ -32,6 +32,9 @@ import bisq.core.dao.governance.asset.AssetService; import bisq.core.dao.governance.asset.StatefulAsset; import bisq.core.dao.governance.proposal.TxException; +import bisq.core.dao.state.DaoStateListener; +import bisq.core.dao.state.DaoStateService; +import bisq.core.dao.state.model.blockchain.Block; import bisq.core.locale.CurrencyUtil; import bisq.core.locale.Res; import bisq.core.util.FormattingUtils; @@ -39,6 +42,7 @@ import bisq.core.util.coin.BsqFormatter; import bisq.core.util.coin.CoinFormatter; +import bisq.common.UserThread; import bisq.common.app.DevEnv; import org.bitcoinj.core.Coin; @@ -56,7 +60,6 @@ import javafx.scene.control.TextField; import javafx.scene.layout.GridPane; -import javafx.beans.InvalidationListener; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.value.ChangeListener; @@ -68,6 +71,8 @@ import javafx.util.StringConverter; import java.util.Comparator; +import java.util.List; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import javax.annotation.Nullable; @@ -77,7 +82,7 @@ import static bisq.desktop.util.FormBuilder.addTitledGroupBg; @FxmlView -public class AssetFeeView extends ActivatableView implements BsqBalanceListener { +public class AssetFeeView extends ActivatableView implements BsqBalanceListener, DaoStateListener { private ComboBox assetComboBox; private InputTextField feeAmountInputTextField; private TextField trialPeriodTextField; @@ -88,6 +93,7 @@ public class AssetFeeView extends ActivatableView implements Bsq private final BsqWalletService bsqWalletService; private final BsqValidator bsqValidator; private final AssetService assetService; + private final DaoStateService daoStateService; private final CoinFormatter btcFormatter; private final ObservableList observableList = FXCollections.observableArrayList(); @@ -99,7 +105,6 @@ public class AssetFeeView extends ActivatableView implements Bsq private ChangeListener amountInputTextFieldListener; @Nullable private StatefulAsset selectedAsset; - private InvalidationListener updateListener; /////////////////////////////////////////////////////////////////////////////////////////// @@ -107,15 +112,17 @@ public class AssetFeeView extends ActivatableView implements Bsq /////////////////////////////////////////////////////////////////////////////////////////// @Inject - private AssetFeeView(BsqFormatter bsqFormatter, - BsqWalletService bsqWalletService, - BsqValidator bsqValidator, - AssetService assetService, - @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter) { + public AssetFeeView(BsqFormatter bsqFormatter, + BsqWalletService bsqWalletService, + BsqValidator bsqValidator, + AssetService assetService, + DaoStateService daoStateService, + @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter) { this.bsqFormatter = bsqFormatter; this.bsqWalletService = bsqWalletService; this.bsqValidator = bsqValidator; this.assetService = assetService; + this.daoStateService = daoStateService; this.btcFormatter = btcFormatter; } @@ -162,8 +169,13 @@ protected void activate() { sortedList.comparatorProperty().bind(tableView.comparatorProperty()); - assetService.getUpdateFlag().addListener(updateListener); + daoStateService.addDaoStateListener(this); + bsqWalletService.addBsqBalanceListener(this); + + assetService.updateAssetStates(); + updateList(); + onUpdateAvailableConfirmedBalance(bsqWalletService.getAvailableConfirmedBalance()); payFeeButton.setOnAction((event) -> { @@ -194,7 +206,7 @@ protected void activate() { } }); - updateList(); + GUIUtil.setFitToRowsForTableView(tableView, 41, 28, 2, 100); updateButtonState(); @@ -208,7 +220,8 @@ protected void deactivate() { feeAmountInputTextField.textProperty().removeListener(amountInputTextFieldListener); feeAmountInputTextField.focusedProperty().removeListener(amountFocusOutListener); - assetService.getUpdateFlag().removeListener(updateListener); + daoStateService.removeDaoStateListener(this); + bsqWalletService.removeBsqBalanceListener(this); sortedList.comparatorProperty().unbind(); @@ -216,6 +229,7 @@ protected void deactivate() { payFeeButton.setOnAction(null); } + /////////////////////////////////////////////////////////////////////////////////////////// // BsqBalanceListener /////////////////////////////////////////////////////////////////////////////////////////// @@ -233,6 +247,20 @@ public void onUpdateBalances(Coin availableConfirmedBalance, } + /////////////////////////////////////////////////////////////////////////////////////////// + // DaoStateListener + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public void onParseBlockCompleteAfterBatchProcessing(Block block) { + // Delay a bit to reduce load at onParseBlockCompleteAfterBatchProcessing event + UserThread.runAfter(() -> { + assetService.updateAssetStates(); + updateList(); + }, 300, TimeUnit.MILLISECONDS); + } + + /////////////////////////////////////////////////////////////////////////////////////////// // Private /////////////////////////////////////////////////////////////////////////////////////////// @@ -248,8 +276,6 @@ private void createListeners() { trialPeriodTextField.setText(Res.get("dao.burnBsq.assets.days", getDays())); updateButtonState(); }; - - updateListener = observable -> updateList(); } private void onUpdateAvailableConfirmedBalance(Coin availableConfirmedBalance) { @@ -261,17 +287,19 @@ private long getDays() { return getListingFee().value / assetService.getFeePerDay().value; } + // We only update on new BSQ blocks and at view activation. We do not update at each trade statistics change as + // that would cause too much CPU load. The assetService.updateAssetStates() call takes about 22 ms. private void updateList() { // Here we exclude the assets which have been removed by voting. Paying a fee would not change the state. - ObservableList nonRemovedStatefulAssets = FXCollections.observableArrayList(assetService.getStatefulAssets().stream() + List statefulAssets = assetService.getStatefulAssets(); + ObservableList nonRemovedStatefulAssets = FXCollections.observableArrayList(statefulAssets.stream() .filter(e -> !e.wasRemovedByVoting()) .collect(Collectors.toList())); assetComboBox.setItems(nonRemovedStatefulAssets); - // In the table we want to show all. - observableList.setAll(assetService.getStatefulAssets().stream() + // In the table we want to show all including removed assets. + observableList.setAll(statefulAssets.stream() .map(statefulAsset -> new AssetListItem(statefulAsset, bsqFormatter)) - /*.sorted(Comparator.comparing(AssetListItem::getNameAndCode))*/ .collect(Collectors.toList())); GUIUtil.setFitToRowsForTableView(tableView, 41, 28, 2, 100); }