Skip to content

Commit f82b197

Browse files
committed
Add block validation
1 parent 966cc6d commit f82b197

File tree

4 files changed

+130
-10
lines changed

4 files changed

+130
-10
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};
@@ -538,6 +539,36 @@ impl BlockSpentOutputs {
538539
pub fn as_ref(&self) -> BlockSpentOutputsRef<'_> {
539540
unsafe { BlockSpentOutputsRef::from_ptr(self.inner as *const _) }
540541
}
542+
543+
pub fn new(coins: &[Vec<Coin>]) -> Self {
544+
struct CallbackContext<'a> {
545+
coins: &'a [Vec<Coin>],
546+
}
547+
548+
extern "C" fn coin_getter(
549+
context: *mut c_void,
550+
tx_index: usize,
551+
coin_index: usize,
552+
) -> *const btck_Coin {
553+
let ctx = unsafe { &*(context as *const CallbackContext) };
554+
ctx.coins[tx_index][coin_index].as_ptr()
555+
}
556+
557+
extern "C" fn count_getter(context: *mut c_void, tx_index: usize) -> usize {
558+
let ctx = unsafe { &*(context as *const CallbackContext) };
559+
ctx.coins[tx_index].len()
560+
}
561+
562+
let context = CallbackContext { coins };
563+
unsafe {
564+
BlockSpentOutputs::from_ptr(btck_block_spent_outputs_create(
565+
&context as *const CallbackContext as *mut c_void,
566+
Some(coin_getter),
567+
Some(count_getter),
568+
coins.len(),
569+
))
570+
}
571+
}
541572
}
542573

543574
impl FromMutPtr<btck_BlockSpentOutputs> for BlockSpentOutputs {
@@ -870,6 +901,10 @@ unsafe impl Send for Coin {}
870901
unsafe impl Sync for Coin {}
871902

872903
impl Coin {
904+
pub fn new(output: &TxOut) -> Coin {
905+
unsafe { Coin::from_ptr(btck_coin_create(output.as_ptr(), 0, 0)) }
906+
}
907+
873908
pub fn as_ref(&self) -> CoinRef<'_> {
874909
unsafe { CoinRef::from_ptr(self.inner as *const _) }
875910
}

src/core/transaction.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ use libbitcoinkernel_sys::{
1010
btck_transaction_count_outputs, btck_transaction_create, btck_transaction_destroy,
1111
btck_transaction_get_input_at, btck_transaction_get_output_at, btck_transaction_get_txid,
1212
btck_transaction_input_copy, btck_transaction_input_destroy,
13-
btck_transaction_input_get_out_point, btck_transaction_out_point_copy,
14-
btck_transaction_out_point_destroy, btck_transaction_out_point_get_index,
15-
btck_transaction_out_point_get_txid, btck_transaction_output_copy,
16-
btck_transaction_output_create, btck_transaction_output_destroy,
13+
btck_transaction_input_get_out_point, btck_transaction_is_coinbase,
14+
btck_transaction_out_point_copy, btck_transaction_out_point_destroy,
15+
btck_transaction_out_point_get_index, btck_transaction_out_point_get_txid,
16+
btck_transaction_output_copy, btck_transaction_output_create, btck_transaction_output_destroy,
1717
btck_transaction_output_get_amount, btck_transaction_output_get_script_pubkey,
1818
btck_transaction_to_bytes, btck_txid_copy, btck_txid_destroy, btck_txid_equals,
1919
btck_txid_to_bytes,
@@ -22,7 +22,7 @@ use libbitcoinkernel_sys::{
2222
use crate::{
2323
c_serialize,
2424
ffi::{
25-
c_helpers::present,
25+
c_helpers::{self, present},
2626
sealed::{AsPtr, FromMutPtr, FromPtr},
2727
},
2828
KernelError, ScriptPubkeyExt,
@@ -100,6 +100,10 @@ pub trait TransactionExt: AsPtr<btck_Transaction> {
100100
fn outputs(&self) -> TxOutIter<'_> {
101101
TxOutIter::new(unsafe { TransactionRef::from_ptr(self.as_ptr()) })
102102
}
103+
104+
fn is_coinbase(&self) -> bool {
105+
unsafe { c_helpers::enabled(btck_transaction_is_coinbase(self.as_ptr())) }
106+
}
103107
}
104108

105109
/// 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 & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#[cfg(test)]
22
mod tests {
3+
use bitcoinkernel::core::transaction::{TxOutPointExt, TxOutPointRef};
34
use bitcoinkernel::{
45
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
56
};
@@ -371,13 +372,74 @@ mod tests {
371372
let (context, data_dir) = testing_setup();
372373
let blocks_dir = data_dir.clone() + "/blocks";
373374
let block_data = read_block_data();
375+
let blocks: Vec<Block> = block_data
376+
.iter()
377+
.map(|data| Block::new(data.as_slice()).unwrap())
378+
.collect();
374379
let chainman = ChainstateManager::new(&context, &data_dir, &blocks_dir).unwrap();
375380

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

0 commit comments

Comments
 (0)