diff --git a/crates/sc-consensus-nakamoto/src/verification.rs b/crates/sc-consensus-nakamoto/src/verification.rs index e607dc56..fdce327f 100644 --- a/crates/sc-consensus-nakamoto/src/verification.rs +++ b/crates/sc-consensus-nakamoto/src/verification.rs @@ -1,3 +1,29 @@ +//! This module provides block verification functionalities based on Bitcoin's consensus rules. +//! The primary reference for these consensus rules is Bitcoin Core. We utilize the `rust-bitcoinconsensus` +//! from `rust-bitcoin` for handling the most complex aspects of script verification. +//! +//! The main components of this module are: +//! - `header_verify`: Module responsible for verifying block headers. +//! - `tx_verify`: Module responsible for verifying individual transactions within a block. +//! +//! This module ensures that blocks adhere to Bitcoin's consensus rules by performing checks on +//! the proof of work, timestamps, transaction validity, and more. +//! +//! # Components +//! +//! ## Modules +//! +//! - `header_verify`: Contains functions and structures for verifying block headers. +//! - `tx_verify`: Contains functions and structures for verifying transactions. +//! +//! ## Structures +//! +//! - [`BlockVerifier`]: Responsible for verifying Bitcoin blocks, including headers and transactions. +//! +//! ## Enums +//! +//! - [`BlockVerification`]: Represents the level of block verification (None, Full, HeaderOnly). + mod header_verify; mod tx_verify; @@ -202,9 +228,8 @@ where } // Size limits, without tx witness data. - if Weight::from_wu(block.txdata.len() as u64 * WITNESS_SCALE_FACTOR as u64) - > MAX_BLOCK_WEIGHT - || Weight::from_wu(block_base_size(block) as u64 * WITNESS_SCALE_FACTOR as u64) + if Weight::from_wu((block.txdata.len() * WITNESS_SCALE_FACTOR) as u64) > MAX_BLOCK_WEIGHT + || Weight::from_wu((block_base_size(block) * WITNESS_SCALE_FACTOR) as u64) > MAX_BLOCK_WEIGHT { return Err(Error::BadBlockLength); @@ -324,11 +349,16 @@ where let access_coin = |out_point: OutPoint| -> Option<(TxOut, bool, u32)> { match self.find_utxo_in_state(parent_hash, out_point) { Some(coin) => { - let is_coinbase = coin.is_coinbase; - let height = coin.height; + let Coin { + is_coinbase, + amount, + height, + script_pubkey, + } = coin; + let txout = TxOut { - value: Amount::from_sat(coin.amount), - script_pubkey: ScriptBuf::from_bytes(coin.script_pubkey), + value: Amount::from_sat(amount), + script_pubkey: ScriptBuf::from_bytes(script_pubkey), }; Some((txout, is_coinbase, height)) @@ -469,6 +499,8 @@ fn find_utxo_in_current_block( } /// Returns the script validation flags for the specified block. +/// +/// fn get_block_script_flags( height: u32, block_hash: BlockHash, @@ -484,18 +516,22 @@ fn get_block_script_flags( let mut flags = bitcoinconsensus::VERIFY_P2SH | bitcoinconsensus::VERIFY_WITNESS; - if height >= chain_params.params.bip65_height { - flags |= bitcoinconsensus::VERIFY_CHECKLOCKTIMEVERIFY; - } - + // Enforce the DERSIG (BIP66) rule if height >= chain_params.params.bip66_height { flags |= bitcoinconsensus::VERIFY_DERSIG; } + // Enforce CHECKLOCKTIMEVERIFY (BIP65) + if height >= chain_params.params.bip65_height { + flags |= bitcoinconsensus::VERIFY_CHECKLOCKTIMEVERIFY; + } + + // Enforce CHECKSEQUENCEVERIFY (BIP112) if height >= chain_params.csv_height { flags |= bitcoinconsensus::VERIFY_CHECKSEQUENCEVERIFY; } + // Enforce BIP147 NULLDUMMY (activated simultaneously with segwit) if height >= chain_params.segwit_height { flags |= bitcoinconsensus::VERIFY_NULLDUMMY; } diff --git a/crates/sc-consensus-nakamoto/src/verification/header_verify.rs b/crates/sc-consensus-nakamoto/src/verification/header_verify.rs index 06ed5fe0..1d2f738e 100644 --- a/crates/sc-consensus-nakamoto/src/verification/header_verify.rs +++ b/crates/sc-consensus-nakamoto/src/verification/header_verify.rs @@ -62,7 +62,7 @@ where Client: HeaderBackend + AuxStore, { /// Validates the header and returns the block time, which is used for verifying the finality of - /// transactions in [`super::tx_verify::is_final`]. + /// transactions. /// /// The validation process includes: /// - Checking the proof of work. @@ -116,6 +116,15 @@ where let block_number = prev_block_height + 1; + let version = header.version.to_consensus(); + + if version < 2 && block_number >= self.chain_params.params.bip34_height + || version < 3 && block_number >= self.chain_params.params.bip66_height + || version < 4 && block_number >= self.chain_params.params.bip65_height + { + return Err(Error::BadVersion); + } + // BIP 113 let lock_time_cutoff = if block_number >= self.chain_params.csv_height { let mtp = self.calculate_median_time_past(header); @@ -127,15 +136,6 @@ where header.time }; - let version = header.version.to_consensus(); - - if version < 2 && block_number >= self.chain_params.params.bip34_height - || version < 3 && block_number >= self.chain_params.params.bip66_height - || version < 4 && block_number >= self.chain_params.params.bip65_height - { - return Err(Error::BadVersion); - } - Ok(lock_time_cutoff) } diff --git a/crates/sc-consensus-nakamoto/src/verification/tx_verify.rs b/crates/sc-consensus-nakamoto/src/verification/tx_verify.rs index d0d6a2a7..3ec16b63 100644 --- a/crates/sc-consensus-nakamoto/src/verification/tx_verify.rs +++ b/crates/sc-consensus-nakamoto/src/verification/tx_verify.rs @@ -1,6 +1,7 @@ use super::MAX_BLOCK_WEIGHT; use bitcoin::absolute::{LockTime, LOCK_TIME_THRESHOLD}; -use bitcoin::{Amount, Transaction}; +use bitcoin::blockdata::weight::WITNESS_SCALE_FACTOR; +use bitcoin::{Amount, Transaction, Weight}; use std::collections::HashSet; // MinCoinbaseScriptLen is the minimum length a coinbase script can be. @@ -17,7 +18,7 @@ pub enum Error { #[error("Transaction has no outputs")] EmptyOutput, #[error("Transaction is too large")] - BadTransactionLength, + TransactionOversize, #[error("Transaction contains duplicate inputs at index {0}")] DuplicateTxInput(usize), #[error("Output value (0) is too large")] @@ -30,7 +31,7 @@ pub enum Error { )] BadCoinbaseLength(usize), #[error("Transaction input refers to a previous output that is null")] - PreviousOutPointNull, + PreviousOutputNull, } pub fn is_final(tx: &Transaction, height: u32, block_time: u32) -> bool { @@ -61,20 +62,20 @@ pub fn check_transaction_sanity(tx: &Transaction) -> Result<(), Error> { return Err(Error::EmptyOutput); } - if tx.weight() > MAX_BLOCK_WEIGHT { - return Err(Error::BadTransactionLength); + if Weight::from_wu((tx.base_size() * WITNESS_SCALE_FACTOR) as u64) > MAX_BLOCK_WEIGHT { + return Err(Error::TransactionOversize); } - let mut total_output_value = Amount::ZERO; + let mut value_out = Amount::ZERO; tx.output.iter().try_for_each(|txout| { if txout.value > Amount::MAX_MONEY { return Err(Error::OutputValueTooLarge(txout.value)); } - total_output_value += txout.value; + value_out += txout.value; - if total_output_value > Amount::MAX_MONEY { - return Err(Error::TotalOutputValueTooLarge(total_output_value)); + if value_out > Amount::MAX_MONEY { + return Err(Error::TotalOutputValueTooLarge(value_out)); } Ok(()) @@ -99,7 +100,7 @@ pub fn check_transaction_sanity(tx: &Transaction) -> Result<(), Error> { // Previous transaction outputs referenced by the inputs to this // transaction must not be null. if tx.input.iter().any(|txin| txin.previous_output.is_null()) { - return Err(Error::PreviousOutPointNull); + return Err(Error::PreviousOutputNull); } }