Problem
dash-spv accepts unconfirmed transactions from the network without any cryptographic validation of their contents. A tx message from a single peer is enough to insert the transaction into the local mempool as long as it matches the bloom filter.
Entry point: MempoolManager::handle_tx — the transaction is deduped by txid, handed to the wallet for relevance checking only, and stored if relevant. There is:
- No script/signature verification (and SPV cannot do this without prevouts).
- No multi-peer corroboration — one peer is enough.
- No per-peer rate limit on announcements.
- No integration between
MempoolManager and the existing reputation/ban-score system in dash-spv/src/network/reputation.rs.
Threat
A single byzantine peer can flood the client with fabricated transactions:
- The peer learns the wallet's bloom filter (BIP37 leaks it on
filterload).
- It grinds outputs whose hashes match the filter (the default false-positive rate is 0.0005, but with knowledge of the filter the peer can simply construct matching outputs directly).
- It pushes transactions until the global mempool cap (
max_transactions, default ~500) is hit.
Consequences:
- Legitimate incoming transactions are dropped once the cap is hit.
- The wallet UI may surface bogus "received" transactions to the user, who could act on them (ship goods, etc.) before they fail to confirm.
- Memory pressure and wasted bandwidth.
Proposed Fix
Require a transaction to be relayed by at least 2 distinct peers before it is accepted into the local mempool / surfaced to the wallet.
Sketch:
- Track an
announcing_peers: HashSet<PeerId> per pending txid.
- When a
tx (or inv followed by getdata → tx) arrives, record the peer in the set but do not insert into self.transactions yet.
- Only call
wallet.process_mempool_transaction(...) and store the tx once announcing_peers.len() >= MIN_RELAYING_PEERS (configurable, default 2).
- Bound the size of the pending-announcement table independently from the main mempool to avoid a different flooding vector.
- Drop pending entries after a timeout if they never reach the threshold.
This raises the cost of the attack from one rogue peer to a Sybil cluster across distinct peer connections, which is dramatically harder under typical peer-selection assumptions.
Notes / Considerations
- The threshold should be configurable; some users on slow / few-peer connections may want
1, others paranoid users may want 3+.
- Should not gate ISLOCK-bearing transactions: an ISLOCK-verified tx is already cryptographically anchored to the masternode quorum and can be accepted unilaterally.
- Should not affect transactions found inside merkle blocks (those are anchored to header chain).
- Worth pairing with wiring
MempoolManager into the existing network/reputation.rs ban-score system so peers whose announced txs never confirm or never get corroborated lose reputation over time. (Tracked separately.)
References
- Current accept path:
dash-spv/src/sync/mempool/manager.rs:306 (handle_tx)
- Mempool cap:
MempoolManager::max_transactions
- Existing (unused) reputation system:
dash-spv/src/network/reputation.rs
- ISLOCK verification (already implemented, used for separate flag, not for acceptance gating):
dash-spv/src/sync/instantsend/manager.rs:150
Problem
dash-spvaccepts unconfirmed transactions from the network without any cryptographic validation of their contents. Atxmessage from a single peer is enough to insert the transaction into the local mempool as long as it matches the bloom filter.Entry point:
MempoolManager::handle_tx— the transaction is deduped by txid, handed to the wallet for relevance checking only, and stored if relevant. There is:MempoolManagerand the existing reputation/ban-score system indash-spv/src/network/reputation.rs.Threat
A single byzantine peer can flood the client with fabricated transactions:
filterload).max_transactions, default ~500) is hit.Consequences:
Proposed Fix
Require a transaction to be relayed by at least 2 distinct peers before it is accepted into the local mempool / surfaced to the wallet.
Sketch:
announcing_peers: HashSet<PeerId>per pending txid.tx(orinvfollowed bygetdata→tx) arrives, record the peer in the set but do not insert intoself.transactionsyet.wallet.process_mempool_transaction(...)and store the tx onceannouncing_peers.len() >= MIN_RELAYING_PEERS(configurable, default2).This raises the cost of the attack from one rogue peer to a Sybil cluster across distinct peer connections, which is dramatically harder under typical peer-selection assumptions.
Notes / Considerations
1, others paranoid users may want3+.MempoolManagerinto the existingnetwork/reputation.rsban-score system so peers whose announced txs never confirm or never get corroborated lose reputation over time. (Tracked separately.)References
dash-spv/src/sync/mempool/manager.rs:306(handle_tx)MempoolManager::max_transactionsdash-spv/src/network/reputation.rsdash-spv/src/sync/instantsend/manager.rs:150