Skip to content

Commit 42acbeb

Browse files
author
Ludo Galabru
committed
fix: off-by-one in sats number resolution
1 parent d071484 commit 42acbeb

File tree

4 files changed

+134
-50
lines changed

4 files changed

+134
-50
lines changed

components/chainhook-cli/src/cli/mod.rs

+29-8
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ use chainhook_event_observer::chainhooks::types::{
1313
StacksPrintEventBasedPredicate,
1414
};
1515
use chainhook_event_observer::hord::db::{
16-
fetch_and_cache_blocks_in_hord_db, find_inscriptions_at_wached_outpoint,
17-
find_latest_compacted_block_known, open_readonly_hord_db_conn, open_readwrite_hord_db_conn,
16+
delete_data_in_hord_db, fetch_and_cache_blocks_in_hord_db,
17+
find_inscriptions_at_wached_outpoint, find_latest_compacted_block_known,
18+
open_readonly_hord_db_conn, open_readwrite_hord_db_conn,
1819
retrieve_satoshi_point_using_local_storage,
1920
};
2021
use chainhook_event_observer::observer::BitcoinConfig;
@@ -185,6 +186,9 @@ enum DbCommand {
185186
/// Update hord db
186187
#[clap(name = "update", bin_name = "update")]
187188
Update(UpdateHordDbCommand),
189+
/// Rebuild inscriptions entries for a given block
190+
#[clap(name = "drop", bin_name = "drop")]
191+
Drop(DropHordDbCommand),
188192
}
189193

190194
#[derive(Subcommand, PartialEq, Clone, Debug)]
@@ -265,6 +269,17 @@ struct UpdateHordDbCommand {
265269
pub config_path: Option<String>,
266270
}
267271

