Skip to content

Commit cf9bae3

Browse files
committed
Add block validation
1 parent 3d235d0 commit cf9bae3

File tree

6 files changed

+147
-14
lines changed

6 files changed

+147
-14
lines changed

src/core/block.rs

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,9 @@ use libbitcoinkernel_sys::{
116116
btck_block_hash_destroy, btck_block_hash_equals, btck_block_hash_to_bytes,
117117
btck_block_header_copy, btck_block_header_create, btck_block_header_destroy,
118118
btck_block_header_get_hash, btck_block_spent_outputs_copy, btck_block_spent_outputs_count,
119-
btck_block_spent_outputs_destroy, btck_block_spent_outputs_get_transaction_spent_outputs_at,
120-
btck_block_to_bytes, btck_coin_confirmation_height, btck_coin_copy, btck_coin_destroy,
119+
btck_block_spent_outputs_create, btck_block_spent_outputs_destroy,
120+
btck_block_spent_outputs_get_transaction_spent_outputs_at, btck_block_to_bytes,
121+
btck_coin_confirmation_height, btck_coin_copy, btck_coin_create, btck_coin_destroy,
121122
btck_coin_get_output, btck_coin_is_coinbase, btck_transaction_spent_outputs_copy,
122123
btck_transaction_spent_outputs_count, btck_transaction_spent_outputs_destroy,
123124
btck_transaction_spent_outputs_get_coin_at,
@@ -129,7 +130,7 @@ use crate::{
129130
c_helpers::present,
130131
sealed::{AsPtr, FromMutPtr, FromPtr},
131132
},
132-
KernelError,
133+
KernelError, TxOut,
133134
};
134135

135136
use super::transaction::{TransactionRef, TxOutRef};
@@ -489,6 +490,14 @@ impl Drop for BlockHeader {
489490
}
490491
}
491492

493+
impl TryFrom<&[u8]> for BlockHeader {
494+
type Error = KernelError;
495+
496+
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
497+
BlockHeader::new(bytes)
498+
}
499+
}
500+
492501
pub struct BlockHeaderRef<'a> {
493502
inner: *const btck_BlockHeader,
494503
marker: PhantomData<&'a ()>,
@@ -1005,6 +1014,36 @@ impl BlockSpentOutputs {
10051014
pub fn as_ref(&self) -> BlockSpentOutputsRef<'_> {
10061015
unsafe { BlockSpentOutputsRef::from_ptr(self.inner as *const _) }
10071016
}
1017+
1018+
pub fn new(coins: &[Vec<Coin>]) -> Self {
1019+
struct CallbackContext<'a> {
1020+
coins: &'a [Vec<Coin>],
1021+
}
1022+
1023+
extern "C" fn coin_getter(
1024+
context: *mut c_void,
1025+
tx_index: usize,
1026+
coin_index: usize,
1027+
) -> *const btck_Coin {
1028+
let ctx = unsafe { &*(context as *const CallbackContext) };
1029+
ctx.coins[tx_index][coin_index].as_ptr()
1030+
}
1031+
1032+
extern "C" fn count_getter(context: *mut c_void, tx_index: usize) -> usize {
1033+
let ctx = unsafe { &*(context as *const CallbackContext) };
1034+
ctx.coins[tx_index].len()
1035+
}
1036+
1037+
let context = CallbackContext { coins };
1038+
unsafe {
1039+
BlockSpentOutputs::from_ptr(btck_block_spent_outputs_create(
1040+
&context as *const CallbackContext as *mut c_void,
1041+
Some(coin_getter),
1042+
Some(count_getter),
1043+
coins.len(),
1044+
))
1045+
}
1046+
}
10081047
}
10091048

