Fix cubic time bug in BondedRolesRepository.update()

Alleviate a cubic time bug during the update of the bonded roles
repository, reducing it to quadratic running time. On Mainnet, this
gives a roughly ten-fold speedup and should allow better scaling in the
event that many new bonded roles are added.

Replace calls to 'BondedRolesRepository.findBondedAssetByHash' with a
lookup into a lazily initialised map of bonded assets (Roles) by hash
(reset at the start of each call to 'BondRepository.update()' to prevent
stale caching). This avoids rescanning the roles list for every pair of
roles and lockup tx outputs, thus reducing the number of steps (to
highest order) from:

  #roles * #roles * #lockup-tx-outputs

to:

  #roles * #lockup-tx-outputs

(The logs show 2 or 3 calls to 'BondedRepository.update()' every time a
new block arrives, and while this was only taking around a second or so
on Mainnet, it could potentially grow to something problematic with
cubic scaling in the number of bonded roles.)
This commit is contained in:
Steven Barclay 2024-04-19 23:49:48 +02:00
parent 0a1df44daf
commit 3a97953152
No known key found for this signature in database
GPG key ID: 9FED6BF1176D500B
2 changed files with 15 additions and 10 deletions

View file

@ -28,6 +28,8 @@ import bisq.core.dao.state.model.blockchain.Tx;
import bisq.core.dao.state.model.blockchain.TxOutput;
import bisq.core.dao.state.model.blockchain.TxType;
import com.google.protobuf.ByteString;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionInput;
@ -141,8 +143,9 @@ public abstract class BondRepository<B extends Bond<T>, T extends BondedAsset> i
protected final DaoStateService daoStateService;
protected final BsqWalletService bsqWalletService;
// This map is just for convenience. The data which are used to fill the map are stored in the DaoState (role, txs).
// These maps are just for convenience. The data which are used to fill the maps are stored in the DaoState (role, txs).
protected final Map<String, B> bondByUidMap = new HashMap<>();
private Map<ByteString, T> bondedAssetByHashMap;
@Getter
protected final ObservableList<B> bonds = FXCollections.observableArrayList();
@ -212,9 +215,16 @@ public abstract class BondRepository<B extends Bond<T>, T extends BondedAsset> i
protected abstract Stream<T> getBondedAssetStream();
protected Map<ByteString, T> getBondedAssetByHashMap() {
return bondedAssetByHashMap != null ? bondedAssetByHashMap
: (bondedAssetByHashMap = getBondedAssetStream()
.collect(Collectors.toMap(a -> ByteString.copyFrom(a.getHash()), a -> a, (a, b) -> a)));
}
protected void update() {
long ts = System.currentTimeMillis();
log.debug("update");
bondedAssetByHashMap = null;
getBondedAssetStream().forEach(bondedAsset -> {
String uid = bondedAsset.getUid();
B bond = bondByUidMap.computeIfAbsent(uid, u -> createBond(bondedAsset));

View file

@ -27,12 +27,13 @@ import bisq.core.dao.state.model.governance.Proposal;
import bisq.core.dao.state.model.governance.Role;
import bisq.core.dao.state.model.governance.RoleProposal;
import com.google.protobuf.ByteString;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Transaction;
import javax.inject.Inject;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@ -107,18 +108,12 @@ public class BondedRolesRepository extends BondRepository<BondedRole, Role> {
// We used the hash of the bonded bondedAsset object as our hash in OpReturn of the lock up tx to have a
// unique binding of the tx to the data object.
byte[] hash = BondConsensus.getHashFromOpReturnData(opReturnData);
Optional<Role> candidate = findBondedAssetByHash(hash);
if (candidate.isPresent() && bondedAsset.equals(candidate.get()))
Role candidateOrNull = getBondedAssetByHashMap().get(ByteString.copyFrom(hash));
if (bondedAsset.equals(candidateOrNull))
applyBondState(daoStateService, bond, lockupTx, lockupTxOutput);
});
}
private Optional<Role> findBondedAssetByHash(byte[] hash) {
return getBondedAssetStream()
.filter(bondedAsset -> Arrays.equals(bondedAsset.getHash(), hash))
.findAny();
}
private Stream<RoleProposal> getBondedRoleProposalStream() {
return daoStateService.getEvaluatedProposalList().stream()
.filter(evaluatedProposal -> evaluatedProposal.getProposal() instanceof RoleProposal)