From fd5da52df4f92a6a55a0149cf6b28f264afecc30 Mon Sep 17 00:00:00 2001 From: Ludo Galabru Date: Thu, 25 May 2023 15:35:14 -0400 Subject: [PATCH] feat: handle transfer --- components/chainhook-cli/src/cli/mod.rs | 6 +- .../src/hord/db/mod.rs | 92 ++++++++++++-- .../chainhook-event-observer/src/hord/mod.rs | 112 +++++++++--------- components/chainhook-types-rs/src/rosetta.rs | 1 + 4 files changed, 142 insertions(+), 69 deletions(-) diff --git a/components/chainhook-cli/src/cli/mod.rs b/components/chainhook-cli/src/cli/mod.rs index be5d9d8..bb6fe3f 100644 --- a/components/chainhook-cli/src/cli/mod.rs +++ b/components/chainhook-cli/src/cli/mod.rs @@ -15,9 +15,8 @@ use chainhook_event_observer::chainhooks::types::{ use chainhook_event_observer::hord::db::{ delete_data_in_hord_db, fetch_and_cache_blocks_in_hord_db, find_last_block_inserted, find_lazy_block_at_block_height, find_watched_satpoint_for_inscription, initialize_hord_db, - insert_entry_in_blocks, open_readonly_hord_db_conn, open_readonly_hord_db_conn_rocks_db, - open_readwrite_hord_db_conn, open_readwrite_hord_db_conn_rocks_db, - retrieve_satoshi_point_using_lazy_storage, LazyBlock, + open_readonly_hord_db_conn, open_readonly_hord_db_conn_rocks_db, open_readwrite_hord_db_conn, + open_readwrite_hord_db_conn_rocks_db, retrieve_satoshi_point_using_lazy_storage, }; use chainhook_event_observer::hord::{ new_traversals_lazy_cache, retrieve_inscribed_satoshi_points_from_block, @@ -643,6 +642,7 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> { &block_identifier, &transaction_identifier, 0, + 0, Arc::new(traversals_cache), &ctx, )?; diff --git a/components/chainhook-event-observer/src/hord/db/mod.rs b/components/chainhook-event-observer/src/hord/db/mod.rs index 08cd3dd..17271cc 100644 --- a/components/chainhook-event-observer/src/hord/db/mod.rs +++ b/components/chainhook-event-observer/src/hord/db/mod.rs @@ -425,7 +425,7 @@ pub fn find_inscription_with_id( ) -> Option { let args: &[&dyn ToSql] = &[&inscription_id.to_sql().unwrap()]; let mut stmt = inscriptions_db_conn - .prepare("SELECT inscription_number, ordinal_number, block_hash FROM inscriptions WHERE inscription_id = ?") + .prepare("SELECT inscription_number, ordinal_number, block_hash, offset, outpoint_to_watch FROM inscriptions WHERE inscription_id = ?") .unwrap(); let mut rows = stmt.query(args).unwrap(); while let Ok(Some(row)) = rows.next() { @@ -433,9 +433,13 @@ pub fn find_inscription_with_id( if block_hash.eq(&inscription_block_hash) { let inscription_number: i64 = row.get(0).unwrap(); let ordinal_number: u64 = row.get(1).unwrap(); + let inscription_offset: u64 = row.get(3).unwrap(); + let outpoint_to_watch: String = row.get(4).unwrap(); let traversal = TraversalResult { inscription_number, ordinal_number, + inscription_offset, + outpoint_to_watch, transfers: 0, }; return Some(traversal); @@ -449,7 +453,7 @@ pub fn find_all_inscriptions( ) -> BTreeMap> { let args: &[&dyn ToSql] = &[]; let mut stmt = inscriptions_db_conn - .prepare("SELECT inscription_number, ordinal_number, block_height, inscription_id FROM inscriptions ORDER BY inscription_number ASC") + .prepare("SELECT inscription_number, ordinal_number, block_height, inscription_id, offset, outpoint_to_watch FROM inscriptions ORDER BY inscription_number ASC") .unwrap(); let mut results: BTreeMap> = BTreeMap::new(); let mut rows = stmt.query(args).unwrap(); @@ -463,10 +467,14 @@ pub fn find_all_inscriptions( hash: format!("0x{}", &inscription_id[0..inscription_id.len() - 2]), } }; + let inscription_offset: u64 = row.get(4).unwrap(); + let outpoint_to_watch: String = row.get(5).unwrap(); let traversal = TraversalResult { inscription_number, ordinal_number, transfers: 0, + inscription_offset, + outpoint_to_watch, }; results .entry(block_height) @@ -817,6 +825,8 @@ pub async fn fetch_and_cache_blocks_in_hord_db( #[derive(Clone, Debug)] pub struct TraversalResult { pub inscription_number: i64, + pub inscription_offset: u64, + pub outpoint_to_watch: String, pub ordinal_number: u64, pub transfers: u32, } @@ -880,6 +890,7 @@ pub fn retrieve_satoshi_point_using_lazy_storage( blocks_db: &DB, block_identifier: &BlockIdentifier, transaction_identifier: &TransactionIdentifier, + input_index: usize, inscription_number: i64, traversals_cache: Arc< DashMap<(u32, [u8; 8]), LazyBlockTransaction, BuildHasherDefault>, @@ -894,16 +905,13 @@ pub fn retrieve_satoshi_point_using_lazy_storage( block_identifier.index ) }); - + let mut inscription_localized = false; + let mut inscription_offset = 0; + let mut inscription_output_index: usize = 0; let mut ordinal_offset = 0; let mut ordinal_block_number = block_identifier.index as u32; - let txid = { - let bytes = hex::decode(&transaction_identifier.hash[2..]).unwrap(); - [ - bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], - ] - }; - let mut tx_cursor = (txid, 0); + let txid = transaction_identifier.get_8_hash_bytes(); + let mut tx_cursor = (txid, input_index); let mut hops: u32 = 0; loop { hops += 1; @@ -916,6 +924,31 @@ pub fn retrieve_satoshi_point_using_lazy_storage( if let Some(cached_tx) = traversals_cache.get(&(ordinal_block_number, tx_cursor.0)) { let tx = cached_tx.value(); + + if !inscription_localized { + inscription_localized = true; + let mut sats_ranges = vec![]; + let mut bound = 0; + for output_value in tx.outputs.iter() { + sats_ranges.push((bound, bound + output_value)); + bound += output_value; + } + let mut input_offset = 0; + for (i, input) in tx.inputs.iter().enumerate() { + if i == input_index { + break; + } + input_offset += input.txin_value; + } + + for (i, (min, max)) in sats_ranges.into_iter().enumerate() { + if input_offset >= min && input_offset < max { + inscription_output_index = i; + inscription_offset = input_offset - min; + } + } + } + let mut next_found_in_cache = false; let mut sats_out = 0; for (index, output_value) in tx.outputs.iter().enumerate() { @@ -955,6 +988,11 @@ pub fn retrieve_satoshi_point_using_lazy_storage( inscription_number: 0, ordinal_number: 0, transfers: 0, + inscription_offset, + outpoint_to_watch: format_outpoint_to_watch( + &transaction_identifier, + inscription_output_index, + ), }); } } @@ -1022,6 +1060,30 @@ pub fn retrieve_satoshi_point_using_lazy_storage( None => unreachable!(), }; + if !inscription_localized { + inscription_localized = true; + let mut sats_ranges = vec![]; + let mut bound = 0; + for output_value in lazy_tx.outputs.iter() { + sats_ranges.push((bound, bound + output_value)); + bound += output_value; + } + let mut input_offset = 0; + for (i, input) in lazy_tx.inputs.iter().enumerate() { + if i == input_index { + break; + } + input_offset += input.txin_value; + } + + for (i, (min, max)) in sats_ranges.into_iter().enumerate() { + if input_offset >= min && input_offset < max { + inscription_output_index = i; + inscription_offset = input_offset - min; + } + } + } + let mut sats_out = 0; for (index, output_value) in lazy_tx.outputs.iter().enumerate() { if index == tx_cursor.1 { @@ -1056,6 +1118,11 @@ pub fn retrieve_satoshi_point_using_lazy_storage( inscription_number: 0, ordinal_number: 0, transfers: 0, + inscription_offset, + outpoint_to_watch: format_outpoint_to_watch( + &transaction_identifier, + inscription_output_index, + ), }); } } @@ -1068,6 +1135,11 @@ pub fn retrieve_satoshi_point_using_lazy_storage( inscription_number, ordinal_number, transfers: hops, + inscription_offset, + outpoint_to_watch: format_outpoint_to_watch( + &transaction_identifier, + inscription_output_index, + ), }) } diff --git a/components/chainhook-event-observer/src/hord/mod.rs b/components/chainhook-event-observer/src/hord/mod.rs index fcb9c75..99de797 100644 --- a/components/chainhook-event-observer/src/hord/mod.rs +++ b/components/chainhook-event-observer/src/hord/mod.rs @@ -22,6 +22,7 @@ use std::sync::mpsc::channel; use std::sync::Arc; use threadpool::ThreadPool; +use crate::hord::db::format_outpoint_to_watch; use crate::indexer::bitcoin::BitcoinTransactionFullBreakdown; use crate::{ hord::{ @@ -87,48 +88,27 @@ pub fn parse_ordinal_operations( None }; + let payload = OrdinalInscriptionRevealData { + content_type: inscription.content_type().unwrap_or("unknown").to_string(), + content_bytes: format!("0x{}", hex::encode(&inscription_content_bytes)), + content_length: inscription_content_bytes.len(), + inscription_id: inscription_id.to_string(), + inscriber_address, + inscription_output_value, + inscription_fee: 0, + inscription_input_index: input_index, + inscription_number: 0, + ordinal_number: 0, + ordinal_block_height: 0, + ordinal_offset: 0, + transfers_pre_inscription: 0, + satpoint_post_inscription: format!("{}:0:0", tx.txid.clone()), + }; + if input_index == 0 { - operations.push(OrdinalOperation::InscriptionRevealed( - OrdinalInscriptionRevealData { - content_type: inscription - .content_type() - .unwrap_or("unknown") - .to_string(), - content_bytes: format!("0x{}", hex::encode(&inscription_content_bytes)), - content_length: inscription_content_bytes.len(), - inscription_id: inscription_id.to_string(), - inscriber_address, - inscription_output_value, - inscription_fee: 0, - inscription_number: 0, - ordinal_number: 0, - ordinal_block_height: 0, - ordinal_offset: 0, - transfers_pre_inscription: 0, - satpoint_post_inscription: format!("{}:0:0", tx.txid.clone()), - }, - )) + operations.push(OrdinalOperation::InscriptionRevealed(payload)); } else { - operations.push(OrdinalOperation::CursedInscriptionRevealed( - OrdinalInscriptionRevealData { - content_type: inscription - .content_type() - .unwrap_or("unknown") - .to_string(), - content_bytes: format!("0x{}", hex::encode(&inscription_content_bytes)), - content_length: inscription_content_bytes.len(), - inscription_id: inscription_id.to_string(), - inscriber_address, - inscription_output_value, - inscription_fee: 0, - inscription_number: 0, - ordinal_number: 0, - ordinal_block_height: 0, - ordinal_offset: 0, - transfers_pre_inscription: 0, - satpoint_post_inscription: format!("{}:0:0", tx.txid.clone()), - }, - )) + operations.push(OrdinalOperation::CursedInscriptionRevealed(payload)); } } } @@ -221,23 +201,38 @@ pub fn retrieve_inscribed_satoshi_points_from_block( for tx in block.transactions.iter().skip(1) { // Have a new inscription been revealed, if so, are looking at a re-inscription for ordinal_event in tx.metadata.ordinal_operations.iter() { - if let OrdinalOperation::InscriptionRevealed(inscription_data) = ordinal_event { - if let Some(inscriptions_db_conn) = inscriptions_db_conn { - if let Some(traversal) = find_inscription_with_id( - &inscription_data.inscription_id, - &block.block_identifier.hash, - inscriptions_db_conn, - ctx, - ) { - traversals.insert(tx.transaction_identifier.clone(), traversal); - } else { - // Enqueue for traversals - transactions_ids.push(tx.transaction_identifier.clone()); - } + let (inscription_data, _is_cursed) = match ordinal_event { + OrdinalOperation::InscriptionRevealed(inscription_data) => { + (inscription_data, false) + } + OrdinalOperation::CursedInscriptionRevealed(inscription_data) => { + (inscription_data, false) + } + OrdinalOperation::InscriptionTransferred(_) => { + continue; + } + }; + if let Some(inscriptions_db_conn) = inscriptions_db_conn { + if let Some(traversal) = find_inscription_with_id( + &inscription_data.inscription_id, + &block.block_identifier.hash, + inscriptions_db_conn, + ctx, + ) { + traversals.insert(tx.transaction_identifier.clone(), traversal); } else { // Enqueue for traversals - transactions_ids.push(tx.transaction_identifier.clone()); + transactions_ids.push(( + tx.transaction_identifier.clone(), + inscription_data.inscription_input_index, + )); } + } else { + // Enqueue for traversals + transactions_ids.push(( + tx.transaction_identifier.clone(), + inscription_data.inscription_input_index, + )); } } } @@ -249,7 +244,7 @@ pub fn retrieve_inscribed_satoshi_points_from_block( let mut rng = thread_rng(); transactions_ids.shuffle(&mut rng); - for transaction_id in transactions_ids.into_iter() { + for (transaction_id, input_index) in transactions_ids.into_iter() { let moved_traversal_tx = traversal_tx.clone(); let moved_ctx = ctx.clone(); let block_identifier = block.block_identifier.clone(); @@ -262,6 +257,7 @@ pub fn retrieve_inscribed_satoshi_points_from_block( &blocks_db, &block_identifier, &transaction_id, + input_index, 0, local_cache, &moved_ctx, @@ -466,6 +462,10 @@ pub fn update_storage_and_augment_bitcoin_block_with_inscription_reveal_data( inscription.inscription_number = traversal.inscription_number; inscription.transfers_pre_inscription = traversal.transfers; inscription.inscription_fee = new_tx.metadata.fee; + inscription.satpoint_post_inscription = format!( + "{}:{}", + traversal.outpoint_to_watch, traversal.inscription_offset + ); match storage { Storage::Sqlite(rw_hord_db_conn) => { @@ -644,7 +644,7 @@ pub fn update_storage_and_augment_bitcoin_block_with_inscription_transfer_data( ) = match post_transfer_output { Some(index) => { let outpoint = - format!("{}:{}", &new_tx.transaction_identifier.hash[2..], index); + format_outpoint_to_watch(&new_tx.transaction_identifier, index); let offset = (sats_in_offset + watched_satpoint.offset) - sats_out_offset; let script_pub_key_hex = new_tx.metadata.outputs[index].get_script_pubkey_hex(); diff --git a/components/chainhook-types-rs/src/rosetta.rs b/components/chainhook-types-rs/src/rosetta.rs index 63ce6f8..e21bef3 100644 --- a/components/chainhook-types-rs/src/rosetta.rs +++ b/components/chainhook-types-rs/src/rosetta.rs @@ -327,6 +327,7 @@ pub struct OrdinalInscriptionRevealData { pub inscription_fee: u64, pub inscription_output_value: u64, pub inscription_id: String, + pub inscription_input_index: usize, pub inscriber_address: Option, pub ordinal_number: u64, pub ordinal_block_height: u64,