Skip to content

Commit e22be3a

Browse files
Merge pull request #177 from pzafonte/context-free-block-validation
feat(core): expose Block::check
2 parents 74472e3 + 1a3b495 commit e22be3a

6 files changed

Lines changed: 234 additions & 44 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
- Added `Block::check` to perform context-free validation of a block (size, weight, coinbase, transactions, sigops), with optional proof-of-work and merkle-root checks toggled via the `BLOCK_CHECK_BASE` / `_POW` / `_MERKLE` / `_ALL` flags. Returns a `BlockCheckResult` enum carrying the validation state on failure.
12+
1013
### Changed
1114
- The `verify` function's `flags` parameter now uses `ScriptVerificationFlags` instead of `u32`, making the type explicit in the public API.
1215

libbitcoinkernel-sys/src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,14 @@ pub const btck_ScriptVerificationFlags_ALL: btck_ScriptVerificationFlags =
8080
| btck_ScriptVerificationFlags_WITNESS
8181
| btck_ScriptVerificationFlags_TAPROOT;
8282

83+
// btck_BlockCheckFlags
84+
85+
pub const btck_BlockCheckFlags_BASE: btck_BlockCheckFlags = 0;
86+
pub const btck_BlockCheckFlags_POW: btck_BlockCheckFlags = 1 << 0;
87+
pub const btck_BlockCheckFlags_MERKLE: btck_BlockCheckFlags = 1 << 1;
88+
pub const btck_BlockCheckFlags_ALL: btck_BlockCheckFlags =
89+
btck_BlockCheckFlags_POW | btck_BlockCheckFlags_MERKLE;
90+
8391
// btck_ScriptVerifyStatus
8492

8593
pub const btck_ScriptVerifyStatus_OK: btck_ScriptVerifyStatus = 0;

src/core/block.rs

Lines changed: 202 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -128,32 +128,61 @@ use std::{
128128
};
129129

130130
use 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

148149
use 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+
157186
use 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

9471021
impl 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
}

src/core/mod.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ pub mod transaction;
55
pub mod verify;
66

77
pub use block::{
8-
Block, BlockHash, BlockHeader, BlockSpentOutputs, BlockSpentOutputsRef, Coin, CoinRef,
9-
TransactionSpentOutputs, TransactionSpentOutputsRef,
8+
Block, BlockCheckFlags, BlockCheckResult, BlockHash, BlockHeader, BlockSpentOutputs,
9+
BlockSpentOutputsRef, Coin, CoinRef, TransactionSpentOutputs, TransactionSpentOutputsRef,
1010
};
1111
pub use block_tree_entry::BlockTreeEntry;
1212
pub use script::{ScriptPubkey, ScriptPubkeyRef};
@@ -29,3 +29,9 @@ pub mod verify_flags {
2929
VERIFY_DERSIG, VERIFY_NONE, VERIFY_NULLDUMMY, VERIFY_P2SH, VERIFY_TAPROOT, VERIFY_WITNESS,
3030
};
3131
}
32+
33+
pub mod block_check_flags {
34+
pub use super::block::{
35+
BLOCK_CHECK_ALL, BLOCK_CHECK_BASE, BLOCK_CHECK_MERKLE, BLOCK_CHECK_POW,
36+
};
37+
}

src/lib.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -281,9 +281,9 @@ impl std::error::Error for KernelError {
281281
}
282282

283283
pub use crate::core::{
284-
verify, Block, BlockHash, BlockHeader, BlockSpentOutputs, BlockSpentOutputsRef, BlockTreeEntry,
285-
Coin, CoinRef, PrecomputedTransactionData, ScriptPubkey, ScriptPubkeyRef,
286-
ScriptVerificationFlags, ScriptVerifyError, Transaction, TransactionRef,
284+
verify, Block, BlockCheckFlags, BlockCheckResult, BlockHash, BlockHeader, BlockSpentOutputs,
285+
BlockSpentOutputsRef, BlockTreeEntry, Coin, CoinRef, PrecomputedTransactionData, ScriptPubkey,
286+
ScriptPubkeyRef, ScriptVerificationFlags, ScriptVerifyError, Transaction, TransactionRef,
287287
TransactionSpentOutputs, TransactionSpentOutputsRef, TxIn, TxInRef, TxOut, TxOutPoint,
288288
TxOutPointRef, TxOutRef, Txid, TxidRef,
289289
};
@@ -302,6 +302,10 @@ pub use crate::state::{
302302
ContextBuilder, ProcessBlockHeaderResult, ProcessBlockResult,
303303
};
304304

305+
pub use crate::core::block_check_flags::{
306+
BLOCK_CHECK_ALL, BLOCK_CHECK_BASE, BLOCK_CHECK_MERKLE, BLOCK_CHECK_POW,
307+
};
308+
305309
pub use crate::core::verify_flags::{
306310
VERIFY_ALL, VERIFY_ALL_PRE_TAPROOT, VERIFY_CHECKLOCKTIMEVERIFY, VERIFY_CHECKSEQUENCEVERIFY,
307311
VERIFY_DERSIG, VERIFY_NONE, VERIFY_NULLDUMMY, VERIFY_P2SH, VERIFY_TAPROOT, VERIFY_WITNESS,

src/state/context.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,12 @@ impl Drop for ChainParams {
143143
}
144144
}
145145

146+
impl AsPtr<btck_ChainParameters> for ChainParams {
147+
fn as_ptr(&self) -> *const btck_ChainParameters {
148+
self.inner as *const _
149+
}
150+
}
151+
146152
/// The main context for the Bitcoin Kernel library.
147153
///
148154
/// The [`Context`] manages the global state of the Bitcoin Kernel library

0 commit comments

Comments
 (0)