Skip to content

Commit 5da8522

Browse files
committed
Add block validation
1 parent 9ebf391 commit 5da8522

File tree

4 files changed

+130
-11
lines changed

4 files changed

+130
-11
lines changed

src/core/block.rs

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ use libbitcoinkernel_sys::{
88
btck_block_hash_destroy, btck_block_hash_equals, btck_block_hash_to_bytes,
99
btck_block_header_copy, btck_block_header_create, btck_block_header_destroy,
1010
btck_block_header_get_hash, btck_block_spent_outputs_copy, btck_block_spent_outputs_count,
11-
btck_block_spent_outputs_destroy, btck_block_spent_outputs_get_transaction_spent_outputs_at,
12-
btck_block_to_bytes, btck_coin_confirmation_height, btck_coin_copy, btck_coin_destroy,
11+
btck_block_spent_outputs_create, btck_block_spent_outputs_destroy,
12+
btck_block_spent_outputs_get_transaction_spent_outputs_at, btck_block_to_bytes,
13+
btck_coin_confirmation_height, btck_coin_copy, btck_coin_create, btck_coin_destroy,
1314
btck_coin_get_output, btck_coin_is_coinbase, btck_transaction_spent_outputs_copy,
1415
btck_transaction_spent_outputs_count, btck_transaction_spent_outputs_destroy,
1516
btck_transaction_spent_outputs_get_coin_at,
@@ -21,7 +22,7 @@ use crate::{
2122
c_helpers::present,
2223
sealed::{AsPtr, FromMutPtr, FromPtr},
2324
},
24-
KernelError,
25+
KernelError, TxOut,
2526
};
2627

2728
use super::transaction::{TransactionRef, TxOutRef};
@@ -540,6 +541,36 @@ impl BlockSpentOutputs {
540541
pub fn as_ref(&self) -> BlockSpentOutputsRef<'_> {
541542
unsafe { BlockSpentOutputsRef::from_ptr(self.inner as *const _) }
542543
}
544+
545+
pub fn new(coins: &[Vec<Coin>]) -> Self {
546+
struct CallbackContext<'a> {
547+
coins: &'a [Vec<Coin>],
548+
}
549+
550+
extern "C" fn coin_getter(
551+
context: *mut c_void,
552+
tx_index: usize,
553+
coin_index: usize,
554+
) -> *const btck_Coin {
555+
let ctx = unsafe { &*(context as *const CallbackContext) };
556+
ctx.coins[tx_index][coin_index].as_ptr()
557+
}
558+
559+
extern "C" fn count_getter(context: *mut c_void, tx_index: usize) -> usize {
560+
let ctx = unsafe { &*(context as *const CallbackContext) };
561+
ctx.coins[tx_index].len()
562+
}
563+
564+
let context = CallbackContext { coins };
565+
unsafe {
566+
BlockSpentOutputs::from_ptr(btck_block_spent_outputs_create(
567+
&context as *const CallbackContext as *mut c_void,
568+
Some(coin_getter),
569+
Some(count_getter),
570+
coins.len(),
571+
))
572+
}
573+
}
543574
}
544575

