Skip to content

Commit a282655

Browse files
author
Ludo Galabru
committed
fix: compatibility with clarinet
1 parent f55a5ee commit a282655

File tree

7 files changed

+181
-61
lines changed

7 files changed

+181
-61
lines changed

README.md

+10
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,16 @@ The current `stacks` predicates supports the following `if_this` constructs:
372372
}
373373
}
374374

375+
// Get any stacks block matching constraints
376+
// `block_height` mandatory argument admits:
377+
// - `equals`, `higher_than`, `lower_than`, `between`: integer type.
378+
{
379+
"if_this": {
380+
"scope": "block_height",
381+
"higher_than": 10000
382+
}
383+
}
384+
375385
// Get any transaction related to a given fungible token asset identifier
376386
// `asset-identifier` mandatory argument admits:
377387
// - string type, fully qualifying the asset identifier to observe. example: `ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.cbtc-sip10::cbtc`

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

+7-4
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::scan::stacks::scan_stacks_chain_with_predicate;
77
use chainhook_event_observer::chainhooks::types::ChainhookFullSpecification;
88
use chainhook_event_observer::indexer::ordinals::db::{
99
build_bitcoin_traversal_local_storage, open_readonly_ordinals_db_conn,
10-
retrieve_satoshi_point_using_local_storage,
10+
retrieve_satoshi_point_using_local_storage, open_readwrite_ordinals_db_conn, initialize_ordinal_state_storage,
1111
};
1212
use chainhook_event_observer::indexer::ordinals::ord::height::Height;
1313
use chainhook_event_observer::observer::BitcoinConfig;
@@ -294,8 +294,9 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> {
294294
index: cmd.block_height,
295295
hash: "".into(),
296296
};
297-
let storage_conn =
298-
open_readonly_ordinals_db_conn(&config.expected_cache_path()).unwrap();
297+
298+
let storage_conn = open_readonly_ordinals_db_conn(&config.expected_cache_path(), &ctx).unwrap();
299+
299300
let (block_height, offset) = retrieve_satoshi_point_using_local_storage(
300301
&storage_conn,
301302
&block_identifier,
@@ -318,9 +319,11 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> {
318319
rpc_url: config.network.bitcoin_node_rpc_url.clone(),
319320
};
320321

322+
let storage_conn = initialize_ordinal_state_storage(&config.expected_cache_path(), &ctx);
323+
321324
let _ = build_bitcoin_traversal_local_storage(
322325
&bitcoin_config,
323-
&config.expected_cache_path(),
326+
&storage_conn,
324327
cmd.start_block,
325328
cmd.end_block,
326329
&ctx,

components/chainhook-cli/src/scan/bitcoin.rs

+5-6
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,8 @@ use chainhook_event_observer::indexer::bitcoin::{
1313
retrieve_block_hash, retrieve_full_block_breakdown_with_retry,
1414
};
1515
use chainhook_event_observer::indexer::ordinals::db::{
16-
get_default_ordinals_db_file_path, initialize_ordinal_state_storage,
17-
open_readonly_ordinals_db_conn, retrieve_satoshi_point_using_local_storage,
18-
write_compacted_block_to_index, CompactedBlock,
16+
initialize_ordinal_state_storage, open_readonly_ordinals_db_conn,
17+
retrieve_satoshi_point_using_local_storage, write_compacted_block_to_index, CompactedBlock,
1918
};
2019
use chainhook_event_observer::indexer::ordinals::ord::indexing::entry::Entry;
2120
use chainhook_event_observer::indexer::ordinals::ord::indexing::{
@@ -193,7 +192,8 @@ pub async fn scan_bitcoin_chain_with_predicate(
193192
ctx_.expect_logger(),
194193
"Retrieving satoshi point for {}", transaction.transaction_identifier.hash
195194
);
196-
let storage_conn = open_readonly_ordinals_db_conn(&cache_path).unwrap();
195+
196+
let storage_conn = open_readonly_ordinals_db_conn(&cache_path, &ctx_).unwrap();
197197
let res = retrieve_satoshi_point_using_local_storage(
198198
&storage_conn,
199199
&block_identifier,
@@ -237,10 +237,9 @@ pub async fn scan_bitcoin_chain_with_predicate(
237237
.expect("unable to detach thread");
238238

239239
let ctx_ = ctx.clone();
240-
let db_file = get_default_ordinals_db_file_path(&config.expected_cache_path());
240+
let conn = initialize_ordinal_state_storage(&config.expected_cache_path(), &ctx_);
241241
let handle_3 = hiro_system_kit::thread_named("Ordinal ingestion")
242242
.spawn(move || {
243-
let conn = initialize_ordinal_state_storage(&db_file, &ctx_);
244243
while let Ok(Some((height, compacted_block))) = cache_block_rx.recv() {
245244
info!(ctx_.expect_logger(), "Caching block #{height}");
246245
write_compacted_block_to_index(height, &compacted_block, &conn, &ctx_);

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

+10
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,16 @@ impl BitcoinPredicateType {
406406
}
407407
false
408408
}
409+
BitcoinPredicateType::Protocol(Protocols::Ordinal(
410+
OrdinalOperations::InscriptionTransfered,
411+
)) => {
412+
for op in tx.metadata.ordinal_operations.iter() {
413+
if let OrdinalOperation::InscriptionTransfered(_) = op {
414+
return true;
415+
}
416+
}
417+
false
418+
}
409419
}
410420
}
411421
}

components/chainhook-event-observer/src/chainhooks/types.rs

+1
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,7 @@ pub enum StacksOperations {
516516
#[serde(rename_all = "snake_case")]
517517
pub enum OrdinalOperations {
518518
InscriptionRevealed,
519+
InscriptionTransfered,
519520
}
520521

521522
pub fn get_stacks_canonical_magic_bytes(network: &BitcoinNetwork) -> [u8; 2] {

components/chainhook-event-observer/src/indexer/ordinals/db/mod.rs

+109-43
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use std::{path::PathBuf, time::Duration};
22

3-
use chainhook_types::{BlockIdentifier, OrdinalInscriptionRevealData, TransactionIdentifier};
3+
use chainhook_types::{
4+
BitcoinBlockData, BlockIdentifier, OrdinalInscriptionRevealData, TransactionIdentifier,
5+
};
46
use hiro_system_kit::slog;
57
use rand::RngCore;
68
use rusqlite::{Connection, OpenFlags, ToSql};
@@ -14,15 +16,26 @@ use crate::{
1416
utils::Context,
1517
};
1618

17-
pub fn get_default_ordinals_db_file_path(base_dir: &PathBuf) -> PathBuf {
19+
fn get_default_ordinals_db_file_path(base_dir: &PathBuf) -> PathBuf {
1820
let mut destination_path = base_dir.clone();
19-
destination_path.push("bitcoin_block_traversal-readonly.sqlite");
21+
destination_path.push("bitcoin_block_traversal.sqlite");
2022
destination_path
2123
}
2224

23-
pub fn open_readonly_ordinals_db_conn(base_dir: &PathBuf) -> Result<Connection, String> {
25+
pub fn open_readonly_ordinals_db_conn(
26+
base_dir: &PathBuf,
27+
ctx: &Context,
28+
) -> Result<Connection, String> {
2429
let path = get_default_ordinals_db_file_path(&base_dir);
25-
let conn = open_existing_readonly_db(&path);
30+
let conn = open_existing_readonly_db(&path, ctx);
31+
Ok(conn)
32+
}
33+
34+
pub fn open_readwrite_ordinals_db_conn(
35+
base_dir: &PathBuf,
36+
ctx: &Context,
37+
) -> Result<Connection, String> {
38+
let conn = create_or_open_readwrite_db(&base_dir, ctx);
2639
Ok(conn)
2740
}
2841

@@ -65,12 +78,13 @@ pub fn initialize_ordinal_state_storage(path: &PathBuf, ctx: &Context) -> Connec
6578
conn
6679
}
6780

68-
fn create_or_open_readwrite_db(path: &PathBuf, ctx: &Context) -> Connection {
69-
let open_flags = match std::fs::metadata(path) {
81+
fn create_or_open_readwrite_db(cache_path: &PathBuf, ctx: &Context) -> Connection {
82+
let path = get_default_ordinals_db_file_path(&cache_path);
83+
let open_flags = match std::fs::metadata(&path) {
7084
Err(e) => {
7185
if e.kind() == std::io::ErrorKind::NotFound {
7286
// need to create
73-
if let Some(dirp) = PathBuf::from(path).parent() {
87+
if let Some(dirp) = PathBuf::from(&path).parent() {
7488
std::fs::create_dir_all(dirp).unwrap_or_else(|e| {
7589
ctx.try_log(|logger| slog::error!(logger, "{}", e.to_string()));
7690
});
@@ -86,15 +100,23 @@ fn create_or_open_readwrite_db(path: &PathBuf, ctx: &Context) -> Connection {
86100
}
87101
};
88102

89-
let conn = Connection::open_with_flags(path, open_flags).unwrap();
103+
let conn = loop {
104+
match Connection::open_with_flags(&path, open_flags) {
105+
Ok(conn) => break conn,
106+
Err(e) => {
107+
ctx.try_log(|logger| slog::error!(logger, "{}", e.to_string()));
108+
}
109+
};
110+
std::thread::sleep(std::time::Duration::from_secs(1));
111+
};
90112
// db.profile(Some(trace_profile));
91113
// db.busy_handler(Some(tx_busy_handler))?;
92114
conn.pragma_update(None, "journal_mode", &"WAL").unwrap();
93115
conn.pragma_update(None, "synchronous", &"NORMAL").unwrap();
94116
conn
95117
}
96118

97-
fn open_existing_readonly_db(path: &PathBuf) -> Connection {
119+
fn open_existing_readonly_db(path: &PathBuf, ctx: &Context) -> Connection {
98120
let open_flags = match std::fs::metadata(path) {
99121
Err(e) => {
100122
if e.kind() == std::io::ErrorKind::NotFound {
@@ -109,8 +131,16 @@ fn open_existing_readonly_db(path: &PathBuf) -> Connection {
109131
}
110132
};
111133

112-
let conn = Connection::open_with_flags(path, open_flags).unwrap();
113-
conn
134+
let conn = loop {
135+
match Connection::open_with_flags(path, open_flags) {
136+
Ok(conn) => break conn,
137+
Err(e) => {
138+
ctx.try_log(|logger| slog::error!(logger, "{}", e.to_string()));
139+
}
140+
};
141+
std::thread::sleep(std::time::Duration::from_secs(1));
142+
};
143+
return conn;
114144
}
115145

116146
#[derive(Debug, Serialize, Deserialize)]
@@ -155,6 +185,39 @@ impl CompactedBlock {
155185
CompactedBlock(((coinbase_txid, coinbase_value), txs))
156186
}
157187

188+
pub fn from_standardized_block(block: &BitcoinBlockData) -> CompactedBlock {
189+
let mut txs = vec![];
190+
let mut coinbase_value = 0;
191+
let coinbase_txid = {
192+
let txid =
193+
hex::decode(&block.transactions[0].transaction_identifier.hash[2..]).unwrap();
194+
[txid[0], txid[1], txid[2], txid[3]]
195+
};
196+
for coinbase_output in block.transactions[0].metadata.outputs.iter() {
197+
coinbase_value += coinbase_output.value;
198+
}
199+
for tx in block.transactions.iter().skip(1) {
200+
let mut inputs = vec![];
201+
for input in tx.metadata.inputs.iter() {
202+
let txin = hex::decode(&input.previous_output.txid[2..]).unwrap();
203+
204+
inputs.push((
205+
[txin[0], txin[1], txin[2], txin[3]],
206+
input.previous_output.block_height as u32,
207+
input.previous_output.vout as u16,
208+
input.previous_output.value,
209+
));
210+
}
211+
let mut outputs = vec![];
212+
for output in tx.metadata.outputs.iter() {
213+
outputs.push(output.value);
214+
}
215+
let txid = hex::decode(&tx.transaction_identifier.hash[2..]).unwrap();
216+
txs.push(([txid[0], txid[1], txid[2], txid[3]], inputs, outputs));
217+
}
218+
CompactedBlock(((coinbase_txid, coinbase_value), txs))
219+
}
220+
158221
pub fn from_hex_bytes(bytes: &str) -> CompactedBlock {
159222
let bytes = hex::decode(&bytes).unwrap();
160223
let value = ciborium::de::from_reader(&bytes[..]).unwrap();
@@ -327,7 +390,7 @@ pub fn write_compacted_block_to_index(
327390

328391
pub async fn build_bitcoin_traversal_local_storage(
329392
bitcoin_config: &BitcoinConfig,
330-
cache_path: &PathBuf,
393+
storage_conn: &Connection,
331394
start_block: u64,
332395
end_block: u64,
333396
ctx: &Context,
@@ -351,62 +414,68 @@ pub async fn build_bitcoin_traversal_local_storage(
351414
let future = retrieve_block_hash(&config, &block_height);
352415
match hiro_system_kit::nestable_block_on(future) {
353416
Ok(block_hash) => {
354-
err_count = 0;
355-
block_hash_tx.send(Some((block_cursor, block_hash)));
417+
let _ = block_hash_tx.send(Some((block_cursor, block_hash)));
356418
break;
357419
}
358420
Err(e) => {
359421
err_count += 1;
360422
let delay = (err_count + (rng.next_u64() % 3)) * 1000;
361-
println!("retry hash:fetch in {delay}");
362423
std::thread::sleep(std::time::Duration::from_millis(delay));
363424
}
364425
}
365426
}
366427
});
367428
}
368429

369-
let db_file = get_default_ordinals_db_file_path(&cache_path);
370430
let bitcoin_config = bitcoin_config.clone();
371431
let moved_ctx = ctx.clone();
372-
let handle = hiro_system_kit::thread_named("Block data retrieval").spawn(move || {
432+
let block_data_tx_moved = block_data_tx.clone();
433+
let handle_1 = hiro_system_kit::thread_named("Block data retrieval").spawn(move || {
373434
while let Ok(Some((block_height, block_hash))) = block_hash_rx.recv() {
374-
println!("fetch {block_height}:{block_hash}");
375435
let moved_bitcoin_config = bitcoin_config.clone();
376-
let block_data_tx = block_data_tx.clone();
436+
let block_data_tx = block_data_tx_moved.clone();
377437
let moved_ctx = moved_ctx.clone();
378438
retrieve_block_data_pool.execute(move || {
439+
moved_ctx.try_log(|logger| slog::info!(logger, "Fetching block #{block_height}"));
379440
let future = retrieve_full_block_breakdown_with_retry(
380441
&moved_bitcoin_config,
381442
&block_hash,
382443
&moved_ctx,
383444
);
384445
let block_data = hiro_system_kit::nestable_block_on(future).unwrap();
385-
block_data_tx.send(Some(block_data));
446+
let _ = block_data_tx.send(Some(block_data));
386447
});
387-
retrieve_block_data_pool.join()
448+
let res = retrieve_block_data_pool.join();
449+
res
388450
}
389-
});
451+
}).expect("unable to spawn thread");
390452

391-
let handle = hiro_system_kit::thread_named("Block data compression").spawn(move || {
453+
let handle_2 = hiro_system_kit::thread_named("Block data compression").spawn(move || {
392454
while let Ok(Some(block_data)) = block_data_rx.recv() {
393-
println!("store {}:{}", block_data.height, block_data.hash);
394-
let block_compressed_tx = block_compressed_tx.clone();
455+
let block_compressed_tx_moved = block_compressed_tx.clone();
395456
compress_block_data_pool.execute(move || {
396457
let compressed_block = CompactedBlock::from_full_block(&block_data);
397-
block_compressed_tx.send(Some((block_data.height as u32, compressed_block)));
458+
let _ = block_compressed_tx_moved
459+
.send(Some((block_data.height as u32, compressed_block)));
398460
});
399461

400-
compress_block_data_pool.join()
462+
let res = compress_block_data_pool.join();
463+
// let _ = block_compressed_tx.send(None);
464+
res
401465
}
402-
});
403-
404-
let conn = initialize_ordinal_state_storage(&db_file, &ctx);
466+
}).expect("unable to spawn thread");
405467

468+
let mut blocks_stored = 0;
406469
while let Ok(Some((block_height, compacted_block))) = block_compressed_rx.recv() {
407-
ctx.try_log(|logger| slog::debug!(logger, "Storing block #{block_height}"));
408-
409-
write_compacted_block_to_index(block_height, &compacted_block, &conn, &ctx);
470+
ctx.try_log(|logger| slog::info!(logger, "Storing block #{block_height}"));
471+
write_compacted_block_to_index(block_height, &compacted_block, &storage_conn, &ctx);
472+
blocks_stored+= 1;
473+
if blocks_stored == end_block - start_block {
474+
let _ = block_data_tx.send(None);
475+
let _ = block_hash_tx.send(None);
476+
ctx.try_log(|logger| slog::info!(logger, "Local ordinals storage successfully seeded with #{blocks_stored} blocks"));
477+
return Ok(())
478+
}
410479
}
411480

412481
retrieve_block_hash_pool.join();
@@ -429,7 +498,12 @@ pub fn retrieve_satoshi_point_using_local_storage(
429498
let mut tx_cursor = (txid, 0);
430499

431500
loop {
432-
let res = retrieve_compacted_block_from_index(ordinal_block_number, &storage_conn).unwrap();
501+
let res = match retrieve_compacted_block_from_index(ordinal_block_number, &storage_conn) {
502+
Some(res) => res,
503+
None => {
504+
return Err(format!("unable to retrieve block ##{ordinal_block_number}"));
505+
}
506+
};
433507

434508
ctx.try_log(|logger| {
435509
slog::debug!(
@@ -527,11 +601,3 @@ pub fn retrieve_satoshi_point_using_local_storage(
527601
Ok((ordinal_block_number.into(), ordinal_offset))
528602
}
529603

530-
// pub async fn scan_bitcoin_chain_for_ordinal_inscriptions(
531-
// subscribers: Vec<HookAction>,
532-
// first_inscription_height: u64,
533-
// config: &Config,
534-
// ctx: &Context,
535-
// ) -> Result<(), String> {
536-
// Ok(())
537-
// }

0 commit comments

Comments
 (0)