Skip to content

add_by_txids not evicting replaced txs in submit_package #236

Description

@Randy808

This issue is to track the problem described by @PeteClubSeven here:

Hi guys, thanks for adding broadcast_package support. I tried implementing electrum support in bark, typically we use core RPC or the REST API in this version of electrs. Unfortunately it looks like you've broken RBF'ing a TRUC package. It worked for a long time via the REST API but I updated to master so I can enable electrum support but both protocols are broken. I had Claude investigate it and it thinks the issue started when #227 was merged. Here is the summary it wrote up for additional context:

Affected: new-index branch since 7fed8f41 ("feat(electrum): add blockchain.transaction.broadcast_package", merged 2026-07-02 via #227). Reproduced at HEAD 4b348320. Both interfaces are affected — REST POST /txs/package and electrum blockchain.transaction.broadcast_package — since both go through Query::submit_package.

Symptom: the main thread panics and the daemon dies:

thread 'main' panicked at src/new_index/mempool.rs:519:17:
missing mempool edge for outpoint <O> (tx <C>)

Mechanism: Query::submit_package now calls Mempool::add_by_txids for the accepted txids immediately after bitcoind's submitpackage (src/new_index/query.rs, ~line 96–114). This bypasses the invariant the poll-based Mempool::update maintains — evicted txs are always removed before new ones are added, so two conflicting spenders of one outpoint never coexist in the view:

  1. Tx A spending outpoint O is indexed via the normal poll.
  2. A package containing tx C that RBF-replaces A (also spends O) is submitted; bitcoind evicts A, accepts C.
  3. add_by_txids inserts C while A is still indexed → edges[O] is overwritten with C's entry.
  4. Next poll evicts Aremove(A) deletes edges[O] (actually C's entry).
  5. When C is later confirmed or evicted, remove(C) finds no edge for O → the assert! at mempool.rs:519 panics.

Repro: deterministic within seconds on regtest: index a spend of some outpoint, then submit a package containing a replacement of that spend between two mempool polls, then let both removals happen (mine a block). We hit it from wallet integration tests doing anchor/CPFP package broadcasts after an RBF bump.

Suggested direction: before add_by_txids, evict from the local view any indexed tx (plus descendants) that conflicts with an incoming package tx's prevouts — mirroring what the poll's remove-before-add ordering guarantees. For reference, mempool/electrs is unaffected because its submit_package proxies to bitcoind without touching the local view.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions