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:
commit
1b7801dff8
5 changed files with 334 additions and 299 deletions
|
|
@ -192,6 +192,7 @@ impl BCMRDownloader {
|
||||||
&conn,
|
&conn,
|
||||||
&entry.utxo,
|
&entry.utxo,
|
||||||
&entry.txid,
|
&entry.txid,
|
||||||
|
&entry.token_id,
|
||||||
"Invalid BCMR OP_RETURN in DB",
|
"Invalid BCMR OP_RETURN in DB",
|
||||||
true,
|
true,
|
||||||
) {
|
) {
|
||||||
|
|
@ -211,6 +212,7 @@ impl BCMRDownloader {
|
||||||
&conn,
|
&conn,
|
||||||
&entry.utxo,
|
&entry.utxo,
|
||||||
&entry.txid,
|
&entry.txid,
|
||||||
|
&entry.token_id,
|
||||||
&format!("Failed to fetch BCMR: {error}"),
|
&format!("Failed to fetch BCMR: {error}"),
|
||||||
is_fatal,
|
is_fatal,
|
||||||
) {
|
) {
|
||||||
|
|
@ -227,6 +229,7 @@ impl BCMRDownloader {
|
||||||
&conn,
|
&conn,
|
||||||
&entry.utxo,
|
&entry.utxo,
|
||||||
&entry.txid,
|
&entry.txid,
|
||||||
|
&entry.token_id,
|
||||||
&format!("BCMR invalid JSON error: {e}"),
|
&format!("BCMR invalid JSON error: {e}"),
|
||||||
true,
|
true,
|
||||||
) {
|
) {
|
||||||
|
|
@ -249,6 +252,7 @@ impl BCMRDownloader {
|
||||||
&conn,
|
&conn,
|
||||||
&entry.utxo,
|
&entry.utxo,
|
||||||
&entry.txid,
|
&entry.txid,
|
||||||
|
&entry.token_id,
|
||||||
&format!("BCMR contents error: {err}"),
|
&format!("BCMR contents error: {err}"),
|
||||||
true,
|
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}")
|
warn!("bcmr: Failed to insert BCMR data {err}")
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
196
src/bcmr/mod.rs
196
src/bcmr/mod.rs
|
|
@ -6,7 +6,7 @@
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::Result;
|
||||||
use bitcoin_hashes::{hex::FromHex, hex::ToHex, Hash};
|
use bitcoin_hashes::{hex::FromHex, hex::ToHex, Hash};
|
||||||
use bitcoincash::{BlockHash, Script, TokenID, Transaction, Txid};
|
use bitcoincash::{BlockHash, Script, TokenID, Transaction, Txid};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
|
|
@ -51,65 +51,50 @@ pub struct BCMR {
|
||||||
pub op_return: Vec<u8>,
|
pub op_return: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_parent_auth_entry(conn: &Connection, tx: &Transaction) -> Result<Option<AuthChainEntry>> {
|
fn find_parent_auth_entries(conn: &Connection, tx: &Transaction) -> Result<Vec<AuthChainEntry>> {
|
||||||
let mut best: Option<AuthChainEntry> = None;
|
let mut parents = Vec::new();
|
||||||
|
|
||||||
for vin in &tx.input {
|
for vin in &tx.input {
|
||||||
// BCMR auth chain is always the previous tx’s vout 0
|
|
||||||
if vin.previous_output.vout != 0 {
|
if vin.previous_output.vout != 0 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let prev = &vin.previous_output;
|
let prev = &vin.previous_output;
|
||||||
let prev_utxo: OutPointHash = compute_outpoint_hash(&prev.txid, prev.vout);
|
let prev_utxo: OutPointHash = compute_outpoint_hash(&prev.txid, prev.vout);
|
||||||
|
|
||||||
let mut stmt = conn
|
let mut stmt = conn.prepare(
|
||||||
.prepare(
|
"SELECT token_id, txid, height, bcmr_data, utxo
|
||||||
"SELECT token_id, txid, height, bcmr_data, utxo
|
|
||||||
FROM auth_chain_entry
|
FROM auth_chain_entry
|
||||||
WHERE utxo = ?1
|
WHERE utxo = ?1",
|
||||||
LIMIT 1",
|
)?;
|
||||||
)
|
let mut rows = stmt.query(params![prev_utxo.to_hex()])?;
|
||||||
.context("prepare SELECT parent auth_chain_entry by utxo")?;
|
|
||||||
|
|
||||||
let mut rows = stmt
|
while let Some(row) = rows.next()? {
|
||||||
.query(params![prev_utxo.to_hex()])
|
|
||||||
.context("query parent auth_chain_entry by utxo")?;
|
|
||||||
|
|
||||||
if let Some(row) = rows.next().context("iterate parent rows")? {
|
|
||||||
let token_hex: String = row.get(0)?;
|
let token_hex: String = row.get(0)?;
|
||||||
let txid_hex: String = row.get(1)?;
|
let txid_hex: String = row.get(1)?;
|
||||||
let height: usize = row.get(2)?;
|
let height: usize = row.get(2)?;
|
||||||
let bcmr_data_hex: Option<String> = row.get(3)?;
|
let bcmr_data_hex: Option<String> = row.get(3)?;
|
||||||
let utxo_hex: String = row.get(4)?;
|
let utxo_hex: String = row.get(4)?;
|
||||||
|
|
||||||
let token_id = TokenID::from_hex(&token_hex).context("parse token_id from hex")?;
|
let token_id = TokenID::from_hex(&token_hex)?;
|
||||||
let txid = Txid::from_hex(&txid_hex).context("parse txid from hex")?;
|
let txid = Txid::from_hex(&txid_hex)?;
|
||||||
let utxo = OutPointHash::from_hex(&utxo_hex).context("parse utxo from hex")?;
|
let utxo = OutPointHash::from_hex(&utxo_hex)?;
|
||||||
|
|
||||||
let bcmr_data = if let Some(h) = bcmr_data_hex {
|
let bcmr_data = match bcmr_data_hex {
|
||||||
Some(hex::decode(&h).context("decode bcmr_data hex")?)
|
Some(h) => Some(hex::decode(&h)?),
|
||||||
} else {
|
None => None,
|
||||||
None
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let candidate = AuthChainEntry {
|
parents.push(AuthChainEntry {
|
||||||
utxo,
|
utxo,
|
||||||
token_id,
|
token_id,
|
||||||
txid,
|
txid,
|
||||||
height,
|
height,
|
||||||
bcmr_data,
|
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> {
|
pub fn parse_bcmr_from_opreturn(bcmr_op_return: &Script) -> Option<BCMR> {
|
||||||
|
|
@ -213,13 +198,18 @@ pub fn index_bcmr(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) Update path: if any input spends a known auth utxo, extend that chain
|
let parents = find_parent_auth_entries(conn, tx)?;
|
||||||
if let Some(parent) = find_parent_auth_entry(conn, tx)? {
|
if parents.is_empty() {
|
||||||
let bcmr = parse_bcmr(tx);
|
continue;
|
||||||
let txid = tx.txid();
|
}
|
||||||
let new_utxo = compute_outpoint_hash(&txid, 0);
|
|
||||||
let new_height = parent.height + 1;
|
|
||||||
|
|
||||||
|
// 2) Update path: if any input spends a known auth utxo, extend that chain
|
||||||
|
let bcmr = parse_bcmr(tx);
|
||||||
|
let txid = tx.txid();
|
||||||
|
let new_utxo = compute_outpoint_hash(&txid, 0);
|
||||||
|
|
||||||
|
for parent in parents {
|
||||||
|
let new_height = parent.height + 1;
|
||||||
debug!(
|
debug!(
|
||||||
"Found authheader update {} in {}; has bcmr: {}; new height: {}, utxo: {}",
|
"Found authheader update {} in {}; has bcmr: {}; new height: {}, utxo: {}",
|
||||||
parent.token_id.to_hex(),
|
parent.token_id.to_hex(),
|
||||||
|
|
@ -236,16 +226,138 @@ pub fn index_bcmr(
|
||||||
&txid,
|
&txid,
|
||||||
&parent.token_id,
|
&parent.token_id,
|
||||||
new_height,
|
new_height,
|
||||||
bcmr.map(|b| b.op_return),
|
bcmr.as_ref().map(|b| b.op_return.clone()),
|
||||||
)?;
|
)?;
|
||||||
inserts += 1;
|
inserts += 1;
|
||||||
}
|
}
|
||||||
// else: not a BCMR auth-chain tx → ignore
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(inserts)
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use bitcoincash::{consensus::deserialize, Block};
|
use bitcoincash::{consensus::deserialize, Block};
|
||||||
|
|
@ -427,6 +539,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn olando_issue() {
|
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).
|
// 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
|
// Map: block height -> expected txid of BCMR auth update transaction
|
||||||
|
|
|
||||||
|
|
@ -27,12 +27,13 @@ pub struct AuthChainEntry {
|
||||||
pub fn prepare_tables(conn: &Connection) {
|
pub fn prepare_tables(conn: &Connection) {
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"CREATE TABLE auth_chain_entry (
|
"CREATE TABLE auth_chain_entry (
|
||||||
utxo TEXT PRIMARY KEY,
|
token_id TEXT NOT NULL,
|
||||||
|
utxo TEXT NOT NULL,
|
||||||
blockhash TEXT NOT NULL,
|
blockhash TEXT NOT NULL,
|
||||||
txid TEXT NOT NULL,
|
txid TEXT NOT NULL,
|
||||||
token_id TEXT NOT NULL,
|
height INT NOT NULL,
|
||||||
height INT NOT NULL,
|
bcmr_data TEXT,
|
||||||
bcmr_data TEXT
|
PRIMARY KEY (token_id, utxo)
|
||||||
)",
|
)",
|
||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
|
|
@ -40,17 +41,22 @@ pub fn prepare_tables(conn: &Connection) {
|
||||||
|
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"CREATE TABLE bcmr_data (
|
"CREATE TABLE bcmr_data (
|
||||||
utxo TEXT PRIMARY KEY,
|
token_id TEXT NOT NULL,
|
||||||
symbol TEXT NOT NULL,
|
utxo TEXT NOT NULL,
|
||||||
decimals INT NOT NULL,
|
symbol TEXT NOT NULL,
|
||||||
name TEXT NOT NULL,
|
decimals INT NOT NULL,
|
||||||
description TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
icon TEXT NOT NULL,
|
description TEXT NOT NULL,
|
||||||
web TEXT NOT NULL,
|
icon TEXT NOT NULL,
|
||||||
|
web TEXT NOT NULL,
|
||||||
expected_hash TEXT NOT NULL,
|
expected_hash TEXT NOT NULL,
|
||||||
actual_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");
|
.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)
|
// NOTE: PRIMARY KEY (utxo, txid) is required for ON CONFLICT(utxo, txid)
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"CREATE TABLE bcmr_failure (
|
"CREATE TABLE bcmr_failure (
|
||||||
utxo TEXT NOT NULL,
|
token_id TEXT NOT NULL,
|
||||||
txid TEXT NOT NULL,
|
utxo TEXT NOT NULL,
|
||||||
|
txid TEXT NOT NULL,
|
||||||
last_attempt INT NOT NULL,
|
last_attempt INT NOT NULL,
|
||||||
attempts INT NOT NULL,
|
attempts INT NOT NULL,
|
||||||
error_message TEXT,
|
error_message TEXT,
|
||||||
give_up BOOLEAN,
|
give_up BOOLEAN,
|
||||||
PRIMARY KEY (utxo, txid)
|
PRIMARY KEY (token_id, utxo, txid)
|
||||||
)",
|
)",
|
||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
|
|
@ -94,6 +101,10 @@ pub fn prepare_tables(conn: &Connection) {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.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
|
// Create index for efficient token BCMR lookups
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"CREATE INDEX IF NOT EXISTS idx_auth_chain_token_bcmr_height
|
"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
|
FROM ranked r
|
||||||
LEFT JOIN bcmr_data bd
|
LEFT JOIN bcmr_data bd
|
||||||
ON bd.utxo = r.utxo
|
ON bd.utxo = r.utxo
|
||||||
|
AND bd.token_id = r.token_id
|
||||||
LEFT JOIN bcmr_failure bf
|
LEFT JOIN bcmr_failure bf
|
||||||
ON bf.utxo = r.utxo
|
ON bf.utxo = r.utxo
|
||||||
AND bf.txid = r.txid
|
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)
|
AND (bf.give_up = 1 OR (strftime('%s','now') - bf.last_attempt) < 1800)
|
||||||
WHERE r.rn = 1
|
WHERE r.rn = 1
|
||||||
AND bd.utxo IS NULL
|
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(
|
pub fn insert_bcmr_data(
|
||||||
conn: &rusqlite::Connection,
|
conn: &rusqlite::Connection,
|
||||||
|
token_id: &TokenID,
|
||||||
utxo: &OutPointHash,
|
utxo: &OutPointHash,
|
||||||
bcmr: &ParsedBCMR,
|
bcmr: &ParsedBCMR,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let sql = "INSERT OR REPLACE INTO bcmr_data (utxo, symbol, decimals, name, description, icon, web, expected_hash, actual_hash)
|
let sql = "INSERT OR REPLACE INTO bcmr_data (
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
token_id,
|
||||||
|
utxo,
|
||||||
|
symbol,
|
||||||
|
decimals,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
icon,
|
||||||
|
web,
|
||||||
|
expected_hash,
|
||||||
|
actual_hash
|
||||||
|
)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||||
|
|
||||||
info!("Trying to insert {}", utxo.to_hex());
|
info!("Trying to insert {}", utxo.to_hex());
|
||||||
let empty_string: String = "".to_owned();
|
let empty_string: String = "".to_owned();
|
||||||
|
|
||||||
conn.execute(
|
conn.execute(
|
||||||
sql,
|
sql,
|
||||||
rusqlite::params![
|
rusqlite::params![
|
||||||
|
&token_id.to_hex(),
|
||||||
&utxo.to_hex(),
|
&utxo.to_hex(),
|
||||||
&bcmr.token.symbol,
|
&bcmr.token.symbol,
|
||||||
bcmr.token.decimals,
|
bcmr.token.decimals,
|
||||||
|
|
@ -297,10 +324,11 @@ pub fn insert_bcmr_data(
|
||||||
&bcmr.description,
|
&bcmr.description,
|
||||||
&bcmr.uris.icon.as_ref().unwrap_or(&empty_string),
|
&bcmr.uris.icon.as_ref().unwrap_or(&empty_string),
|
||||||
&bcmr.uris.web.as_ref().unwrap_or(&empty_string),
|
&bcmr.uris.web.as_ref().unwrap_or(&empty_string),
|
||||||
&bcmr.filemeta.expected_hash,
|
&bcmr.filemeta.expected_hash.as_deref().unwrap_or(""),
|
||||||
&bcmr.filemeta.actual_hash
|
&bcmr.filemeta.actual_hash.as_deref().unwrap_or(""),
|
||||||
],
|
],
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -335,26 +363,34 @@ pub fn update_bcmr_failure(
|
||||||
conn: &Connection,
|
conn: &Connection,
|
||||||
utxo: &OutPointHash,
|
utxo: &OutPointHash,
|
||||||
txid: &Txid,
|
txid: &Txid,
|
||||||
|
token_id: &TokenID,
|
||||||
error_message: &str,
|
error_message: &str,
|
||||||
give_up: bool,
|
give_up: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let sql = format!(
|
let sql = format!(
|
||||||
"
|
"
|
||||||
INSERT INTO bcmr_failure (utxo, txid, last_attempt, attempts, error_message, give_up)
|
INSERT INTO bcmr_failure (token_id, utxo, txid, last_attempt, attempts, error_message, give_up)
|
||||||
VALUES (?1, ?2, strftime('%s','now'), 1, ?3, ?4)
|
VALUES (?1, ?2, ?3, strftime('%s','now'), 1, ?4, ?5)
|
||||||
ON CONFLICT(utxo, txid) DO UPDATE SET
|
ON CONFLICT(token_id, utxo, txid) DO UPDATE SET
|
||||||
last_attempt = excluded.last_attempt,
|
last_attempt = excluded.last_attempt,
|
||||||
attempts = bcmr_failure.attempts + 1,
|
attempts = bcmr_failure.attempts + 1,
|
||||||
error_message= excluded.error_message,
|
error_message = excluded.error_message,
|
||||||
give_up = CASE
|
give_up = CASE
|
||||||
WHEN bcmr_failure.attempts + 1 > {max} THEN 1
|
WHEN bcmr_failure.attempts + 1 > {max} THEN 1
|
||||||
ELSE excluded.give_up
|
ELSE excluded.give_up
|
||||||
END",
|
END
|
||||||
max = MAX_DOWNLOAD_ATTEMPTS
|
",
|
||||||
|
max = MAX_DOWNLOAD_ATTEMPTS,
|
||||||
);
|
);
|
||||||
conn.execute(
|
conn.execute(
|
||||||
&sql,
|
&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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -362,18 +398,11 @@ pub fn update_bcmr_failure(
|
||||||
pub fn get_token_bcmr(conn: &Connection, token_hex: &str) -> Result<Option<ParsedBCMR>> {
|
pub fn get_token_bcmr(conn: &Connection, token_hex: &str) -> Result<Option<ParsedBCMR>> {
|
||||||
// Pick *exactly* the head utxo for this token.
|
// Pick *exactly* the head utxo for this token.
|
||||||
let sql = r#"
|
let sql = r#"
|
||||||
SELECT b.symbol, b.decimals, b.name, b.description, b.icon, b.web,
|
SELECT symbol, decimals, name, description, icon, web, actual_hash, expected_hash
|
||||||
b.actual_hash, b.expected_hash
|
FROM bcmr_data
|
||||||
FROM bcmr_data b
|
WHERE token_id = ?
|
||||||
WHERE b.utxo = (
|
ORDER BY ROWID DESC
|
||||||
SELECT ace.utxo
|
LIMIT 1;
|
||||||
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
|
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
let mut stmt = conn.prepare(sql)?;
|
let mut stmt = conn.prepare(sql)?;
|
||||||
|
|
|
||||||
|
|
@ -197,7 +197,7 @@ mod tests {
|
||||||
source: "src".to_string(),
|
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(
|
insert_authheader(
|
||||||
&rw,
|
&rw,
|
||||||
&utxo_a,
|
&utxo_a,
|
||||||
|
|
@ -1035,7 +1035,7 @@ mod tests {
|
||||||
source: "test".into(),
|
source: "test".into(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
insert_bcmr_data(&bcmr_w, &utxo, &row).unwrap();
|
insert_bcmr_data(&bcmr_w, &token, &utxo, &row).unwrap();
|
||||||
insert_authheader(
|
insert_authheader(
|
||||||
&bcmr_w,
|
&bcmr_w,
|
||||||
&utxo,
|
&utxo,
|
||||||
|
|
@ -1101,6 +1101,7 @@ mod tests {
|
||||||
// decimals = 28 OK, but we’ll make price huge by tiny tokens
|
// decimals = 28 OK, but we’ll make price huge by tiny tokens
|
||||||
insert_bcmr_data(
|
insert_bcmr_data(
|
||||||
&bcmr_w,
|
&bcmr_w,
|
||||||
|
&token,
|
||||||
&utxo,
|
&utxo,
|
||||||
&BCMRRow {
|
&BCMRRow {
|
||||||
name: "HugePrice".into(),
|
name: "HugePrice".into(),
|
||||||
|
|
|
||||||
|
|
@ -431,8 +431,9 @@ mod tests {
|
||||||
let utxo1 = OutPointHash::from_hex(
|
let utxo1 = OutPointHash::from_hex(
|
||||||
"a3f1d42e2a5c9f2b5f1b9d7f7c2b19e7a3b1d2c3f4a5e6f2d1c3e4f5a1b2c3d4",
|
"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 {
|
let token1 = Token {
|
||||||
category: "asset_category".to_string(),
|
category: "asset_category".to_string(),
|
||||||
symbol: "TONE".to_string(),
|
symbol: "TONE".to_string(),
|
||||||
|
|
@ -454,101 +455,72 @@ mod tests {
|
||||||
uris: uris1,
|
uris: uris1,
|
||||||
filemeta: filemeta1,
|
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 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);
|
let cauldron1 = dummy_cauldron(&txid1, &utxo1, &token_id1, 1000, 500, &owner_pkh);
|
||||||
if let Err(e) = insert_new_pool(conn, &cauldron1) {
|
insert_new_pool(conn, &cauldron1)?;
|
||||||
println!("Failed to insert pool for token1: {e:?}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 3: Insert auth_chain_entry for token1
|
insert_authheader(
|
||||||
if let Err(e) = insert_authheader(
|
|
||||||
conn,
|
conn,
|
||||||
&utxo1,
|
&utxo1,
|
||||||
&BlockHash::all_zeros(),
|
&block_zero,
|
||||||
&txid1,
|
&txid1,
|
||||||
&token_id1,
|
&token_id1,
|
||||||
10,
|
10,
|
||||||
Some(Vec::from("bcmr_data1".as_bytes())),
|
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
|
insert_utxo_funding(conn, &vec![cauldron1.clone()], &txid1, true)?;
|
||||||
if let Err(e) = insert_utxo_funding(conn, &vec![cauldron1.clone()], &txid1, true) {
|
insert_block_tx(conn, &txid1, &block_zero, thirty_days_ago)?;
|
||||||
println!("Failed to insert initial UTXO funding for token1: {e:?}");
|
insert_mempool_tx(conn, &txid1, thirty_days_ago as u64)?;
|
||||||
}
|
|
||||||
|
|
||||||
// Step 5: Insert the transaction for utxo1
|
// Simulate volume on token1
|
||||||
if let Err(e) = insert_block_tx(conn, &txid1, &block_zero, thirty_days_ago) {
|
let txid1_spend = Txid::from_inner([0xf1; 32]);
|
||||||
println!("Failed to insert block transaction for utxo1: {e:?}");
|
let utxo1_spend = OutPointHash::from_inner([0xe1; 32]);
|
||||||
}
|
let cauldron1_spent = ParsedContract {
|
||||||
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 {
|
|
||||||
pkh: owner_pkh,
|
pkh: owner_pkh,
|
||||||
is_withdrawn: false,
|
is_withdrawn: false,
|
||||||
spent_utxo_hash: utxo1,
|
spent_utxo_hash: utxo1,
|
||||||
new_utxo_hash: Some(utxo2),
|
new_utxo_hash: Some(utxo1_spend),
|
||||||
new_utxo_txid: Some(txid2),
|
new_utxo_txid: Some(txid1_spend),
|
||||||
new_utxo_n: Some(0),
|
new_utxo_n: Some(0),
|
||||||
token_id: Some(token_id1),
|
token_id: Some(token_id1),
|
||||||
sats: Some(2000),
|
sats: Some(2000),
|
||||||
token_amount: Some(1000),
|
token_amount: Some(1000),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = insert_utxo_funding(conn, &vec![cauldron2.clone()], &txid2, true) {
|
insert_utxo_funding(conn, &vec![cauldron1_spent.clone()], &txid1_spend, true)?;
|
||||||
println!("Failed to insert second UTXO funding for token1: {e:?}");
|
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)
|
insert_pool_history_entry(
|
||||||
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(
|
|
||||||
conn,
|
conn,
|
||||||
&utxo1,
|
&utxo1,
|
||||||
&cauldron1.clone(),
|
&cauldron1,
|
||||||
Some(thirty_days_ago as u64),
|
Some(thirty_days_ago as u64),
|
||||||
Some(thirty_days_ago as u64),
|
Some(thirty_days_ago as u64),
|
||||||
0, // sats_delta for initial funding (no trading activity)
|
0,
|
||||||
0, // token_delta for initial funding (no trading activity)
|
0,
|
||||||
) {
|
)?;
|
||||||
println!("Failed to insert pool history entry for initial funding of token1: {e:?}");
|
insert_pool_history_entry(
|
||||||
}
|
|
||||||
if let Err(e) = insert_pool_history_entry(
|
|
||||||
conn,
|
conn,
|
||||||
&utxo1,
|
&utxo1,
|
||||||
&cauldron2,
|
&cauldron1_spent,
|
||||||
Some(current_timestamp as u64),
|
Some(current_timestamp as u64),
|
||||||
Some(current_timestamp as u64),
|
Some(current_timestamp as u64),
|
||||||
2000, // sats_delta for spending (actual trading activity)
|
2000,
|
||||||
1000, // token_delta for spending (actual trading activity)
|
1000,
|
||||||
) {
|
)?;
|
||||||
println!("Failed to insert pool history entry for spending of token1: {e:?}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ================== TOKEN 2 (BCMR Token) ==================
|
// ================== TOKEN 2 (BCMR Token) ==================
|
||||||
let utxo2 = OutPointHash::from_hex(
|
let utxo2 = OutPointHash::from_hex(
|
||||||
"c3d2e1f4b6a7c8d1e2f5d7c3b1a9e4f2b1d3f4e6c2b9f3a8d1e4f5b6c3d2e7a4",
|
"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 {
|
let token2 = Token {
|
||||||
category: "asset_category_2".to_string(),
|
category: "asset_category_2".to_string(),
|
||||||
symbol: "TWO".to_string(),
|
symbol: "TWO".to_string(),
|
||||||
|
|
@ -570,99 +542,71 @@ mod tests {
|
||||||
uris: uris2,
|
uris: uris2,
|
||||||
filemeta: filemeta2,
|
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
|
insert_bcmr_data(conn, &token_id2, &utxo2, &parsed_bcmr2)?;
|
||||||
let token_id2 = TokenID::from_inner([0xdb; 32]);
|
|
||||||
let cauldron2 = dummy_cauldron(
|
let txid2_init = Txid::from_inner([0xf2; 32]);
|
||||||
&Txid::from_inner([0xf2; 32]),
|
let cauldron2 = dummy_cauldron(&txid2_init, &utxo2, &token_id2, 1500, 700, &owner_pkh);
|
||||||
&utxo2,
|
|
||||||
&token_id2,
|
|
||||||
1500,
|
|
||||||
700,
|
|
||||||
&owner_pkh,
|
|
||||||
);
|
|
||||||
insert_new_pool(conn, &cauldron2)?;
|
insert_new_pool(conn, &cauldron2)?;
|
||||||
|
|
||||||
// Step 9: Insert auth_chain_entry for token2
|
insert_authheader(
|
||||||
if let Err(e) = insert_authheader(
|
|
||||||
conn,
|
conn,
|
||||||
&utxo2,
|
&utxo2,
|
||||||
&BlockHash::all_zeros(),
|
&block_zero,
|
||||||
&txid2,
|
&txid2_init,
|
||||||
&token_id2,
|
&token_id2,
|
||||||
15,
|
15,
|
||||||
Some(Vec::from("bcmr_data2".as_bytes())),
|
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
|
insert_utxo_funding(conn, &vec![cauldron2.clone()], &txid2_init, true)?;
|
||||||
let txid2 = Txid::from_inner([0xf2; 32]);
|
insert_block_tx(conn, &txid2_init, &block_zero, thirty_days_ago)?;
|
||||||
insert_block_tx(conn, &txid2, &block_zero, thirty_days_ago)?;
|
insert_mempool_tx(conn, &txid2_init, thirty_days_ago as u64)?;
|
||||||
insert_mempool_tx(conn, &txid2, thirty_days_ago as u64)?;
|
|
||||||
|
|
||||||
// Step 5: Define cauldron3 for token2 with spent_utxo_hash referring to utxo2, then insert it
|
// Simulate second funding for token2
|
||||||
let txid3 = Txid::from_inner([0xf3; 32]);
|
let txid2_spend = Txid::from_inner([0xf3; 32]);
|
||||||
let utxo3 = OutPointHash::from_inner([0xe2; 32]);
|
let utxo2_spend = OutPointHash::from_inner([0xe2; 32]);
|
||||||
|
let cauldron2_spent = ParsedContract {
|
||||||
let cauldron3 = ParsedContract {
|
|
||||||
pkh: owner_pkh,
|
pkh: owner_pkh,
|
||||||
is_withdrawn: false,
|
is_withdrawn: false,
|
||||||
spent_utxo_hash: utxo2,
|
spent_utxo_hash: utxo2,
|
||||||
new_utxo_hash: Some(utxo3),
|
new_utxo_hash: Some(utxo2_spend),
|
||||||
new_utxo_txid: Some(txid3),
|
new_utxo_txid: Some(txid2_spend),
|
||||||
new_utxo_n: Some(0),
|
new_utxo_n: Some(0),
|
||||||
token_id: Some(token_id2),
|
token_id: Some(token_id2),
|
||||||
sats: Some(3000),
|
sats: Some(3000),
|
||||||
token_amount: Some(1500),
|
token_amount: Some(1500),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Insert the second funding with `spent_utxo_hash` correctly set
|
insert_utxo_funding(conn, &vec![cauldron2_spent.clone()], &txid2_spend, true)?;
|
||||||
insert_utxo_funding(conn, &vec![cauldron3.clone()], &txid3, 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(
|
insert_pool_history_entry(
|
||||||
conn,
|
conn,
|
||||||
&utxo2,
|
&utxo2,
|
||||||
&cauldron2.clone(),
|
&cauldron2,
|
||||||
Some(thirty_days_ago as u64),
|
Some(thirty_days_ago as u64),
|
||||||
Some(thirty_days_ago as u64),
|
Some(thirty_days_ago as u64),
|
||||||
0, // sats_delta for initial funding (no trading activity)
|
0,
|
||||||
0, // token_delta for initial funding (no trading activity)
|
0,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Step 8: Insert pool history entry for the spending of utxo2 (creation of utxo3)
|
|
||||||
insert_pool_history_entry(
|
insert_pool_history_entry(
|
||||||
conn,
|
conn,
|
||||||
&utxo2,
|
&utxo2,
|
||||||
&cauldron3,
|
&cauldron2_spent,
|
||||||
Some(current_timestamp as u64),
|
Some(current_timestamp as u64),
|
||||||
Some(current_timestamp as u64),
|
Some(current_timestamp as u64),
|
||||||
3000, // sats_delta for spending (actual trading activity)
|
3000,
|
||||||
1500, // token_delta for spending (actual trading activity)
|
1500,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// ================== TOKEN 3 (BCMR Token with No Volume) ==================
|
// ================== TOKEN 3 (BCMR Token with No Volume) ==================
|
||||||
let utxo3 = OutPointHash::from_hex(
|
let utxo3 = OutPointHash::from_hex(
|
||||||
"d5e1f2a3b4c3d2f5b6a8e7d3c2f4b9a6d2e3f1c4b5a9e3d1b2c5f7a3d4b8e2c3",
|
"d5e1f2a3b4c3d2f5b6a8e7d3c2f4b9a6d2e3f1c4b5a9e3d1b2c5f7a3d4b8e2c3",
|
||||||
)?;
|
)?;
|
||||||
|
let token_id3 = TokenID::from_inner([0xdd; 32]);
|
||||||
|
|
||||||
// Prepare and insert BCMR data for TOKEN 3 with unique values
|
|
||||||
let token3 = Token {
|
let token3 = Token {
|
||||||
category: "asset_category_3".to_string(),
|
category: "asset_category_3".to_string(),
|
||||||
symbol: "TN3".to_string(),
|
symbol: "TN3".to_string(),
|
||||||
|
|
@ -685,77 +629,50 @@ mod tests {
|
||||||
filemeta: filemeta3,
|
filemeta: filemeta3,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = insert_bcmr_data(conn, &utxo3, &parsed_bcmr3) {
|
insert_bcmr_data(conn, &token_id3, &utxo3, &parsed_bcmr3)?;
|
||||||
println!("Failed to insert BCMR data for TOKEN 3: {e:?}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert Pool for TOKEN 3
|
let txid3_init = Txid::from_inner([0xf6; 32]);
|
||||||
let txid3_initial = Txid::from_inner([0xf6; 32]);
|
let cauldron3_init = dummy_cauldron(&txid3_init, &utxo3, &token_id3, 1000, 500, &owner_pkh);
|
||||||
let token_id3 = TokenID::from_inner([0xdd; 32]);
|
insert_new_pool(conn, &cauldron3_init)?;
|
||||||
let cauldron3_initial =
|
|
||||||
dummy_cauldron(&txid3_initial, &utxo3, &token_id3, 1000, 500, &owner_pkh);
|
|
||||||
|
|
||||||
if let Err(e) = insert_new_pool(conn, &cauldron3_initial) {
|
insert_authheader(
|
||||||
println!("Failed to insert pool for TOKEN 3: {e:?}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert auth_chain_entry for TOKEN 3
|
|
||||||
if let Err(e) = insert_authheader(
|
|
||||||
conn,
|
conn,
|
||||||
&utxo3,
|
&utxo3,
|
||||||
&BlockHash::all_zeros(),
|
&block_zero,
|
||||||
&txid3,
|
&txid3_init,
|
||||||
&token_id3,
|
&token_id3,
|
||||||
20,
|
20,
|
||||||
Some(Vec::from("bcmr_data3".as_bytes())),
|
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)
|
insert_utxo_funding(conn, &vec![cauldron3_init.clone()], &txid3_init, true)?;
|
||||||
if let Err(e) =
|
insert_block_tx(conn, &txid3_init, &block_zero, thirty_days_ago)?;
|
||||||
insert_utxo_funding(conn, &vec![cauldron3_initial.clone()], &txid3_initial, true)
|
insert_mempool_tx(conn, &txid3_init, thirty_days_ago as u64)?;
|
||||||
{
|
|
||||||
println!("Failed to insert initial UTXO funding for TOKEN 3: {e:?}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert transaction for the initial funding without spending
|
insert_pool_history_entry(
|
||||||
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(
|
|
||||||
conn,
|
conn,
|
||||||
&utxo3,
|
&utxo3,
|
||||||
&cauldron3_initial,
|
&cauldron3_init,
|
||||||
Some(thirty_days_ago as u64),
|
Some(thirty_days_ago as u64),
|
||||||
Some(thirty_days_ago as u64),
|
Some(thirty_days_ago as u64),
|
||||||
0, // sats_delta for initial funding (no trading activity)
|
0,
|
||||||
0, // token_delta for initial funding (no trading activity)
|
0,
|
||||||
) {
|
)?;
|
||||||
println!("Failed to insert pool history entry for TOKEN 3 initial funding: {e:?}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ================== TOKEN 4 (CRC20 Token with Volume) ==================
|
// ================== TOKEN 4 (CRC20 Token with Volume) ==================
|
||||||
let utxo4_initial = OutPointHash::from_hex(
|
let utxo4_initial = OutPointHash::from_hex(
|
||||||
"f1d4e2a3b5c4d2f7b6a8e7d3c2f4b9a6d3e1f1c4b7a8e2d3b6c7f9a5d4b1e6c3",
|
"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(
|
conn.execute(
|
||||||
"INSERT INTO crc20 (token_id, name, symbol, decimals) VALUES (?, ?, ?, ?)",
|
"INSERT INTO crc20 (token_id, name, symbol, decimals) VALUES (?, ?, ?, ?)",
|
||||||
params![&token_id4.to_hex(), "TokenFour", "TFOUR", 18],
|
params![&token_id4.to_hex(), "TokenFour", "TFOUR", 18],
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Step 1: Insert initial funding for TOKEN 4
|
let txid4_init = Txid::from_inner([0xf4; 32]);
|
||||||
let txid4_initial = Txid::from_inner([0xf4; 32]);
|
let cauldron4_init = dummy_cauldron(
|
||||||
let cauldron4_initial = dummy_cauldron(
|
&txid4_init,
|
||||||
&txid4_initial,
|
|
||||||
&utxo4_initial,
|
&utxo4_initial,
|
||||||
&token_id4,
|
&token_id4,
|
||||||
3600,
|
3600,
|
||||||
|
|
@ -763,26 +680,11 @@ mod tests {
|
||||||
&owner_pkh,
|
&owner_pkh,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Insert pool for TOKEN 4
|
insert_new_pool(conn, &cauldron4_init)?;
|
||||||
if let Err(e) = insert_new_pool(conn, &cauldron4_initial) {
|
insert_utxo_funding(conn, &vec![cauldron4_init.clone()], &txid4_init, true)?;
|
||||||
println!("Failed to insert pool for TOKEN 4: {e:?}");
|
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 txid4_spend = Txid::from_inner([0xf5; 32]);
|
||||||
let utxo4_spent = OutPointHash::from_inner([0xe4; 32]);
|
let utxo4_spent = OutPointHash::from_inner([0xe4; 32]);
|
||||||
let cauldron4_spent = ParsedContract {
|
let cauldron4_spent = ParsedContract {
|
||||||
|
|
@ -797,43 +699,28 @@ mod tests {
|
||||||
token_amount: Some(1500),
|
token_amount: Some(1500),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) =
|
insert_utxo_funding(conn, &vec![cauldron4_spent.clone()], &txid4_spend, true)?;
|
||||||
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)?;
|
||||||
println!("Failed to insert spent UTXO funding for TOKEN 4: {e:?}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert transaction for the spending UTXO
|
insert_pool_history_entry(
|
||||||
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(
|
|
||||||
conn,
|
conn,
|
||||||
&utxo4_initial,
|
&utxo4_initial,
|
||||||
&cauldron4_initial,
|
&cauldron4_init,
|
||||||
Some(thirty_days_ago as u64),
|
Some(thirty_days_ago as u64),
|
||||||
Some(thirty_days_ago as u64),
|
Some(thirty_days_ago as u64),
|
||||||
0, // sats_delta for initial funding (no trading activity)
|
0,
|
||||||
0, // token_delta for initial funding (no trading activity)
|
0,
|
||||||
) {
|
)?;
|
||||||
println!("Failed to insert pool history entry for TOKEN 4 initial funding: {e:?}");
|
insert_pool_history_entry(
|
||||||
}
|
|
||||||
if let Err(e) = insert_pool_history_entry(
|
|
||||||
conn,
|
conn,
|
||||||
&utxo4_initial,
|
&utxo4_initial,
|
||||||
&cauldron4_spent,
|
&cauldron4_spent,
|
||||||
Some(current_timestamp as u64),
|
Some(current_timestamp as u64),
|
||||||
Some(current_timestamp as u64),
|
Some(current_timestamp as u64),
|
||||||
5000, // sats_delta for spending (actual trading activity)
|
5000,
|
||||||
1500, // token_delta for spending (actual trading activity)
|
1500,
|
||||||
) {
|
)?;
|
||||||
println!("Failed to insert pool history entry for TOKEN 4 spending: {e:?}");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue