ido: replace txchain model with unified state-chain indexing
Replace index_block/index_mempool with a single index_txs that takes an Option<blockhash> (Some for confirmed, None for mempool). Add delete_entries(blockhash) for reorg undo and to drop stale mempool state before applying confirmed blocks. Replace has_txchain_tx with has_indexed_tx; chain-follow now keys on ido_state.next_output_index instead of the removed tracker map. Remove the debug-only txchain RPC endpoints and the debug config gating. Keep txchain_head as a public RPC field. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e2f72e608e
commit
0831834d8f
5 changed files with 773 additions and 930 deletions
|
|
@ -88,6 +88,12 @@ impl BlockUndoer for StoreBlockUndoer {
|
||||||
{
|
{
|
||||||
was_indexed = true;
|
was_indexed = true;
|
||||||
}
|
}
|
||||||
|
if db::ido::delete_entries(&self.db.ido_w, Some(&blockheader.block_hash()))
|
||||||
|
.await?
|
||||||
|
> 0
|
||||||
|
{
|
||||||
|
was_indexed = true;
|
||||||
|
}
|
||||||
|
|
||||||
if was_indexed {
|
if was_indexed {
|
||||||
config_set(
|
config_set(
|
||||||
|
|
|
||||||
1581
src/db/ido/mod.rs
1581
src/db/ido/mod.rs
File diff suppressed because one or more lines are too long
34
src/index.rs
34
src/index.rs
|
|
@ -26,6 +26,7 @@ use crate::{
|
||||||
crc20::index_crc20,
|
crc20::index_crc20,
|
||||||
db::{
|
db::{
|
||||||
self,
|
self,
|
||||||
|
blob::ToBlob,
|
||||||
cauldron::{
|
cauldron::{
|
||||||
config::{config_get, config_set},
|
config::{config_get, config_set},
|
||||||
header::{db_get_header, store_headers},
|
header::{db_get_header, store_headers},
|
||||||
|
|
@ -189,17 +190,10 @@ pub async fn update_mempool(
|
||||||
// ido updates
|
// ido updates
|
||||||
let ido_electrum = electrum.clone();
|
let ido_electrum = electrum.clone();
|
||||||
|
|
||||||
// highest known block height; mempool tracker entries are stamped with its
|
// filter txs already recorded in an ido's state chain
|
||||||
// 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();
|
let mut ido_to_add = Vec::new();
|
||||||
for txid in ido_txs {
|
for txid in ido_txs {
|
||||||
if !db::ido::has_txchain_tx(&db.ido_w, &txid).await? {
|
if !db::ido::has_indexed_tx(&db.ido_w, &txid.to_blob()).await? {
|
||||||
ido_to_add.push(txid);
|
ido_to_add.push(txid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -220,9 +214,9 @@ pub async fn update_mempool(
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// an ido txchain can have several unconfirmed txs in flight; index parents first
|
// an ido chain can have several unconfirmed txs in flight; index parents first
|
||||||
let txs_to_add = ttor_sorted_kahn(txs_to_add);
|
let txs_to_add = ttor_sorted_kahn(txs_to_add);
|
||||||
db::ido::index_mempool(network, &db.ido_w, &txs_to_add, tip_height).await?;
|
db::ido::index_txs(network, &db.ido_w, &txs_to_add, None).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -305,6 +299,14 @@ pub async fn index_blocks(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Before applying confirmed blocks, drop any mempool-indexed IDO state
|
||||||
|
// (blockhash IS NULL). A mempool tx that never confirmed — e.g. one replaced
|
||||||
|
// by a different on-chain tx — must not leave a stale snapshot that wins
|
||||||
|
// MAX(seq) or resurfaces after the reorg undo above. Confirmed blocks
|
||||||
|
// re-create rows for the txs they contain, and the next mempool pass
|
||||||
|
// (update_mempool) rebuilds the rest.
|
||||||
|
db::ido::delete_entries(&db.ido_w, None).await?;
|
||||||
|
|
||||||
let db_cpy = db.clone();
|
let db_cpy = db.clone();
|
||||||
let chain_cpy = chain.clone();
|
let chain_cpy = chain.clone();
|
||||||
let client_cpy = client.clone();
|
let client_cpy = client.clone();
|
||||||
|
|
@ -486,15 +488,7 @@ pub async fn index_blocks(
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
db::ido::index_block(
|
db::ido::index_txs(network, &db.ido_w, &sorted_txs, Some(&blockhash)).await?;
|
||||||
network,
|
|
||||||
&db.ido_w,
|
|
||||||
&sorted_txs,
|
|
||||||
&blockhash,
|
|
||||||
mtp as i64,
|
|
||||||
block_height as i64,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let autheader_updates = if bcmr_enabled {
|
let autheader_updates = if bcmr_enabled {
|
||||||
let updates = index_bcmr(&db.bcmr_w, &blockhash, sorted_txs).await?;
|
let updates = index_bcmr(&db.bcmr_w, &blockhash, sorted_txs).await?;
|
||||||
|
|
|
||||||
13
src/main.rs
13
src/main.rs
|
|
@ -380,9 +380,6 @@ async fn launch() -> _ {
|
||||||
config
|
config
|
||||||
};
|
};
|
||||||
|
|
||||||
// Debug-only RPC endpoints (e.g. txchain inspection) are gated behind this flag.
|
|
||||||
let debug_endpoints = config.debug;
|
|
||||||
|
|
||||||
let (
|
let (
|
||||||
dbpool,
|
dbpool,
|
||||||
bcmrdownloader,
|
bcmrdownloader,
|
||||||
|
|
@ -570,20 +567,12 @@ async fn launch() -> _ {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always-on IDO endpoints.
|
// Always-on IDO endpoints.
|
||||||
let mut ido_routes = routes![
|
let ido_routes = routes![
|
||||||
rpc::ido::list_idos,
|
rpc::ido::list_idos,
|
||||||
rpc::ido::get_ido_by_id,
|
rpc::ido::get_ido_by_id,
|
||||||
rpc::ido::get_ido_by_offering_token,
|
rpc::ido::get_ido_by_offering_token,
|
||||||
rpc::ido::list_ido_entries,
|
rpc::ido::list_ido_entries,
|
||||||
rpc::ido::get_txchain_tx,
|
|
||||||
];
|
];
|
||||||
// Debug-only IDO endpoints, mounted only when `debug = true` in the config.
|
|
||||||
if debug_endpoints {
|
|
||||||
ido_routes.extend(routes![
|
|
||||||
rpc::ido::list_ido_txchain,
|
|
||||||
rpc::ido::list_txchain_tracker_map,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
rocket::build()
|
rocket::build()
|
||||||
.attach(signal::ShutdownFairing)
|
.attach(signal::ShutdownFairing)
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,6 @@ const MAX_OWNER_NFTHASH_FILTERS: usize = 20;
|
||||||
/// Length in bytes of an owner_nfthash.
|
/// Length in bytes of an owner_nfthash.
|
||||||
const OWNER_NFTHASH_LEN: usize = 32;
|
const OWNER_NFTHASH_LEN: usize = 32;
|
||||||
|
|
||||||
const DEBUG_DEFAULT_LIMIT: i64 = 100;
|
|
||||||
const DEBUG_MAX_LIMIT: i64 = 10_000;
|
|
||||||
|
|
||||||
/// List IDOs with optional filters and pagination.
|
/// List IDOs with optional filters and pagination.
|
||||||
///
|
///
|
||||||
/// Filters:
|
/// Filters:
|
||||||
|
|
@ -190,51 +187,6 @@ pub async fn list_ido_entries(
|
||||||
Ok(cached_ok(serde_json::to_value(items).unwrap(), CACHE_NONE))
|
Ok(cached_ok(serde_json::to_value(items).unwrap(), CACHE_NONE))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the raw transaction hex for a txchain item by its txid.
|
|
||||||
/// Returns `{"tx": "<hex>"}` or `{"tx": null}` if not found.
|
|
||||||
#[get("/txchain/<txid>/tx")]
|
|
||||||
pub async fn get_txchain_tx(txid: &str, db: &State<DB>) -> CachedApiResult<Value> {
|
|
||||||
let txid_blob = display_hex_to_blob::<Txid>(txid).map_err(|e| {
|
|
||||||
bad_request(
|
|
||||||
ApiErrorCode::InvalidParameters,
|
|
||||||
&format!("Invalid txid hex: {e}"),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let tx_hex = crate::db::ido::get_txchain_tx_hex(&db.ido_r, &txid_blob)
|
|
||||||
.await
|
|
||||||
.map_err(db_error)?;
|
|
||||||
|
|
||||||
Ok(cached_ok(serde_json::json!({ "tx": tx_hex }), CACHE_NONE))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// [Debug] List the txchain for an IDO, sorted oldest-first (head is last).
|
|
||||||
///
|
|
||||||
/// - `id`: the IDO's public id (preinit txid, 64-char hex)
|
|
||||||
///
|
|
||||||
/// Pagination: `offset` (default 0), `limit` (default 100, max 10000)
|
|
||||||
#[get("/<id>/txchain?<offset>&<limit>")]
|
|
||||||
pub async fn list_ido_txchain(
|
|
||||||
id: &str,
|
|
||||||
offset: Option<i64>,
|
|
||||||
limit: Option<i64>,
|
|
||||||
db: &State<DB>,
|
|
||||||
) -> CachedApiResult<Value> {
|
|
||||||
let offset = offset.unwrap_or(0).max(0);
|
|
||||||
let limit = limit
|
|
||||||
.unwrap_or(DEBUG_DEFAULT_LIMIT)
|
|
||||||
.clamp(1, DEBUG_MAX_LIMIT);
|
|
||||||
|
|
||||||
let (internal_id, preinit_txid_hex) = resolve_ido(id, &db.ido_r).await?;
|
|
||||||
|
|
||||||
let items =
|
|
||||||
crate::db::ido::list_ido_txchain(&db.ido_r, internal_id, &preinit_txid_hex, offset, limit)
|
|
||||||
.await
|
|
||||||
.map_err(db_error)?;
|
|
||||||
|
|
||||||
Ok(cached_ok(serde_json::to_value(items).unwrap(), CACHE_NONE))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn resolve_ido(
|
async fn resolve_ido(
|
||||||
id: &str,
|
id: &str,
|
||||||
pool: &sqlx::SqlitePool,
|
pool: &sqlx::SqlitePool,
|
||||||
|
|
@ -260,24 +212,3 @@ async fn resolve_ido(
|
||||||
|
|
||||||
Ok((internal_id, preinit_txid_hex))
|
Ok((internal_id, preinit_txid_hex))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [Debug] List all entries in the txchain tracker map.
|
|
||||||
///
|
|
||||||
/// Pagination: `offset` (default 0), `limit` (default 100, max 10000)
|
|
||||||
#[get("/txchain-tracker?<offset>&<limit>")]
|
|
||||||
pub async fn list_txchain_tracker_map(
|
|
||||||
offset: Option<i64>,
|
|
||||||
limit: Option<i64>,
|
|
||||||
db: &State<DB>,
|
|
||||||
) -> CachedApiResult<Value> {
|
|
||||||
let offset = offset.unwrap_or(0).max(0);
|
|
||||||
let limit = limit
|
|
||||||
.unwrap_or(DEBUG_DEFAULT_LIMIT)
|
|
||||||
.clamp(1, DEBUG_MAX_LIMIT);
|
|
||||||
|
|
||||||
let items = crate::db::ido::list_txchain_tracker_map(&db.ido_r, offset, limit)
|
|
||||||
.await
|
|
||||||
.map_err(db_error)?;
|
|
||||||
|
|
||||||
Ok(cached_ok(serde_json::to_value(items).unwrap(), CACHE_NONE))
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue