Skip to content

Commit 8e95358

Browse files
committed
Add block validation
1 parent 358f3b4 commit 8e95358

File tree

4 files changed

+135
-11
lines changed

4 files changed

+135
-11
lines changed

src/core/block.rs

Lines changed: 38 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};
@@ -1005,6 +1006,36 @@ impl BlockSpentOutputs {
10051006
pub fn as_ref(&self) -> BlockSpentOutputsRef<'_> {
10061007
unsafe { BlockSpentOutputsRef::from_ptr(self.inner as *const _) }
10071008
}
1009+
1010+
pub fn new(coins: &[Vec<Coin>]) -> Self {
1011+
struct CallbackContext<'a> {
1012+
coins: &'a [Vec<Coin>],
1013+
}
1014+
1015+
extern "C" fn coin_getter(
1016+
context: *mut c_void,
1017+
tx_index: usize,
1018+
coin_index: usize,
1019+
) -> *const btck_Coin {
1020+
let ctx = unsafe { &*(context as *const CallbackContext) };
1021+
ctx.coins[tx_index][coin_index].as_ptr()
1022+
}
1023+
1024+
extern "C" fn count_getter(context: *mut c_void, tx_index: usize) -> usize {
1025+
let ctx = unsafe { &*(context as *const CallbackContext) };
1026+
ctx.coins[tx_index].len()
1027+
}
1028+
1029+
let context = CallbackContext { coins };
1030+
unsafe {
1031+
BlockSpentOutputs::from_ptr(btck_block_spent_outputs_create(
1032+
&context as *const CallbackContext as *mut c_void,
1033+
Some(coin_getter),
1034+
Some(count_getter),
1035+
coins.len(),
1036+
))
1037+
}
1038+
}
10081039
}
10091040

10101041
impl FromMutPtr<btck_BlockSpentOutputs> for BlockSpentOutputs {
@@ -1560,6 +1591,10 @@ unsafe impl Send for Coin {}
15601591
unsafe impl Sync for Coin {}
15611592

15621593
impl Coin {
1594+
pub fn new(output: &TxOut) -> Coin {
1595+
unsafe { Coin::from_ptr(btck_coin_create(output.as_ptr(), 0, 0)) }
1596+
}
1597+
15631598
/// Creates a borrowed reference to this coin.
15641599
///
15651600
/// This allows converting from owned [`Coin`] to [`CoinRef`] without

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

0 commit comments

Comments
 (0)