ido: only update block_height when tx is already in the txchain
on_add_ido_tx rebuilt the whole chain whenever the input tx was not the txchain head. A mempool tx confirming after the chain advanced past it would needlessly trigger a full rebuild. Reconstruct the current chain (txchain_head .. txchain_entrypoint) and, if the tx is already part of it, only update its block height. Extract the shared chain-walking logic into reconstruct_txchain, reused by both the membership check and the rebuild branch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9b0a54c926
commit
62b8fb6436
1 changed files with 43 additions and 17 deletions
|
|
@ -12,7 +12,7 @@ use serde_with::{serde_as, DisplayFromStr};
|
||||||
use bitcoincash::blockdata::transaction::Transaction;
|
use bitcoincash::blockdata::transaction::Transaction;
|
||||||
use bitcoincash::{Txid, BlockHash, TokenID, Network};
|
use bitcoincash::{Txid, BlockHash, TokenID, Network};
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
use std::collections::{HashMap};
|
use std::collections::{HashMap, HashSet};
|
||||||
use sqlx::{Row, SqlitePool};
|
use sqlx::{Row, SqlitePool};
|
||||||
use crate::db::blob::{ToBlob, blob_to_display_hex};
|
use crate::db::blob::{ToBlob, blob_to_display_hex};
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
|
|
@ -2295,6 +2295,28 @@ async fn update_ido_txchain_tracker_block_height(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reconstruct the ordered txchain (entrypoint .. start) by following prev_id
|
||||||
|
/// back from `start_id` through `items_by_id`. The returned vec is ordered from
|
||||||
|
/// the oldest item to `start_id`; it stops at the entrypoint (the item with no
|
||||||
|
/// prev_id) and is guarded against a malformed prev_id cycle. The chain is only
|
||||||
|
/// well-formed if its first item is the ido's entrypoint.
|
||||||
|
fn reconstruct_txchain<'a>(
|
||||||
|
items_by_id: &HashMap<i64, &'a IdoTxChainDBRecord>,
|
||||||
|
start_id: Option<i64>,
|
||||||
|
) -> Vec<&'a IdoTxChainDBRecord> {
|
||||||
|
let mut chain: Vec<&IdoTxChainDBRecord> = Vec::new();
|
||||||
|
let mut visited: HashSet<i64> = HashSet::new();
|
||||||
|
let mut cursor = start_id.and_then(|id| items_by_id.get(&id).copied());
|
||||||
|
while let Some(item) = cursor {
|
||||||
|
if !visited.insert(item.id) {
|
||||||
|
break; // guard against a malformed prev_id cycle
|
||||||
|
}
|
||||||
|
chain.insert(0, item);
|
||||||
|
cursor = item.prev_id.and_then(|prev_id| items_by_id.get(&prev_id).copied());
|
||||||
|
}
|
||||||
|
chain
|
||||||
|
}
|
||||||
|
|
||||||
async fn on_add_ido_tx(
|
async fn on_add_ido_tx(
|
||||||
network: Option<Network>,
|
network: Option<Network>,
|
||||||
pool: &SqlitePool,
|
pool: &SqlitePool,
|
||||||
|
|
@ -2305,32 +2327,36 @@ async fn on_add_ido_tx(
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
debug!("IDO on_add_ido_tx: {}", blob_to_display_hex::<Txid>(&tx.compute_txid().to_blob())?);
|
debug!("IDO on_add_ido_tx: {}", blob_to_display_hex::<Txid>(&tx.compute_txid().to_blob())?);
|
||||||
let mut dbtx = pool.begin().await?;
|
let mut dbtx = pool.begin().await?;
|
||||||
let current_item = get_txchain_item_by_txid(pool, &tx.compute_txid())
|
let items = get_ido_txchain_list(pool, ido.internal_id)
|
||||||
.await?;
|
.await?;
|
||||||
if current_item.is_some() && Some(current_item.unwrap().id) == ido.txchain_head {
|
// Reconstruct this ido's current txchain — exactly txchain_head back to
|
||||||
// already added, only update block height
|
// txchain_entrypoint, following prev_id — and check whether this tx is part
|
||||||
|
// of it. A mempool tx that confirms after the chain has advanced past it is
|
||||||
|
// still part of the chain even though it is no longer the head; it only
|
||||||
|
// needs its block height updated, not a full rebuild.
|
||||||
|
let items_by_id: HashMap<i64, &IdoTxChainDBRecord> =
|
||||||
|
items.iter().map(|item| (item.id, item)).collect();
|
||||||
|
let chain = reconstruct_txchain(&items_by_id, ido.txchain_head);
|
||||||
|
// the chain is only valid if it runs the whole way from the head down to
|
||||||
|
// the entrypoint (preinit) — nothing more, nothing less
|
||||||
|
let is_complete_chain = ido.txchain_entrypoint.is_some()
|
||||||
|
&& chain.first().map(|item| item.id) == ido.txchain_entrypoint;
|
||||||
|
let tx_txid = tx.compute_txid().to_blob();
|
||||||
|
let tx_in_current_chain =
|
||||||
|
is_complete_chain && chain.iter().any(|item| item.txid == tx_txid);
|
||||||
|
if tx_in_current_chain {
|
||||||
|
// already part of the current txchain, only update block height
|
||||||
update_ido_txchain_tracker_block_height(&mut dbtx, tx, block_height)
|
update_ido_txchain_tracker_block_height(&mut dbtx, tx, block_height)
|
||||||
.await?;
|
.await?;
|
||||||
} else if prev_txchain_item.id != ido.txchain_head.unwrap_or(0) {
|
} else if prev_txchain_item.id != ido.txchain_head.unwrap_or(0) {
|
||||||
debug!("IDO rebuild: {}, prev: {}", blob_to_display_hex::<Txid>(&tx.compute_txid().to_blob())?, blob_to_display_hex::<Txid>(&prev_txchain_item.txid)?);
|
debug!("IDO rebuild: {}, prev: {}", blob_to_display_hex::<Txid>(&tx.compute_txid().to_blob())?, blob_to_display_hex::<Txid>(&prev_txchain_item.txid)?);
|
||||||
// rebuild the state, txchain is broken, recreate the chain
|
// rebuild the state, txchain is broken, recreate the chain
|
||||||
let items = get_ido_txchain_list(pool, ido.internal_id)
|
|
||||||
.await?;
|
|
||||||
let mut items_tx_map: HashMap<Vec<u8>, Transaction> = HashMap::new();
|
let mut items_tx_map: HashMap<Vec<u8>, Transaction> = HashMap::new();
|
||||||
for item in &items {
|
for item in &items {
|
||||||
let tx_deser: Transaction = bitcoincash::consensus::deserialize(&item.tx).map_err(|e| anyhow::anyhow!("failed to deserialize tx: {e}"))?;
|
let tx_deser: Transaction = bitcoincash::consensus::deserialize(&item.tx).map_err(|e| anyhow::anyhow!("failed to deserialize tx: {e}"))?;
|
||||||
items_tx_map.insert(item.txid.clone(), tx_deser);
|
items_tx_map.insert(item.txid.clone(), tx_deser);
|
||||||
}
|
}
|
||||||
let mut new_chain: Vec<&IdoTxChainDBRecord> = Vec::new();
|
let new_chain = reconstruct_txchain(&items_by_id, Some(prev_txchain_item.id));
|
||||||
let mut items_copy: Vec<&IdoTxChainDBRecord> = items.iter().rev().collect();
|
|
||||||
let mut current_item = prev_txchain_item;
|
|
||||||
new_chain.insert(0, current_item);
|
|
||||||
while current_item.prev_id.is_some() {
|
|
||||||
let prev_id = current_item.prev_id.unwrap();
|
|
||||||
let found_index = items_copy.iter().position(|a| a.id == prev_id).ok_or_else(|| anyhow::anyhow!("txchain prev item not found!"))?;
|
|
||||||
current_item = items_copy.remove(found_index);
|
|
||||||
new_chain.insert(0, current_item);
|
|
||||||
}
|
|
||||||
if new_chain.get(0).ok_or_else(|| anyhow::anyhow!("txchain is empty"))?.txid != ido.preinit_txid {
|
if new_chain.get(0).ok_or_else(|| anyhow::anyhow!("txchain is empty"))?.txid != ido.preinit_txid {
|
||||||
return Err(anyhow::anyhow!("txchain entrypoint does not match preinit_txid"));
|
return Err(anyhow::anyhow!("txchain entrypoint does not match preinit_txid"));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue