Skip to content

Commit 9cbc877

Browse files
committed
Add block validation
1 parent df14122 commit 9cbc877

4 files changed

Lines changed: 136 additions & 26 deletions

File tree

src/core/block.rs

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -128,20 +128,7 @@ 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,
139-
btck_block_header_get_version, btck_block_spent_outputs_copy, btck_block_spent_outputs_count,
140-
btck_block_spent_outputs_destroy, btck_block_spent_outputs_get_transaction_spent_outputs_at,
141-
btck_block_to_bytes, btck_coin_confirmation_height, btck_coin_copy, btck_coin_destroy,
142-
btck_coin_get_output, btck_coin_is_coinbase, btck_transaction_spent_outputs_copy,
143-
btck_transaction_spent_outputs_count, btck_transaction_spent_outputs_destroy,
144-
btck_transaction_spent_outputs_get_coin_at,
131+
btck_Block, btck_BlockHash, btck_BlockHeader, btck_BlockSpentOutputs, btck_Coin, btck_TransactionSpentOutputs, btck_block_copy, btck_block_count_transactions, btck_block_create, btck_block_destroy, btck_block_get_hash, btck_block_get_header, btck_block_get_transaction_at, btck_block_hash_copy, btck_block_hash_create, btck_block_hash_destroy, btck_block_hash_equals, btck_block_hash_to_bytes, btck_block_header_copy, btck_block_header_create, btck_block_header_destroy, btck_block_header_get_bits, btck_block_header_get_hash, btck_block_header_get_nonce, btck_block_header_get_prev_hash, btck_block_header_get_timestamp, btck_block_header_get_version, btck_block_spent_outputs_copy, btck_block_spent_outputs_count, btck_block_spent_outputs_create, btck_block_spent_outputs_destroy, btck_block_spent_outputs_get_transaction_spent_outputs_at, btck_block_to_bytes, btck_coin_confirmation_height, btck_coin_copy, btck_coin_create, btck_coin_destroy, btck_coin_get_output, btck_coin_is_coinbase, btck_transaction_spent_outputs_copy, btck_transaction_spent_outputs_count, btck_transaction_spent_outputs_destroy, btck_transaction_spent_outputs_get_coin_at
145132
};
146133

147134
use crate::{
@@ -150,7 +137,7 @@ use crate::{
150137
c_helpers::present,
151138
sealed::{AsPtr, FromMutPtr, FromPtr},
152139
},
153-
KernelError,
140+
KernelError, TxOut,
154141
};
155142

156143
use super::transaction::{TransactionRef, TxOutRef};
@@ -1155,6 +1142,36 @@ impl BlockSpentOutputs {
11551142
pub fn as_ref(&self) -> BlockSpentOutputsRef<'_> {
11561143
unsafe { BlockSpentOutputsRef::from_ptr(self.inner as *const _) }
11571144
}
1145+
1146+
pub fn new(coins: &[Vec<Coin>]) -> Self {
1147+
struct CallbackContext<'a> {
1148+
coins: &'a [Vec<Coin>],
1149+
}
1150+
1151+
extern "C" fn coin_getter(
1152+
context: *mut c_void,
1153+
tx_index: usize,
1154+
coin_index: usize,
1155+
) -> *const btck_Coin {
1156+
let ctx = unsafe { &*(context as *const CallbackContext) };
1157+
ctx.coins[tx_index][coin_index].as_ptr()
1158+
}
1159+
1160+
extern "C" fn count_getter(context: *mut c_void, tx_index: usize) -> usize {
1161+
let ctx = unsafe { &*(context as *const CallbackContext) };
1162+
ctx.coins[tx_index].len()
1163+
}
1164+
1165+
let context = CallbackContext { coins };
1166+
unsafe {
1167+
BlockSpentOutputs::from_ptr(btck_block_spent_outputs_create(
1168+
&context as *const CallbackContext as *mut c_void,
1169+
Some(coin_getter),
1170+
Some(count_getter),
1171+
coins.len(),
1172+
))
1173+
}
1174+
}
11581175
}
11591176

