Merge branch 'bug-bcmr' into 'master'

Fix bcmr_failure unique constraint for multi-token transactions

Closes #17

See merge request riftenlabs/riftenlabs-indexer!50
This commit is contained in:
Dagur Valberg Johannsson 2026-01-21 10:01:23 +00:00
commit 16ed485711

View file

@ -61,7 +61,6 @@ pub fn prepare_tables(conn: &Connection) {
)
.expect("failed to create bcmr_data table");
// NOTE: PRIMARY KEY (utxo, txid) is required for ON CONFLICT(utxo, txid)
conn.execute(
"CREATE TABLE bcmr_failure (
token_id TEXT NOT NULL,
@ -133,13 +132,6 @@ pub fn prepare_tables(conn: &Connection) {
)
.expect("failed to create idx_bcmr_data_utxo");
// Enforce per-(utxo, txid) backoff and dedupe updates
conn.execute(
"CREATE UNIQUE INDEX IF NOT EXISTS idx_bcmr_failure_utxo_txid ON bcmr_failure(utxo, txid)",
[],
)
.expect("failed to create idx_bcmr_failure_utxo_txid");
// Optional: if you often filter by recent failures/backoff window
conn.execute(
"CREATE INDEX IF NOT EXISTS idx_bcmr_failure_last_attempt ON bcmr_failure(last_attempt)",
@ -483,3 +475,66 @@ pub fn get_well_known_bcmr(conn: &Connection, token_hex: &str) -> Result<Vec<Par
Ok(entries)
}
#[cfg(test)]
mod tests {
use super::*;
use bitcoin_hashes::hex::FromHex;
#[test]
fn test_update_bcmr_failure_multiple_tokens_same_utxo_txid() {
// Test that bcmr_failure can record failures for multiple tokens
// that share the same (utxo, txid) - this happens when a single
// transaction updates the auth chain for multiple tokens.
let conn = Connection::open_in_memory().unwrap();
prepare_tables(&conn);
// Two different tokens
let token_a =
TokenID::from_hex("0101010101010101010101010101010101010101010101010101010101010101")
.unwrap();
let token_b =
TokenID::from_hex("0202020202020202020202020202020202020202020202020202020202020202")
.unwrap();
// Same utxo and txid (simulates a tx that updates auth chain for both tokens)
let utxo = OutPointHash::from_hex(
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
)
.unwrap();
let txid =
Txid::from_hex("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
.unwrap();
// Record failure for token A - should succeed
update_bcmr_failure(&conn, &utxo, &txid, &token_a, "error A", false)
.expect("first update_bcmr_failure should succeed");
// Record failure for token B with same (utxo, txid) - should also succeed
update_bcmr_failure(&conn, &utxo, &txid, &token_b, "error B", false).expect(
"second update_bcmr_failure with same utxo/txid but different token should succeed",
);
// Verify both entries exist
let count: i64 = conn
.query_row("SELECT COUNT(*) FROM bcmr_failure", [], |row| row.get(0))
.unwrap();
assert_eq!(count, 2, "should have 2 failure entries");
// Verify we can update both independently
update_bcmr_failure(&conn, &utxo, &txid, &token_a, "error A updated", false)
.expect("updating token A failure should succeed");
update_bcmr_failure(&conn, &utxo, &txid, &token_b, "error B updated", false)
.expect("updating token B failure should succeed");
// Still only 2 entries (updates, not new inserts)
let count: i64 = conn
.query_row("SELECT COUNT(*) FROM bcmr_failure", [], |row| row.get(0))
.unwrap();
assert_eq!(
count, 2,
"should still have 2 failure entries after updates"
);
}
}