Skip to content

Commit

Permalink
Add transient tx map to DaoState to speed up getTx queries
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
stejbac committed Dec 9, 2019
1 parent 30f9664 commit ce311cd
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -31,7 +30,6 @@
import javax.inject.Inject;

import java.util.LinkedList;
import java.util.List;

import lombok.extern.slf4j.Slf4j;

Expand Down Expand Up @@ -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<Tx> txList = block.getTxs();

rawBlock.getRawTxs().forEach(rawTx ->
txParser.findTx(rawTx,
genesisTxId,
genesisBlockHeight,
genesisTotalSupply)
.ifPresent(txList::add));
.ifPresent(daoStateService::onNewTxForLastBlock));

log.info("Parsing {} transactions at block height {} took {} ms", rawBlock.getRawTxs().size(),
blockHeight, System.currentTimeMillis() - startTs);
Expand Down
24 changes: 18 additions & 6 deletions core/src/main/java/bisq/core/dao/state/DaoStateService.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@
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;

Expand All @@ -47,6 +47,7 @@
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;
Expand Down Expand Up @@ -115,6 +116,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());

Expand Down Expand Up @@ -226,7 +230,15 @@ 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(Tx tx) {
getLastBlock().ifPresent(block -> {
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());
Expand Down Expand Up @@ -348,16 +360,16 @@ public Stream<Tx> getTxStream() {
.flatMap(block -> block.getTxs().stream());
}

public TreeMap<String, Tx> getTxMap() {
return new TreeMap<>(getTxStream().collect(Collectors.toMap(Tx::getId, tx -> tx)));
public Map<String, Tx> getTxMap() {
return daoState.getTxMap();
}

public Set<Tx> getTxs() {
return getTxStream().collect(Collectors.toSet());
return new HashSet<>(getTxMap().values());
}

public Optional<Tx> getTx(String txId) {
return getTxStream().filter(tx -> tx.getId().equals(txId)).findAny();
return Optional.ofNullable(getTxMap().get(txId));
}

public List<Tx> getInvalidTxs() {
Expand Down
25 changes: 22 additions & 3 deletions core/src/main/java/bisq/core/dao/state/model/DaoState.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -98,6 +102,11 @@ public static DaoState getClone(DaoState daoState) {
@Getter
private final List<DecryptedBallotsWithMerits> decryptedBallotsWithMeritsList;

// Transient data used only as an index - must be kept in sync with the block list
@Getter
@JsonExclude
private transient final Map<String, Tx> txMap; // key is txId


///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
Expand All @@ -114,7 +123,8 @@ public DaoState() {
new TreeMap<>(),
new ArrayList<>(),
new ArrayList<>(),
new ArrayList<>()
new ArrayList<>(),
new HashMap<>()
);
}

Expand All @@ -132,7 +142,8 @@ private DaoState(int chainHeight,
TreeMap<String, Issuance> issuanceMap,
List<ParamChange> paramChangeList,
List<EvaluatedProposal> evaluatedProposalList,
List<DecryptedBallotsWithMerits> decryptedBallotsWithMeritsList) {
List<DecryptedBallotsWithMerits> decryptedBallotsWithMeritsList,
Map<String, Tx> txMap) {
this.chainHeight = chainHeight;
this.blocks = blocks;
this.cycles = cycles;
Expand All @@ -145,6 +156,8 @@ private DaoState(int chainHeight,
this.paramChangeList = paramChangeList;
this.evaluatedProposalList = evaluatedProposalList;
this.decryptedBallotsWithMeritsList = decryptedBallotsWithMeritsList;

this.txMap = txMap;
}

@Override
Expand Down Expand Up @@ -176,6 +189,10 @@ private protobuf.DaoState.Builder getBsqStateBuilderExcludingBlocks() {
}

public static DaoState fromProto(protobuf.DaoState proto) {
Map<String, Tx> txMap = proto.getBlocksList().stream()
.map(Block::fromProto)
.flatMap(block -> block.getTxs().stream())
.collect(Collectors.toMap(Tx::getId, Function.identity(), (x, y) -> y, HashMap::new));
LinkedList<Block> blocks = proto.getBlocksList().stream()
.map(Block::fromProto)
.collect(Collectors.toCollection(LinkedList::new));
Expand Down Expand Up @@ -203,7 +220,8 @@ public static DaoState fromProto(protobuf.DaoState proto) {
issuanceMap,
paramChangeList,
evaluatedProposalList,
decryptedBallotsWithMeritsList);
decryptedBallotsWithMeritsList,
txMap);
}


Expand Down Expand Up @@ -237,6 +255,7 @@ public String toString() {
",\n paramChangeList=" + paramChangeList +
",\n evaluatedProposalList=" + evaluatedProposalList +
",\n decryptedBallotsWithMeritsList=" + decryptedBallotsWithMeritsList +
",\n txMap=" + txMap +
"\n}";
}
}

0 comments on commit ce311cd

Please sign in to comment.