1use bitcoin::blockdata::block::Header as BitcoinHeader;
4use bitcoin::consensus::{Decodable, Encodable};
5use bitcoin::constants::genesis_block;
6use bitcoin::hashes::Hash;
7use bitcoin::{Block as BitcoinBlock, BlockHash, Transaction, Txid};
8use codec::{Decode, Encode};
9use sc_client_api::AuxStore;
10use sp_blockchain::HeaderBackend;
11use sp_runtime::generic::{Digest, DigestItem};
12use sp_runtime::traits::{Block as BlockT, Header as HeaderT};
13use std::sync::Arc;
14use subcoin_runtime_primitives::{NAKAMOTO_HASH_ENGINE_ID, NAKAMOTO_HEADER_ENGINE_ID};
15
16pub use subcoin_runtime_primitives as runtime;
17
18type Height = u32;
19
20pub const CONFIRMATION_DEPTH: u32 = 6u32;
22
23pub fn raw_genesis_tx(network: bitcoin::Network) -> Vec<u8> {
27 let mut data = Vec::new();
28
29 genesis_block(network)
30 .txdata
31 .into_iter()
32 .next()
33 .expect("Bitcoin genesis tx must exist; qed")
34 .consensus_encode(&mut data)
35 .expect("Genesis tx must be valid; qed");
36
37 data
38}
39
40pub fn bitcoin_genesis_tx() -> Vec<u8> {
42 raw_genesis_tx(bitcoin::Network::Bitcoin)
43}
44
45#[derive(Debug, Clone, Copy)]
47pub struct IndexedBlock {
48 pub number: u32,
50 pub hash: BlockHash,
52}
53
54impl std::fmt::Display for IndexedBlock {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 write!(f, "#{},{}", self.number, self.hash)
57 }
58}
59
60impl Default for IndexedBlock {
61 fn default() -> Self {
62 Self {
63 number: 0u32,
64 hash: BlockHash::all_zeros(),
65 }
66 }
67}
68
69pub trait BitcoinTransactionAdapter<Block: BlockT> {
71 fn extrinsic_to_bitcoin_transaction(extrinsics: &Block::Extrinsic) -> Transaction;
73
74 fn bitcoin_transaction_into_extrinsic(btc_tx: Transaction) -> Block::Extrinsic;
76}
77
78pub trait BackendExt<Block: BlockT> {
85 fn block_exists(&self, bitcoin_block_hash: BlockHash) -> bool;
87
88 fn block_number(&self, bitcoin_block_hash: BlockHash) -> Option<Height>;
92
93 fn block_hash(&self, block_number: u32) -> Option<BlockHash>;
97
98 fn block_header(&self, bitcoin_block_hash: BlockHash) -> Option<BitcoinHeader>;
100
101 fn bitcoin_block_hash_for(
103 &self,
104 substrate_block_hash: <Block as BlockT>::Hash,
105 ) -> Option<BlockHash>;
106
107 fn substrate_block_hash_for(
109 &self,
110 bitcoin_block_hash: BlockHash,
111 ) -> Option<<Block as BlockT>::Hash>;
112}
113
114impl<Block, Client> BackendExt<Block> for Arc<Client>
115where
116 Block: BlockT,
117 Client: HeaderBackend<Block> + AuxStore,
118{
119 fn block_exists(&self, bitcoin_block_hash: BlockHash) -> bool {
120 self.get_aux(bitcoin_block_hash.as_ref())
121 .ok()
122 .flatten()
123 .is_some()
124 }
125
126 fn block_number(&self, bitcoin_block_hash: BlockHash) -> Option<Height> {
127 self.substrate_block_hash_for(bitcoin_block_hash)
128 .and_then(|substrate_block_hash| self.number(substrate_block_hash).ok().flatten())
129 .map(|number| {
130 number
131 .try_into()
132 .unwrap_or_else(|_| panic!("BlockNumber must fit into u32; qed"))
133 })
134 }
135
136 fn block_hash(&self, number: u32) -> Option<BlockHash> {
137 self.hash(number.into())
138 .ok()
139 .flatten()
140 .and_then(|substrate_block_hash| self.bitcoin_block_hash_for(substrate_block_hash))
141 }
142
143 fn block_header(&self, bitcoin_block_hash: BlockHash) -> Option<BitcoinHeader> {
144 self.substrate_block_hash_for(bitcoin_block_hash)
145 .and_then(|substrate_block_hash| self.header(substrate_block_hash).ok().flatten())
146 .and_then(|header| extract_bitcoin_block_header::<Block>(&header).ok())
147 }
148
149 fn bitcoin_block_hash_for(
150 &self,
151 substrate_block_hash: <Block as BlockT>::Hash,
152 ) -> Option<BlockHash> {
153 self.header(substrate_block_hash)
154 .ok()
155 .flatten()
156 .and_then(|substrate_header| {
157 extract_bitcoin_block_hash::<Block>(&substrate_header).ok()
158 })
159 }
160
161 fn substrate_block_hash_for(
162 &self,
163 bitcoin_block_hash: BlockHash,
164 ) -> Option<<Block as BlockT>::Hash> {
165 self.get_aux(bitcoin_block_hash.as_ref())
166 .map_err(|err| {
167 tracing::error!(
168 ?bitcoin_block_hash,
169 "Failed to fetch substrate block hash: {err:?}"
170 );
171 })
172 .ok()
173 .flatten()
174 .and_then(|substrate_hash| Decode::decode(&mut substrate_hash.as_slice()).ok())
175 }
176}
177
178pub trait ClientExt<Block> {
180 fn best_number(&self) -> u32;
182}
183
184impl<Block, Client> ClientExt<Block> for Arc<Client>
185where
186 Block: BlockT,
187 Client: HeaderBackend<Block>,
188{
189 fn best_number(&self) -> u32 {
190 self.info()
191 .best_number
192 .try_into()
193 .unwrap_or_else(|_| panic!("BlockNumber must fit into u32; qed"))
194 }
195}
196
197pub trait CoinStorageKey: Send + Sync {
199 fn storage_key(&self, txid: bitcoin::Txid, vout: u32) -> Vec<u8>;
201
202 fn storage_prefix(&self) -> [u8; 32];
204}
205
206#[derive(Debug, Clone)]
208pub struct BlockLocator {
209 pub latest_block: u32,
211 pub locator_hashes: Vec<BlockHash>,
213}
214
215impl BlockLocator {
216 pub fn empty() -> Self {
217 Self {
218 latest_block: 0u32,
219 locator_hashes: Vec::new(),
220 }
221 }
222}
223
224pub trait BlockLocatorProvider<Block: BlockT> {
226 fn block_locator(
230 &self,
231 from: Option<Height>,
232 search_pending_block: impl Fn(Height) -> Option<BlockHash>,
233 ) -> BlockLocator;
234}
235
236impl<Block, Client> BlockLocatorProvider<Block> for Arc<Client>
237where
238 Block: BlockT,
239 Client: HeaderBackend<Block> + AuxStore,
240{
241 fn block_locator(
242 &self,
243 from: Option<Height>,
244 search_pending_block: impl Fn(Height) -> Option<BlockHash>,
245 ) -> BlockLocator {
246 let mut locator_hashes = Vec::new();
247
248 let from = from.unwrap_or_else(|| self.best_number());
249
250 for height in locator_indexes(from) {
251 if let Some(bitcoin_hash) = search_pending_block(height) {
258 locator_hashes.push(bitcoin_hash);
259 continue;
260 }
261
262 let Ok(Some(hash)) = self.hash(height.into()) else {
263 continue;
264 };
265
266 if let Ok(Some(header)) = self.header(hash) {
267 let maybe_bitcoin_block_hash =
268 BackendExt::<Block>::bitcoin_block_hash_for(self, header.hash());
269 if let Some(bitcoin_block_hash) = maybe_bitcoin_block_hash {
270 locator_hashes.push(bitcoin_block_hash);
271 }
272 }
273 }
274
275 BlockLocator {
276 latest_block: from,
277 locator_hashes,
278 }
279 }
280}
281
282fn locator_indexes(mut from: Height) -> Vec<Height> {
285 let mut indexes = Vec::new();
286 let mut step = 1;
287
288 while from > 0 {
289 if indexes.len() >= 8 {
291 step *= 2;
292 }
293 indexes.push(from as Height);
294 from = from.saturating_sub(step);
295 }
296
297 indexes.push(0);
299
300 indexes
301}
302
303#[derive(Debug, Clone, Encode, Decode)]
305pub struct TxPosition {
306 pub block_number: u32,
308 pub index: u32,
310}
311
312pub trait TransactionIndex {
314 fn tx_index(&self, txid: Txid) -> sp_blockchain::Result<Option<TxPosition>>;
316}
317
318pub struct NoTransactionIndex;
320
321impl TransactionIndex for NoTransactionIndex {
322 fn tx_index(&self, _txid: Txid) -> sp_blockchain::Result<Option<TxPosition>> {
323 Ok(None)
324 }
325}
326
327pub fn substrate_header_digest(bitcoin_header: &BitcoinHeader) -> Digest {
332 let mut raw_bitcoin_block_hash = bitcoin_header.block_hash().to_byte_array().to_vec();
333 raw_bitcoin_block_hash.reverse();
334
335 let mut encoded_bitcoin_header = Vec::with_capacity(32);
336 bitcoin_header
337 .consensus_encode(&mut encoded_bitcoin_header)
338 .expect("Bitcoin header must be valid; qed");
339
340 Digest {
345 logs: vec![
346 DigestItem::PreRuntime(NAKAMOTO_HASH_ENGINE_ID, raw_bitcoin_block_hash),
347 DigestItem::PreRuntime(NAKAMOTO_HEADER_ENGINE_ID, encoded_bitcoin_header),
348 ],
349 }
350}
351
352#[derive(Debug, Clone)]
354pub enum HeaderError {
355 MultiplePreRuntimeDigests,
356 MissingBitcoinBlockHashDigest,
357 InvalidBitcoinBlockHashDigest,
358 MissingBitcoinBlockHeader,
359 InvalidBitcoinBlockHeader(String),
360}
361
362pub fn extract_bitcoin_block_hash<Block: BlockT>(
364 header: &Block::Header,
365) -> Result<BlockHash, HeaderError> {
366 let mut pre_digest: Option<_> = None;
367
368 for log in header.digest().logs() {
369 tracing::trace!("Checking log {:?}, looking for pre runtime digest", log);
370 match (log, pre_digest.is_some()) {
371 (DigestItem::PreRuntime(NAKAMOTO_HASH_ENGINE_ID, _), true) => {
372 return Err(HeaderError::MultiplePreRuntimeDigests);
373 }
374 (DigestItem::PreRuntime(NAKAMOTO_HASH_ENGINE_ID, v), false) => {
375 pre_digest.replace(v);
376 }
377 (_, _) => tracing::trace!("Ignoring digest not meant for us"),
378 }
379 }
380
381 let mut raw_bitcoin_block_hash = pre_digest
382 .ok_or(HeaderError::MissingBitcoinBlockHashDigest)?
383 .to_vec();
384 raw_bitcoin_block_hash.reverse();
385
386 BlockHash::from_slice(&raw_bitcoin_block_hash)
387 .map_err(|_| HeaderError::InvalidBitcoinBlockHashDigest)
388}
389
390pub fn extract_bitcoin_block_header<Block: BlockT>(
392 header: &Block::Header,
393) -> Result<BitcoinHeader, HeaderError> {
394 let mut pre_digest: Option<_> = None;
395
396 for log in header.digest().logs() {
397 tracing::trace!("Checking log {:?}, looking for pre runtime digest", log);
398 match (log, pre_digest.is_some()) {
399 (DigestItem::PreRuntime(NAKAMOTO_HEADER_ENGINE_ID, _), true) => {
400 return Err(HeaderError::MultiplePreRuntimeDigests);
401 }
402 (DigestItem::PreRuntime(NAKAMOTO_HEADER_ENGINE_ID, v), false) => {
403 pre_digest.replace(v);
404 }
405 (_, _) => tracing::trace!("Ignoring digest not meant for us"),
406 }
407 }
408
409 let bitcoin_block_header = pre_digest.ok_or(HeaderError::MissingBitcoinBlockHeader)?;
410
411 BitcoinHeader::consensus_decode(&mut bitcoin_block_header.as_slice())
412 .map_err(|err| HeaderError::InvalidBitcoinBlockHeader(err.to_string()))
413}
414
415pub fn convert_to_bitcoin_block<
417 Block: BlockT,
418 TransactionAdapter: BitcoinTransactionAdapter<Block>,
419>(
420 substrate_block: Block,
421) -> Result<BitcoinBlock, HeaderError> {
422 let header = extract_bitcoin_block_header::<Block>(substrate_block.header())?;
423
424 let txdata = substrate_block
425 .extrinsics()
426 .iter()
427 .map(TransactionAdapter::extrinsic_to_bitcoin_transaction)
428 .collect();
429
430 Ok(BitcoinBlock { header, txdata })
431}