11601177
impl FromMutPtr<btck_BlockSpentOutputs> for BlockSpentOutputs {
@@ -1710,6 +1727,10 @@ unsafe impl Send for Coin {}
17101727
unsafe impl Sync for Coin {}
17111728

17121729
impl Coin {
1730+
pub fn new(output: &TxOut) -> Coin {
1731+
unsafe { Coin::from_ptr(btck_coin_create(output.as_ptr(), 0, 0)) }
1732+
}
1733+
17131734
/// Creates a borrowed reference to this coin.
17141735
///
17151736
/// 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: 28 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::{
@@ -321,6 +322,32 @@ impl ChainstateManager {
321322
}
322323
}
323324

325+
/// Returns a pair indicating wether the block was accepted, and whether it is valid.
326+
/// # Example
327+
/// ```no_run
328+
/// # use bitcoinkernel::{BlockSpentOutputs, Block, ChainstateManager};
329+
/// # let chainman: ChainstateManager = unimplemented!();
330+
/// # let block: Block = unimplemented!();
331+
/// # let spent_outputs: BlockSpentOutputs= unimplemented!();
332+
/// let res = chainman.validate_block(&block, &spent_outputs);
333+
/// ```
334+
pub fn validate_block(
335+
&self,
336+
block: &Block,
337+
block_spent_outputs: &BlockSpentOutputs,
338+
) -> (bool, BlockValidationState) {
339+
let state = BlockValidationState::new();
340+
let accepted = unsafe {
341+
btck_chainstate_manager_validate_block(
342+
self.inner,
343+
block.as_ptr(),
344+
block_spent_outputs.as_ptr(),
345+
state.as_ptr() as *mut btck_BlockValidationState,
346+
)
347+
};
348+
(c_helpers::success(accepted), state)
349+
}
350+
324351
/// Initialize the chainstate manager and optionally trigger a reindex.
325352
///
326353
/// This should be called after creating the chainstate manager to complete
@@ -329,6 +356,7 @@ impl ChainstateManager {
329356
///
330357
/// # Errors
331358
/// Returns [`KernelError::Internal`] if initialization fails.
359+
332360
pub fn import_blocks(&self) -> Result<(), KernelError> {
333361
let result = unsafe {
334362
btck_chainstate_manager_import_blocks(

tests/test.rs

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,7 @@ mod tests {
33
use bitcoinkernel::notifications::types::BlockValidationState;
44
use bitcoinkernel::state::chainstate::ProcessBlockHeaderResult;
55
use bitcoinkernel::{
6-
prelude::*, verify, Block, BlockHash, BlockHeader, BlockSpentOutputs, BlockTreeEntry,
7-
BlockValidationStateRef, ChainParams, ChainType, ChainstateManager,
8-
ChainstateManagerBuilder, Coin, Context, ContextBuilder, KernelError, Log, Logger,
9-
PrecomputedTransactionData, ScriptPubkey, ScriptVerifyError, Transaction,
10-
TransactionSpentOutputs, TxIn, TxOut, ValidationMode, VERIFY_ALL, VERIFY_ALL_PRE_TAPROOT,
11-
VERIFY_TAPROOT, VERIFY_WITNESS,
6+
Block, BlockHash, BlockHeader, BlockSpentOutputs, BlockTreeEntry, BlockValidationStateRef, ChainParams, ChainType, ChainstateManager, ChainstateManagerBuilder, Coin, Context, ContextBuilder, KernelError, Log, Logger, PrecomputedTransactionData, ScriptPubkey, ScriptVerifyError, Transaction, TransactionSpentOutputs, TxIn, TxOut, TxOutPointRef, VERIFY_ALL, VERIFY_ALL_PRE_TAPROOT, VERIFY_TAPROOT, VERIFY_WITNESS, ValidationMode, prelude::*, verify
127
};
138
use libbitcoinkernel_sys::btck_ScriptVerificationFlags;
149
use std::fs::File;
@@ -385,6 +380,68 @@ mod tests {
385380
}
386381
}
387382

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

0 commit comments

Comments
 (0)