272+
#[derive(Parser, PartialEq, Clone, Debug)]
273+
struct DropHordDbCommand {
274+
/// Starting block
275+
pub start_block: u64,
276+
/// Starting block
277+
pub end_block: u64,
278+
/// Load config file path
279+
#[clap(long = "config-path")]
280+
pub config_path: Option<String>,
281+
}
282+
268283
pub fn main() {
269284
let logger = hiro_system_kit::log::setup_logger();
270285
let _guard = hiro_system_kit::log::setup_global_logger(logger.clone());
@@ -454,11 +469,6 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> {
454469
let config =
455470
Config::default(cmd.devnet, cmd.testnet, cmd.mainnet, &cmd.config_path)?;
456471

457-
info!(
458-
ctx.expect_logger(),
459-
"Computing satoshi number for satoshi at offet 0 in 1st output of transaction {} (block ${})", cmd.txid, cmd.block_height
460-
);
461-
462472
let hord_db_conn =
463473
open_readonly_hord_db_conn(&config.expected_cache_path(), &ctx).unwrap();
464474

@@ -536,6 +546,17 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> {
536546
)
537547
.await?;
538548
}
549+
DbCommand::Drop(cmd) => {
550+
let config = Config::default(false, false, false, &cmd.config_path)?;
551+
let rw_hord_db_conn =
552+
open_readwrite_hord_db_conn(&config.expected_cache_path(), &ctx)?;
553+
delete_data_in_hord_db(cmd.start_block, cmd.end_block, &rw_hord_db_conn, &ctx)?;
554+
info!(
555+
ctx.expect_logger(),
556+
"Cleaning hord_db: {} blocks dropped",
557+
cmd.end_block - cmd.start_block + 1
558+
);
559+
}
539560
},
540561
}
541562
Ok(())
@@ -550,7 +571,7 @@ pub async fn perform_hord_db_update(
550571
) -> Result<(), String> {
551572
info!(
552573
ctx.expect_logger(),
553-
"Syncing hord_db: {} blocks to download ({start_block}: {end_block}), using {network_threads} network threads", end_block - start_block
574+
"Syncing hord_db: {} blocks to download ({start_block}: {end_block}), using {network_threads} network threads", end_block - start_block + 1
554575
);
555576

556577
let bitcoin_config = BitcoinConfig {

components/chainhook-event-observer/src/hord/db/mod.rs

+71-17
Original file line numberDiff line numberDiff line change
@@ -305,17 +305,17 @@ pub fn update_transfered_inscription(
305305
pub fn find_latest_inscription_block_height(
306306
hord_db_conn: &Connection,
307307
_ctx: &Context,
308-
) -> Result<u64, String> {
308+
) -> Result<Option<u64>, String> {
309309
let args: &[&dyn ToSql] = &[];
310310
let mut stmt = hord_db_conn
311311
.prepare("SELECT block_height FROM inscriptions ORDER BY block_height DESC LIMIT 1")
312312
.unwrap();
313313
let mut rows = stmt.query(args).unwrap();
314314
while let Ok(Some(row)) = rows.next() {
315315
let block_height: u64 = row.get(0).unwrap();
316-
return Ok(block_height);
316+
return Ok(Some(block_height));
317317
}
318-
Ok(0)
318+
Ok(None)
319319
}
320320

321321
pub fn find_latest_inscription_number(
@@ -418,6 +418,34 @@ pub fn remove_entry_from_blocks(block_id: u32, hord_db_conn: &Connection, ctx: &
418418
}
419419
}
420420

421+
pub fn delete_blocks_in_block_range(
422+
start_block: u32,
423+
end_block: u32,
424+
rw_hord_db_conn: &Connection,
425+
ctx: &Context,
426+
) {
427+
if let Err(e) = rw_hord_db_conn.execute(
428+
"DELETE FROM blocks WHERE id >= ?1 AND id <= ?2",
429+
rusqlite::params![&start_block, &end_block],
430+
) {
431+
ctx.try_log(|logger| slog::error!(logger, "{}", e.to_string()));
432+
}
433+
}
434+
435+
pub fn delete_inscriptions_in_block_range(
436+
start_block: u32,
437+
end_block: u32,
438+
rw_hord_db_conn: &Connection,
439+
ctx: &Context,
440+
) {
441+
if let Err(e) = rw_hord_db_conn.execute(
442+
"DELETE FROM inscriptions WHERE block_height >= ?1 AND block_height <= ?2",
443+
rusqlite::params![&start_block, &end_block],
444+
) {
445+
ctx.try_log(|logger| slog::error!(logger, "{}", e.to_string()));
446+
}
447+
}
448+
421449
pub fn remove_entry_from_inscriptions(
422450
inscription_id: &str,
423451
hord_db_conn: &Connection,
@@ -513,6 +541,17 @@ pub fn remove_entry_from_inscriptions(
513541
// Ok(())
514542
// }
515543

544+
pub fn delete_data_in_hord_db(
545+
start_block: u64,
546+
end_block: u64,
547+
rw_hord_db_conn: &Connection,
548+
ctx: &Context,
549+
) -> Result<(), String> {
550+
delete_blocks_in_block_range(start_block as u32, end_block as u32, rw_hord_db_conn, &ctx);
551+
delete_inscriptions_in_block_range(start_block as u32, end_block as u32, rw_hord_db_conn, &ctx);
552+
Ok(())
553+
}
554+
516555
pub async fn fetch_and_cache_blocks_in_hord_db(
517556
bitcoin_config: &BitcoinConfig,
518557
rw_hord_db_conn: &Connection,
@@ -521,6 +560,7 @@ pub async fn fetch_and_cache_blocks_in_hord_db(
521560
ctx: &Context,
522561
network_thread: usize,
523562
) -> Result<(), String> {
563+
let number_of_blocks_to_process = end_block - start_block + 1;
524564
let retrieve_block_hash_pool = ThreadPool::new(network_thread);
525565
let (block_hash_tx, block_hash_rx) = crossbeam_channel::unbounded();
526566
let retrieve_block_data_pool = ThreadPool::new(network_thread);
@@ -589,29 +629,32 @@ pub async fn fetch_and_cache_blocks_in_hord_db(
589629
.expect("unable to spawn thread");
590630

591631
let mut blocks_stored = 0;
592-
let mut cursor = find_latest_inscription_block_height(&rw_hord_db_conn, &ctx)
632+
let mut cursor = 1 + find_latest_inscription_block_height(&rw_hord_db_conn, &ctx)?
593633
.unwrap_or(first_inscription_block_height) as usize;
594634
let mut inbox = HashMap::new();
595635

596636
while let Ok(Some((block_height, compacted_block, raw_block))) = block_compressed_rx.recv() {
597637
ctx.try_log(|logger| slog::info!(logger, "Storing compacted block #{block_height}"));
598638

599639
insert_entry_in_blocks(block_height, &compacted_block, &rw_hord_db_conn, &ctx);
640+
blocks_stored += 1;
600641

642+
// println!("{} < {}", raw_block.height, cursor);
601643
// Early return, only considering blocks after 1st inscription
602-
if raw_block.height < cursor {
603-
continue;
604-
}
605-
let block_height = raw_block.height;
644+
// if raw_block.height < cursor {
645+
// continue;
646+
// }
647+
648+
// let block_height = raw_block.height;
606649
inbox.insert(raw_block.height, raw_block);
607650

608651
// In the context of ordinals, we're constrained to process blocks sequentially
609652
// Blocks are processed by a threadpool and could be coming out of order.
610653
// Inbox block for later if the current block is not the one we should be
611654
// processing.
612-
if block_height != cursor {
613-
continue;
614-
}
655+
// if block_height != cursor {
656+
// continue;
657+
// }
615658

616659
// Is the action of processing a block allows us
617660
// to process more blocks present in the inbox?
@@ -627,9 +670,12 @@ pub async fn fetch_and_cache_blocks_in_hord_db(
627670
}
628671
};
629672

630-
if let Err(e) =
631-
update_hord_db_and_augment_bitcoin_block(&mut new_block, &rw_hord_db_conn, &ctx)
632-
{
673+
if let Err(e) = update_hord_db_and_augment_bitcoin_block(
674+
&mut new_block,
675+
&rw_hord_db_conn,
676+
&ctx,
677+
false,
678+
) {
633679
ctx.try_log(|logger| {
634680
slog::error!(logger, "Unable to augment bitcoin block with hord_db: {e}",)
635681
});
@@ -638,8 +684,7 @@ pub async fn fetch_and_cache_blocks_in_hord_db(
638684
cursor += 1;
639685
}
640686

641-
blocks_stored += 1;
642-
if blocks_stored == end_block - start_block {
687+
if blocks_stored == number_of_blocks_to_process {
643688
let _ = block_data_tx.send(None);
644689
let _ = block_hash_tx.send(None);
645690
ctx.try_log(|logger| {
@@ -663,6 +708,15 @@ pub fn retrieve_satoshi_point_using_local_storage(
663708
transaction_identifier: &TransactionIdentifier,
664709
ctx: &Context,
665710
) -> Result<(u64, u64, u64, u32), String> {
711+
ctx.try_log(|logger| {
712+
slog::info!(
713+
logger,
714+
"Computing Satoshi # for sat_point {}:0:0 (block #{})",
715+
transaction_identifier.hash,
716+
block_identifier.index
717+
)
718+
});
719+
666720
let mut ordinal_offset = 0;
667721
let mut ordinal_block_number = block_identifier.index as u32;
668722
let txid = {
@@ -676,7 +730,7 @@ pub fn retrieve_satoshi_point_using_local_storage(
676730
let res = match find_compacted_block_at_block_height(ordinal_block_number, &hord_db_conn) {
677731
Some(res) => res,
678732
None => {
679-
return Err(format!("unable to retrieve block ##{ordinal_block_number}"));
733+
return Err(format!("unable to retrieve block #{ordinal_block_number}"));
680734
}
681735
};
682736

components/chainhook-event-observer/src/hord/mod.rs

+32-25
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,9 @@ pub fn update_hord_db_and_augment_bitcoin_block(
6565
new_block: &mut BitcoinBlockData,
6666
rw_hord_db_conn: &Connection,
6767
ctx: &Context,
68+
write_block: bool,
6869
) -> Result<(), String> {
69-
{
70+
if write_block {
7071
ctx.try_log(|logger| {
7172
slog::info!(
7273
logger,
@@ -156,9 +157,10 @@ pub fn update_hord_db_and_augment_bitcoin_block(
156157
ctx.try_log(|logger| {
157158
slog::info!(
158159
logger,
159-
"Transaction {} in block {} includes a new inscription {}",
160+
"Transaction {} in block {} inscribed some content ({}) on Satoshi #{}",
160161
new_tx.transaction_identifier.hash,
161162
new_block.block_identifier.index,
163+
inscription.content_type,
162164
ordinal_number
163165
);
164166
});
@@ -177,7 +179,7 @@ pub fn update_hord_db_and_augment_bitcoin_block(
177179

178180
// Have inscriptions been transfered?
179181
let mut sats_in_offset = 0;
180-
let mut sats_out_offset = 0;
182+
let mut sats_out_offset = new_tx.metadata.outputs[0].value;
181183

182184
for input in new_tx.metadata.inputs.iter() {
183185
// input.previous_output.txid
@@ -192,39 +194,32 @@ pub fn update_hord_db_and_augment_bitcoin_block(
192194
let entries =
193195
find_inscriptions_at_wached_outpoint(&outpoint_pre_transfer, &rw_hord_db_conn);
194196

195-
ctx.try_log(|logger| {
196-
slog::info!(
197-
logger,
198-
"Checking if {} is part of our watch outpoints set: {}",
199-
outpoint_pre_transfer,
200-
entries.len(),
201-
)
202-
});
197+
// ctx.try_log(|logger| {
198+
// slog::info!(
199+
// logger,
200+
// "Checking if {} is part of our watch outpoints set: {}",
201+
// outpoint_pre_transfer,
202+
// entries.len(),
203+
// )
204+
// });
203205

204206
for (inscription_id, inscription_number, ordinal_number, offset) in entries.into_iter()
205207
{
206208
let satpoint_pre_transfer = format!("{}:{}", outpoint_pre_transfer, offset);
207-
// At this point we know that inscriptions are being moved.
208-
ctx.try_log(|logger| {
209-
slog::info!(
210-
logger,
211-
"Detected transaction {} involving txin {} that includes watched ordinals",
212-
new_tx.transaction_identifier.hash,
213-
satpoint_pre_transfer,
214-
)
215-
});
216209

217210
// Question is: are inscriptions moving to a new output,
218211
// burnt or lost in fees and transfered to the miner?
219212
let post_transfer_output = loop {
220213
if sats_out_offset >= sats_in_offset + offset {
221214
break Some(post_transfer_output_index);
222215
}
223-
if post_transfer_output_index >= new_tx.metadata.outputs.len() {
216+
if post_transfer_output_index + 1 >= new_tx.metadata.outputs.len() {
224217
break None;
218+
} else {
219+
post_transfer_output_index += 1;
220+
sats_out_offset +=
221+
new_tx.metadata.outputs[post_transfer_output_index].value;
225222
}
226-
sats_out_offset += new_tx.metadata.outputs[post_transfer_output_index].value;
227-
post_transfer_output_index += 1;
228223
};
229224

230225
let (outpoint_post_transfer, offset_post_transfer, updated_address) =
@@ -271,12 +266,24 @@ pub fn update_hord_db_and_augment_bitcoin_block(
271266
}
272267
};
273268

269+
// ctx.try_log(|logger| {
270+
// slog::info!(
271+
// logger,
272+
// "Updating watched outpoint {} to outpoint {}",
273+
// outpoint_post_transfer,
274+
// outpoint_pre_transfer,
275+
// )
276+
// });
277+
// At this point we know that inscriptions are being moved.
274278
ctx.try_log(|logger| {
275279
slog::info!(
276280
logger,
277-
"Updating watched outpoint {} to outpoint {}",
281+
"Transaction {} in block {} moved inscribed Satoshi #{} from {} to {}",
282+
new_tx.transaction_identifier.hash,
283+
new_block.block_identifier.index,
284+
ordinal_number,
285+
satpoint_pre_transfer,
278286
outpoint_post_transfer,
279-
outpoint_pre_transfer,
280287
)
281288
});
282289

components/chainhook-event-observer/src/observer/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,7 @@ pub async fn start_observer_commands_handler(
554554
block,
555555
&rw_hord_db_conn,
556556
&ctx,
557+
true,
557558
) {
558559
ctx.try_log(|logger| {
559560
slog::error!(
@@ -663,6 +664,7 @@ pub async fn start_observer_commands_handler(
663664
block,
664665
&rw_hord_db_conn,
665666
&ctx,
667+
true,
666668
) {
667669
ctx.try_log(|logger| {
668670
slog::error!(

0 commit comments

Comments
 (0)