fix concurrent modification of descriptor maps in bitcoind client
What changed, and why it matters
This commit fixes a thread-safety bug in Sparrow Wallet's connection to a local Bitcoin node. Two internal data maps that track wallet descriptors could be modified by multiple threads at the same time, which can cause crashes, corrupted data, or unexpected behavior. The fix wraps one map in a synchronized wrapper and replaces the other with a thread-safe concurrent map.
Review the remaining descriptor-related collections and all read/write sites for consistent synchronization; add regression tests for concurrent descriptor updates; consider whether transient corruption of these maps could have produced incorrect wallet state or balance information.
Security signals we found
ConcurrentModificationException / race condition in descriptor tracking maps
Use of unsynchronized HashMap in multi-threaded bitcoind client context
Partial fix: only two of several maps changed; other maps were already synchronized
No explicit security framing in commit message or diff
Evidence from the diff
In BitcoindClient.java, descriptorBirthDates (HashMap
Changed components
src/main/java/com/sparrowwallet/sparrow/net/cormorant/bitcoind/BitcoindClient.javadescriptorBirthDates mapdescriptorUsedIndexes mapInspect captured patch +2 / −2
diff --git a/src/main/java/com/sparrowwallet/sparrow/net/cormorant/bitcoind/BitcoindClient.java b/src/main/java/com/sparrowwallet/sparrow/net/cormorant/bitcoind/BitcoindClient.java
index 5fd4992..0808695 100644
--- a/src/main/java/com/sparrowwallet/sparrow/net/cormorant/bitcoind/BitcoindClient.java
+++ b/src/main/java/com/sparrowwallet/sparrow/net/cormorant/bitcoind/BitcoindClient.java
@@ -66,8 +66,8 @@ public class BitcoindClient {
private final Map<String, Lock> descriptorLocks = Collections.synchronizedMap(new HashMap<>());
private final Map<String, ScanDate> importedDescriptors = Collections.synchronizedMap(new HashMap<>());
- private final Map<String, Date> descriptorBirthDates = new HashMap<>();
- private final Map<String, Integer> descriptorUsedIndexes = new HashMap<>();
+ private final Map<String, Date> descriptorBirthDates = Collections.synchronizedMap(new HashMap<>());
+ private final Map<String, Integer> descriptorUsedIndexes = new ConcurrentHashMap<>();
private boolean initialized;
private boolean stopped;
Why this scored 40/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.