1pub mod consensus;
4pub mod tx_pool;
5
6use bitcoin::blockdata::block::Header as BitcoinHeader;
7use bitcoin::consensus::{Decodable, Encodable};
8use bitcoin::constants::genesis_block;
9use bitcoin::hashes::Hash;
10use bitcoin::{
11 Amount, Block as BitcoinBlock, BlockHash, ScriptBuf, Transaction, TxOut, Txid, Weight,
12};
13use codec::{Decode, Encode};
14use sc_client_api::AuxStore;
15use sp_blockchain::HeaderBackend;
16use sp_runtime::generic::{Digest, DigestItem};
17use sp_runtime::traits::{Block as BlockT, Header as HeaderT};
18use std::sync::Arc;
19use subcoin_runtime_primitives::{NAKAMOTO_HASH_ENGINE_ID, NAKAMOTO_HEADER_ENGINE_ID};
20
21pub use subcoin_runtime_primitives as runtime;
22
23type Height = u32;
24
25pub const CONFIRMATION_DEPTH: u32 = 6u32;
27
28pub const MAX_BLOCK_WEIGHT: Weight = Weight::MAX_BLOCK;
30
31pub fn raw_genesis_tx(network: bitcoin::Network) -> Vec<u8> {
35 let mut data = Vec::new();
36
37 genesis_block(network)
38 .txdata
39 .into_iter()
40 .next()
41 .expect("Bitcoin genesis tx must exist; qed")
42 .consensus_encode(&mut data)
43 .expect("Genesis tx must be valid; qed");
44
45 data
46}
47
48pub fn bitcoin_genesis_tx() -> Vec<u8> {
50 raw_genesis_tx(bitcoin::Network::Bitcoin)
51}
52
53#[derive(Debug, Clone, Copy)]
55pub struct IndexedBlock {
56 pub number: u32,
58 pub hash: BlockHash,
60}
61
62impl std::fmt::Display for IndexedBlock {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 write!(f, "#{},{}", self.number, self.hash)
65 }
66}
67
68impl Default for IndexedBlock {
69 fn default() -> Self {
70 Self {
71 number: 0u32,
72 hash: BlockHash::all_zeros(),
73 }
74 }
75}
76
77pub trait BitcoinTransactionAdapter<Block: BlockT> {
79 fn extrinsic_to_bitcoin_transaction(extrinsics: &Block::Extrinsic) -> Transaction;
81
82 fn bitcoin_transaction_into_extrinsic(btc_tx: Transaction) -> Block::Extrinsic;
84}
85
86pub trait BackendExt<Block: BlockT> {
93 fn block_exists(&self, bitcoin_block_hash: BlockHash) -> bool;
95
96 fn block_number(&self, bitcoin_block_hash: BlockHash) -> Option<Height>;
100
101 fn block_hash(&self, block_number: u32) -> Option<BlockHash>;
105
106 fn block_header(&self, bitcoin_block_hash: BlockHash) -> Option<BitcoinHeader>;
108
109 fn bitcoin_block_hash_for(
111 &self,
112 substrate_block_hash: <Block as BlockT>::Hash,
113 ) -> Option<BlockHash>;
114
115 fn substrate_block_hash_for(
117 &self,
118 bitcoin_block_hash: BlockHash,
119 ) -> Option<<Block as BlockT>::Hash>;
120}
121
122impl<Block, Client> BackendExt<Block> for Arc<Client>
123where
124 Block: BlockT,
125 Client: HeaderBackend<Block> + AuxStore,
126{
127 fn block_exists(&self, bitcoin_block_hash: BlockHash) -> bool {
128 self.get_aux(bitcoin_block_hash.as_ref())
129 .ok()
130 .flatten()
131 .is_some()
132 }
133
134 fn block_number(&self, bitcoin_block_hash: BlockHash) -> Option<Height> {
135 self.substrate_block_hash_for(bitcoin_block_hash)
136 .and_then(|substrate_block_hash| self.number(substrate_block_hash).ok().flatten())
137 .map(|number| {
138 number
139 .try_into()
140 .unwrap_or_else(|_| panic!("BlockNumber must fit into u32; qed"))
141 })
142 }
143
144 fn block_hash(&self, number: u32) -> Option<BlockHash> {
145 self.hash(number.into())
146 .ok()
147 .flatten()
148 .and_then(|substrate_block_hash| self.bitcoin_block_hash_for(substrate_block_hash))
149 }
150
151 fn block_header(&self, bitcoin_block_hash: BlockHash) -> Option<BitcoinHeader> {
152 self.substrate_block_hash_for(bitcoin_block_hash)
153 .and_then(|substrate_block_hash| self.header(substrate_block_hash).ok().flatten())
154 .and_then(|header| extract_bitcoin_block_header::<Block>(&header).ok())
155 }
156
157 fn bitcoin_block_hash_for(
158 &self,
159 substrate_block_hash: <Block as BlockT>::Hash,
160 ) -> Option<BlockHash> {
161 self.header(substrate_block_hash)
162 .ok()
163 .flatten()
164 .and_then(|substrate_header| {
165 extract_bitcoin_block_hash::<Block>(&substrate_header).ok()
166 })
167 }
168
169 fn substrate_block_hash_for(
170 &self,
171 bitcoin_block_hash: BlockHash,
172 ) -> Option<<Block as BlockT>::Hash> {
173 self.get_aux(bitcoin_block_hash.as_ref())
174 .map_err(|err| {
175 tracing::error!(
176 ?bitcoin_block_hash,
177 "Failed to fetch substrate block hash: {err:?}"
178 );
179 })
180 .ok()
181 .flatten()
182 .and_then(|substrate_hash| Decode::decode(&mut substrate_hash.as_slice()).ok())
183 }
184}
185
186pub const MEDIAN_TIME_SPAN: usize = 11;
188
189pub trait ClientExt<Block> {
191 fn best_number(&self) -> u32;
193
194 fn calculate_median_time_past(&self, block_hash: BlockHash) -> Option<i64>;
198
199 fn get_block_metadata(&self, block_hash: BlockHash) -> Option<BlockMetadata>;
203
204 fn is_block_on_active_chain(&self, block_hash: BlockHash) -> bool;
208}
209
210impl<Block, Client> ClientExt<Block> for Arc<Client>
211where
212 Block: BlockT,
213 Client: HeaderBackend<Block> + AuxStore,
214{
215 fn best_number(&self) -> u32 {
216 self.info()
217 .best_number
218 .try_into()
219 .unwrap_or_else(|_| panic!("BlockNumber must fit into u32; qed"))
220 }
221
222 fn calculate_median_time_past(&self, block_hash: BlockHash) -> Option<i64> {
223 let mut timestamps = Vec::with_capacity(MEDIAN_TIME_SPAN);
224
225 let header = self.block_header(block_hash)?;
226 timestamps.push(header.time as i64);
227
228 let zero_hash = BlockHash::all_zeros();
229 let mut prev_hash = header.prev_blockhash;
230
231 for _ in 0..MEDIAN_TIME_SPAN - 1 {
233 if prev_hash == zero_hash {
234 break;
235 }
236
237 let header = self.block_header(prev_hash)?;
238 timestamps.push(header.time as i64);
239 prev_hash = header.prev_blockhash;
240 }
241
242 timestamps.sort_unstable();
243 Some(timestamps[timestamps.len() / 2])
244 }
245
246 fn get_block_metadata(&self, block_hash: BlockHash) -> Option<BlockMetadata> {
247 let height = self.block_number(block_hash)?;
248 let median_time_past = self.calculate_median_time_past(block_hash)?;
249
250 Some(BlockMetadata {
251 height,
252 median_time_past,
253 })
254 }
255
256 fn is_block_on_active_chain(&self, block_hash: BlockHash) -> bool {
257 self.block_number(block_hash)
258 .and_then(|height| self.block_hash(height))
259 .map(|canonical_hash| canonical_hash == block_hash)
260 .unwrap_or(false)
261 }
262}
263
264pub trait CoinStorageKey: Send + Sync {
266 fn storage_key(&self, txid: bitcoin::Txid, vout: u32) -> Vec<u8>;
268
269 fn storage_prefix(&self) -> [u8; 32];
271}
272
273#[derive(Debug, Clone)]
275pub struct BlockLocator {
276 pub latest_block: u32,
278 pub locator_hashes: Vec<BlockHash>,
280}
281
282impl BlockLocator {
283 pub fn empty() -> Self {
284 Self {
285 latest_block: 0u32,
286 locator_hashes: Vec::new(),
287 }
288 }
289}
290
291pub trait BlockLocatorProvider<Block: BlockT> {
293 fn block_locator(
297 &self,
298 from: Option<Height>,
299 search_pending_block: impl Fn(Height) -> Option<BlockHash>,
300 ) -> BlockLocator;
301}
302
303impl<Block, Client> BlockLocatorProvider<Block> for Arc<Client>
304where
305 Block: BlockT,
306 Client: HeaderBackend<Block> + AuxStore,
307{
308 fn block_locator(
309 &self,
310 from: Option<Height>,
311 search_pending_block: impl Fn(Height) -> Option<BlockHash>,
312 ) -> BlockLocator {
313 let mut locator_hashes = Vec::new();
314
315 let from = from.unwrap_or_else(|| self.best_number());
316
317 for height in locator_indexes(from) {
318 if let Some(bitcoin_hash) = search_pending_block(height) {
325 locator_hashes.push(bitcoin_hash);
326 continue;
327 }
328
329 let Ok(Some(hash)) = self.hash(height.into()) else {
330 continue;
331 };
332
333 if let Ok(Some(header)) = self.header(hash) {
334 let maybe_bitcoin_block_hash =
335 BackendExt::<Block>::bitcoin_block_hash_for(self, header.hash());
336 if let Some(bitcoin_block_hash) = maybe_bitcoin_block_hash {
337 locator_hashes.push(bitcoin_block_hash);
338 }
339 }
340 }
341
342 BlockLocator {
343 latest_block: from,
344 locator_hashes,
345 }
346 }
347}
348
349fn locator_indexes(mut from: Height) -> Vec<Height> {
352 let mut indexes = Vec::new();
353 let mut step = 1;
354
355 while from > 0 {
356 if indexes.len() >= 8 {
358 step *= 2;
359 }
360 indexes.push(from as Height);
361 from = from.saturating_sub(step);
362 }
363
364 indexes.push(0);
366
367 indexes
368}
369
370#[derive(Debug, Clone, Encode, Decode)]
372pub struct TxPosition {
373 pub block_number: u32,
375 pub index: u32,
377}
378
379pub trait TransactionIndex {
381 fn tx_index(&self, txid: Txid) -> sp_blockchain::Result<Option<TxPosition>>;
383}
384
385pub struct NoTransactionIndex;
387
388impl TransactionIndex for NoTransactionIndex {
389 fn tx_index(&self, _txid: Txid) -> sp_blockchain::Result<Option<TxPosition>> {
390 Ok(None)
391 }
392}
393
394pub fn substrate_header_digest(bitcoin_header: &BitcoinHeader) -> Digest {
399 let mut raw_bitcoin_block_hash = bitcoin_header.block_hash().to_byte_array().to_vec();
400 raw_bitcoin_block_hash.reverse();
401
402 let mut encoded_bitcoin_header = Vec::with_capacity(32);
403 bitcoin_header
404 .consensus_encode(&mut encoded_bitcoin_header)
405 .expect("Bitcoin header must be valid; qed");
406
407 Digest {
412 logs: vec![
413 DigestItem::PreRuntime(NAKAMOTO_HASH_ENGINE_ID, raw_bitcoin_block_hash),
414 DigestItem::PreRuntime(NAKAMOTO_HEADER_ENGINE_ID, encoded_bitcoin_header),
415 ],
416 }
417}
418
419#[derive(Debug, Clone)]
421pub enum HeaderError {
422 MultiplePreRuntimeDigests,
423 MissingBitcoinBlockHashDigest,
424 InvalidBitcoinBlockHashDigest,
425 MissingBitcoinBlockHeader,
426 InvalidBitcoinBlockHeader(String),
427}
428
429pub fn extract_bitcoin_block_hash<Block: BlockT>(
431 header: &Block::Header,
432) -> Result<BlockHash, HeaderError> {
433 let mut pre_digest: Option<_> = None;
434
435 for log in header.digest().logs() {
436 tracing::trace!("Checking log {:?}, looking for pre runtime digest", log);
437 match (log, pre_digest.is_some()) {
438 (DigestItem::PreRuntime(NAKAMOTO_HASH_ENGINE_ID, _), true) => {
439 return Err(HeaderError::MultiplePreRuntimeDigests);
440 }
441 (DigestItem::PreRuntime(NAKAMOTO_HASH_ENGINE_ID, v), false) => {
442 pre_digest.replace(v);
443 }
444 (_, _) => tracing::trace!("Ignoring digest not meant for us"),
445 }
446 }
447
448 let mut raw_bitcoin_block_hash = pre_digest
449 .ok_or(HeaderError::MissingBitcoinBlockHashDigest)?
450 .to_vec();
451 raw_bitcoin_block_hash.reverse();
452
453 BlockHash::from_slice(&raw_bitcoin_block_hash)
454 .map_err(|_| HeaderError::InvalidBitcoinBlockHashDigest)
455}
456
457pub fn extract_bitcoin_block_header<Block: BlockT>(
459 header: &Block::Header,
460) -> Result<BitcoinHeader, HeaderError> {
461 let mut pre_digest: Option<_> = None;
462
463 for log in header.digest().logs() {
464 tracing::trace!("Checking log {:?}, looking for pre runtime digest", log);
465 match (log, pre_digest.is_some()) {
466 (DigestItem::PreRuntime(NAKAMOTO_HEADER_ENGINE_ID, _), true) => {
467 return Err(HeaderError::MultiplePreRuntimeDigests);
468 }
469 (DigestItem::PreRuntime(NAKAMOTO_HEADER_ENGINE_ID, v), false) => {
470 pre_digest.replace(v);
471 }
472 (_, _) => tracing::trace!("Ignoring digest not meant for us"),
473 }
474 }
475
476 let bitcoin_block_header = pre_digest.ok_or(HeaderError::MissingBitcoinBlockHeader)?;
477
478 BitcoinHeader::consensus_decode(&mut bitcoin_block_header.as_slice())
479 .map_err(|err| HeaderError::InvalidBitcoinBlockHeader(err.to_string()))
480}
481
482pub fn convert_to_bitcoin_block<
484 Block: BlockT,
485 TransactionAdapter: BitcoinTransactionAdapter<Block>,
486>(
487 substrate_block: Block,
488) -> Result<BitcoinBlock, HeaderError> {
489 let header = extract_bitcoin_block_header::<Block>(substrate_block.header())?;
490
491 let txdata = substrate_block
492 .extrinsics()
493 .iter()
494 .map(TransactionAdapter::extrinsic_to_bitcoin_transaction)
495 .collect();
496
497 Ok(BitcoinBlock { header, txdata })
498}
499
500pub const MEMPOOL_HEIGHT: u32 = 0x7FFFFFFF;
502
503#[derive(Debug, Clone)]
505pub struct PoolCoin {
506 pub output: TxOut,
508 pub height: u32,
510 pub is_coinbase: bool,
512 pub median_time_past: i64,
514}
515
516impl From<subcoin_runtime_primitives::Coin> for PoolCoin {
517 fn from(coin: subcoin_runtime_primitives::Coin) -> Self {
518 Self {
519 output: TxOut {
520 value: Amount::from_sat(coin.amount),
521 script_pubkey: ScriptBuf::from_bytes(coin.script_pubkey),
522 },
523 height: coin.height,
524 is_coinbase: coin.is_coinbase,
525 median_time_past: 0, }
527 }
528}
529
530#[derive(Debug, Clone, Copy)]
532pub struct BlockMetadata {
533 pub height: u32,
535 pub median_time_past: i64,
537}