2024-03-04 16:40:50 +01:00
// Copyright (C) 2024 Riften Labs AS
//
// 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 anyhow ::Result ;
2024-09-25 11:56:55 +02:00
use bitcoin_hashes ::hex ::{ FromHex , ToHex } ;
use bitcoincash ::{ BlockHash , TokenID , Txid } ;
2024-03-04 16:40:50 +01:00
use rusqlite ::{ params , Connection } ;
pub fn create_table ( conn : & Connection ) {
let tbl = " CREATE TABLE tx (
txid TEXT PRIMARY KEY ,
blockhash TEXT ,
mtp_timestamp BIGINT ,
first_seen_timestamp BIGINT
) " ;
conn . execute ( tbl , [ ] ) . expect ( " failed to create tx table " ) ;
2025-08-15 22:22:07 +02:00
// Create index for volume queries that filter by COALESCE(timestamp)
conn . execute ( " CREATE INDEX idx_tx_coalesce_timestamp ON tx(COALESCE(first_seen_timestamp, mtp_timestamp)) " , [ ] ) . expect ( " failed to create tx timestamp index " ) ;
2024-03-04 16:40:50 +01:00
}
pub fn insert_block_tx (
conn : & rusqlite ::Connection ,
txid : & Txid ,
blockhash : & BlockHash ,
mtp : i64 ,
) -> Result < ( ) > {
let sql = " INSERT INTO tx (txid, blockhash, mtp_timestamp)
VALUES ( ? 1 , ? 2 , ? 3 )
ON CONFLICT ( txid ) DO UPDATE SET
blockhash = excluded . blockhash ,
mtp_timestamp = excluded . mtp_timestamp " ;
conn . execute ( sql , params! [ & txid . to_hex ( ) , & blockhash . to_hex ( ) , & mtp ] ) ? ;
Ok ( ( ) )
}
2024-10-08 17:21:22 +02:00
pub fn insert_mempool_tx (
conn : & rusqlite ::Connection ,
txid : & Txid ,
first_seen_timestamp : u64 ,
) -> Result < ( ) > {
2024-03-04 16:40:50 +01:00
let sql = " INSERT INTO tx (txid, first_seen_timestamp)
VALUES ( ? 1 , ? 2 )
ON CONFLICT ( txid ) DO UPDATE SET
first_seen_timestamp = excluded . first_seen_timestamp " ;
2024-10-08 17:21:22 +02:00
conn . execute ( sql , params! [ & txid . to_hex ( ) , first_seen_timestamp ] ) ? ;
2024-03-04 16:40:50 +01:00
Ok ( ( ) )
}
2024-09-25 11:56:55 +02:00
pub fn latest (
conn : & rusqlite ::Connection ,
limit : usize ,
offset : usize ,
token_id : Option < TokenID > ,
) -> Result < Vec < ( Txid , Option < BlockHash > , u64 ) > , rusqlite ::Error > {
let ( sql , params ) = match token_id {
Some ( tid ) = > (
" SELECT DISTINCT tx.txid, tx.blockhash, tx.mtp_timestamp, tx.first_seen_timestamp
FROM tx
JOIN utxo_funding ON tx . txid = utxo_funding . txid
WHERE utxo_funding . token_id = ?
ORDER BY COALESCE ( tx . first_seen_timestamp , tx . mtp_timestamp ) DESC
LIMIT ? OFFSET ? " ,
params! [ tid . to_hex ( ) , limit , offset ] ,
) ,
None = > (
" SELECT tx.txid, tx.blockhash, tx.mtp_timestamp, tx.first_seen_timestamp
FROM tx
ORDER BY COALESCE ( tx . first_seen_timestamp , tx . mtp_timestamp ) DESC
LIMIT ? OFFSET ? " ,
params! [ limit , offset ] ,
) ,
} ;
let mut stmt = conn . prepare ( sql ) ? ;
let tx_iter = stmt . query_map ( params , | row | {
let txid_hex : String = row . get ( 0 ) ? ;
let blockhash_hex : Option < String > = row . get ( 1 ) ? ;
let mtp_timestamp : Option < u64 > = row . get ( 2 ) . ok ( ) ; // Handle potential NULL
let first_seen_timestamp : Option < u64 > = row . get ( 3 ) . ok ( ) ; // Handle potential NULL
// Use first_seen_timestamp if available, otherwise mtp_timestamp, or return an error if both are NULL
let timestamp = first_seen_timestamp . or ( mtp_timestamp ) . ok_or_else ( | | {
rusqlite ::Error ::InvalidColumnType (
2 ,
" timestamp " . to_string ( ) ,
rusqlite ::types ::Type ::Null ,
)
} ) ? ;
Ok ( (
Txid ::from_hex ( & txid_hex ) . expect ( " Invalid Txid hex " ) ,
blockhash_hex . map ( | hex | BlockHash ::from_hex ( & hex ) . expect ( " Invalid BlockHash hex " ) ) ,
timestamp ,
) )
} ) ? ;
let mut txs = Vec ::with_capacity ( limit ) ;
for tx in tx_iter {
txs . push ( tx ? ) ;
}
Ok ( txs )
}