sc_consensus_nakamoto/
verification.rs

1//! This module provides block verification functionalities based on Bitcoin's consensus rules.
2//! The primary code reference for these consensus rules is Bitcoin Core.
3//!
4//! We utilize the `rust-bitcoinconsensus` from `rust-bitcoin` for handling the most complex
5//! aspects of script verification.
6//!
7//! The main components of this module are:
8//! - `header_verify`: Module responsible for verifying block headers.
9//! - `tx_verify`: Module responsible for verifying individual transactions within a block.
10//!
11//! This module ensures that blocks adhere to Bitcoin's consensus rules by performing checks on
12//! the proof of work, timestamps, transaction validity, and more.
13//!
14//! # Components
15//!
16//! ## Modules
17//!
18//! - `header_verify`: Contains functions and structures for verifying block headers.
19//! - `tx_verify`: Contains functions and structures for verifying transactions.
20//!
21//! ## Structures
22//!
23//! - [`BlockVerifier`]: Responsible for verifying Bitcoin blocks, including headers and transactions.
24//!
25//! ## Enums
26//!
27//! - [`BlockVerification`]: Represents the level of block verification (None, Full, HeaderOnly).
28
29mod header_verify;
30mod script_verify;
31mod tx_verify;
32
33use crate::chain_params::ChainParams;
34use bitcoin::block::Bip34Error;
35use bitcoin::blockdata::block::Header as BitcoinHeader;
36use bitcoin::blockdata::constants::{COINBASE_MATURITY, MAX_BLOCK_SIGOPS_COST};
37use bitcoin::blockdata::weight::WITNESS_SCALE_FACTOR;
38use bitcoin::consensus::Encodable;
39use bitcoin::{
40    Amount, Block as BitcoinBlock, BlockHash, OutPoint, ScriptBuf, TxMerkleNode, TxOut, Txid,
41    VarInt, Weight,
42};
43pub use header_verify::{Error as HeaderError, HeaderVerifier};
44use sc_client_api::{AuxStore, Backend, StorageProvider};
45use sp_blockchain::HeaderBackend;
46use sp_runtime::traits::Block as BlockT;
47use std::collections::{HashMap, HashSet};
48use std::marker::PhantomData;
49use std::sync::Arc;
50use subcoin_primitives::consensus::{TxError, check_transaction_sanity};
51use subcoin_primitives::runtime::{Coin, bitcoin_block_subsidy};
52use subcoin_primitives::{CoinStorageKey, MAX_BLOCK_WEIGHT};
53use tx_verify::{get_legacy_sig_op_count, is_final_tx};
54
55/// Represents the Bitcoin script backend.
56#[derive(Copy, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
57#[cfg_attr(feature = "cli", derive(clap::ValueEnum))]
58pub enum ScriptEngine {
59    /// Uses the Bitcoin Core bindings for script verification.
60    #[default]
61    Core,
62    /// No script verification.
63    None,
64    /// Uses the Rust-based Bitcoin script interpreter (Subcoin) for script verification.
65    /// This is an experimental feature, not yet fully validated for production use.
66    Subcoin,
67}
68
69/// Represents the level of block verification.
70#[derive(Copy, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
71#[cfg_attr(feature = "cli", derive(clap::ValueEnum))]
72pub enum BlockVerification {
73    /// No verification performed.
74    None,
75    /// Full verification, including verifying the transactions.
76    #[default]
77    Full,
78    /// Verify the block header only, without the transaction veification.
79    HeaderOnly,
80}
81
82/// Represents the context of a transaction within a block.
83#[derive(Debug)]
84pub struct TransactionContext {
85    /// Block number containing the transaction.
86    pub block_number: u32,
87    /// Index of the transaction in the block.
88    pub tx_index: usize,
89    /// ID of the transaction.
90    pub txid: Txid,
91}
92
93impl std::fmt::Display for TransactionContext {
94    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95        write!(
96            f,
97            "Tx #{}:{}: {}",
98            self.block_number, self.tx_index, self.txid
99        )
100    }
101}
102
103/// Block verification error.
104#[derive(Debug, thiserror::Error)]
105pub enum Error {
106    /// The merkle root of the block is invalid.
107    #[error("Invalid merkle root in block#{0}")]
108    BadMerkleRoot(BlockHash),
109    /// Block must contain at least one coinbase transaction.
110    #[error("Block#{0} has an empty transaction list")]
111    EmptyTransactionList(BlockHash),
112    /// Block size exceeds the limit.
113    #[error("Block#{0} exceeds maximum allowed size")]
114    BlockTooLarge(BlockHash),
115    /// First transaction must be a coinbase.
116    #[error("First transaction in block#{0} is not a coinbase")]
117    FirstTransactionIsNotCoinbase(BlockHash),
118    /// A block must contain only one coinbase transaction.
119    #[error("Block#{0} contains multiple coinbase transactions")]
120    MultipleCoinbase(BlockHash),
121    #[error("Block#{block_hash} has incorrect coinbase height, (expected: {expected}, got: {got})")]
122    BadCoinbaseBlockHeight {
123        block_hash: BlockHash,
124        got: u32,
125        expected: u32,
126    },
127    /// Coinbase transaction is prematurely spent.
128    #[error("Premature spend of coinbase transaction in block#{0}")]
129    PrematureSpendOfCoinbase(BlockHash),
130
131    /// Block contains duplicate transactions.
132    #[error("Block#{block_hash} contains duplicate transaction at index {index}")]
133    DuplicateTransaction { block_hash: BlockHash, index: usize },
134    /// A transaction is not finalized.
135    #[error("Transaction in block#{0} is not finalized")]
136    TransactionNotFinal(BlockHash),
137    /// Transaction script contains too many signature operations.
138    #[error(
139        "Transaction in block #{block_number},{block_hash} exceeds signature operation limit (max: {MAX_BLOCK_SIGOPS_COST})"
140    )]
141    TooManySigOps {
142        block_hash: BlockHash,
143        block_number: u32,
144    },
145    /// Witness commitment in the block is invalid.
146    #[error("Invalid witness commitment in block#{0}")]
147    BadWitnessCommitment(BlockHash),
148
149    /// UTXO referenced by a transaction is missing
150    #[error(
151        "Missing UTXO in state for transaction ({context}) in block {block_hash}. Missing UTXO: {missing_utxo:?}"
152    )]
153    MissingUtxoInState {
154        block_hash: BlockHash,
155        /// Context of the transaction being processed.
156        context: TransactionContext,
157        /// UTXO missing from the UTXO set.
158        missing_utxo: OutPoint,
159    },
160    /// UTXO has already been spent within the same block.
161    #[error("UTXO already spent in current block (#{block_number},{block_hash}:{txid}: {utxo:?})")]
162    AlreadySpentInCurrentBlock {
163        block_hash: BlockHash,
164        block_number: u32,
165        txid: Txid,
166        utxo: OutPoint,
167    },
168    /// Insufficient funds: total input amount is lower than the total output amount.
169    #[error(
170        "Block#{block_hash} has an invalid transaction: total input {value_in} < total output {value_out}"
171    )]
172    InsufficientFunds {
173        block_hash: BlockHash,
174        value_in: u64,
175        value_out: u64,
176    },
177    /// Block reward (coinbase value) exceeds the allowed subsidy + transaction fees.
178    #[error("Block#{0} reward exceeds allowed amount (subsidy + fees)")]
179    InvalidBlockReward(BlockHash),
180    /// A transaction script failed verification.
181    #[error(
182        "Script verification failure in block#{block_hash}. Context: {context}, input_index: {input_index}: {error:?}"
183    )]
184    InvalidScript {
185        block_hash: BlockHash,
186        context: TransactionContext,
187        input_index: usize,
188        error: Box<subcoin_script::Error>,
189    },
190    #[error("BIP34 error in block#{0}: {1:?}")]
191    Bip34(BlockHash, Bip34Error),
192    #[error("Bitcoin codec error: {0:?}")]
193    BitcoinCodec(bitcoin::io::Error),
194    #[error(transparent)]
195    BitcoinConsensus(#[from] bitcoinconsensus::Error),
196    /// An error occurred in the client.
197    #[error(transparent)]
198    Client(#[from] sp_blockchain::Error),
199    #[error(transparent)]
200    Transaction(#[from] TxError),
201    /// Block header error.
202    #[error(transparent)]
203    Header(#[from] HeaderError),
204}
205
206/// A struct responsible for verifying Bitcoin blocks.
207#[derive(Clone)]
208pub struct BlockVerifier<Block, Client, BE> {
209    client: Arc<Client>,
210    chain_params: ChainParams,
211    header_verifier: HeaderVerifier<Block, Client>,
212    block_verification: BlockVerification,
213    coin_storage_key: Arc<dyn CoinStorageKey>,
214    script_engine: ScriptEngine,
215    _phantom: PhantomData<(Block, BE)>,
216}
217
218impl<Block, Client, BE> BlockVerifier<Block, Client, BE> {
219    /// Constructs a new instance of [`BlockVerifier`].
220    pub fn new(
221        client: Arc<Client>,
222        network: bitcoin::Network,
223        block_verification: BlockVerification,
224        coin_storage_key: Arc<dyn CoinStorageKey>,
225        script_engine: ScriptEngine,
226    ) -> Self {
227        let chain_params = ChainParams::new(network);
228        let header_verifier = HeaderVerifier::new(client.clone(), chain_params.clone());
229        Self {
230            client,
231            chain_params,
232            header_verifier,
233            block_verification,
234            coin_storage_key,
235            script_engine,
236            _phantom: Default::default(),
237        }
238    }
239}
240
241impl<Block, Client, BE> BlockVerifier<Block, Client, BE>
242where
243    Block: BlockT,
244    BE: Backend<Block>,
245    Client: HeaderBackend<Block> + StorageProvider<Block, BE> + AuxStore,
246{
247    /// Performs full block verification.
248    ///
249    /// References:
250    /// - <https://en.bitcoin.it/wiki/Protocol_rules#.22block.22_messages>
251    pub fn verify_block(&self, block_number: u32, block: &BitcoinBlock) -> Result<(), Error> {
252        let txids = self.check_block_sanity(block_number, block)?;
253
254        self.contextual_check_block(block_number, block.block_hash(), block, txids)
255    }
256
257    fn contextual_check_block(
258        &self,
259        block_number: u32,
260        block_hash: BlockHash,
261        block: &BitcoinBlock,
262        txids: HashMap<usize, Txid>,
263    ) -> Result<(), Error> {
264        match self.block_verification {
265            BlockVerification::Full => {
266                let lock_time_cutoff = self.header_verifier.verify(&block.header)?;
267
268                if block_number >= self.chain_params.segwit_height
269                    && !block.check_witness_commitment()
270                {
271                    return Err(Error::BadWitnessCommitment(block_hash));
272                }
273
274                // Check the block weight with witness data.
275                if block.weight() > MAX_BLOCK_WEIGHT {
276                    return Err(Error::BlockTooLarge(block_hash));
277                }
278
279                self.verify_transactions(block_number, block, txids, lock_time_cutoff)?;
280            }
281            BlockVerification::HeaderOnly => {
282                self.header_verifier.verify(&block.header)?;
283            }
284            BlockVerification::None => {}
285        }
286
287        Ok(())
288    }
289
290    /// Performs preliminary checks.
291    ///
292    /// - Transaction list must be non-empty.
293    /// - Block size must not exceed [`MAX_BLOCK_WEIGHT`].
294    /// - First transaction must be coinbase, the rest must not be.
295    /// - No duplicate transactions in the block.
296    /// - Check the sum of transaction sig opcounts does not exceed [`MAX_BLOCK_SIGOPS_COST`].
297    /// - Check the calculated merkle root of transactions matches the one declared in the header.
298    ///
299    /// <https://github.com/bitcoin/bitcoin/blob/6f9db1ebcab4064065ccdCOIN787161bf2b87e03cc1f/src/validation.cpp#L3986>
300    fn check_block_sanity(
301        &self,
302        block_number: u32,
303        block: &BitcoinBlock,
304    ) -> Result<HashMap<usize, Txid>, Error> {
305        let block_hash = block.block_hash();
306
307        if block.txdata.is_empty() {
308            return Err(Error::EmptyTransactionList(block_hash));
309        }
310
311        // Size limits, without tx witness data.
312        if Weight::from_wu((block.txdata.len() * WITNESS_SCALE_FACTOR) as u64) > MAX_BLOCK_WEIGHT
313            || Weight::from_wu((block_base_size(block) * WITNESS_SCALE_FACTOR) as u64)
314                > MAX_BLOCK_WEIGHT
315        {
316            return Err(Error::BlockTooLarge(block_hash));
317        }
318
319        if !block.txdata[0].is_coinbase() {
320            return Err(Error::FirstTransactionIsNotCoinbase(block_hash));
321        }
322
323        // Check duplicate transactions
324        let tx_count = block.txdata.len();
325
326        let mut seen_transactions = HashSet::with_capacity(tx_count);
327        let mut txids = HashMap::with_capacity(tx_count);
328
329        let mut sig_ops = 0;
330
331        for (index, tx) in block.txdata.iter().enumerate() {
332            if index > 0 && tx.is_coinbase() {
333                return Err(Error::MultipleCoinbase(block_hash));
334            }
335
336            let txid = tx.compute_txid();
337            if !seen_transactions.insert(txid) {
338                // If txid is already in the set, we've found a duplicate.
339                return Err(Error::DuplicateTransaction { block_hash, index });
340            }
341
342            check_transaction_sanity(tx)?;
343
344            sig_ops += get_legacy_sig_op_count(tx);
345
346            txids.insert(index, txid);
347        }
348
349        if sig_ops * WITNESS_SCALE_FACTOR > MAX_BLOCK_SIGOPS_COST as usize {
350            return Err(Error::TooManySigOps {
351                block_hash,
352                block_number,
353            });
354        }
355
356        // Inline `Block::check_merkle_root()` to avoid redundantly computing txid.
357        let hashes = block
358            .txdata
359            .iter()
360            .enumerate()
361            .filter_map(|(index, _obj)| txids.get(&index).map(|txid| txid.to_raw_hash()));
362
363        let maybe_merkle_root: Option<TxMerkleNode> =
364            bitcoin::merkle_tree::calculate_root(hashes).map(|h| h.into());
365
366        if !maybe_merkle_root
367            .map(|merkle_root| block.header.merkle_root == merkle_root)
368            .unwrap_or(false)
369        {
370            return Err(Error::BadMerkleRoot(block_hash));
371        }
372
373        Ok(txids)
374    }
375
376    fn verify_transactions(
377        &self,
378        block_number: u32,
379        block: &BitcoinBlock,
380        txids: HashMap<usize, Txid>,
381        lock_time_cutoff: u32,
382    ) -> Result<(), Error> {
383        let parent_number = block_number - 1;
384        let parent_hash =
385            self.client
386                .hash(parent_number.into())?
387                .ok_or(sp_blockchain::Error::Backend(format!(
388                    "Parent block #{parent_number} not found"
389                )))?;
390
391        let get_txid = |tx_index: usize| {
392            txids
393                .get(&tx_index)
394                .copied()
395                .expect("Txid must exist as initialized in `check_block_sanity()`; qed")
396        };
397
398        let tx_context = |tx_index| TransactionContext {
399            block_number,
400            tx_index,
401            txid: get_txid(tx_index),
402        };
403
404        let flags = script_verify::get_block_script_flags(
405            block_number,
406            block.block_hash(),
407            &self.chain_params,
408        );
409
410        let block_hash = block.block_hash();
411
412        let mut block_fee = 0;
413        let mut spent_coins_in_block = HashSet::new();
414
415        // Preallocated buffer for serializing tx when using the Core scripg engine.
416        let mut tx_buffer = if matches!(self.script_engine, ScriptEngine::Core) {
417            Vec::<u8>::with_capacity(4096)
418        } else {
419            Vec::new()
420        };
421
422        // TODO: verify transactions in parallel.
423        // https://github.com/bitcoin/bitcoin/blob/6f9db1ebcab4064065ccd787161bf2b87e03cc1f/src/validation.cpp#L2611
424        for (tx_index, tx) in block.txdata.iter().enumerate() {
425            if tx_index == 0 {
426                // Enforce rule that the coinbase starts with serialized block height.
427                if block_number >= self.chain_params.params.bip34_height {
428                    let block_height_in_coinbase = block
429                        .bip34_block_height()
430                        .map_err(|err| Error::Bip34(block_hash, err))?
431                        as u32;
432                    if block_height_in_coinbase != block_number {
433                        return Err(Error::BadCoinbaseBlockHeight {
434                            block_hash,
435                            expected: block_number,
436                            got: block_height_in_coinbase,
437                        });
438                    }
439                }
440
441                continue;
442            }
443
444            if !is_final_tx(tx, block_number, lock_time_cutoff) {
445                return Err(Error::TransactionNotFinal(block_hash));
446            }
447
448            tx_buffer.clear();
449            tx.consensus_encode(&mut tx_buffer)
450                .map_err(Error::BitcoinCodec)?;
451
452            let spending_transaction = tx_buffer.as_slice();
453
454            let access_coin = |out_point: OutPoint| -> Result<(TxOut, bool, u32), Error> {
455                let maybe_coin = match self.find_utxo_in_state(parent_hash, out_point) {
456                    Some(coin) => {
457                        let Coin {
458                            is_coinbase,
459                            amount,
460                            height,
461                            script_pubkey,
462                        } = coin;
463
464                        let txout = TxOut {
465                            value: Amount::from_sat(amount),
466                            script_pubkey: ScriptBuf::from_bytes(script_pubkey),
467                        };
468
469                        Some((txout, is_coinbase, height))
470                    }
471                    None => find_utxo_in_current_block(block, out_point, tx_index, get_txid)
472                        .map(|(txout, is_coinbase)| (txout, is_coinbase, block_number)),
473                };
474
475                maybe_coin.ok_or_else(|| Error::MissingUtxoInState {
476                    block_hash,
477                    context: tx_context(tx_index),
478                    missing_utxo: out_point,
479                })
480            };
481
482            // CheckTxInputs.
483            let mut value_in = 0;
484            let mut sig_ops_cost = 0;
485
486            // TODO: Check input in parallel.
487            for (input_index, input) in tx.input.iter().enumerate() {
488                let coin = input.previous_output;
489
490                if spent_coins_in_block.contains(&coin) {
491                    return Err(Error::AlreadySpentInCurrentBlock {
492                        block_hash,
493                        block_number,
494                        txid: get_txid(tx_index),
495                        utxo: coin,
496                    });
497                }
498
499                // Access coin.
500                let (spent_output, is_coinbase, coin_height) = access_coin(coin)?;
501
502                // If coin is coinbase, check that it's matured.
503                if is_coinbase && block_number - coin_height < COINBASE_MATURITY {
504                    return Err(Error::PrematureSpendOfCoinbase(block_hash));
505                }
506
507                // tracing::debug!(
508                // target: "subcoin_script",
509                // block_number,
510                // tx_index,
511                // txid = ?get_txid(tx_index),
512                // input_index,
513                // "Verifying input script"
514                // );
515                match self.script_engine {
516                    ScriptEngine::Core => {
517                        script_verify::verify_input_script(
518                            &spent_output,
519                            spending_transaction,
520                            input_index,
521                            flags,
522                        )?;
523                    }
524                    ScriptEngine::None => {
525                        // Skip script verification.
526                    }
527                    ScriptEngine::Subcoin => {
528                        let mut checker = subcoin_script::TransactionSignatureChecker::new(
529                            tx,
530                            input_index,
531                            spent_output.value.to_sat(),
532                        );
533
534                        let verify_flags =
535                            subcoin_script::VerifyFlags::from_bits(flags).expect("Invalid flags");
536
537                        let script_result = subcoin_script::verify_script(
538                            &input.script_sig,
539                            &spent_output.script_pubkey,
540                            &input.witness,
541                            &verify_flags,
542                            &mut checker,
543                        );
544
545                        if let Err(script_err) = script_result {
546                            let context = tx_context(tx_index);
547                            return Err(Error::InvalidScript {
548                                block_hash,
549                                context,
550                                input_index,
551                                error: Box::new(script_err),
552                            });
553                        }
554                    }
555                }
556
557                spent_coins_in_block.insert(coin);
558                value_in += spent_output.value.to_sat();
559            }
560
561            // > GetTransactionSigOpCost counts 3 types of sigops:
562            // > * legacy (always)
563            // > * p2sh (when P2SH enabled in flags and excludes coinbase)
564            // > * witness (when witness enabled in flags and excludes coinbase)
565            sig_ops_cost += tx.total_sigop_cost(|out_point: &OutPoint| {
566                access_coin(*out_point).map(|(txout, _, _)| txout).ok()
567            });
568
569            if sig_ops_cost > MAX_BLOCK_SIGOPS_COST as usize {
570                return Err(Error::TooManySigOps {
571                    block_hash,
572                    block_number,
573                });
574            }
575
576            let value_out = tx
577                .output
578                .iter()
579                .map(|output| output.value.to_sat())
580                .sum::<u64>();
581
582            // Total input value must be no less than total output value.
583            // Tx fee is the difference between inputs and outputs.
584            let tx_fee = value_in
585                .checked_sub(value_out)
586                .ok_or(Error::InsufficientFunds {
587                    block_hash,
588                    value_in,
589                    value_out,
590                })?;
591
592            block_fee += tx_fee;
593        }
594
595        let coinbase_value = block.txdata[0]
596            .output
597            .iter()
598            .map(|output| output.value.to_sat())
599            .sum::<u64>();
600
601        let subsidy = bitcoin_block_subsidy(block_number);
602
603        // Ensures no inflation.
604        if coinbase_value > block_fee + subsidy {
605            return Err(Error::InvalidBlockReward(block_hash));
606        }
607
608        Ok(())
609    }
610
611    /// Finds a UTXO in the state backend.
612    fn find_utxo_in_state(&self, block_hash: Block::Hash, out_point: OutPoint) -> Option<Coin> {
613        use codec::Decode;
614
615        // Read state from the backend
616        //
617        // TODO: optimizations:
618        // - Read the state from the in memory backend.
619        // - Maintain a flat in-memory UTXO cache and try to read from cache first.
620        let OutPoint { txid, vout } = out_point;
621        let storage_key = self.coin_storage_key.storage_key(txid, vout);
622
623        let maybe_storage_data = self
624            .client
625            .storage(block_hash, &sc_client_api::StorageKey(storage_key))
626            .ok()
627            .flatten();
628
629        maybe_storage_data.and_then(|data| Coin::decode(&mut data.0.as_slice()).ok())
630    }
631}
632
633// Find a UTXO from the previous transactions in current block.
634fn find_utxo_in_current_block(
635    block: &BitcoinBlock,
636    out_point: OutPoint,
637    tx_index: usize,
638    get_txid: impl Fn(usize) -> Txid,
639) -> Option<(TxOut, bool)> {
640    let OutPoint { txid, vout } = out_point;
641    block
642        .txdata
643        .iter()
644        .take(tx_index)
645        .enumerate()
646        .find_map(|(index, tx)| (get_txid(index) == txid).then_some((tx, index == 0)))
647        .and_then(|(tx, is_coinbase)| {
648            tx.output
649                .get(vout as usize)
650                .cloned()
651                .map(|txout| (txout, is_coinbase))
652        })
653}
654
655/// Returns the base block size.
656///
657/// > Base size is the block size in bytes with the original transaction serialization without
658/// > any witness-related data, as seen by a non-upgraded node.
659// TODO: copied from rust-bitcoin, send a patch upstream to make this API public?
660fn block_base_size(block: &BitcoinBlock) -> usize {
661    let mut size = BitcoinHeader::SIZE;
662
663    size += VarInt::from(block.txdata.len()).size();
664    size += block.txdata.iter().map(|tx| tx.base_size()).sum::<usize>();
665
666    size
667}
668
669#[cfg(test)]
670mod tests {
671    use super::*;
672    use bitcoin::consensus::encode::deserialize_hex;
673
674    #[test]
675    fn test_find_utxo_in_current_block() {
676        let test_block = std::env::current_dir()
677            .unwrap()
678            .parent()
679            .unwrap()
680            .parent()
681            .unwrap()
682            .join("test_data")
683            .join("btc_mainnet_385044.data");
684        let raw_block = std::fs::read_to_string(test_block).unwrap();
685        let block = deserialize_hex::<BitcoinBlock>(raw_block.trim()).unwrap();
686
687        let txids = block
688            .txdata
689            .iter()
690            .enumerate()
691            .map(|(index, tx)| (index, tx.compute_txid()))
692            .collect::<HashMap<_, _>>();
693
694        // 385044:35:1
695        let out_point = OutPoint {
696            txid: "2b102a19161e5c93f71e16f9e8c9b2438f362c51ecc8f2a62e3c31d7615dd17d"
697                .parse()
698                .unwrap(),
699            vout: 1,
700        };
701
702        // The input of block 385044:36 is from the previous transaction 385044:35:1.
703        // https://www.blockchain.com/explorer/transactions/btc/5645cb0a3953b7766836919566b25321a976d06c958e69ff270358233a8c82d6
704        assert_eq!(
705            find_utxo_in_current_block(&block, out_point, 36, |index| txids
706                .get(&index)
707                .copied()
708                .unwrap())
709            .map(|(txout, is_coinbase)| (txout.value.to_sat(), is_coinbase))
710            .unwrap(),
711            (295600000, false)
712        );
713    }
714}