Merge branch 'manage-several-entries-one-txid' into 'master'

Manage several entries one txid

See merge request riftenlabs/riftenlabs-indexer!44
This commit is contained in:
jakobsn 2025-11-17 09:21:51 +00:00
commit 1b7801dff8
5 changed files with 334 additions and 299 deletions

View file

@ -192,6 +192,7 @@ impl BCMRDownloader {
&conn,
&entry.utxo,
&entry.txid,
&entry.token_id,
"Invalid BCMR OP_RETURN in DB",
true,
) {
@ -211,6 +212,7 @@ impl BCMRDownloader {
&conn,
&entry.utxo,
&entry.txid,
&entry.token_id,
&format!("Failed to fetch BCMR: {error}"),
is_fatal,
) {
@ -227,6 +229,7 @@ impl BCMRDownloader {
&conn,
&entry.utxo,
&entry.txid,
&entry.token_id,
&format!("BCMR invalid JSON error: {e}"),
true,
) {
@ -249,6 +252,7 @@ impl BCMRDownloader {
&conn,
&entry.utxo,
&entry.txid,
&entry.token_id,
&format!("BCMR contents error: {err}"),
true,
) {
@ -258,7 +262,7 @@ impl BCMRDownloader {
}
};
if let Err(err) = insert_bcmr_data(&conn, &entry.utxo, &bcmr_parsed) {
if let Err(err) = insert_bcmr_data(&conn, &entry.token_id, &entry.utxo, &bcmr_parsed) {
warn!("bcmr: Failed to insert BCMR data {err}")
}
});

View file

@ -6,7 +6,7 @@
use rayon::prelude::*;
use std::convert::TryInto;
use anyhow::{Context, Result};
use anyhow::Result;
use bitcoin_hashes::{hex::FromHex, hex::ToHex, Hash};
use bitcoincash::{BlockHash, Script, TokenID, Transaction, Txid};
use log::debug;
@ -51,65 +51,50 @@ pub struct BCMR {
pub op_return: Vec<u8>,
}
fn find_parent_auth_entry(conn: &Connection, tx: &Transaction) -> Result<Option<AuthChainEntry>> {
let mut best: Option<AuthChainEntry> = None;
fn find_parent_auth_entries(conn: &Connection, tx: &Transaction) -> Result<Vec<AuthChainEntry>> {
let mut parents = Vec::new();
for vin in &tx.input {
// BCMR auth chain is always the previous txs vout 0
if vin.previous_output.vout != 0 {
continue;
}
let prev = &vin.previous_output;
let prev_utxo: OutPointHash = compute_outpoint_hash(&prev.txid, prev.vout);
let mut stmt = conn
.prepare(
let mut stmt = conn.prepare(
"SELECT token_id, txid, height, bcmr_data, utxo
FROM auth_chain_entry
WHERE utxo = ?1
LIMIT 1",
)
.context("prepare SELECT parent auth_chain_entry by utxo")?;
WHERE utxo = ?1",
)?;
let mut rows = stmt.query(params![prev_utxo.to_hex()])?;
let mut rows = stmt
.query(params![prev_utxo.to_hex()])
.context("query parent auth_chain_entry by utxo")?;
if let Some(row) = rows.next().context("iterate parent rows")? {
while let Some(row) = rows.next()? {
let token_hex: String = row.get(0)?;
let txid_hex: String = row.get(1)?;
let height: usize = row.get(2)?;
let bcmr_data_hex: Option<String> = row.get(3)?;
let utxo_hex: String = row.get(4)?;
let token_id = TokenID::from_hex(&token_hex).context("parse token_id from hex")?;
let txid = Txid::from_hex(&txid_hex).context("parse txid from hex")?;
let utxo = OutPointHash::from_hex(&utxo_hex).context("parse utxo from hex")?;
let token_id = TokenID::from_hex(&token_hex)?;
let txid = Txid::from_hex(&txid_hex)?;
let utxo = OutPointHash::from_hex(&utxo_hex)?;
let bcmr_data = if let Some(h) = bcmr_data_hex {
Some(hex::decode(&h).context("decode bcmr_data hex")?)
} else {
None
let bcmr_data = match bcmr_data_hex {
Some(h) => Some(hex::decode(&h)?),
None => None,
};
let candidate = AuthChainEntry {
parents.push(AuthChainEntry {
utxo,
token_id,
txid,
height,
bcmr_data,
};
// Keep the *highest* height (current head)
match &best {
None => best = Some(candidate),
Some(cur) if candidate.height > cur.height => best = Some(candidate),
_ => {}
}
});
}
}
Ok(best)
Ok(parents)
}
pub fn parse_bcmr_from_opreturn(bcmr_op_return: &Script) -> Option<BCMR> {
@ -213,13 +198,18 @@ pub fn index_bcmr(
continue;
}
let parents = find_parent_auth_entries(conn, tx)?;
if parents.is_empty() {
continue;
}
// 2) Update path: if any input spends a known auth utxo, extend that chain
if let Some(parent) = find_parent_auth_entry(conn, tx)? {
let bcmr = parse_bcmr(tx);
let txid = tx.txid();
let new_utxo = compute_outpoint_hash(&txid, 0);
let new_height = parent.height + 1;
for parent in parents {
let new_height = parent.height + 1;
debug!(
"Found authheader update {} in {}; has bcmr: {}; new height: {}, utxo: {}",
parent.token_id.to_hex(),
@ -236,16 +226,138 @@ pub fn index_bcmr(
&txid,
&parent.token_id,
new_height,
bcmr.map(|b| b.op_return),
bcmr.as_ref().map(|b| b.op_return.clone()),
)?;
inserts += 1;
}
// else: not a BCMR auth-chain tx → ignore
}
Ok(inserts)
}
#[test]
fn multi_parent_auth_update_creates_entries_for_all_tokens() {
use crate::db::bcmr::prepare_tables;
use bitcoincash::{OutPoint, PackedLockTime, Sequence, TxIn, TxOut};
// In-memory DB
let mut conn = Connection::open_in_memory().unwrap();
prepare_tables(&conn);
let db_tx = conn.transaction().unwrap(); // <-- DB transaction
let bh = BlockHash::all_zeros();
// Two fake token IDs
let token_a =
TokenID::from_hex("0101010101010101010101010101010101010101010101010101010101010101")
.unwrap();
let token_b =
TokenID::from_hex("0202020202020202020202020202020202020202020202020202020202020202")
.unwrap();
// Two fake previous txids (current heads for A and B)
let prev_txid_a =
Txid::from_hex("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap();
let prev_txid_b =
Txid::from_hex("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb").unwrap();
// Their auth-head UTXOs (vout 0)
let utxo_a = compute_outpoint_hash(&prev_txid_a, 0);
let utxo_b = compute_outpoint_hash(&prev_txid_b, 0);
// Insert both auth heads at height 0
insert_authheader(&db_tx, &utxo_a, &bh, &prev_txid_a, &token_a, 0, None).unwrap();
insert_authheader(&db_tx, &utxo_b, &bh, &prev_txid_b, &token_b, 0, None).unwrap();
// Build a single tx that spends BOTH auth heads
let input_a = TxIn {
previous_output: OutPoint {
txid: prev_txid_a,
vout: 0,
},
script_sig: Script::new(),
sequence: Sequence(0xFFFF_FFFF),
..Default::default()
};
let input_b = TxIn {
previous_output: OutPoint {
txid: prev_txid_b,
vout: 0,
},
script_sig: Script::new(),
sequence: Sequence(0xFFFF_FFFF),
..Default::default()
};
let output0 = TxOut {
value: 0,
script_pubkey: Script::new(),
..Default::default()
};
let update_tx = Transaction {
version: 2,
lock_time: PackedLockTime(0),
input: vec![input_a, input_b],
output: vec![output0],
};
let update_txid = update_tx.txid();
// Index this one tx as if it's in a block
index_bcmr(&db_tx, &bh, vec![update_tx]).unwrap();
// ---- ASSERTIONS ----
// Expect 2 auth_chain_entry rows for this tx (one per token)
{
let mut stmt = db_tx
.prepare("SELECT COUNT(*) FROM auth_chain_entry WHERE txid = ?")
.unwrap();
let count: i64 = stmt
.query_row([update_txid.to_hex()], |row| row.get(0))
.unwrap();
assert_eq!(
count, 2,
"Expected 2 auth_chain_entry rows for multi-parent auth update tx"
);
}
// Expect each token to have height 1 in this tx
{
let mut stmt = db_tx
.prepare(
"SELECT token_id, height
FROM auth_chain_entry
WHERE txid = ?
ORDER BY token_id",
)
.unwrap();
let mut rows = stmt.query([update_txid.to_hex()]).unwrap();
let mut seen: Vec<(String, i64)> = Vec::new();
while let Some(row) = rows.next().unwrap() {
let t: String = row.get(0).unwrap();
let h: i64 = row.get(1).unwrap();
seen.push((t, h));
}
assert_eq!(seen.len(), 2, "expected 2 rows for update tx");
seen.sort_by(|a, b| a.0.cmp(&b.0));
assert_eq!(seen[0].0, token_a.to_hex());
assert_eq!(seen[0].1, 1);
assert_eq!(seen[1].0, token_b.to_hex());
assert_eq!(seen[1].1, 1);
}
// No need to commit in the test; we're only asserting state
// db_tx.commit().unwrap();
}
#[cfg(test)]
mod tests {
use bitcoincash::{consensus::deserialize, Block};
@ -427,6 +539,8 @@ mod tests {
#[test]
fn olando_issue() {
#[ignore]
// Integration test - requires network access. Run with `cargo test olando_issue --ignored`
// Test that the indexer is able to follow the auth chain for OLANDO token (part of issue #15).
// Map: block height -> expected txid of BCMR auth update transaction

View file

@ -27,12 +27,13 @@ pub struct AuthChainEntry {
pub fn prepare_tables(conn: &Connection) {
conn.execute(
"CREATE TABLE auth_chain_entry (
utxo TEXT PRIMARY KEY,
token_id TEXT NOT NULL,
utxo TEXT NOT NULL,
blockhash TEXT NOT NULL,
txid TEXT NOT NULL,
token_id TEXT NOT NULL,
height INT NOT NULL,
bcmr_data TEXT
bcmr_data TEXT,
PRIMARY KEY (token_id, utxo)
)",
[],
)
@ -40,7 +41,8 @@ pub fn prepare_tables(conn: &Connection) {
conn.execute(
"CREATE TABLE bcmr_data (
utxo TEXT PRIMARY KEY,
token_id TEXT NOT NULL,
utxo TEXT NOT NULL,
symbol TEXT NOT NULL,
decimals INT NOT NULL,
name TEXT NOT NULL,
@ -49,8 +51,12 @@ pub fn prepare_tables(conn: &Connection) {
web TEXT NOT NULL,
expected_hash TEXT NOT NULL,
actual_hash TEXT NOT NULL,
FOREIGN KEY (utxo) REFERENCES auth_chain_entry(utxo) ON DELETE CASCADE
)",
PRIMARY KEY (token_id, utxo),
FOREIGN KEY (token_id, utxo)
REFERENCES auth_chain_entry(token_id, utxo)
ON DELETE CASCADE
);
",
[],
)
.expect("failed to create bcmr_data table");
@ -58,13 +64,14 @@ pub fn prepare_tables(conn: &Connection) {
// NOTE: PRIMARY KEY (utxo, txid) is required for ON CONFLICT(utxo, txid)
conn.execute(
"CREATE TABLE bcmr_failure (
token_id TEXT NOT NULL,
utxo TEXT NOT NULL,
txid TEXT NOT NULL,
last_attempt INT NOT NULL,
attempts INT NOT NULL,
error_message TEXT,
give_up BOOLEAN,
PRIMARY KEY (utxo, txid)
PRIMARY KEY (token_id, utxo, txid)
)",
[],
)
@ -94,6 +101,10 @@ pub fn prepare_tables(conn: &Connection) {
)
.unwrap();
// Create index for efficient token BCMR lookups
conn.execute("CREATE INDEX idx_auth_utxo ON auth_chain_entry(utxo);", [])
.expect("failed to create index for token BCMR lookups");
// Create index for efficient token BCMR lookups
conn.execute(
"CREATE INDEX IF NOT EXISTS idx_auth_chain_token_bcmr_height
@ -226,9 +237,11 @@ pub fn get_entries_missing_bcmr_download(conn: &Connection) -> Result<Vec<AuthCh
FROM ranked r
LEFT JOIN bcmr_data bd
ON bd.utxo = r.utxo
AND bd.token_id = r.token_id
LEFT JOIN bcmr_failure bf
ON bf.utxo = r.utxo
AND bf.txid = r.txid
AND bf.token_id = r.token_id
AND (bf.give_up = 1 OR (strftime('%s','now') - bf.last_attempt) < 1800)
WHERE r.rn = 1
AND bd.utxo IS NULL
@ -279,17 +292,31 @@ pub fn get_entries_missing_bcmr_download(conn: &Connection) -> Result<Vec<AuthCh
pub fn insert_bcmr_data(
conn: &rusqlite::Connection,
token_id: &TokenID,
utxo: &OutPointHash,
bcmr: &ParsedBCMR,
) -> Result<()> {
let sql = "INSERT OR REPLACE INTO bcmr_data (utxo, symbol, decimals, name, description, icon, web, expected_hash, actual_hash)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
let sql = "INSERT OR REPLACE INTO bcmr_data (
token_id,
utxo,
symbol,
decimals,
name,
description,
icon,
web,
expected_hash,
actual_hash
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
info!("Trying to insert {}", utxo.to_hex());
let empty_string: String = "".to_owned();
conn.execute(
sql,
rusqlite::params![
&token_id.to_hex(),
&utxo.to_hex(),
&bcmr.token.symbol,
bcmr.token.decimals,
@ -297,10 +324,11 @@ pub fn insert_bcmr_data(
&bcmr.description,
&bcmr.uris.icon.as_ref().unwrap_or(&empty_string),
&bcmr.uris.web.as_ref().unwrap_or(&empty_string),
&bcmr.filemeta.expected_hash,
&bcmr.filemeta.actual_hash
&bcmr.filemeta.expected_hash.as_deref().unwrap_or(""),
&bcmr.filemeta.actual_hash.as_deref().unwrap_or(""),
],
)?;
Ok(())
}
@ -335,26 +363,34 @@ pub fn update_bcmr_failure(
conn: &Connection,
utxo: &OutPointHash,
txid: &Txid,
token_id: &TokenID,
error_message: &str,
give_up: bool,
) -> Result<()> {
let sql = format!(
"
INSERT INTO bcmr_failure (utxo, txid, last_attempt, attempts, error_message, give_up)
VALUES (?1, ?2, strftime('%s','now'), 1, ?3, ?4)
ON CONFLICT(utxo, txid) DO UPDATE SET
INSERT INTO bcmr_failure (token_id, utxo, txid, last_attempt, attempts, error_message, give_up)
VALUES (?1, ?2, ?3, strftime('%s','now'), 1, ?4, ?5)
ON CONFLICT(token_id, utxo, txid) DO UPDATE SET
last_attempt = excluded.last_attempt,
attempts = bcmr_failure.attempts + 1,
error_message= excluded.error_message,
error_message = excluded.error_message,
give_up = CASE
WHEN bcmr_failure.attempts + 1 > {max} THEN 1
ELSE excluded.give_up
END",
max = MAX_DOWNLOAD_ATTEMPTS
END
",
max = MAX_DOWNLOAD_ATTEMPTS,
);
conn.execute(
&sql,
params![&utxo.to_hex(), &txid.to_hex(), error_message, give_up],
params![
&token_id.to_hex(),
&utxo.to_hex(),
&txid.to_hex(),
error_message,
give_up
],
)?;
Ok(())
}
@ -362,18 +398,11 @@ pub fn update_bcmr_failure(
pub fn get_token_bcmr(conn: &Connection, token_hex: &str) -> Result<Option<ParsedBCMR>> {
// Pick *exactly* the head utxo for this token.
let sql = r#"
SELECT b.symbol, b.decimals, b.name, b.description, b.icon, b.web,
b.actual_hash, b.expected_hash
FROM bcmr_data b
WHERE b.utxo = (
SELECT ace.utxo
FROM auth_chain_entry ace
WHERE ace.token_id = ?
AND ace.bcmr_data IS NOT NULL
ORDER BY ace.height DESC
LIMIT 1
)
LIMIT 1
SELECT symbol, decimals, name, description, icon, web, actual_hash, expected_hash
FROM bcmr_data
WHERE token_id = ?
ORDER BY ROWID DESC
LIMIT 1;
"#;
let mut stmt = conn.prepare(sql)?;

View file

@ -197,7 +197,7 @@ mod tests {
source: "src".to_string(),
},
};
insert_bcmr_data(&rw, &utxo_a, &row_a).unwrap();
insert_bcmr_data(&rw, &token_a, &utxo_a, &row_a).unwrap();
insert_authheader(
&rw,
&utxo_a,
@ -1035,7 +1035,7 @@ mod tests {
source: "test".into(),
},
};
insert_bcmr_data(&bcmr_w, &utxo, &row).unwrap();
insert_bcmr_data(&bcmr_w, &token, &utxo, &row).unwrap();
insert_authheader(
&bcmr_w,
&utxo,
@ -1101,6 +1101,7 @@ mod tests {
// decimals = 28 OK, but well make price huge by tiny tokens
insert_bcmr_data(
&bcmr_w,
&token,
&utxo,
&BCMRRow {
name: "HugePrice".into(),

View file

@ -431,8 +431,9 @@ mod tests {
let utxo1 = OutPointHash::from_hex(
"a3f1d42e2a5c9f2b5f1b9d7f7c2b19e7a3b1d2c3f4a5e6f2d1c3e4f5a1b2c3d4",
)?;
let token_id1: TokenID = TokenID::from_inner([0xda; 32]);
// Step 1: Prepare and insert BCMR data for token1 with non-null fields
// BCMR data for Token 1
let token1 = Token {
category: "asset_category".to_string(),
symbol: "TONE".to_string(),
@ -454,101 +455,72 @@ mod tests {
uris: uris1,
filemeta: filemeta1,
};
if let Err(e) = insert_bcmr_data(conn, &utxo1, &parsed_bcmr1) {
println!("Failed to insert BCMR data for token1: {e:?}");
}
// Step 2: Insert Pool for token1
insert_bcmr_data(conn, &token_id1, &utxo1, &parsed_bcmr1)?;
let txid1 = Txid::from_inner([0xf0; 32]);
let token_id1: TokenID = TokenID::from_inner([0xda; 32]);
let cauldron1 = dummy_cauldron(&txid1, &utxo1, &token_id1, 1000, 500, &owner_pkh);
if let Err(e) = insert_new_pool(conn, &cauldron1) {
println!("Failed to insert pool for token1: {e:?}");
}
insert_new_pool(conn, &cauldron1)?;
// Step 3: Insert auth_chain_entry for token1
if let Err(e) = insert_authheader(
insert_authheader(
conn,
&utxo1,
&BlockHash::all_zeros(),
&block_zero,
&txid1,
&token_id1,
10,
Some(Vec::from("bcmr_data1".as_bytes())),
) {
println!("Failed to insert auth_chain_entry for token1: {e:?}");
}
)?;
// Step 4: Insert the initial UTXO funding for token1
if let Err(e) = insert_utxo_funding(conn, &vec![cauldron1.clone()], &txid1, true) {
println!("Failed to insert initial UTXO funding for token1: {e:?}");
}
insert_utxo_funding(conn, &vec![cauldron1.clone()], &txid1, true)?;
insert_block_tx(conn, &txid1, &block_zero, thirty_days_ago)?;
insert_mempool_tx(conn, &txid1, thirty_days_ago as u64)?;
// Step 5: Insert the transaction for utxo1
if let Err(e) = insert_block_tx(conn, &txid1, &block_zero, thirty_days_ago) {
println!("Failed to insert block transaction for utxo1: {e:?}");
}
if let Err(e) = insert_mempool_tx(conn, &txid1, thirty_days_ago as u64) {
println!("Failed to insert mempool transaction for utxo1: {e:?}");
}
// Step 6: Simulate Volume - Insert second funding for token1
let txid2 = Txid::from_inner([0xf1; 32]);
let utxo2 = OutPointHash::from_inner([0xe1; 32]);
let cauldron2 = ParsedContract {
// Simulate volume on token1
let txid1_spend = Txid::from_inner([0xf1; 32]);
let utxo1_spend = OutPointHash::from_inner([0xe1; 32]);
let cauldron1_spent = ParsedContract {
pkh: owner_pkh,
is_withdrawn: false,
spent_utxo_hash: utxo1,
new_utxo_hash: Some(utxo2),
new_utxo_txid: Some(txid2),
new_utxo_hash: Some(utxo1_spend),
new_utxo_txid: Some(txid1_spend),
new_utxo_n: Some(0),
token_id: Some(token_id1),
sats: Some(2000),
token_amount: Some(1000),
};
if let Err(e) = insert_utxo_funding(conn, &vec![cauldron2.clone()], &txid2, true) {
println!("Failed to insert second UTXO funding for token1: {e:?}");
}
insert_utxo_funding(conn, &vec![cauldron1_spent.clone()], &txid1_spend, true)?;
insert_block_tx(conn, &txid1_spend, &block_zero, current_timestamp)?;
insert_mempool_tx(conn, &txid1_spend, current_timestamp as u64)?;
// Step 7: Insert the transaction for utxo2 (spending utxo1)
if let Err(e) = insert_block_tx(conn, &txid2, &block_zero, current_timestamp) {
println!("Failed to insert block transaction for utxo2: {e:?}");
}
if let Err(e) = insert_mempool_tx(conn, &txid2, current_timestamp as u64) {
println!("Failed to insert mempool transaction for utxo2: {e:?}");
}
// Step 8: Insert pool history entries for initial funding and spending
if let Err(e) = insert_pool_history_entry(
insert_pool_history_entry(
conn,
&utxo1,
&cauldron1.clone(),
&cauldron1,
Some(thirty_days_ago as u64),
Some(thirty_days_ago as u64),
0, // sats_delta for initial funding (no trading activity)
0, // token_delta for initial funding (no trading activity)
) {
println!("Failed to insert pool history entry for initial funding of token1: {e:?}");
}
if let Err(e) = insert_pool_history_entry(
0,
0,
)?;
insert_pool_history_entry(
conn,
&utxo1,
&cauldron2,
&cauldron1_spent,
Some(current_timestamp as u64),
Some(current_timestamp as u64),
2000, // sats_delta for spending (actual trading activity)
1000, // token_delta for spending (actual trading activity)
) {
println!("Failed to insert pool history entry for spending of token1: {e:?}");
}
2000,
1000,
)?;
// ================== TOKEN 2 (BCMR Token) ==================
let utxo2 = OutPointHash::from_hex(
"c3d2e1f4b6a7c8d1e2f5d7c3b1a9e4f2b1d3f4e6c2b9f3a8d1e4f5b6c3d2e7a4",
)?;
let token_id2 = TokenID::from_inner([0xdb; 32]);
// Step 1: Prepare and insert BCMR data for token2 with unique values
// BCMR for Token 2
let token2 = Token {
category: "asset_category_2".to_string(),
symbol: "TWO".to_string(),
@ -570,99 +542,71 @@ mod tests {
uris: uris2,
filemeta: filemeta2,
};
if let Err(e) = insert_bcmr_data(conn, &utxo2, &parsed_bcmr2) {
println!("Failed to insert BCMR data for token2: {e:?}");
}
// Step 2: Insert Pool for token2
let token_id2 = TokenID::from_inner([0xdb; 32]);
let cauldron2 = dummy_cauldron(
&Txid::from_inner([0xf2; 32]),
&utxo2,
&token_id2,
1500,
700,
&owner_pkh,
);
insert_bcmr_data(conn, &token_id2, &utxo2, &parsed_bcmr2)?;
let txid2_init = Txid::from_inner([0xf2; 32]);
let cauldron2 = dummy_cauldron(&txid2_init, &utxo2, &token_id2, 1500, 700, &owner_pkh);
insert_new_pool(conn, &cauldron2)?;
// Step 9: Insert auth_chain_entry for token2
if let Err(e) = insert_authheader(
insert_authheader(
conn,
&utxo2,
&BlockHash::all_zeros(),
&txid2,
&block_zero,
&txid2_init,
&token_id2,
15,
Some(Vec::from("bcmr_data2".as_bytes())),
) {
println!("Failed to insert auth_chain_entry for TOKEN 3: {e:?}");
};
// Step 3: Insert the initial UTXO funding for token2
insert_utxo_funding(
conn,
&vec![cauldron2.clone()],
&Txid::from_inner([0xf2; 32]),
true,
)?;
// Step 4: Insert the transaction for utxo2
let txid2 = Txid::from_inner([0xf2; 32]);
insert_block_tx(conn, &txid2, &block_zero, thirty_days_ago)?;
insert_mempool_tx(conn, &txid2, thirty_days_ago as u64)?;
insert_utxo_funding(conn, &vec![cauldron2.clone()], &txid2_init, true)?;
insert_block_tx(conn, &txid2_init, &block_zero, thirty_days_ago)?;
insert_mempool_tx(conn, &txid2_init, thirty_days_ago as u64)?;
// Step 5: Define cauldron3 for token2 with spent_utxo_hash referring to utxo2, then insert it
let txid3 = Txid::from_inner([0xf3; 32]);
let utxo3 = OutPointHash::from_inner([0xe2; 32]);
let cauldron3 = ParsedContract {
// Simulate second funding for token2
let txid2_spend = Txid::from_inner([0xf3; 32]);
let utxo2_spend = OutPointHash::from_inner([0xe2; 32]);
let cauldron2_spent = ParsedContract {
pkh: owner_pkh,
is_withdrawn: false,
spent_utxo_hash: utxo2,
new_utxo_hash: Some(utxo3),
new_utxo_txid: Some(txid3),
new_utxo_hash: Some(utxo2_spend),
new_utxo_txid: Some(txid2_spend),
new_utxo_n: Some(0),
token_id: Some(token_id2),
sats: Some(3000),
token_amount: Some(1500),
};
// Insert the second funding with `spent_utxo_hash` correctly set
insert_utxo_funding(conn, &vec![cauldron3.clone()], &txid3, true)?;
insert_utxo_funding(conn, &vec![cauldron2_spent.clone()], &txid2_spend, true)?;
insert_block_tx(conn, &txid2_spend, &block_zero, current_timestamp)?;
insert_mempool_tx(conn, &txid2_spend, current_timestamp as u64)?;
// Step 6: Insert the transaction for utxo3 (the transaction that spent utxo2)
insert_block_tx(conn, &txid3, &block_zero, current_timestamp)?;
insert_mempool_tx(conn, &txid3, current_timestamp as u64)?;
// Step 7: Insert pool history entry for the initial UTXO funding (utxo2)
insert_pool_history_entry(
conn,
&utxo2,
&cauldron2.clone(),
&cauldron2,
Some(thirty_days_ago as u64),
Some(thirty_days_ago as u64),
0, // sats_delta for initial funding (no trading activity)
0, // token_delta for initial funding (no trading activity)
0,
0,
)?;
// Step 8: Insert pool history entry for the spending of utxo2 (creation of utxo3)
insert_pool_history_entry(
conn,
&utxo2,
&cauldron3,
&cauldron2_spent,
Some(current_timestamp as u64),
Some(current_timestamp as u64),
3000, // sats_delta for spending (actual trading activity)
1500, // token_delta for spending (actual trading activity)
3000,
1500,
)?;
// ================== TOKEN 3 (BCMR Token with No Volume) ==================
let utxo3 = OutPointHash::from_hex(
"d5e1f2a3b4c3d2f5b6a8e7d3c2f4b9a6d2e3f1c4b5a9e3d1b2c5f7a3d4b8e2c3",
)?;
let token_id3 = TokenID::from_inner([0xdd; 32]);
// Prepare and insert BCMR data for TOKEN 3 with unique values
let token3 = Token {
category: "asset_category_3".to_string(),
symbol: "TN3".to_string(),
@ -685,77 +629,50 @@ mod tests {
filemeta: filemeta3,
};
if let Err(e) = insert_bcmr_data(conn, &utxo3, &parsed_bcmr3) {
println!("Failed to insert BCMR data for TOKEN 3: {e:?}");
}
insert_bcmr_data(conn, &token_id3, &utxo3, &parsed_bcmr3)?;
// Insert Pool for TOKEN 3
let txid3_initial = Txid::from_inner([0xf6; 32]);
let token_id3 = TokenID::from_inner([0xdd; 32]);
let cauldron3_initial =
dummy_cauldron(&txid3_initial, &utxo3, &token_id3, 1000, 500, &owner_pkh);
let txid3_init = Txid::from_inner([0xf6; 32]);
let cauldron3_init = dummy_cauldron(&txid3_init, &utxo3, &token_id3, 1000, 500, &owner_pkh);
insert_new_pool(conn, &cauldron3_init)?;
if let Err(e) = insert_new_pool(conn, &cauldron3_initial) {
println!("Failed to insert pool for TOKEN 3: {e:?}");
}
// Insert auth_chain_entry for TOKEN 3
if let Err(e) = insert_authheader(
insert_authheader(
conn,
&utxo3,
&BlockHash::all_zeros(),
&txid3,
&block_zero,
&txid3_init,
&token_id3,
20,
Some(Vec::from("bcmr_data3".as_bytes())),
) {
println!("Failed to insert auth_chain_entry for TOKEN 3: {e:?}");
}
)?;
// Insert the initial UTXO funding for TOKEN 3 (No additional funding to keep volume at 0)
if let Err(e) =
insert_utxo_funding(conn, &vec![cauldron3_initial.clone()], &txid3_initial, true)
{
println!("Failed to insert initial UTXO funding for TOKEN 3: {e:?}");
}
insert_utxo_funding(conn, &vec![cauldron3_init.clone()], &txid3_init, true)?;
insert_block_tx(conn, &txid3_init, &block_zero, thirty_days_ago)?;
insert_mempool_tx(conn, &txid3_init, thirty_days_ago as u64)?;
// Insert transaction for the initial funding without spending
if let Err(e) = insert_block_tx(conn, &txid3_initial, &block_zero, thirty_days_ago) {
println!("Failed to insert block transaction for TOKEN 3 initial funding: {e:?}");
}
if let Err(e) = insert_mempool_tx(conn, &txid3_initial, thirty_days_ago as u64) {
println!("Failed to insert mempool transaction for TOKEN 3 initial funding: {e:?}");
}
// Insert pool history entry for the initial funding of TOKEN 3 (no spending history)
if let Err(e) = insert_pool_history_entry(
insert_pool_history_entry(
conn,
&utxo3,
&cauldron3_initial,
&cauldron3_init,
Some(thirty_days_ago as u64),
Some(thirty_days_ago as u64),
0, // sats_delta for initial funding (no trading activity)
0, // token_delta for initial funding (no trading activity)
) {
println!("Failed to insert pool history entry for TOKEN 3 initial funding: {e:?}");
}
0,
0,
)?;
// ================== TOKEN 4 (CRC20 Token with Volume) ==================
let utxo4_initial = OutPointHash::from_hex(
"f1d4e2a3b5c4d2f7b6a8e7d3c2f4b9a6d3e1f1c4b7a8e2d3b6c7f9a5d4b1e6c3",
)?;
let token_id4 = TokenID::from_inner([0xdc; 32]); // Unique ID for TOKEN 4
let token_id4 = TokenID::from_inner([0xdc; 32]);
// Insert CRC20 data for TOKEN 4
conn.execute(
"INSERT INTO crc20 (token_id, name, symbol, decimals) VALUES (?, ?, ?, ?)",
params![&token_id4.to_hex(), "TokenFour", "TFOUR", 18],
)?;
// Step 1: Insert initial funding for TOKEN 4
let txid4_initial = Txid::from_inner([0xf4; 32]);
let cauldron4_initial = dummy_cauldron(
&txid4_initial,
let txid4_init = Txid::from_inner([0xf4; 32]);
let cauldron4_init = dummy_cauldron(
&txid4_init,
&utxo4_initial,
&token_id4,
3600,
@ -763,26 +680,11 @@ mod tests {
&owner_pkh,
);
// Insert pool for TOKEN 4
if let Err(e) = insert_new_pool(conn, &cauldron4_initial) {
println!("Failed to insert pool for TOKEN 4: {e:?}");
}
insert_new_pool(conn, &cauldron4_init)?;
insert_utxo_funding(conn, &vec![cauldron4_init.clone()], &txid4_init, true)?;
insert_block_tx(conn, &txid4_init, &block_zero, thirty_days_ago)?;
insert_mempool_tx(conn, &txid4_init, thirty_days_ago as u64)?;
if let Err(e) =
insert_utxo_funding(conn, &vec![cauldron4_initial.clone()], &txid4_initial, true)
{
println!("Failed to insert initial UTXO funding for TOKEN 4: {e:?}");
}
// Insert transaction for the initial funding
if let Err(e) = insert_block_tx(conn, &txid4_initial, &block_zero, thirty_days_ago) {
println!("Failed to insert block transaction for TOKEN 4 initial funding: {e:?}");
}
if let Err(e) = insert_mempool_tx(conn, &txid4_initial, thirty_days_ago as u64) {
println!("Failed to insert mempool transaction for TOKEN 4 initial funding: {e:?}");
}
// Step 2: Insert second funding entry to simulate volume
let txid4_spend = Txid::from_inner([0xf5; 32]);
let utxo4_spent = OutPointHash::from_inner([0xe4; 32]);
let cauldron4_spent = ParsedContract {
@ -797,43 +699,28 @@ mod tests {
token_amount: Some(1500),
};
if let Err(e) =
insert_utxo_funding(conn, &vec![cauldron4_spent.clone()], &txid4_spend, true)
{
println!("Failed to insert spent UTXO funding for TOKEN 4: {e:?}");
}
insert_utxo_funding(conn, &vec![cauldron4_spent.clone()], &txid4_spend, true)?;
insert_block_tx(conn, &txid4_spend, &block_zero, current_timestamp)?;
insert_mempool_tx(conn, &txid4_spend, current_timestamp as u64)?;
// Insert transaction for the spending UTXO
if let Err(e) = insert_block_tx(conn, &txid4_spend, &block_zero, current_timestamp) {
println!("Failed to insert block transaction for TOKEN 4 spending: {e:?}");
}
if let Err(e) = insert_mempool_tx(conn, &txid4_spend, current_timestamp as u64) {
println!("Failed to insert mempool transaction for TOKEN 4 spending: {e:?}");
}
// Step 3: Insert pool history entries for funding and spending
if let Err(e) = insert_pool_history_entry(
insert_pool_history_entry(
conn,
&utxo4_initial,
&cauldron4_initial,
&cauldron4_init,
Some(thirty_days_ago as u64),
Some(thirty_days_ago as u64),
0, // sats_delta for initial funding (no trading activity)
0, // token_delta for initial funding (no trading activity)
) {
println!("Failed to insert pool history entry for TOKEN 4 initial funding: {e:?}");
}
if let Err(e) = insert_pool_history_entry(
0,
0,
)?;
insert_pool_history_entry(
conn,
&utxo4_initial,
&cauldron4_spent,
Some(current_timestamp as u64),
Some(current_timestamp as u64),
5000, // sats_delta for spending (actual trading activity)
1500, // token_delta for spending (actual trading activity)
) {
println!("Failed to insert pool history entry for TOKEN 4 spending: {e:?}");
}
5000,
1500,
)?;
Ok(())
}