From 0fa21b5f1a0db263229a7fca939c266d00f4f070 Mon Sep 17 00:00:00 2001 From: Steven Barclay Date: Mon, 9 Dec 2019 23:54:26 +0000 Subject: [PATCH 1/3] Add transient tx map to DaoState to speed up getTx queries Build a HashMap of all BSQ transactions found, when loading the DaoState from disc, and store it in a transient field which is always kept in sync with the associated list of blocks. (The latter is only modified in a couple of places in DaoStateService, making this straightforward.) This is to speed up daoStateService.getTx(id), which is called from many places and appears to be a significant bottleneck. In particular, the initial load of the results in VoteResultView.doFillCycleList was very slow (taking nearly a minute on a Core i3 machine) and likely to suffer a quadratic slowdown (#cycles * #tx's) over time. --- .../core/dao/node/parser/BlockParser.java | 5 +--- .../bisq/core/dao/state/DaoStateService.java | 27 ++++++++++++++----- .../bisq/core/dao/state/model/DaoState.java | 14 ++++++++++ 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/bisq/core/dao/node/parser/BlockParser.java b/core/src/main/java/bisq/core/dao/node/parser/BlockParser.java index 859ad34cfa4..354c0e47dca 100644 --- a/core/src/main/java/bisq/core/dao/node/parser/BlockParser.java +++ b/core/src/main/java/bisq/core/dao/node/parser/BlockParser.java @@ -22,7 +22,6 @@ import bisq.core.dao.node.parser.exceptions.BlockHeightNotConnectingException; import bisq.core.dao.state.DaoStateService; import bisq.core.dao.state.model.blockchain.Block; -import bisq.core.dao.state.model.blockchain.Tx; import bisq.common.app.DevEnv; @@ -31,7 +30,6 @@ import javax.inject.Inject; import java.util.LinkedList; -import java.util.List; import lombok.extern.slf4j.Slf4j; @@ -106,14 +104,13 @@ public Block parseBlock(RawBlock rawBlock) throws BlockHashNotConnectingExceptio // one get resolved. // Lately there is a patter with 24 iterations observed long startTs = System.currentTimeMillis(); - List txList = block.getTxs(); rawBlock.getRawTxs().forEach(rawTx -> txParser.findTx(rawTx, genesisTxId, genesisBlockHeight, genesisTotalSupply) - .ifPresent(txList::add)); + .ifPresent(tx -> daoStateService.onNewTxForLastBlock(block, tx))); log.info("Parsing {} transactions at block height {} took {} ms", rawBlock.getRawTxs().size(), blockHeight, System.currentTimeMillis() - startTs); diff --git a/core/src/main/java/bisq/core/dao/state/DaoStateService.java b/core/src/main/java/bisq/core/dao/state/DaoStateService.java index 8cbfc5553d6..a76d630596c 100644 --- a/core/src/main/java/bisq/core/dao/state/DaoStateService.java +++ b/core/src/main/java/bisq/core/dao/state/DaoStateService.java @@ -35,18 +35,21 @@ import bisq.core.dao.state.model.governance.Issuance; import bisq.core.dao.state.model.governance.IssuanceType; import bisq.core.dao.state.model.governance.ParamChange; -import bisq.core.util.coin.BsqFormatter; import bisq.core.util.ParsingUtils; +import bisq.core.util.coin.BsqFormatter; import org.bitcoinj.core.Coin; import javax.inject.Inject; +import com.google.common.base.Preconditions; + import java.util.ArrayList; import java.util.Comparator; import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.TreeMap; @@ -115,6 +118,9 @@ public void applySnapshot(DaoState snapshot) { daoState.setChainHeight(snapshot.getChainHeight()); + daoState.getTxMap().clear(); + daoState.getTxMap().putAll(snapshot.getTxMap()); + daoState.getBlocks().clear(); daoState.getBlocks().addAll(snapshot.getBlocks()); @@ -226,7 +232,16 @@ public void onNewBlockWithEmptyTxs(Block block) { } } - // Third we get the onParseBlockComplete called after all rawTxs of blocks have been parsed + // Third we add each successfully parsed BSQ tx to the last block + public void onNewTxForLastBlock(Block block, Tx tx) { + // At least one block must be present else no rawTx would have been recognised as a BSQ tx. + Preconditions.checkArgument(block == getLastBlock().orElseThrow()); + + block.getTxs().add(tx); + daoState.getTxMap().put(tx.getId(), tx); + } + + // Fourth we get the onParseBlockComplete called after all rawTxs of blocks have been parsed public void onParseBlockComplete(Block block) { if (parseBlockChainComplete) log.info("Parse block completed: Block height {}, {} BSQ transactions.", block.getHeight(), block.getTxs().size()); @@ -348,16 +363,16 @@ public Stream getTxStream() { .flatMap(block -> block.getTxs().stream()); } - public TreeMap getTxMap() { - return new TreeMap<>(getTxStream().collect(Collectors.toMap(Tx::getId, tx -> tx))); + public Map getTxMap() { + return daoState.getTxMap(); } public Set getTxs() { - return getTxStream().collect(Collectors.toSet()); + return new HashSet<>(getTxMap().values()); } public Optional getTx(String txId) { - return getTxStream().filter(tx -> tx.getId().equals(txId)).findAny(); + return Optional.ofNullable(getTxMap().get(txId)); } public List getInvalidTxs() { diff --git a/core/src/main/java/bisq/core/dao/state/model/DaoState.java b/core/src/main/java/bisq/core/dao/state/model/DaoState.java index 0d98cbe996a..d9be03bb646 100644 --- a/core/src/main/java/bisq/core/dao/state/model/DaoState.java +++ b/core/src/main/java/bisq/core/dao/state/model/DaoState.java @@ -19,6 +19,7 @@ import bisq.core.dao.state.model.blockchain.Block; import bisq.core.dao.state.model.blockchain.SpentInfo; +import bisq.core.dao.state.model.blockchain.Tx; import bisq.core.dao.state.model.blockchain.TxOutput; import bisq.core.dao.state.model.blockchain.TxOutputKey; import bisq.core.dao.state.model.governance.Cycle; @@ -28,16 +29,19 @@ import bisq.core.dao.state.model.governance.ParamChange; import bisq.common.proto.persistable.PersistablePayload; +import bisq.common.util.JsonExclude; import com.google.protobuf.Message; import javax.inject.Inject; import java.util.ArrayList; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.TreeMap; +import java.util.function.Function; import java.util.stream.Collectors; import lombok.Getter; @@ -98,6 +102,11 @@ public static DaoState getClone(DaoState daoState) { @Getter private final List decryptedBallotsWithMeritsList; + // Transient data used only as an index - must be kept in sync with the block list + @Getter + @JsonExclude + private transient final Map txMap; // key is txId + /////////////////////////////////////////////////////////////////////////////////////////// // Constructor @@ -145,6 +154,10 @@ private DaoState(int chainHeight, this.paramChangeList = paramChangeList; this.evaluatedProposalList = evaluatedProposalList; this.decryptedBallotsWithMeritsList = decryptedBallotsWithMeritsList; + + txMap = blocks.stream() + .flatMap(block -> block.getTxs().stream()) + .collect(Collectors.toMap(Tx::getId, Function.identity(), (x, y) -> y, HashMap::new)); } @Override @@ -237,6 +250,7 @@ public String toString() { ",\n paramChangeList=" + paramChangeList + ",\n evaluatedProposalList=" + evaluatedProposalList + ",\n decryptedBallotsWithMeritsList=" + decryptedBallotsWithMeritsList + + ",\n txMap=" + txMap + "\n}"; } } From 4277cf83cafade3d0bf5dbc148717de8a1543e22 Mon Sep 17 00:00:00 2001 From: Steven Barclay Date: Wed, 11 Dec 2019 12:44:34 +0000 Subject: [PATCH 2/3] Use unordered Tx stream from daoState.txMap wherever possible Add getUnorderedTxStream() method to DaoStateService to stream directly from the txMap cache/index wherever it is obviously safe to do so, instead of iterating through the entire block list via getTxStream(). Also make getTxs() return a view of the txMap values in place of a copy. This should improve efficiency slightly. --- .../main/java/bisq/core/dao/DaoFacade.java | 4 +- .../bisq/core/dao/state/DaoStateService.java | 43 ++++++++++--------- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/core/src/main/java/bisq/core/dao/DaoFacade.java b/core/src/main/java/bisq/core/dao/DaoFacade.java index d8d4ec72021..43b7e88a28b 100644 --- a/core/src/main/java/bisq/core/dao/DaoFacade.java +++ b/core/src/main/java/bisq/core/dao/DaoFacade.java @@ -94,6 +94,7 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.Set; @@ -624,7 +625,8 @@ public Set getUnspentTxOutputs() { return daoStateService.getUnspentTxOutputs(); } - public Set getTxs() { + // Returns a view rather than a copy of all the txs. + public Collection getTxs() { return daoStateService.getTxs(); } diff --git a/core/src/main/java/bisq/core/dao/state/DaoStateService.java b/core/src/main/java/bisq/core/dao/state/DaoStateService.java index a76d630596c..08c28ffcb98 100644 --- a/core/src/main/java/bisq/core/dao/state/DaoStateService.java +++ b/core/src/main/java/bisq/core/dao/state/DaoStateService.java @@ -45,11 +45,12 @@ import com.google.common.base.Preconditions; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.LinkedList; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.TreeMap; @@ -363,24 +364,24 @@ public Stream getTxStream() { .flatMap(block -> block.getTxs().stream()); } - public Map getTxMap() { - return daoState.getTxMap(); + private Stream getUnorderedTxStream() { + return getTxs().stream(); } - public Set getTxs() { - return new HashSet<>(getTxMap().values()); - } - - public Optional getTx(String txId) { - return Optional.ofNullable(getTxMap().get(txId)); + public Collection getTxs() { + return Collections.unmodifiableCollection(daoState.getTxMap().values()); } public List getInvalidTxs() { - return getTxStream().filter(tx -> tx.getTxType() == TxType.INVALID).collect(Collectors.toList()); + return getUnorderedTxStream().filter(tx -> tx.getTxType() == TxType.INVALID).collect(Collectors.toList()); } public List getIrregularTxs() { - return getTxStream().filter(tx -> tx.getTxType() == TxType.IRREGULAR).collect(Collectors.toList()); + return getUnorderedTxStream().filter(tx -> tx.getTxType() == TxType.IRREGULAR).collect(Collectors.toList()); + } + + public Optional getTx(String txId) { + return Optional.ofNullable(daoState.getTxMap().get(txId)); } public boolean containsTx(String txId) { @@ -410,11 +411,11 @@ public boolean hasTxBurntFee(String txId) { } public long getTotalBurntFee() { - return getTxStream().mapToLong(Tx::getBurntFee).sum(); + return getUnorderedTxStream().mapToLong(Tx::getBurntFee).sum(); } public Set getBurntFeeTxs() { - return getTxStream() + return getUnorderedTxStream() .filter(tx -> tx.getBurntFee() > 0) .collect(Collectors.toSet()); } @@ -433,17 +434,17 @@ public Optional getConnectedTxOutput(TxInput txInput) { // TxOutput /////////////////////////////////////////////////////////////////////////////////////////// - public Stream getTxOutputStream() { - return getTxStream() + private Stream getUnorderedTxOutputStream() { + return getUnorderedTxStream() .flatMap(tx -> tx.getTxOutputs().stream()); } public boolean existsTxOutput(TxOutputKey key) { - return getTxOutputStream().anyMatch(txOutput -> txOutput.getKey().equals(key)); + return getUnorderedTxOutputStream().anyMatch(txOutput -> txOutput.getKey().equals(key)); } public Optional getTxOutput(TxOutputKey txOutputKey) { - return getTxOutputStream() + return getUnorderedTxOutputStream() .filter(txOutput -> txOutput.getKey().equals(txOutputKey)) .findAny(); } @@ -528,8 +529,8 @@ public boolean isTxOutputSpendable(TxOutputKey key) { // TxOutputType /////////////////////////////////////////////////////////////////////////////////////////// - public Set getTxOutputsByTxOutputType(TxOutputType txOutputType) { - return getTxOutputStream() + private Set getTxOutputsByTxOutputType(TxOutputType txOutputType) { + return getUnorderedTxOutputStream() .filter(txOutput -> txOutput.getTxOutputType() == txOutputType) .collect(Collectors.toSet()); } @@ -838,12 +839,12 @@ public long getTotalAmountOfConfiscatedTxOutputs() { } public long getTotalAmountOfInvalidatedBsq() { - return getTxStream().mapToLong(Tx::getInvalidatedBsq).sum(); + return getUnorderedTxStream().mapToLong(Tx::getInvalidatedBsq).sum(); } // Contains burnt fee and invalidated bsq due invalid txs public long getTotalAmountOfBurntBsq() { - return getTxStream().mapToLong(Tx::getBurntBsq).sum(); + return getUnorderedTxStream().mapToLong(Tx::getBurntBsq).sum(); } // Confiscate bond From b22e4ad113ecc36fd32a6116b59d0fe4434d2e83 Mon Sep 17 00:00:00 2001 From: Steven Barclay Date: Thu, 12 Dec 2019 03:08:56 +0000 Subject: [PATCH 3/3] Encapsulate mutable Block tx list and DaoState tx cache Avoid mutating the Block tx list or the DaoState tx cache/index via a Lombok getter. Instead wrap each in an unmodifiable[List|Map] & provide specific mutator methods for use by DaoStateService to add newly parsed transactions or load a DAO snapshot. Also rename txMap to txCache, replace remaining use of getTxStream() in the JSON file exporter with getUnorderedTxStream() (as this is safe) and swap the arguments of the txCache initialisation merge function, for exact consistency with the pre-caching behaviour. Finally, add a missing assertDaoStateChange() and remove a potentially harmful assertion from DaoStateService.onNewTxForLastBlock. This is based on a suggested patch by @chimp1984 in the PR #3773 review. --- .../main/java/bisq/core/dao/DaoFacade.java | 6 +-- .../node/explorer/ExportJsonFilesService.java | 2 +- .../core/dao/node/parser/BlockParser.java | 1 - .../bisq/core/dao/state/DaoStateService.java | 39 +++++++++---------- .../bisq/core/dao/state/model/DaoState.java | 26 ++++++++++--- .../dao/state/model/blockchain/Block.java | 15 ++++++- .../transactions/BSQTransactionsView.java | 2 +- 7 files changed, 56 insertions(+), 35 deletions(-) diff --git a/core/src/main/java/bisq/core/dao/DaoFacade.java b/core/src/main/java/bisq/core/dao/DaoFacade.java index 43b7e88a28b..15712f5ab7f 100644 --- a/core/src/main/java/bisq/core/dao/DaoFacade.java +++ b/core/src/main/java/bisq/core/dao/DaoFacade.java @@ -94,7 +94,6 @@ import java.io.IOException; import java.util.ArrayList; -import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.Set; @@ -625,9 +624,8 @@ public Set getUnspentTxOutputs() { return daoStateService.getUnspentTxOutputs(); } - // Returns a view rather than a copy of all the txs. - public Collection getTxs() { - return daoStateService.getTxs(); + public int getNumTxs() { + return daoStateService.getNumTxs(); } public Optional getLockupTxOutput(String txId) { diff --git a/core/src/main/java/bisq/core/dao/node/explorer/ExportJsonFilesService.java b/core/src/main/java/bisq/core/dao/node/explorer/ExportJsonFilesService.java index 2d0632435c9..2ddcd5355de 100644 --- a/core/src/main/java/bisq/core/dao/node/explorer/ExportJsonFilesService.java +++ b/core/src/main/java/bisq/core/dao/node/explorer/ExportJsonFilesService.java @@ -140,7 +140,7 @@ public void maybeExportToJson() { // Access to daoStateService is single threaded, we must not access daoStateService from the thread. List allJsonTxOutputs = new ArrayList<>(); - List jsonTxs = daoStateService.getTxStream() + List jsonTxs = daoStateService.getUnorderedTxStream() .map(tx -> { JsonTx jsonTx = getJsonTx(tx); allJsonTxOutputs.addAll(jsonTx.getOutputs()); diff --git a/core/src/main/java/bisq/core/dao/node/parser/BlockParser.java b/core/src/main/java/bisq/core/dao/node/parser/BlockParser.java index 354c0e47dca..900634bfb07 100644 --- a/core/src/main/java/bisq/core/dao/node/parser/BlockParser.java +++ b/core/src/main/java/bisq/core/dao/node/parser/BlockParser.java @@ -53,7 +53,6 @@ public class BlockParser { // Constructor /////////////////////////////////////////////////////////////////////////////////////////// - @SuppressWarnings("WeakerAccess") @Inject public BlockParser(TxParser txParser, DaoStateService daoStateService) { diff --git a/core/src/main/java/bisq/core/dao/state/DaoStateService.java b/core/src/main/java/bisq/core/dao/state/DaoStateService.java index 08c28ffcb98..5ad729e25d5 100644 --- a/core/src/main/java/bisq/core/dao/state/DaoStateService.java +++ b/core/src/main/java/bisq/core/dao/state/DaoStateService.java @@ -42,11 +42,7 @@ import javax.inject.Inject; -import com.google.common.base.Preconditions; - import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.LinkedList; @@ -119,8 +115,7 @@ public void applySnapshot(DaoState snapshot) { daoState.setChainHeight(snapshot.getChainHeight()); - daoState.getTxMap().clear(); - daoState.getTxMap().putAll(snapshot.getTxMap()); + daoState.setTxCache(snapshot.getTxCache()); daoState.getBlocks().clear(); daoState.getBlocks().addAll(snapshot.getBlocks()); @@ -235,11 +230,20 @@ public void onNewBlockWithEmptyTxs(Block block) { // Third we add each successfully parsed BSQ tx to the last block public void onNewTxForLastBlock(Block block, Tx tx) { - // At least one block must be present else no rawTx would have been recognised as a BSQ tx. - Preconditions.checkArgument(block == getLastBlock().orElseThrow()); + assertDaoStateChange(); - block.getTxs().add(tx); - daoState.getTxMap().put(tx.getId(), tx); + getLastBlock().ifPresent(lastBlock -> { + if (block == lastBlock) { + // We need to ensure that the txs in all blocks are in sync with the txs in our txMap (cache). + block.addTx(tx); + daoState.addToTxCache(tx); + } else { + // Not clear if this case can happen but at onNewBlockWithEmptyTxs we handle such a potential edge + // case as well, so we need to reflect that here as well. + log.warn("Block for parsing does not match last block. That might happen in edge cases at reorgs. " + + "Received block={}", block); + } + }); } // Fourth we get the onParseBlockComplete called after all rawTxs of blocks have been parsed @@ -359,17 +363,12 @@ public Optional getGenesisTx() { // Tx /////////////////////////////////////////////////////////////////////////////////////////// - public Stream getTxStream() { - return getBlocks().stream() - .flatMap(block -> block.getTxs().stream()); - } - - private Stream getUnorderedTxStream() { - return getTxs().stream(); + public Stream getUnorderedTxStream() { + return daoState.getTxCache().values().stream(); } - public Collection getTxs() { - return Collections.unmodifiableCollection(daoState.getTxMap().values()); + public int getNumTxs() { + return daoState.getTxCache().size(); } public List getInvalidTxs() { @@ -381,7 +380,7 @@ public List getIrregularTxs() { } public Optional getTx(String txId) { - return Optional.ofNullable(daoState.getTxMap().get(txId)); + return Optional.ofNullable(daoState.getTxCache().get(txId)); } public boolean containsTx(String txId) { diff --git a/core/src/main/java/bisq/core/dao/state/model/DaoState.java b/core/src/main/java/bisq/core/dao/state/model/DaoState.java index d9be03bb646..c18338b033a 100644 --- a/core/src/main/java/bisq/core/dao/state/model/DaoState.java +++ b/core/src/main/java/bisq/core/dao/state/model/DaoState.java @@ -36,6 +36,7 @@ import javax.inject.Inject; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -47,7 +48,6 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; - /** * Root class for mutable state of the DAO. * Holds both blockchain data as well as data derived from the governance process (voting). @@ -103,9 +103,8 @@ public static DaoState getClone(DaoState daoState) { private final List decryptedBallotsWithMeritsList; // Transient data used only as an index - must be kept in sync with the block list - @Getter @JsonExclude - private transient final Map txMap; // key is txId + private transient final Map txCache; // key is txId /////////////////////////////////////////////////////////////////////////////////////////// @@ -155,9 +154,9 @@ private DaoState(int chainHeight, this.evaluatedProposalList = evaluatedProposalList; this.decryptedBallotsWithMeritsList = decryptedBallotsWithMeritsList; - txMap = blocks.stream() + txCache = blocks.stream() .flatMap(block -> block.getTxs().stream()) - .collect(Collectors.toMap(Tx::getId, Function.identity(), (x, y) -> y, HashMap::new)); + .collect(Collectors.toMap(Tx::getId, Function.identity(), (x, y) -> x, HashMap::new)); } @Override @@ -237,6 +236,21 @@ public byte[] getSerializedStateForHashChain() { return getBsqStateBuilderExcludingBlocks().addBlocks(getBlocks().getLast().toProtoMessage()).build().toByteArray(); } + public void addToTxCache(Tx tx) { + // We shouldn't get duplicate txIds, but use putIfAbsent instead of put for consistency with the map merge + // function used in the constructor to initialise txCache (and to exactly match the pre-caching behaviour). + txCache.putIfAbsent(tx.getId(), tx); + } + + public void setTxCache(Map txCache) { + this.txCache.clear(); + this.txCache.putAll(txCache); + } + + public Map getTxCache() { + return Collections.unmodifiableMap(txCache); + } + @Override public String toString() { return "DaoState{" + @@ -250,7 +264,7 @@ public String toString() { ",\n paramChangeList=" + paramChangeList + ",\n evaluatedProposalList=" + evaluatedProposalList + ",\n decryptedBallotsWithMeritsList=" + decryptedBallotsWithMeritsList + - ",\n txMap=" + txMap + + ",\n txCache=" + txCache + "\n}"; } } diff --git a/core/src/main/java/bisq/core/dao/state/model/blockchain/Block.java b/core/src/main/java/bisq/core/dao/state/model/blockchain/Block.java index 2763b74c69f..e438197e932 100644 --- a/core/src/main/java/bisq/core/dao/state/model/blockchain/Block.java +++ b/core/src/main/java/bisq/core/dao/state/model/blockchain/Block.java @@ -24,11 +24,11 @@ import com.google.common.collect.ImmutableList; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import lombok.EqualsAndHashCode; -import lombok.Value; /** * The Block which gets persisted in the DaoState. During parsing transactions can be @@ -44,8 +44,8 @@ * */ @EqualsAndHashCode(callSuper = true) -@Value public final class Block extends BaseBlock implements PersistablePayload, ImmutableDaoStateModel { + // We do not expose txs with a Lombok getter. We cannot make it immutable as we add transactions during parsing. private final List txs; public Block(int height, long time, String hash, String previousBlockHash) { @@ -93,6 +93,17 @@ public static Block fromProto(protobuf.BaseBlock proto) { txs); } + public void addTx(Tx tx) { + txs.add(tx); + } + + // We want to guarantee that no client can modify the list. We use unmodifiableList and not ImmutableList as + // we want that clients reflect any change to the source list. Also ImmutableList is more expensive as it + // creates a copy. + public List getTxs() { + return Collections.unmodifiableList(txs); + } + @Override public String toString() { return "Block{" + diff --git a/desktop/src/main/java/bisq/desktop/main/dao/economy/transactions/BSQTransactionsView.java b/desktop/src/main/java/bisq/desktop/main/dao/economy/transactions/BSQTransactionsView.java index 95effae2877..36cd962320b 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/economy/transactions/BSQTransactionsView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/economy/transactions/BSQTransactionsView.java @@ -143,7 +143,7 @@ public void onParseBlockCompleteAfterBatchProcessing(Block block) { /////////////////////////////////////////////////////////////////////////////////////////// private void updateWithBsqBlockChainData() { - allTxTextField.setText(String.valueOf(daoFacade.getTxs().size())); + allTxTextField.setText(String.valueOf(daoFacade.getNumTxs())); utxoTextField.setText(String.valueOf(daoFacade.getUnspentTxOutputs().size())); compensationIssuanceTxTextField.setText(String.valueOf(daoFacade.getNumIssuanceTransactions(IssuanceType.COMPENSATION))); reimbursementIssuanceTxTextField.setText(String.valueOf(daoFacade.getNumIssuanceTransactions(IssuanceType.REIMBURSEMENT)));