bitcoindevkit / bitcoindevkit/bdk

bdk_bitcoind_rpc: Unbounded spin loop, memory growth, and missing txid validation in Emitter::mempool_at

Open
#2,282 0 comments 0 reactions 1 assignee Claimed by @tvpeter View on GitHub
bug
Dominant language
Rust
Stars
1.1k
Forks
483
Avg merge
20d 3h
Merged PRs (30d)
3

Description

**Describe the bug**

In `Emitter::mempool_at`, there are three related issues regarding RPC polling behavior, memory usage, and transaction ID validation:
- Unbounded tip-consistency spin loop: The loop in `mempool_at` retries `get_block_count` and `get_block_hash` until two consecutive calls agree on the tip. If an RPC endpoint flaps or alternates responses between calls, this loop spins indefinitely without an iteration limit, timeout, or backoff causing high CPU usage and rapid RPC request flooding.
- Unbounded `mempool_snapshot` memory growth: Every txid returned by `getrawmempool` that is not already cached is fetched in full via `get_raw_transaction` and inserted into `mempool_snapshot`. There is no limit on the number of transactions fetched or tracked per poll, leading to uncontrolled memory growth if the node presents an arbitrary number of transactions.
- Missing txid validation on fetch: When inserting fetched transactions into `mempool_snapshot` via `self.mempool_snapshot.insert(txid, tx.clone())`, the map key is set to the requested txid rather than the computed ID of the returned transaction (tx.compute_txid()). If the RPC server returns a transaction body whose actual txid does not match the requested txid, the crate accepts and caches the mismatched transaction without error.

**To Reproduce**

- Set up Emitter::mempool connected to a Bitcoin RPC mock server.
- For the spin loop: Configure the mock RPC server to alternate tip hash or height responses on consecutive calls during mempool_at. Observe that the client spins indefinitely in the retry loop.
- For unverified txid insertion: Configure the mock RPC so getrawmempool returns txid X. When getrawtransaction is called for X, have the mock RPC return the raw transaction bytes for a different transaction Y.
- Run Emitter::mempool. Observe that the crate emits transaction Y without throwing a txid mismatch error, storing the mis-keyed entry in mempool_snapshot.

**Expected behavior**

- The tip-consistency loop should be bounded with a maximum retry cap or backoff strategy, returning an error if consistency cannot be established within N retries.
- `mempool_snapshot` tracking should impose limits on fetched transactions per poll to guard against unbounded memory consumption.
- Transactions fetched via `get_raw_transaction` should be verified (tx.compute_txid() == txid) before being inserted into mempool_snapshot

**Build environment**
- BDK tag/commit: 337e9d68
- Rust/Cargo version: 1.75.0 or higher

**Which backend(s) are relevant (if any)?**
- [ ] Electrum
- [ ] Esplora
- [x] Bitcoin Core RPC
- [ ] None / not backend-related (e.g. `bdk_chain`, `bdk_core`)
- [ ] Other (please specify): `____`

**Is this blocking production use?**
- [ ] Yes
- [x] No

**Additional context**

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.