From b1df63c6c4fc55314d96f1756039448cdfb367b3 Mon Sep 17 00:00:00 2001 From: Hossein Zoda Date: Thu, 4 Jun 2026 16:58:13 +0000 Subject: [PATCH] ido: garbage collect tracker entries of mempool txs that never confirm Stamp mempool tracker entries with the negative of the highest known block height instead of a fixed -1 sentinel, and trim on abs(height) so entries whose tx is invalidated before confirming (e.g. by a double spend) age out after TRACKER_MAX_BLOCK_DEPTH like confirmed ones. The height is still replaced with the real one once the tx confirms. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/db/ido/mod.rs | 20 +++++++++++++------- src/index.rs | 9 ++++++++- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/db/ido/mod.rs b/src/db/ido/mod.rs index 6fae9d1..fccf8e1 100644 --- a/src/db/ido/mod.rs +++ b/src/db/ido/mod.rs @@ -903,8 +903,11 @@ async fn txchain_trim_tracker( pool: &SqlitePool, current_block_height: i64, ) -> Result<()> { + // A negative height marks a mempool entry stamped with the negative of the + // highest known block height when it was indexed (see index_mempool), so + // abs(height) is the entry's reference height in both cases. sqlx::query( - "DELETE FROM ido_txchain_tracker_map AS t0 WHERE t0.height < ? AND t0.height >= 0 AND NOT EXISTS (SELECT 1 FROM ido t1 WHERE t1.txchain_head = t0.txchain_id)", + "DELETE FROM ido_txchain_tracker_map AS t0 WHERE abs(t0.height) < ? AND NOT EXISTS (SELECT 1 FROM ido t1 WHERE t1.txchain_head = t0.txchain_id)", ) .bind(current_block_height - TRACKER_MAX_BLOCK_DEPTH) .execute(pool) @@ -2288,17 +2291,20 @@ pub async fn index_block( Ok(()) } -// Sentinel block height for txs indexed from the mempool. txchain_trim_tracker -// never trims tracker entries with a negative height; the height is replaced -// with the real one once the tx confirms in a block. -const MEMPOOL_BLOCK_HEIGHT: i64 = -1; - pub async fn index_mempool( network: Option, pool: &SqlitePool, sorted_txs: &[Transaction], + highest_known_block_height: u64, ) -> Result<()> { - index_txs(network, pool, sorted_txs, MEMPOOL_BLOCK_HEIGHT) + // Txs indexed from the mempool are stamped with the negative of the + // highest known block height, so txchain_trim_tracker can garbage collect + // entries that never confirm (e.g. invalidated by a double spend) once + // they pass TRACKER_MAX_BLOCK_DEPTH. The height is replaced with the real + // (positive) one once the tx confirms in a block. max(1) avoids storing 0, + // which would be indistinguishable from a confirmed height. + let mempool_height = -(highest_known_block_height.max(1) as i64); + index_txs(network, pool, sorted_txs, mempool_height) .await } diff --git a/src/index.rs b/src/index.rs index 20d6a54..5019be9 100644 --- a/src/index.rs +++ b/src/index.rs @@ -193,6 +193,13 @@ pub async fn update_mempool( // ido updates let ido_electrum = electrum.clone(); + // highest known block height; mempool tracker entries are stamped with its + // negative so ido's txchain_trim_tracker can age them out (see index_mempool) + let tip_electrum = electrum.clone(); + let (_, tip_height) = + tokio::task::spawn_blocking(move || electrum_get_tip(&tip_electrum.lock().unwrap())) + .await??; + // filter txs already in an ido txchain let mut ido_to_add = Vec::new(); for txid in ido_txs { @@ -219,7 +226,7 @@ pub async fn update_mempool( // an ido txchain can have several unconfirmed txs in flight; index parents first let txs_to_add = ttor_sorted_kahn(txs_to_add); - db::ido::index_mempool(network, &db.ido_w, &txs_to_add).await?; + db::ido::index_mempool(network, &db.ido_w, &txs_to_add, tip_height).await?; Ok(()) }