@@ -128,32 +128,61 @@ use std::{
128128} ;
129129
130130use libbitcoinkernel_sys:: {
131- btck_Block, btck_BlockHash, btck_BlockHeader, btck_BlockSpentOutputs, btck_Coin,
132- btck_TransactionSpentOutputs, btck_block_copy, btck_block_count_transactions,
133- btck_block_create, btck_block_destroy, btck_block_get_hash, btck_block_get_header,
134- btck_block_get_transaction_at, btck_block_hash_copy, btck_block_hash_create,
135- btck_block_hash_destroy, btck_block_hash_equals, btck_block_hash_to_bytes,
136- btck_block_header_copy, btck_block_header_create, btck_block_header_destroy,
137- btck_block_header_get_bits, btck_block_header_get_hash, btck_block_header_get_nonce,
138- btck_block_header_get_prev_hash, btck_block_header_get_timestamp,
131+ btck_Block, btck_BlockCheckFlags, btck_BlockCheckFlags_ALL, btck_BlockCheckFlags_BASE,
132+ btck_BlockCheckFlags_MERKLE, btck_BlockCheckFlags_POW, btck_BlockHash, btck_BlockHeader,
133+ btck_BlockSpentOutputs, btck_Coin, btck_TransactionSpentOutputs, btck_block_check,
134+ btck_block_copy, btck_block_count_transactions, btck_block_create, btck_block_destroy,
135+ btck_block_get_hash, btck_block_get_header, btck_block_get_transaction_at,
136+ btck_block_hash_copy, btck_block_hash_create, btck_block_hash_destroy, btck_block_hash_equals,
137+ btck_block_hash_to_bytes, btck_block_header_copy, btck_block_header_create,
138+ btck_block_header_destroy, btck_block_header_get_bits, btck_block_header_get_hash,
139+ btck_block_header_get_nonce, btck_block_header_get_prev_hash, btck_block_header_get_timestamp,
139140 btck_block_header_get_version, btck_block_header_to_bytes, btck_block_spent_outputs_copy,
140141 btck_block_spent_outputs_count, btck_block_spent_outputs_destroy,
141142 btck_block_spent_outputs_get_transaction_spent_outputs_at, btck_block_to_bytes,
142- btck_coin_confirmation_height , btck_coin_copy , btck_coin_destroy , btck_coin_get_output ,
143- btck_coin_is_coinbase , btck_transaction_spent_outputs_copy ,
144- btck_transaction_spent_outputs_count , btck_transaction_spent_outputs_destroy ,
145- btck_transaction_spent_outputs_get_coin_at,
143+ btck_chain_parameters_get_consensus_params , btck_coin_confirmation_height , btck_coin_copy ,
144+ btck_coin_destroy , btck_coin_get_output , btck_coin_is_coinbase ,
145+ btck_transaction_spent_outputs_copy , btck_transaction_spent_outputs_count ,
146+ btck_transaction_spent_outputs_destroy , btck_transaction_spent_outputs_get_coin_at,
146147} ;
147148
148149use crate :: {
149150 c_helpers, c_serialize,
150151 ffi:: {
151152 c_helpers:: present,
152- sealed:: { AsPtr , FromMutPtr , FromPtr } ,
153+ sealed:: { AsMutPtr , AsPtr , FromMutPtr , FromPtr } ,
153154 } ,
155+ notifications:: types:: BlockValidationState ,
156+ state:: context:: ChainParams ,
154157 KernelError ,
155158} ;
156159
160+ /// Bitmask of flags controlling which checks [`Block::check`] performs.
161+ pub type BlockCheckFlags = btck_BlockCheckFlags ;
162+
163+ /// Run only base context-free block checks (no PoW, no Merkle root).
164+ pub const BLOCK_CHECK_BASE : BlockCheckFlags = btck_BlockCheckFlags_BASE;
165+
166+ /// Enable Proof-of-Work verification via the block header.
167+ pub const BLOCK_CHECK_POW : BlockCheckFlags = btck_BlockCheckFlags_POW;
168+
169+ /// Enable Merkle-root verification (and mutation detection).
170+ pub const BLOCK_CHECK_MERKLE : BlockCheckFlags = btck_BlockCheckFlags_MERKLE;
171+
172+ /// Enable all available context-free block checks (PoW + Merkle root).
173+ pub const BLOCK_CHECK_ALL : BlockCheckFlags = btck_BlockCheckFlags_ALL;
174+
175+ /// Outcome of [`Block::check`].
176+ ///
177+ /// On failure, the [`BlockValidationState`] carries details that can be
178+ /// inspected via [`BlockValidationStateExt`](crate::notifications::BlockValidationStateExt).
179+ pub enum BlockCheckResult {
180+ /// The block passed the requested context-free checks.
181+ Valid ,
182+ /// The block failed; the state holds the validation details.
183+ Invalid ( BlockValidationState ) ,
184+ }
185+
157186use super :: transaction:: { TransactionRef , TxOutRef } ;
158187
159188/// Common operations for block hashes, implemented by both owned and borrowed types.
@@ -942,6 +971,51 @@ impl Block {
942971 pub fn transactions ( & self ) -> BlockTransactionIter < ' _ > {
943972 BlockTransactionIter :: new ( self )
944973 }
974+
975+ /// Performs context-free validation checks on this block.
976+ ///
977+ /// Runs base structural checks (size, weight, coinbase, transactions,
978+ /// sigops) without requiring chainstate or block index access.
979+ /// Proof-of-work and merkle-root checks are optional and toggled via `flags`.
980+ ///
981+ /// # Arguments
982+ /// * `chain_params` - Chain parameters providing consensus rules
983+ /// * `flags` - Bitmask of [`BLOCK_CHECK_BASE`], [`BLOCK_CHECK_POW`],
984+ /// [`BLOCK_CHECK_MERKLE`], or [`BLOCK_CHECK_ALL`]
985+ ///
986+ /// # Returns
987+ /// [`BlockCheckResult::Valid`] on success, otherwise
988+ /// [`BlockCheckResult::Invalid`] carrying the validation state.
989+ ///
990+ /// # Examples
991+ /// ```no_run
992+ /// # use bitcoinkernel::{
993+ /// # prelude::*, Block, BlockCheckResult, ChainParams, ChainType, BLOCK_CHECK_ALL,
994+ /// # };
995+ /// # fn example() -> Result<(), bitcoinkernel::KernelError> {
996+ /// # let block_data = vec![0u8; 100]; // placeholder
997+ /// # let block = Block::new(&block_data)?;
998+ /// let chain_params = ChainParams::new(ChainType::Mainnet);
999+ ///
1000+ /// match block.check(&chain_params, BLOCK_CHECK_ALL) {
1001+ /// BlockCheckResult::Valid => println!("ok"),
1002+ /// BlockCheckResult::Invalid(state) => println!("failed: {:?}", state.result()),
1003+ /// }
1004+ /// # Ok(())
1005+ /// # }
1006+ /// ```
1007+ pub fn check ( & self , chain_params : & ChainParams , flags : BlockCheckFlags ) -> BlockCheckResult {
1008+ let state = BlockValidationState :: new ( ) ;
1009+ let consensus_params =
1010+ unsafe { btck_chain_parameters_get_consensus_params ( chain_params. as_ptr ( ) ) } ;
1011+ let result =
1012+ unsafe { btck_block_check ( self . inner , consensus_params, flags, state. as_mut_ptr ( ) ) } ;
1013+ if c_helpers:: verification_passed ( result) {
1014+ BlockCheckResult :: Valid
1015+ } else {
1016+ BlockCheckResult :: Invalid ( state)
1017+ }
1018+ }
9451019}
9461020
9471021impl AsPtr < btck_Block > for Block {
@@ -1865,6 +1939,8 @@ mod tests {
18651939 use crate :: ffi:: test_utils:: {
18661940 test_owned_clone_and_send, test_owned_trait_requirements, test_ref_trait_requirements,
18671941 } ;
1942+ use crate :: prelude:: * ;
1943+ use crate :: { BlockValidationResult , ChainType , ValidationMode } ;
18681944 use std:: {
18691945 fs:: File ,
18701946 io:: { BufRead , BufReader } ,
@@ -1883,6 +1959,13 @@ mod tests {
18831959 const VALID_HASH_BYTES1 : [ u8 ; 32 ] = [ 1u8 ; 32 ] ;
18841960 const VALID_HASH_BYTES2 : [ u8 ; 32 ] = [ 2u8 ; 32 ] ;
18851961
1962+ const MAINNET_BLOCK_1_HEX : & str = "010000006fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c\
1963+ 68d6190000000000982051fd1e4ba744bbbe680e1fee14677ba1a3c3540bf7b1cdb606e857233e0e61bc6649ff\
1964+ ff001d01e362990101000000010000000000000000000000000000000000000000000000000000000000000000\
1965+ ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae\
1966+ 1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c8\
1967+ 58eeac00000000";
1968+
18861969 test_owned_trait_requirements ! ( test_block_hash_requirements, BlockHash , btck_BlockHash) ;
18871970 test_ref_trait_requirements ! (
18881971 test_block_hash_ref_requirements,
@@ -2243,19 +2326,7 @@ mod tests {
22432326
22442327 #[ test]
22452328 fn test_block_hash_display ( ) {
2246- let block = Block :: new (
2247- hex:: decode (
2248- "010000006fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000982051fd\
2249- 1e4ba744bbbe680e1fee14677ba1a3c3540bf7b1cdb606e857233e0e61bc6649ffff001d01e36299\
2250- 0101000000010000000000000000000000000000000000000000000000000000000000000000ffff\
2251- ffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec1\
2252- 1600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf62\
2253- 1e73a82cbf2342c858eeac00000000",
2254- )
2255- . unwrap ( )
2256- . as_slice ( ) ,
2257- )
2258- . unwrap ( ) ;
2329+ let block = Block :: new ( hex:: decode ( MAINNET_BLOCK_1_HEX ) . unwrap ( ) . as_slice ( ) ) . unwrap ( ) ;
22592330
22602331 let block_hash = block. hash ( ) . to_owned ( ) ;
22612332
@@ -2267,19 +2338,7 @@ mod tests {
22672338
22682339 #[ test]
22692340 fn test_block_hash_ref_display ( ) {
2270- let block = Block :: new (
2271- hex:: decode (
2272- "010000006fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000982051fd\
2273- 1e4ba744bbbe680e1fee14677ba1a3c3540bf7b1cdb606e857233e0e61bc6649ffff001d01e36299\
2274- 0101000000010000000000000000000000000000000000000000000000000000000000000000ffff\
2275- ffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec1\
2276- 1600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf62\
2277- 1e73a82cbf2342c858eeac00000000",
2278- )
2279- . unwrap ( )
2280- . as_slice ( ) ,
2281- )
2282- . unwrap ( ) ;
2341+ let block = Block :: new ( hex:: decode ( MAINNET_BLOCK_1_HEX ) . unwrap ( ) . as_slice ( ) ) . unwrap ( ) ;
22832342
22842343 let block_hash_ref = block. hash ( ) ;
22852344
@@ -2288,4 +2347,108 @@ mod tests {
22882347 "00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048"
22892348 ) ;
22902349 }
2350+
2351+ #[ test]
2352+ fn check_valid_block_passes_base_and_all ( ) {
2353+ let raw_block = hex:: decode ( MAINNET_BLOCK_1_HEX ) . unwrap ( ) ;
2354+ let chain_params = ChainParams :: new ( ChainType :: Mainnet ) ;
2355+ let block = Block :: new ( & raw_block) . unwrap ( ) ;
2356+
2357+ assert ! ( matches!(
2358+ block. check( & chain_params, BLOCK_CHECK_BASE ) ,
2359+ BlockCheckResult :: Valid
2360+ ) ) ;
2361+ assert ! ( matches!(
2362+ block. check( & chain_params, BLOCK_CHECK_ALL ) ,
2363+ BlockCheckResult :: Valid
2364+ ) ) ;
2365+ }
2366+
2367+ #[ test]
2368+ fn check_mutated_merkle_root ( ) {
2369+ const MERKLE_ROOT_OFFSET : usize = 4 // version
2370+ + 32 ; // prev_hash
2371+
2372+ let mut raw_block = hex:: decode ( MAINNET_BLOCK_1_HEX ) . unwrap ( ) ;
2373+ raw_block[ MERKLE_ROOT_OFFSET ] ^= 0x01 ;
2374+ let chain_params = ChainParams :: new ( ChainType :: Mainnet ) ;
2375+ let block = Block :: new ( & raw_block) . unwrap ( ) ;
2376+
2377+ match block. check ( & chain_params, BLOCK_CHECK_MERKLE ) {
2378+ BlockCheckResult :: Invalid ( state) => {
2379+ assert_eq ! ( state. mode( ) , ValidationMode :: Invalid ) ;
2380+ assert_eq ! ( state. result( ) , BlockValidationResult :: Mutated ) ;
2381+ }
2382+ _ => unreachable ! ( "expected BlockCheckResult::Invalid" ) ,
2383+ }
2384+
2385+ // Mutating the merkle root also alters the block hash, so the
2386+ // combined ALL check fails on the PoW path before the merkle
2387+ // path runs.
2388+ match block. check ( & chain_params, BLOCK_CHECK_ALL ) {
2389+ BlockCheckResult :: Invalid ( state) => {
2390+ assert_eq ! ( state. mode( ) , ValidationMode :: Invalid ) ;
2391+ assert_eq ! ( state. result( ) , BlockValidationResult :: InvalidHeader ) ;
2392+ }
2393+ _ => unreachable ! ( "expected BlockCheckResult::Invalid" ) ,
2394+ }
2395+
2396+ assert ! ( matches!(
2397+ block. check( & chain_params, BLOCK_CHECK_BASE ) ,
2398+ BlockCheckResult :: Valid
2399+ ) ) ;
2400+ }
2401+
2402+ #[ test]
2403+ fn check_invalid_pow ( ) {
2404+ const NBITS_OFFSET : usize = 4 // version
2405+ + 32 // prev_hash
2406+ + 32 // merkle_root
2407+ + 4 ; // timestamp
2408+
2409+ let mut raw_block = hex:: decode ( MAINNET_BLOCK_1_HEX ) . unwrap ( ) ;
2410+ raw_block[ NBITS_OFFSET + 3 ] = 0x1c ;
2411+ let chain_params = ChainParams :: new ( ChainType :: Mainnet ) ;
2412+ let block = Block :: new ( & raw_block) . unwrap ( ) ;
2413+
2414+ match block. check ( & chain_params, BLOCK_CHECK_POW ) {
2415+ BlockCheckResult :: Invalid ( state) => {
2416+ assert_eq ! ( state. mode( ) , ValidationMode :: Invalid ) ;
2417+ assert_eq ! ( state. result( ) , BlockValidationResult :: InvalidHeader ) ;
2418+ }
2419+ _ => unreachable ! ( "expected BlockCheckResult::Invalid" ) ,
2420+ }
2421+
2422+ assert ! ( matches!(
2423+ block. check( & chain_params, BLOCK_CHECK_MERKLE ) ,
2424+ BlockCheckResult :: Valid
2425+ ) ) ;
2426+ }
2427+
2428+ #[ test]
2429+ fn check_tampered_coinbase ( ) {
2430+ const COINBASE_PREVOUT_N_OFFSET : usize = 4 // version
2431+ + 32 // prev_hash
2432+ + 32 // merkle_root
2433+ + 4 // timestamp
2434+ + 4 // bits
2435+ + 4 // nonce
2436+ + 1 // tx count varint
2437+ + 4 // tx version
2438+ + 1 // vin count
2439+ + 32 ; // prevout hash
2440+
2441+ let mut raw_block = hex:: decode ( MAINNET_BLOCK_1_HEX ) . unwrap ( ) ;
2442+ raw_block[ COINBASE_PREVOUT_N_OFFSET ] = 0x00 ;
2443+ let chain_params = ChainParams :: new ( ChainType :: Mainnet ) ;
2444+ let block = Block :: new ( & raw_block) . unwrap ( ) ;
2445+
2446+ match block. check ( & chain_params, BLOCK_CHECK_BASE ) {
2447+ BlockCheckResult :: Invalid ( state) => {
2448+ assert_eq ! ( state. mode( ) , ValidationMode :: Invalid ) ;
2449+ assert_eq ! ( state. result( ) , BlockValidationResult :: Consensus ) ;
2450+ }
2451+ _ => unreachable ! ( "expected BlockCheckResult::Invalid" ) ,
2452+ }
2453+ }
22912454}
0 commit comments