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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions