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