sparrow-0001: WalletUtxosEntry.updateUtxos() ArrayList.removeAll O(N^2) UTXO diff uses List.removeAll which is O(current * previous). Fix: Set-based diff via Sets.difference (same pattern already used in WalletTransactionsEntry). MEDIUM, 250x at N=1000 UTXOs. sparrow-0002: UtxoEntry.recountMixesDone stream().anyMatch O(M*I*T) Whirlpool mix chain walk streams all wallet TXOs per input per mix. Fix: HashMap<Sha256Hash, Set<Long>> index for O(1) lookup. MEDIUM-HIGH, 50x at M=200 mixes, T=1000 TXOs. MOAD-0002 (Intertangle): EventManager is a thin Guava EventBus singleton, not a god object. Wallet/network/UI coupling is event-driven, acceptable. MOAD-0003 (Leaked Context): CLEAN, no ThreadLocal usage found. MOAD-0004 (Logged Secret): CLEAN, no private keys/mnemonics/passphrases logged. SecureChannelSession has commented-out secret logging. MOAD-0005 (Thundering Herd): CLEAN, no unsynchronized cache patterns. 4/4 unit tests PASS. UNDF-2026-000000931 through UNDF-2026-000000932.
32 lines
2.2 KiB
Diff
32 lines
2.2 KiB
Diff
# UNDF: UNDF-2026-000000932
|
|
--- a/src/main/java/com/sparrowwallet/sparrow/wallet/UtxoEntry.java
|
|
+++ b/src/main/java/com/sparrowwallet/sparrow/wallet/UtxoEntry.java
|
|
@@ -149,16 +149,23 @@ public class UtxoEntry extends HashIndexEntry {
|
|
|
|
public int recountMixesDone(Wallet postmixWallet, BlockTransactionHashIndex postmixUtxo) {
|
|
int mixesDone = 0;
|
|
- Set<BlockTransactionHashIndex> walletTxos = postmixWallet.getWalletTxos().entrySet().stream()
|
|
- .filter(entry -> entry.getValue().getKeyPurpose() == KeyPurpose.RECEIVE).map(Map.Entry::getKey).collect(Collectors.toSet());
|
|
+ // Build a lookup index: txHash -> set of output indices for O(1) membership test
|
|
+ // instead of streaming the entire walletTxos set per input per mix iteration
|
|
+ Map<Sha256Hash, Set<Long>> txoIndex = new HashMap<>();
|
|
+ for(Map.Entry<BlockTransactionHashIndex, WalletNode> entry : postmixWallet.getWalletTxos().entrySet()) {
|
|
+ if(entry.getValue().getKeyPurpose() == KeyPurpose.RECEIVE) {
|
|
+ BlockTransactionHashIndex txo = entry.getKey();
|
|
+ txoIndex.computeIfAbsent(txo.getHash(), k -> new HashSet<>()).add(txo.getIndex());
|
|
+ }
|
|
+ }
|
|
BlockTransaction blkTx = postmixWallet.getTransactions().get(postmixUtxo.getHash());
|
|
|
|
while(blkTx != null) {
|
|
mixesDone++;
|
|
List<TransactionInput> inputs = blkTx.getTransaction().getInputs();
|
|
blkTx = null;
|
|
for(TransactionInput txInput : inputs) {
|
|
BlockTransaction inputTx = postmixWallet.getTransactions().get(txInput.getOutpoint().getHash());
|
|
- if(inputTx != null && walletTxos.stream().anyMatch(txo -> txo.getHash().equals(inputTx.getHash()) && txo.getIndex() == txInput.getOutpoint().getIndex()) && inputTx.getTransaction() != null) {
|
|
+ Set<Long> indices = inputTx == null ? null : txoIndex.get(inputTx.getHash());
|
|
+ if(inputTx != null && indices != null && indices.contains(txInput.getOutpoint().getIndex()) && inputTx.getTransaction() != null) {
|
|
blkTx = inputTx;
|
|
}
|
|
}
|