2026-01-21 12:34:59 +01:00
|
|
|
// Copyright (C) 2024-2026 Whiterun LLC
|
2024-05-09 11:25:27 +02:00
|
|
|
//
|
|
|
|
|
// This software is licensed under the GNU Affero General Public License (AGPL), version 3.0 or later.
|
|
|
|
|
// A copy of the license can be found in the LICENSE file or at https://www.gnu.org/licenses/agpl-3.0.html
|
|
|
|
|
|
|
|
|
|
use std::{
|
|
|
|
|
collections::HashSet,
|
2026-02-17 17:47:43 +01:00
|
|
|
sync::{atomic::Ordering, Arc, Mutex},
|
2026-01-21 12:23:08 +01:00
|
|
|
time::Duration,
|
2024-05-09 11:25:27 +02:00
|
|
|
};
|
|
|
|
|
|
2025-05-20 16:17:32 +02:00
|
|
|
use bitcoin_hashes::{
|
|
|
|
|
hex::{FromHex, ToHex},
|
|
|
|
|
Hash,
|
|
|
|
|
};
|
2026-03-17 17:40:01 +01:00
|
|
|
use bitcoincash::{
|
|
|
|
|
consensus::deserialize, Block, BlockHash, BlockHeader, Network, TokenID, Transaction, Txid,
|
|
|
|
|
};
|
2024-10-21 12:48:47 +02:00
|
|
|
use electrum_client_netagnostic::{Client, ElectrumApi, Param};
|
2024-05-09 11:25:27 +02:00
|
|
|
use log::{debug, info, warn};
|
2024-10-24 11:52:11 +02:00
|
|
|
use riftenlabs_defi::cauldron::{parse_cauldrons_from_tx, ParsedContract};
|
2026-03-17 17:40:01 +01:00
|
|
|
use riftenlabs_defi::moria::MoriaTokenIds;
|
2024-05-09 11:25:27 +02:00
|
|
|
|
|
|
|
|
use crate::{
|
|
|
|
|
bcmr::index_bcmr,
|
|
|
|
|
chain::{get_new_headers, Chain, StoreBlockUndoer},
|
2024-10-21 12:48:47 +02:00
|
|
|
crc20::index_crc20,
|
2024-05-09 11:25:27 +02:00
|
|
|
db::{
|
|
|
|
|
self,
|
|
|
|
|
cauldron::{
|
|
|
|
|
config::{config_get, config_set},
|
|
|
|
|
header::{db_get_header, store_headers},
|
|
|
|
|
tx::insert_block_tx,
|
2024-09-04 11:11:43 +02:00
|
|
|
user::insert_user_action,
|
2024-05-09 11:25:27 +02:00
|
|
|
utxo_funding::insert_utxo_funding,
|
|
|
|
|
utxo_spending::insert_utxo_spending,
|
|
|
|
|
},
|
2025-05-20 16:17:32 +02:00
|
|
|
oracle::index_oracle,
|
2026-02-17 17:47:43 +01:00
|
|
|
DB,
|
2024-05-09 11:25:27 +02:00
|
|
|
},
|
|
|
|
|
electrum::{electrum_fetch_mempool, electrum_get_tip, electrum_get_tx},
|
2026-01-21 12:23:08 +01:00
|
|
|
signal::shutdown_requested,
|
2025-05-20 16:17:32 +02:00
|
|
|
timeutil::time_now,
|
2025-08-15 18:53:34 +02:00
|
|
|
utiltx::ttor_sorted_kahn,
|
2026-02-02 11:30:45 +01:00
|
|
|
IbdState, CASHTOKEN_ACTIVATION_HEIGHT, CHIPNET_START_BLOCK, KEY_LAST_INDEXED,
|
2024-05-09 11:25:27 +02:00
|
|
|
};
|
2026-01-05 15:40:01 +01:00
|
|
|
use anyhow::{bail, Context, Result};
|
2024-05-09 11:25:27 +02:00
|
|
|
|
2026-03-17 17:40:01 +01:00
|
|
|
/// Get the Moria token IDs for a given network
|
|
|
|
|
fn moria_token_ids(network: Option<Network>) -> MoriaTokenIds {
|
|
|
|
|
match network {
|
|
|
|
|
Some(Network::Chipnet) => MoriaTokenIds {
|
|
|
|
|
moria: TokenID::from_hex(
|
|
|
|
|
"29566f4884539dbcedfcb55cc7f5e66b5ae14975b70c0b6eb12de7e9707775ea",
|
|
|
|
|
)
|
|
|
|
|
.expect("valid chipnet moria token_id"),
|
|
|
|
|
bp_oracle: TokenID::from_hex(
|
|
|
|
|
"f3d6b85bfb0eaaf417ccabc8c8032464c5ec410e40b3b13f0c369a541bfb2a6a",
|
|
|
|
|
)
|
|
|
|
|
.expect("valid chipnet bp_oracle token_id"),
|
|
|
|
|
},
|
|
|
|
|
_ => MoriaTokenIds {
|
|
|
|
|
moria: TokenID::from_hex(
|
|
|
|
|
"b38a33f750f84c5c169a6f23cb873e6e79605021585d4f3408789689ed87f366",
|
|
|
|
|
)
|
|
|
|
|
.expect("valid mainnet moria token_id"),
|
|
|
|
|
bp_oracle: TokenID::from_hex(
|
|
|
|
|
"01711e39e7bf3b8ca0d9a6fc6ea32e340caa1d64dc7d1dc51fae20fd66755558",
|
|
|
|
|
)
|
|
|
|
|
.expect("valid mainnet bp_oracle token_id"),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-17 17:47:43 +01:00
|
|
|
pub async fn update_mempool(db: &DB, electrum: Arc<Mutex<Client>>) -> Result<()> {
|
2025-05-20 16:17:32 +02:00
|
|
|
let our_mempool_txs: HashSet<Txid> =
|
2026-02-17 17:47:43 +01:00
|
|
|
db::cauldron::mempool::load_mempool(&db.cauldron_w).await?;
|
|
|
|
|
|
|
|
|
|
let electrum_clone = electrum.clone();
|
|
|
|
|
let (cauldron_txs, oracle_txs) = tokio::task::spawn_blocking(move || {
|
|
|
|
|
electrum_fetch_mempool(&electrum_clone.lock().unwrap())
|
|
|
|
|
})
|
|
|
|
|
.await??;
|
|
|
|
|
|
|
|
|
|
let txs_to_delete: Vec<Txid> = our_mempool_txs.difference(&cauldron_txs).cloned().collect();
|
|
|
|
|
let txs_to_add: Vec<&Txid> = cauldron_txs.difference(&our_mempool_txs).collect();
|
|
|
|
|
|
|
|
|
|
let electrum_for_fetch = electrum.clone();
|
|
|
|
|
let txids_to_fetch: Vec<Txid> = txs_to_add.iter().map(|t| **t).collect();
|
|
|
|
|
let txs_to_add: Vec<Transaction> = tokio::task::spawn_blocking(move || {
|
|
|
|
|
txids_to_fetch
|
|
|
|
|
.iter()
|
|
|
|
|
.filter_map(
|
|
|
|
|
|txid| match electrum_get_tx(&electrum_for_fetch.lock().unwrap(), txid) {
|
|
|
|
|
Ok(tx) => Some(tx),
|
|
|
|
|
Err(e) => {
|
|
|
|
|
info!("Failed to get mempool tx {txid}: {e}");
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.collect()
|
|
|
|
|
})
|
|
|
|
|
.await?;
|
2024-05-09 11:25:27 +02:00
|
|
|
|
2025-08-15 18:53:34 +02:00
|
|
|
let txs_to_add = ttor_sorted_kahn(txs_to_add);
|
2024-10-21 15:13:16 +02:00
|
|
|
|
2026-02-17 17:47:43 +01:00
|
|
|
{
|
|
|
|
|
let mut db_tx = db.cauldron_w.begin().await?;
|
2024-05-09 11:25:27 +02:00
|
|
|
|
2026-02-17 17:47:43 +01:00
|
|
|
db::cauldron::mempool::delete_mempool_txs(&mut db_tx, txs_to_delete.iter()).await?;
|
2024-05-09 11:25:27 +02:00
|
|
|
|
2026-02-17 17:47:43 +01:00
|
|
|
let mut all_cauldrons = vec![];
|
|
|
|
|
let current_timestamp = time_now() as u64;
|
2024-05-09 11:25:27 +02:00
|
|
|
|
2026-02-17 17:47:43 +01:00
|
|
|
for btx in txs_to_add {
|
|
|
|
|
let cauldrons: Vec<ParsedContract> = parse_cauldrons_from_tx(&btx);
|
2024-10-08 17:21:22 +02:00
|
|
|
|
2026-02-17 17:47:43 +01:00
|
|
|
if cauldrons.is_empty() {
|
|
|
|
|
info!("ignoring non-cauldron tx {}", btx.txid());
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
let txid = btx.txid();
|
|
|
|
|
db::cauldron::tx::insert_mempool_tx(&mut db_tx, &txid, current_timestamp).await?;
|
|
|
|
|
info!(
|
|
|
|
|
"mempool add {} with {} cauldrons",
|
|
|
|
|
txid.to_hex(),
|
|
|
|
|
cauldrons.len()
|
|
|
|
|
);
|
2024-10-25 14:44:14 +02:00
|
|
|
|
2026-02-17 17:47:43 +01:00
|
|
|
insert_utxo_funding(&mut db_tx, &cauldrons, &txid, false).await?;
|
|
|
|
|
insert_utxo_spending(&mut db_tx, &cauldrons, &txid, false).await?;
|
|
|
|
|
insert_user_action(&mut db_tx, &cauldrons, &btx, true).await?;
|
2024-05-09 11:25:27 +02:00
|
|
|
|
2026-02-17 17:47:43 +01:00
|
|
|
all_cauldrons.extend(cauldrons);
|
|
|
|
|
}
|
2024-05-09 11:25:27 +02:00
|
|
|
|
2026-02-17 17:47:43 +01:00
|
|
|
db::cauldron::pool::update_pool_history(
|
|
|
|
|
&mut db_tx,
|
|
|
|
|
all_cauldrons,
|
|
|
|
|
None,
|
|
|
|
|
Some(current_timestamp),
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
db_tx.commit().await?;
|
2024-05-09 11:25:27 +02:00
|
|
|
}
|
|
|
|
|
|
2026-02-17 17:47:43 +01:00
|
|
|
// oracle updates
|
|
|
|
|
let oracle_electrum = electrum.clone();
|
2025-05-20 16:17:32 +02:00
|
|
|
|
2026-02-17 17:47:43 +01:00
|
|
|
// filter entries we have
|
|
|
|
|
let mut oracle_to_add = Vec::new();
|
|
|
|
|
for txid in oracle_txs {
|
|
|
|
|
if !db::oracle::has_entry(&db.oracle_w, &txid).await? {
|
|
|
|
|
oracle_to_add.push(txid);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-05-20 16:17:32 +02:00
|
|
|
|
2026-02-17 17:47:43 +01:00
|
|
|
let txs_to_add: Vec<Transaction> = tokio::task::spawn_blocking(move || {
|
|
|
|
|
oracle_to_add
|
2025-05-20 16:17:32 +02:00
|
|
|
.into_iter()
|
|
|
|
|
.filter_map(
|
2026-02-17 17:47:43 +01:00
|
|
|
|txid| match electrum_get_tx(&oracle_electrum.lock().unwrap(), &txid) {
|
2025-05-20 16:17:32 +02:00
|
|
|
Ok(tx) => Some(tx),
|
|
|
|
|
Err(e) => {
|
2025-07-16 09:31:14 +02:00
|
|
|
info!("Failed to get mempool tx {txid}: {e}");
|
2025-05-20 16:17:32 +02:00
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
)
|
2026-02-17 17:47:43 +01:00
|
|
|
.collect()
|
|
|
|
|
})
|
|
|
|
|
.await?;
|
2025-05-20 16:17:32 +02:00
|
|
|
|
2026-02-17 17:47:43 +01:00
|
|
|
index_oracle(&db.oracle_w, &txs_to_add, &BlockHash::all_zeros()).await?;
|
2025-05-20 16:17:32 +02:00
|
|
|
|
|
|
|
|
Ok(())
|
2024-05-09 11:25:27 +02:00
|
|
|
}
|
|
|
|
|
|
2026-02-17 17:47:43 +01:00
|
|
|
pub async fn index_blocks(
|
2024-05-09 11:25:27 +02:00
|
|
|
chain: Arc<Mutex<Chain>>,
|
|
|
|
|
db: DB,
|
|
|
|
|
client: Arc<Mutex<Client>>,
|
|
|
|
|
bcmr_enabled: bool,
|
2026-01-05 15:40:01 +01:00
|
|
|
network: Option<Network>,
|
2026-02-02 11:30:45 +01:00
|
|
|
ibd_state: Option<Arc<IbdState>>,
|
2026-03-17 17:40:01 +01:00
|
|
|
start_height: u64,
|
2024-05-09 11:25:27 +02:00
|
|
|
) -> Result<BlockHash> {
|
2026-02-17 17:47:43 +01:00
|
|
|
let electrum_clone = client.clone();
|
|
|
|
|
let (tip_header, tip_height) =
|
|
|
|
|
tokio::task::spawn_blocking(move || electrum_get_tip(&electrum_clone.lock().unwrap()))
|
|
|
|
|
.await??;
|
2026-02-02 11:30:45 +01:00
|
|
|
|
|
|
|
|
// Update target height for IBD progress tracking
|
|
|
|
|
if let Some(ref state) = ibd_state {
|
|
|
|
|
state.target_height.store(tip_height, Ordering::Relaxed);
|
|
|
|
|
}
|
2024-05-09 11:25:27 +02:00
|
|
|
|
2026-01-05 15:40:01 +01:00
|
|
|
// Validate network before proceeding
|
|
|
|
|
let start_block_hash = match network {
|
|
|
|
|
Some(Network::Chipnet) => BlockHash::from_hex(CHIPNET_START_BLOCK)
|
|
|
|
|
.map_err(|e| anyhow::anyhow!("Invalid CHIPNET_START_BLOCK: {}", e))?,
|
|
|
|
|
Some(Network::Bitcoin) => BlockHash::from_hex(CASHTOKEN_ACTIVATION_HEIGHT)
|
|
|
|
|
.map_err(|e| anyhow::anyhow!("Invalid CASHTOKEN_ACTIVATION_HEIGHT: {}", e))?,
|
|
|
|
|
None => BlockHash::from_hex(CASHTOKEN_ACTIVATION_HEIGHT)
|
|
|
|
|
.map_err(|e| anyhow::anyhow!("Invalid CASHTOKEN_ACTIVATION_HEIGHT: {}", e))?,
|
|
|
|
|
Some(net) => bail!("Unknown network: {:?}", net),
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-17 17:47:43 +01:00
|
|
|
let (block_send, mut block_recv) = tokio::sync::mpsc::channel::<Option<(u64, u64, Block)>>(10);
|
2024-05-09 11:25:27 +02:00
|
|
|
|
|
|
|
|
// Update header chain (and undo any blocks that may have reorged away)
|
|
|
|
|
{
|
2026-02-17 17:47:43 +01:00
|
|
|
let needs_update = {
|
|
|
|
|
let chain_guard = chain.lock().unwrap();
|
|
|
|
|
tip_header.block_hash() != chain_guard.tip_hash()
|
|
|
|
|
};
|
|
|
|
|
if needs_update {
|
2024-05-09 11:25:27 +02:00
|
|
|
{
|
2026-02-17 17:47:43 +01:00
|
|
|
let chain_guard = chain.lock().unwrap();
|
|
|
|
|
debug!(
|
|
|
|
|
"Updating header chain from {} to {}",
|
|
|
|
|
chain_guard.tip_hash().to_hex(),
|
|
|
|
|
tip_header.block_hash().to_hex()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
let client_for_headers = client.clone();
|
|
|
|
|
let chain_for_headers = chain.clone();
|
|
|
|
|
let tip_hash = tip_header.block_hash();
|
|
|
|
|
let new_headers = tokio::task::spawn_blocking(move || {
|
|
|
|
|
let chain_guard = chain_for_headers.lock().unwrap();
|
|
|
|
|
get_new_headers(&client_for_headers.lock().unwrap(), &chain_guard, &tip_hash)
|
|
|
|
|
})
|
|
|
|
|
.await??;
|
|
|
|
|
debug!("Storing headers");
|
|
|
|
|
for chunk in new_headers.chunks(100000) {
|
|
|
|
|
let mut db_tx = db.cauldron_w.begin().await?;
|
|
|
|
|
store_headers(&mut db_tx, chunk).await?;
|
|
|
|
|
db_tx.commit().await?;
|
2024-05-09 11:25:27 +02:00
|
|
|
}
|
2026-02-17 17:47:43 +01:00
|
|
|
// Run chain update in spawn_blocking because StoreBlockUndoer::undo_block
|
|
|
|
|
// uses Handle::current().block_on() which panics on Tokio worker threads.
|
|
|
|
|
let chain_for_update = chain.clone();
|
|
|
|
|
let undoer_db = db.clone();
|
|
|
|
|
tokio::task::spawn_blocking(move || {
|
|
|
|
|
let chain_guard = chain_for_update.lock().unwrap();
|
|
|
|
|
let undoer = StoreBlockUndoer::new(undoer_db)?;
|
|
|
|
|
chain_guard.update(undoer, new_headers, None)
|
|
|
|
|
})
|
|
|
|
|
.await??;
|
2024-05-09 11:25:27 +02:00
|
|
|
debug!("Header update done");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let db_cpy = db.clone();
|
2026-02-17 17:47:43 +01:00
|
|
|
let chain_cpy = chain.clone();
|
|
|
|
|
let client_cpy = client.clone();
|
2024-05-09 11:25:27 +02:00
|
|
|
|
2026-02-17 17:47:43 +01:00
|
|
|
tokio::task::spawn_blocking(move || {
|
2024-05-09 11:25:27 +02:00
|
|
|
let db = db_cpy;
|
2026-02-17 17:47:43 +01:00
|
|
|
let handle = tokio::runtime::Handle::current();
|
2024-05-09 11:25:27 +02:00
|
|
|
|
2026-02-17 17:47:43 +01:00
|
|
|
let last_indexed = handle
|
|
|
|
|
.block_on(config_get(&db.cauldron_r, KEY_LAST_INDEXED))
|
|
|
|
|
.unwrap();
|
2026-01-05 15:40:01 +01:00
|
|
|
let mut last_indexed = if let Some(last) = last_indexed {
|
2026-03-17 17:40:01 +01:00
|
|
|
if start_height > 0 {
|
|
|
|
|
warn!("--start-height is ignored because database already has indexed blocks. Delete the database to re-index from a different height.");
|
|
|
|
|
}
|
2026-01-05 15:40:01 +01:00
|
|
|
BlockHash::from_hex(&last).unwrap()
|
2026-03-17 17:40:01 +01:00
|
|
|
} else if start_height > 0 {
|
|
|
|
|
// Resolve configured start height to a block hash via electrum
|
|
|
|
|
let resp = client_cpy
|
|
|
|
|
.lock()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.raw_call(
|
|
|
|
|
"blockchain.block.header",
|
|
|
|
|
vec![Param::U32(start_height as u32)],
|
|
|
|
|
)
|
|
|
|
|
.unwrap_or_else(|e| panic!("Failed to fetch header at height {start_height}: {e}"));
|
|
|
|
|
let header_hex: String = serde_json::from_str(&resp.to_string()).unwrap();
|
|
|
|
|
let header: BlockHeader = deserialize(&hex::decode(&header_hex).unwrap()).unwrap();
|
|
|
|
|
info!(
|
|
|
|
|
"Starting indexing from configured height {} ({})",
|
|
|
|
|
start_height,
|
|
|
|
|
header.block_hash().to_hex()
|
|
|
|
|
);
|
|
|
|
|
header.block_hash()
|
2026-01-05 15:40:01 +01:00
|
|
|
} else {
|
|
|
|
|
start_block_hash
|
|
|
|
|
};
|
2024-05-09 11:25:27 +02:00
|
|
|
|
|
|
|
|
// Check if last indexed has been orphaned
|
|
|
|
|
loop {
|
2026-02-17 17:47:43 +01:00
|
|
|
if chain_cpy.lock().unwrap().contains(&last_indexed) {
|
2024-05-09 11:25:27 +02:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
info!(
|
|
|
|
|
"Last indexed block ({}) has been orphaned",
|
|
|
|
|
last_indexed.to_hex()
|
|
|
|
|
);
|
2026-02-17 17:47:43 +01:00
|
|
|
let header = handle
|
|
|
|
|
.block_on(db_get_header(&db.cauldron_r, &last_indexed))
|
2024-05-09 11:25:27 +02:00
|
|
|
.context("Failed to get header for last indexed")
|
|
|
|
|
.unwrap();
|
|
|
|
|
last_indexed = header.prev_blockhash;
|
|
|
|
|
|
|
|
|
|
info!(
|
|
|
|
|
"Last indexed block rolled back to {}",
|
|
|
|
|
last_indexed.to_hex()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
loop {
|
|
|
|
|
if tip_header.block_hash() == last_indexed {
|
2026-02-17 17:47:43 +01:00
|
|
|
if let Err(e) = block_send.blocking_send(None) {
|
|
|
|
|
warn!("Failed to send EOL to block reader: {e}");
|
2024-05-09 11:25:27 +02:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-17 17:47:43 +01:00
|
|
|
let next_height = chain_cpy
|
2024-05-09 11:25:27 +02:00
|
|
|
.lock()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.get_block_height(&last_indexed)
|
|
|
|
|
.expect("last_indexed height not found in main chain")
|
|
|
|
|
+ 1;
|
|
|
|
|
|
2026-02-17 17:47:43 +01:00
|
|
|
let res = client_cpy
|
2024-05-09 11:25:27 +02:00
|
|
|
.lock()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.raw_call("blockchain.block.get", vec![Param::U32(next_height as u32)])
|
2026-02-24 11:31:17 +01:00
|
|
|
.unwrap_or_else(|e| {
|
|
|
|
|
panic!("Failed to fetch block at height {next_height} from electrum: {e}")
|
|
|
|
|
});
|
2024-05-09 11:25:27 +02:00
|
|
|
|
|
|
|
|
let block_hex: String = serde_json::from_str(&res.to_string()).unwrap();
|
|
|
|
|
let block: Block = deserialize(&hex::decode(&block_hex).unwrap()).unwrap();
|
|
|
|
|
let block_hash = block.block_hash();
|
|
|
|
|
|
2026-02-17 17:47:43 +01:00
|
|
|
if let Err(e) = block_send.blocking_send(Some((
|
2024-05-09 11:25:27 +02:00
|
|
|
next_height,
|
2026-02-17 17:47:43 +01:00
|
|
|
chain_cpy.lock().unwrap().get_mtp(next_height).unwrap(),
|
2024-05-09 11:25:27 +02:00
|
|
|
block,
|
|
|
|
|
))) {
|
2025-07-16 09:31:14 +02:00
|
|
|
warn!("Failed to send block to reader: {e}");
|
2024-05-09 11:25:27 +02:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
last_indexed = block_hash;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-21 12:23:08 +01:00
|
|
|
loop {
|
2026-02-17 17:47:43 +01:00
|
|
|
let recv_result = tokio::time::timeout(Duration::from_secs(1), block_recv.recv()).await;
|
2026-01-21 12:23:08 +01:00
|
|
|
let (block_height, mtp, block) = match recv_result {
|
2026-02-17 17:47:43 +01:00
|
|
|
Ok(Some(Some(data))) => data,
|
|
|
|
|
Ok(Some(None)) | Ok(None) => break, // End of blocks signal or channel closed
|
|
|
|
|
Err(_) => {
|
|
|
|
|
// Timeout
|
2026-01-21 12:23:08 +01:00
|
|
|
if shutdown_requested() {
|
|
|
|
|
info!("Shutdown requested, exiting block indexing");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
};
|
2026-02-17 17:47:43 +01:00
|
|
|
|
|
|
|
|
let mut db_tx = db.cauldron_w.begin().await?;
|
2024-05-09 11:25:27 +02:00
|
|
|
|
|
|
|
|
let blockhash = block.block_hash();
|
|
|
|
|
|
|
|
|
|
let mut total_cauldrons = 0;
|
|
|
|
|
|
|
|
|
|
let mut all_cauldrons = vec![];
|
|
|
|
|
|
2025-08-15 18:53:34 +02:00
|
|
|
let sorted_txs = ttor_sorted_kahn(block.txdata);
|
2024-10-21 15:13:16 +02:00
|
|
|
|
|
|
|
|
for tx in &sorted_txs {
|
2024-10-24 11:52:11 +02:00
|
|
|
let cauldrons = parse_cauldrons_from_tx(tx);
|
2024-05-09 11:25:27 +02:00
|
|
|
if cauldrons.is_empty() {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let txid = tx.txid();
|
2026-02-17 17:47:43 +01:00
|
|
|
insert_block_tx(&mut db_tx, &txid, &blockhash, mtp as i64)
|
|
|
|
|
.await
|
|
|
|
|
.context("inserting block tx")?;
|
|
|
|
|
insert_utxo_funding(&mut db_tx, &cauldrons, &txid, true)
|
|
|
|
|
.await
|
2024-05-09 11:25:27 +02:00
|
|
|
.context("inserting funding utxos")?;
|
2026-02-17 17:47:43 +01:00
|
|
|
insert_utxo_spending(&mut db_tx, &cauldrons, &txid, true)
|
|
|
|
|
.await
|
2024-05-09 11:25:27 +02:00
|
|
|
.context("inserting spending utxos")?;
|
2026-02-17 17:47:43 +01:00
|
|
|
insert_user_action(&mut db_tx, &cauldrons, tx, true)
|
|
|
|
|
.await
|
|
|
|
|
.context("inserting user actions")?;
|
2024-09-04 11:11:43 +02:00
|
|
|
|
2024-05-09 11:25:27 +02:00
|
|
|
total_cauldrons += cauldrons.len();
|
|
|
|
|
|
|
|
|
|
all_cauldrons.extend(cauldrons);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Figuring out initial utxo needs to be done on all cauldrons in a block.
|
2026-02-17 17:47:43 +01:00
|
|
|
db::cauldron::pool::update_pool_history(&mut db_tx, all_cauldrons, Some(mtp), None).await?;
|
|
|
|
|
// config_set must be inside the cauldron transaction, as it tracks what the
|
|
|
|
|
// last successful block indexed was. It must commit atomically with block data.
|
|
|
|
|
config_set(&mut *db_tx, KEY_LAST_INDEXED, &blockhash.to_hex()).await;
|
2024-10-21 12:48:47 +02:00
|
|
|
|
2026-02-17 17:47:43 +01:00
|
|
|
// crc20
|
|
|
|
|
index_crc20(&db.crc20_w, &sorted_txs).await?;
|
|
|
|
|
|
|
|
|
|
// oracle updates
|
|
|
|
|
index_oracle(&db.oracle_w, &sorted_txs, &blockhash).await?;
|
2025-05-20 16:17:32 +02:00
|
|
|
|
2026-03-17 17:40:01 +01:00
|
|
|
// moria lending
|
|
|
|
|
let moria_actions = db::moria::index_moria(
|
|
|
|
|
&db.moria_w,
|
|
|
|
|
&sorted_txs,
|
|
|
|
|
&blockhash,
|
|
|
|
|
mtp as i64,
|
|
|
|
|
&moria_token_ids(network),
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
|
2024-05-09 11:25:27 +02:00
|
|
|
let autheader_updates = if bcmr_enabled {
|
2026-02-17 17:47:43 +01:00
|
|
|
let updates = index_bcmr(&db.bcmr_w, &blockhash, sorted_txs).await?;
|
2024-05-09 11:25:27 +02:00
|
|
|
updates as i64
|
|
|
|
|
} else {
|
|
|
|
|
-1
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-17 17:47:43 +01:00
|
|
|
db_tx.commit().await?;
|
2024-05-09 11:25:27 +02:00
|
|
|
|
2026-02-02 11:30:45 +01:00
|
|
|
// Update IBD progress
|
|
|
|
|
if let Some(ref state) = ibd_state {
|
|
|
|
|
state.current_height.store(block_height, Ordering::Relaxed);
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-09 11:25:27 +02:00
|
|
|
info!(
|
2026-03-17 17:40:01 +01:00
|
|
|
"Indexed {}; mtp: {}, height {}, {} trades, {} moria, {} autheader updates.",
|
2024-05-09 11:25:27 +02:00
|
|
|
blockhash.to_hex(),
|
|
|
|
|
mtp,
|
|
|
|
|
block_height,
|
|
|
|
|
total_cauldrons,
|
2026-03-17 17:40:01 +01:00
|
|
|
moria_actions,
|
2024-05-09 11:25:27 +02:00
|
|
|
autheader_updates,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
Ok(tip_header.block_hash())
|
|
|
|
|
}
|