10101049
impl FromMutPtr<btck_BlockSpentOutputs> for BlockSpentOutputs {
@@ -1560,6 +1599,10 @@ unsafe impl Send for Coin {}
15601599
unsafe impl Sync for Coin {}
15611600

15621601
impl Coin {
1602+
pub fn new(output: &TxOut) -> Coin {
1603+
unsafe { Coin::from_ptr(btck_coin_create(output.as_ptr(), 0, 0)) }
1604+
}
1605+
15631606
/// Creates a borrowed reference to this coin.
15641607
///
15651608
/// This allows converting from owned [`Coin`] to [`CoinRef`] without

src/core/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ pub mod transaction;
55
pub mod verify;
66

77
pub use block::{
8-
Block, BlockHash, BlockSpentOutputs, BlockSpentOutputsRef, Coin, CoinRef,
8+
Block, BlockHash, BlockHeader, BlockSpentOutputs, BlockSpentOutputsRef, Coin, CoinRef,
99
TransactionSpentOutputs, TransactionSpentOutputsRef,
1010
};
1111
pub use block_tree_entry::BlockTreeEntry;

src/core/transaction.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,10 @@ use libbitcoinkernel_sys::{
153153
btck_transaction_count_outputs, btck_transaction_create, btck_transaction_destroy,
154154
btck_transaction_get_input_at, btck_transaction_get_output_at, btck_transaction_get_txid,
155155
btck_transaction_input_copy, btck_transaction_input_destroy,
156-
btck_transaction_input_get_out_point, btck_transaction_out_point_copy,
157-
btck_transaction_out_point_destroy, btck_transaction_out_point_get_index,
158-
btck_transaction_out_point_get_txid, btck_transaction_output_copy,
159-
btck_transaction_output_create, btck_transaction_output_destroy,
156+
btck_transaction_input_get_out_point, btck_transaction_is_coinbase,
157+
btck_transaction_out_point_copy, btck_transaction_out_point_destroy,
158+
btck_transaction_out_point_get_index, btck_transaction_out_point_get_txid,
159+
btck_transaction_output_copy, btck_transaction_output_create, btck_transaction_output_destroy,
160160
btck_transaction_output_get_amount, btck_transaction_output_get_script_pubkey,
161161
btck_transaction_to_bytes, btck_txid_copy, btck_txid_destroy, btck_txid_equals,
162162
btck_txid_to_bytes,
@@ -165,7 +165,7 @@ use libbitcoinkernel_sys::{
165165
use crate::{
166166
c_serialize,
167167
ffi::{
168-
c_helpers::present,
168+
c_helpers::{self, present},
169169
sealed::{AsPtr, FromMutPtr, FromPtr},
170170
},
171171
KernelError, ScriptPubkeyExt,
@@ -379,6 +379,10 @@ pub trait TransactionExt: AsPtr<btck_Transaction> {
379379
fn outputs(&self) -> TxOutIter<'_> {
380380
TxOutIter::new(unsafe { TransactionRef::from_ptr(self.as_ptr()) })
381381
}
382+
383+
fn is_coinbase(&self) -> bool {
384+
unsafe { c_helpers::enabled(btck_transaction_is_coinbase(self.as_ptr())) }
385+
}
382386
}
383387

384388
/// A Bitcoin transaction.

src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -291,8 +291,8 @@ impl std::error::Error for KernelError {
291291
}
292292

293293
pub use crate::core::{
294-
verify, Block, BlockHash, BlockSpentOutputs, BlockSpentOutputsRef, BlockTreeEntry, Coin,
295-
CoinRef, ScriptPubkey, ScriptPubkeyRef, ScriptVerifyError, Transaction, TransactionRef,
294+
verify, Block, BlockHash, BlockHeader, BlockSpentOutputs, BlockSpentOutputsRef, BlockTreeEntry,
295+
Coin, CoinRef, ScriptPubkey, ScriptPubkeyRef, ScriptVerifyError, Transaction, TransactionRef,
296296
TransactionSpentOutputs, TransactionSpentOutputsRef, TxIn, TxInRef, TxOut, TxOutPoint,
297297
TxOutPointRef, TxOutRef, Txid, TxidRef,
298298
};

src/state/chainstate.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ use libbitcoinkernel_sys::{
3939
btck_chainstate_manager_options_update_block_tree_db_in_memory,
4040
btck_chainstate_manager_options_update_chainstate_db_in_memory,
4141
btck_chainstate_manager_process_block, btck_chainstate_manager_process_block_header,
42+
btck_chainstate_manager_validate_block,
4243
};
4344

4445
use crate::{
@@ -269,6 +270,23 @@ impl ChainstateManager {
269270
(c_helpers::success(accepted), state)
270271
}
271272

273+
pub fn validate_block(
274+
&self,
275+
block: &Block,
276+
block_spent_outputs: &BlockSpentOutputs,
277+
) -> (bool, BlockValidationState) {
278+
let state = BlockValidationState::new();
279+
let accepted = unsafe {
280+
btck_chainstate_manager_validate_block(
281+
self.inner,
282+
block.as_ptr(),
283+
block_spent_outputs.as_ptr(),
284+
state.as_ptr() as *mut btck_BlockValidationState,
285+
)
286+
};
287+
(c_helpers::success(accepted), state)
288+
}
289+
272290
/// Initialize the chainstate manager and optionally trigger a reindex.
273291
///
274292
/// This should be called after creating the chainstate manager to complete
@@ -277,6 +295,7 @@ impl ChainstateManager {
277295
///
278296
/// # Errors
279297
/// Returns [`KernelError::Internal`] if initialization fails.
298+
280299
pub fn import_blocks(&self) -> Result<(), KernelError> {
281300
let result = unsafe {
282301
btck_chainstate_manager_import_blocks(

tests/test.rs

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
#[cfg(test)]
22
mod tests {
3+
use bitcoinkernel::core::transaction::{TxOutPointExt, TxOutPointRef};
34
use bitcoinkernel::{
4-
Block, BlockHash, BlockSpentOutputs, BlockTreeEntry, BlockValidationStateRef, ChainParams, ChainType, ChainstateManager, ChainstateManagerBuilder, Coin, Context, ContextBuilder, KernelError, Log, Logger, ScriptPubkey, ScriptVerifyError, Transaction, TransactionSpentOutputs, TxIn, TxOut, TxOutRef, VERIFY_ALL_PRE_TAPROOT, VERIFY_TAPROOT, VERIFY_WITNESS, ValidationMode, prelude::*, verify
5+
prelude::*, verify, Block, BlockHash, BlockHeader, BlockSpentOutputs, BlockTreeEntry,
6+
BlockValidationStateRef, ChainParams, ChainType, ChainstateManager,
7+
ChainstateManagerBuilder, Coin, Context, ContextBuilder, KernelError, Log, Logger,
8+
ScriptPubkey, ScriptVerifyError, Transaction, TransactionSpentOutputs, TxIn, TxOut,
9+
TxOutRef, ValidationMode, VERIFY_ALL_PRE_TAPROOT, VERIFY_TAPROOT, VERIFY_WITNESS,
510
};
611
use std::fs::File;
712
use std::io::{BufRead, BufReader};
@@ -371,13 +376,75 @@ mod tests {
371376
let (context, data_dir) = testing_setup();
372377
let blocks_dir = data_dir.clone() + "/blocks";
373378
let block_data = read_block_data();
379+
let blocks: Vec<Block> = block_data
380+
.iter()
381+
.map(|data| Block::new(data.as_slice()).unwrap())
382+
.collect();
374383
let chainman = ChainstateManager::new(&context, &data_dir, &blocks_dir).unwrap();
375384

376-
for raw_block in block_data.iter() {
377-
let block = Block::new(raw_block.as_slice()).unwrap();
385+
for block in blocks.iter() {
386+
let header: BlockHeader = block.header().clone();
387+
let (accepted, state) = chainman.process_header(&header);
388+
assert!(accepted);
389+
assert_eq!(state.mode(), ValidationMode::Valid);
390+
}
391+
}
392+
393+
fn find_output<'a>(blocks: &'a [Block], outpoint: TxOutPointRef) -> Option<TxOut> {
394+
for block in blocks.iter() {
395+
for i in 0..block.transaction_count() {
396+
let tx = block.transaction(i).unwrap();
397+
if tx.txid() != outpoint.txid() {
398+
continue;
399+
}
400+
return tx
401+
.output(outpoint.index() as usize)
402+
.ok()
403+
.map(|out| out.to_owned());
404+
}
405+
}
406+
None
407+
}
408+
409+
#[test]
410+
fn test_block_validation() {
411+
let (context, data_dir) = testing_setup();
412+
let blocks_dir = data_dir.clone() + "/blocks";
413+
let block_data = read_block_data();
414+
let blocks: Vec<Block> = block_data
415+
.iter()
416+
.map(|data| Block::new(data.as_slice()).unwrap())
417+
.collect();
418+
let chainman = ChainstateManager::new(&context, &data_dir, &blocks_dir).unwrap();
419+
420+
let mut block_spent_outputs: Vec<BlockSpentOutputs> = vec![];
421+
422+
for block in blocks.iter() {
423+
let mut coins: Vec<Vec<Coin>> = vec![];
424+
for i in 0..block.transaction_count() {
425+
let tx = block.transaction(i).unwrap();
426+
if tx.is_coinbase() {
427+
println!("tx is coinbase!");
428+
continue;
429+
}
430+
coins.push(Vec::new());
431+
for j in 0..tx.input_count() {
432+
let output = find_output(&blocks, tx.input(j).unwrap().outpoint()).unwrap();
433+
println!("Accessing coins i {i}");
434+
coins[i - 1].push(Coin::new(&output));
435+
}
436+
}
437+
block_spent_outputs.push(BlockSpentOutputs::new(&coins));
438+
}
439+
440+
for (block, block_spent_outputs) in blocks.iter().zip(block_spent_outputs.iter()) {
378441
let (accepted, state) = chainman.process_header(&block.header());
379442
assert!(accepted);
380443
assert_eq!(state.mode(), ValidationMode::Valid);
444+
445+
let (result, state) = chainman.validate_block(block, &block_spent_outputs);
446+
assert!(result);
447+
assert_eq!(state.mode(), ValidationMode::Valid);
381448
}
382449
}
383450

0 commit comments

Comments
 (0)