545576
impl FromMutPtr<btck_BlockSpentOutputs> for BlockSpentOutputs {
@@ -872,6 +903,10 @@ unsafe impl Send for Coin {}
872903
unsafe impl Sync for Coin {}
873904

874905
impl Coin {
906+
pub fn new(output: &TxOut) -> Coin {
907+
unsafe { Coin::from_ptr(btck_coin_create(output.as_ptr(), 0, 0)) }
908+
}
909+
875910
pub fn as_ref(&self) -> CoinRef<'_> {
876911
unsafe { CoinRef::from_ptr(self.inner as *const _) }
877912
}

src/core/transaction.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ use libbitcoinkernel_sys::{
66
btck_transaction_count_outputs, btck_transaction_create, btck_transaction_destroy,
77
btck_transaction_get_input_at, btck_transaction_get_output_at, btck_transaction_get_txid,
88
btck_transaction_input_copy, btck_transaction_input_destroy,
9-
btck_transaction_input_get_out_point, btck_transaction_out_point_copy,
10-
btck_transaction_out_point_destroy, btck_transaction_out_point_get_index,
11-
btck_transaction_out_point_get_txid, btck_transaction_output_copy,
12-
btck_transaction_output_create, btck_transaction_output_destroy,
9+
btck_transaction_input_get_out_point, btck_transaction_is_coinbase,
10+
btck_transaction_out_point_copy, btck_transaction_out_point_destroy,
11+
btck_transaction_out_point_get_index, btck_transaction_out_point_get_txid,
12+
btck_transaction_output_copy, btck_transaction_output_create, btck_transaction_output_destroy,
1313
btck_transaction_output_get_amount, btck_transaction_output_get_script_pubkey,
1414
btck_transaction_to_bytes, btck_txid_copy, btck_txid_destroy, btck_txid_equals,
1515
btck_txid_to_bytes,
@@ -18,7 +18,7 @@ use libbitcoinkernel_sys::{
1818
use crate::{
1919
c_serialize,
2020
ffi::{
21-
c_helpers::present,
21+
c_helpers::{self, present},
2222
sealed::{AsPtr, FromMutPtr, FromPtr},
2323
},
2424
KernelError, ScriptPubkeyExt,
@@ -96,6 +96,10 @@ pub trait TransactionExt: AsPtr<btck_Transaction> {
9696
fn outputs(&self) -> TxOutIter<'_> {
9797
TxOutIter::new(unsafe { TransactionRef::from_ptr(self.as_ptr()) })
9898
}
99+
100+
fn is_coinbase(&self) -> bool {
101+
unsafe { c_helpers::enabled(btck_transaction_is_coinbase(self.as_ptr())) }
102+
}
99103
}
100104

101105
/// A Bitcoin transaction.

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: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#[cfg(test)]
22
mod tests {
33
use bitcoin::consensus::deserialize;
4-
use bitcoinkernel::core::transaction::TxIn;
4+
use bitcoinkernel::core::transaction::{TxIn, TxInExt, TxOutPointExt, TxOutPointRef};
55
use bitcoinkernel::notifications::types::{BlockValidationStateExt, BlockValidationStateRef};
66
use bitcoinkernel::{
77
prelude::*, verify, Block, BlockHash, BlockSpentOutputs, BlockTreeEntry, ChainParams,
@@ -457,13 +457,74 @@ mod tests {
457457
let (context, data_dir) = testing_setup();
458458
let blocks_dir = data_dir.clone() + "/blocks";
459459
let block_data = read_block_data();
460+
let blocks: Vec<Block> = block_data
461+
.iter()
462+
.map(|data| Block::new(data.as_slice()).unwrap())
463+
.collect();
460464
let chainman = ChainstateManager::new(&context, &data_dir, &blocks_dir).unwrap();
461465

462-
for raw_block in block_data.iter() {
463-
let block = Block::new(raw_block.as_slice()).unwrap();
466+
for block in blocks.iter() {
467+
let (accepted, state) = chainman.process_header(&block.header());
468+
assert!(accepted);
469+
assert_eq!(state.mode(), ValidationMode::Valid);
470+
}
471+
}
472+
473+
fn find_output<'a>(blocks: &'a [Block], outpoint: TxOutPointRef) -> Option<TxOut> {
474+
for block in blocks.iter() {
475+
for i in 0..block.transaction_count() {
476+
let tx = block.transaction(i).unwrap();
477+
if tx.txid() != outpoint.txid() {
478+
continue;
479+
}
480+
return tx
481+
.output(outpoint.index() as usize)
482+
.ok()
483+
.map(|out| out.to_owned());
484+
}
485+
}
486+
None
487+
}
488+
489+
#[test]
490+
fn test_block_validation() {
491+
let (context, data_dir) = testing_setup();
492+
let blocks_dir = data_dir.clone() + "/blocks";
493+
let block_data = read_block_data();
494+
let blocks: Vec<Block> = block_data
495+
.iter()
496+
.map(|data| Block::new(data.as_slice()).unwrap())
497+
.collect();
498+
let chainman = ChainstateManager::new(&context, &data_dir, &blocks_dir).unwrap();
499+
500+
let mut block_spent_outputs: Vec<BlockSpentOutputs> = vec![];
501+
502+
for block in blocks.iter() {
503+
let mut coins: Vec<Vec<Coin>> = vec![];
504+
for i in 0..block.transaction_count() {
505+
let tx = block.transaction(i).unwrap();
506+
if tx.is_coinbase() {
507+
println!("tx is coinbase!");
508+
continue;
509+
}
510+
coins.push(Vec::new());
511+
for j in 0..tx.input_count() {
512+
let output = find_output(&blocks, tx.input(j).unwrap().outpoint()).unwrap();
513+
println!("Accessing coins i {i}");
514+
coins[i - 1].push(Coin::new(&output));
515+
}
516+
}
517+
block_spent_outputs.push(BlockSpentOutputs::new(&coins));
518+
}
519+
520+
for (block, block_spent_outputs) in blocks.iter().zip(block_spent_outputs.iter()) {
464521
let (accepted, state) = chainman.process_header(&block.header());
465522
assert!(accepted);
466523
assert_eq!(state.mode(), ValidationMode::Valid);
524+
525+
let (result, state) = chainman.validate_block(block, &block_spent_outputs);
526+
assert!(result);
527+
assert_eq!(state.mode(), ValidationMode::Valid);
467528
}
468529
}
469530

0 commit comments

Comments
 (0)