1use crate::ScriptEngine;
22use crate::metrics::Metrics;
23use crate::verification::{BlockVerification, BlockVerifier};
24use bitcoin::blockdata::block::Header as BitcoinHeader;
25use bitcoin::hashes::Hash;
26use bitcoin::{Block as BitcoinBlock, BlockHash, Network, Work};
27use codec::Encode;
28use sc_client_api::{AuxStore, Backend, BlockBackend, HeaderBackend, StorageProvider};
29use sc_consensus::{
30 BlockImport, BlockImportParams, ForkChoiceStrategy, ImportResult, ImportedAux, StateAction,
31 StorageChanges,
32};
33use sp_api::{ApiExt, CallApiAt, CallContext, Core, ProvideRuntimeApi};
34use sp_blockchain::HashAndNumber;
35use sp_consensus::{BlockOrigin, BlockStatus};
36use sp_runtime::traits::{
37 Block as BlockT, Hash as HashT, HashingFor, Header as HeaderT, NumberFor,
38};
39use sp_runtime::{SaturatedConversion, Saturating};
40use std::marker::PhantomData;
41use std::sync::Arc;
42use std::time::Instant;
43use subcoin_primitives::runtime::SubcoinApi;
44use subcoin_primitives::{
45 BackendExt, BitcoinTransactionAdapter, CoinStorageKey, substrate_header_digest,
46};
47use substrate_prometheus_endpoint::Registry;
48
49#[derive(Debug, Clone)]
51pub struct ImportConfig {
52 pub network: Network,
54 pub block_verification: BlockVerification,
56 pub execute_block: bool,
58 pub script_engine: ScriptEngine,
60}
61
62#[derive(Debug, Default)]
63struct Stats {
64 total_txs: usize,
66 total_blocks: usize,
68 total_execution_time: usize,
70}
71
72impl Stats {
73 fn transaction_per_millisecond(&self) -> f64 {
74 self.total_txs as f64 / self.total_execution_time as f64
75 }
76
77 fn block_per_second(&self) -> f64 {
78 self.total_blocks as f64 * 1000f64 / self.total_execution_time as f64
79 }
80
81 fn record_new_block_execution<Block: BlockT>(
82 &mut self,
83 block_number: NumberFor<Block>,
84 block_hash: Block::Hash,
85 tx_count: usize,
86 execution_time: u128,
87 ) {
88 self.total_txs += tx_count;
89 self.total_blocks += 1;
90 self.total_execution_time += execution_time as usize;
91 let tx_per_ms = self.transaction_per_millisecond();
92 let block_per_second = self.block_per_second();
93
94 tracing::debug!(
95 "Executed block#{block_number} ({tx_count} txs) ({block_hash}) \
96 {tx_per_ms:.2} tx/ms, {block_per_second:.2} block/s, execution time: {execution_time} ms",
97 );
98 }
99}
100
101pub struct BitcoinBlockImporter<Block, Client, BE, BI, TransactionAdapter> {
103 client: Arc<Client>,
104 inner: BI,
105 stats: Stats,
106 config: ImportConfig,
107 verifier: BlockVerifier<Block, Client, BE>,
108 metrics: Option<Metrics>,
109 last_block_execution_report: Instant,
110 _phantom: PhantomData<TransactionAdapter>,
111}
112
113impl<Block, Client, BE, BI, TransactionAdapter>
114 BitcoinBlockImporter<Block, Client, BE, BI, TransactionAdapter>
115where
116 Block: BlockT,
117 BE: Backend<Block> + 'static,
118 Client: HeaderBackend<Block>
119 + BlockBackend<Block>
120 + AuxStore
121 + ProvideRuntimeApi<Block>
122 + StorageProvider<Block, BE>
123 + CallApiAt<Block>
124 + Send
125 + 'static,
126 Client::Api: Core<Block> + SubcoinApi<Block>,
127 BI: BlockImport<Block> + Send + Sync + 'static,
128 TransactionAdapter: BitcoinTransactionAdapter<Block>,
129{
130 pub fn new(
132 client: Arc<Client>,
133 block_import: BI,
134 config: ImportConfig,
135 coin_storage_key: Arc<dyn CoinStorageKey>,
136 registry: Option<&Registry>,
137 ) -> Self {
138 let verifier = BlockVerifier::new(
139 client.clone(),
140 config.network,
141 config.block_verification,
142 coin_storage_key,
143 config.script_engine,
144 );
145 let metrics = match registry {
146 Some(registry) => Metrics::register(registry)
147 .map_err(|err| {
148 tracing::error!("Failed to registry metrics: {err:?}");
149 })
150 .ok(),
151 None => None,
152 };
153 Self {
154 client,
155 inner: block_import,
156 stats: Stats::default(),
157 config,
158 verifier,
159 metrics,
160 last_block_execution_report: Instant::now(),
161 _phantom: Default::default(),
162 }
163 }
164
165 #[inline]
166 fn substrate_block_hash(&self, bitcoin_block_hash: BlockHash) -> Option<Block::Hash> {
167 BackendExt::<Block>::substrate_block_hash_for(&self.client, bitcoin_block_hash)
168 }
169
170 fn fetch_substrate_block_info(
172 &self,
173 bitcoin_block_hash: BlockHash,
174 ) -> sp_blockchain::Result<Option<HashAndNumber<Block>>> {
175 let Some(substrate_block_hash) = self.substrate_block_hash(bitcoin_block_hash) else {
176 return Ok(None);
177 };
178
179 let block_number =
180 self.client
181 .number(substrate_block_hash)?
182 .ok_or(sp_blockchain::Error::UnknownBlock(format!(
183 "Substate block hash mapping exists, \
184 but block#{substrate_block_hash} is not in chain"
185 )))?;
186
187 Ok(Some(HashAndNumber {
188 number: block_number,
189 hash: substrate_block_hash,
190 }))
191 }
192
193 fn execute_block_at(
194 &mut self,
195 block_number: NumberFor<Block>,
196 parent_hash: Block::Hash,
197 block: Block,
198 ) -> sp_blockchain::Result<(
199 Block::Hash,
200 sp_state_machine::StorageChanges<HashingFor<Block>>,
201 )> {
202 let timer = std::time::Instant::now();
203
204 let transactions_count = block.extrinsics().len();
205 let block_size = block.encoded_size();
206
207 let mut runtime_api = self.client.runtime_api();
208 runtime_api.set_call_context(CallContext::Onchain);
209
210 runtime_api.execute_block_without_state_root_check(parent_hash, block)?;
211
212 let state = self.client.state_at(parent_hash)?;
213
214 let storage_changes = runtime_api
215 .into_storage_changes(&state, parent_hash)
216 .map_err(sp_blockchain::Error::StorageChanges)?;
217
218 let state_root = storage_changes.transaction_storage_root;
219
220 if let Some(metrics) = &self.metrics {
221 const BLOCK_EXECUTION_REPORT_INTERVAL: u128 = 50;
222
223 if self.last_block_execution_report.elapsed().as_millis()
227 > BLOCK_EXECUTION_REPORT_INTERVAL
228 {
229 let block_number: u32 = block_number.saturated_into();
230 let execution_time = timer.elapsed().as_millis();
231 metrics.report_block_execution(
232 block_number.saturated_into(),
233 transactions_count,
234 block_size,
235 execution_time,
236 );
237 self.last_block_execution_report = Instant::now();
238 }
239 }
240
241 Ok((state_root, storage_changes))
242 }
243
244 fn prepare_substrate_block_import(
245 &mut self,
246 block: BitcoinBlock,
247 substrate_parent_block: HashAndNumber<Block>,
248 origin: BlockOrigin,
249 ) -> sp_blockchain::Result<BlockImportParams<Block>> {
250 let HashAndNumber {
251 number: parent_block_number,
252 hash: parent_hash,
253 } = substrate_parent_block;
254
255 let block_number = parent_block_number.saturating_add(1u32.into());
256
257 let extrinsics = block
258 .txdata
259 .clone()
260 .into_iter()
261 .map(TransactionAdapter::bitcoin_transaction_into_extrinsic)
262 .collect::<Vec<_>>();
263
264 let extrinsics_root = HashingFor::<Block>::ordered_trie_root(
265 extrinsics.iter().map(|xt| xt.encode()).collect(),
266 sp_core::storage::StateVersion::V0,
267 );
268
269 let digest = substrate_header_digest(&block.header);
270
271 let mut header = <<Block as BlockT>::Header as HeaderT>::new(
274 block_number,
275 extrinsics_root,
276 Default::default(),
277 parent_hash,
278 digest,
279 );
280
281 let state_action = if self.config.execute_block {
283 let now = std::time::Instant::now();
284
285 let tx_count = extrinsics.len();
286
287 let (state_root, storage_changes) = self.execute_block_at(
288 block_number,
289 parent_hash,
290 Block::new(header.clone(), extrinsics.clone()),
291 )?;
292
293 let execution_time = now.elapsed().as_millis();
294
295 self.stats.record_new_block_execution::<Block>(
296 block_number,
297 header.hash(),
298 tx_count,
299 execution_time,
300 );
301
302 header.set_state_root(state_root);
304
305 StateAction::ApplyChanges(StorageChanges::<Block>::Changes(storage_changes))
306 } else {
307 StateAction::Skip
308 };
309
310 let substrate_block_hash = header.hash();
311 let bitcoin_block_hash = block.header.block_hash();
312
313 let mut block_import_params = BlockImportParams::new(origin, header);
314 let (total_work, fork_choice) = calculate_chain_work_and_fork_choice(
315 &self.client,
316 &block.header,
317 block_number.saturated_into(),
318 )?;
319 block_import_params.fork_choice = Some(fork_choice);
320 block_import_params.body = Some(extrinsics);
321 block_import_params.state_action = state_action;
322
323 write_aux_storage(
324 &mut block_import_params,
325 bitcoin_block_hash,
326 substrate_block_hash,
327 total_work,
328 );
329
330 Ok(block_import_params)
331 }
332}
333
334pub(crate) fn calculate_chain_work_and_fork_choice<Block, Client>(
335 client: &Arc<Client>,
336 block_header: &BitcoinHeader,
337 block_number: u32,
338) -> sp_blockchain::Result<(Work, ForkChoiceStrategy)>
339where
340 Block: BlockT,
341 Client: HeaderBackend<Block> + AuxStore,
342{
343 let prev_blockhash = block_header.prev_blockhash;
344
345 let parent_work = if block_number == 1u32 {
346 client
348 .block_header(prev_blockhash)
349 .expect("Genesis header must exist; qed")
350 .work()
351 } else {
352 crate::aux_schema::load_chain_work(client.as_ref(), prev_blockhash)?
353 };
354
355 let total_work = parent_work + block_header.work();
356
357 let fork_choice = {
358 let info = client.info();
359
360 let parent_hash = client.substrate_block_hash_for(prev_blockhash).ok_or(
361 sp_blockchain::Error::Backend(format!(
362 "Missing substrate block hash for #{prev_blockhash}"
363 )),
364 )?;
365
366 let last_best_work = if info.best_hash == parent_hash {
367 parent_work
368 } else {
369 let bitcoin_block_hash = client
370 .bitcoin_block_hash_for(info.best_hash)
371 .expect("Best bitcoin hash must exist; qed");
372 crate::aux_schema::load_chain_work(client.as_ref(), bitcoin_block_hash)?
373 };
374
375 ForkChoiceStrategy::Custom(total_work > last_best_work)
376 };
377
378 Ok((total_work, fork_choice))
379}
380
381pub(crate) fn write_aux_storage<Block: BlockT>(
389 block_import_params: &mut BlockImportParams<Block>,
390 bitcoin_block_hash: BlockHash,
391 substrate_block_hash: Block::Hash,
392 chain_work: Work,
393) {
394 block_import_params.auxiliary.push((
395 bitcoin_block_hash.to_byte_array().to_vec(),
396 Some(substrate_block_hash.encode()),
397 ));
398 crate::aux_schema::write_chain_work(bitcoin_block_hash, chain_work, |(k, v)| {
399 block_import_params.auxiliary.push((k, Some(v)))
400 });
401}
402
403#[derive(Debug, Clone)]
408pub enum ImportStatus {
409 Imported {
411 block_number: u32,
412 block_hash: BlockHash,
413 aux: ImportedAux,
414 },
415 AlreadyInChain(u32),
417 UnknownParent,
419 MissingState,
421 KnownBad,
423}
424
425impl ImportStatus {
426 pub fn is_unknown_parent(&self) -> bool {
428 matches!(self, Self::UnknownParent)
429 }
430}
431
432#[async_trait::async_trait]
434pub trait BitcoinBlockImport: Send + Sync + 'static {
435 async fn import_block(
437 &mut self,
438 block: BitcoinBlock,
439 origin: BlockOrigin,
440 ) -> Result<ImportStatus, sp_consensus::Error>;
441}
442
443#[async_trait::async_trait]
444impl<Block, Client, BE, BI, TransactionAdapter> BitcoinBlockImport
445 for BitcoinBlockImporter<Block, Client, BE, BI, TransactionAdapter>
446where
447 Block: BlockT,
448 BE: Backend<Block> + Send + Sync + 'static,
449 Client: HeaderBackend<Block>
450 + BlockBackend<Block>
451 + StorageProvider<Block, BE>
452 + AuxStore
453 + ProvideRuntimeApi<Block>
454 + CallApiAt<Block>
455 + Send
456 + 'static,
457 Client::Api: Core<Block> + SubcoinApi<Block>,
458 BI: BlockImport<Block> + Send + Sync + 'static,
459 TransactionAdapter: BitcoinTransactionAdapter<Block> + Send + Sync + 'static,
460{
461 async fn import_block(
462 &mut self,
463 block: BitcoinBlock,
464 origin: BlockOrigin,
465 ) -> Result<ImportStatus, sp_consensus::Error> {
466 if let Some(block_number) = self.client.block_number(block.block_hash()) {
467 return Ok(ImportStatus::AlreadyInChain(block_number));
468 }
469
470 let bitcoin_parent_hash = block.header.prev_blockhash;
471
472 let import_err = sp_consensus::Error::ClientImport;
473
474 let Some(substrate_parent_block) = self
475 .fetch_substrate_block_info(bitcoin_parent_hash)
476 .map_err(|err| import_err(err.to_string()))?
477 else {
478 return Ok(ImportStatus::UnknownParent);
480 };
481
482 if self.config.execute_block {
483 match self
485 .client
486 .block_status(substrate_parent_block.hash)
487 .map_err(|err| import_err(err.to_string()))?
488 {
489 BlockStatus::InChainWithState | BlockStatus::Queued => {}
490 BlockStatus::Unknown => return Ok(ImportStatus::UnknownParent),
491 BlockStatus::InChainPruned => return Ok(ImportStatus::MissingState),
492 BlockStatus::KnownBad => return Ok(ImportStatus::KnownBad),
493 }
494 }
495
496 let block_number = substrate_parent_block.number.saturated_into::<u32>() + 1u32;
497 let block_hash = block.block_hash();
498
499 self.verifier
501 .verify_block(block_number, &block)
502 .map_err(|err| import_err(format!("{err:?}")))?;
503
504 let block_import_params = self
505 .prepare_substrate_block_import(block, substrate_parent_block, origin)
506 .map_err(|err| import_err(err.to_string()))?;
507
508 self.inner
509 .import_block(block_import_params)
510 .await
511 .map(|import_result| match import_result {
512 ImportResult::Imported(aux) => ImportStatus::Imported {
513 block_number,
514 block_hash,
515 aux,
516 },
517 ImportResult::AlreadyInChain => ImportStatus::AlreadyInChain(block_number),
518 ImportResult::KnownBad => ImportStatus::KnownBad,
519 ImportResult::UnknownParent => ImportStatus::UnknownParent,
520 ImportResult::MissingState => ImportStatus::MissingState,
521 })
522 .map_err(|err| import_err(err.to_string()))
523 }
524}