Skip to content

Commit 0ba0752

Browse files
committed
Add block validation
1 parent df14122 commit 0ba0752

4 files changed

Lines changed: 139 additions & 10 deletions

File tree

src/core/block.rs

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,9 @@ use libbitcoinkernel_sys::{
137137
btck_block_header_get_bits, btck_block_header_get_hash, btck_block_header_get_nonce,
138138
btck_block_header_get_prev_hash, btck_block_header_get_timestamp,
139139
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,
140+
btck_block_spent_outputs_create, btck_block_spent_outputs_destroy,
141+
btck_block_spent_outputs_get_transaction_spent_outputs_at, btck_block_to_bytes,
142+
btck_coin_confirmation_height, btck_coin_copy, btck_coin_create, btck_coin_destroy,
142143
btck_coin_get_output, btck_coin_is_coinbase, btck_transaction_spent_outputs_copy,
143144
btck_transaction_spent_outputs_count, btck_transaction_spent_outputs_destroy,
144145
btck_transaction_spent_outputs_get_coin_at,
@@ -150,7 +151,7 @@ use crate::{
150151
c_helpers::present,
151152
sealed::{AsPtr, FromMutPtr, FromPtr},
152153
},
153-
KernelError,
154+
KernelError, TxOut,
154155
};
155156

156157
use super::transaction::{TransactionRef, TxOutRef};
@@ -1155,6 +1156,36 @@ impl BlockSpentOutputs {
11551156
pub fn as_ref(&self) -> BlockSpentOutputsRef<'_> {
11561157
unsafe { BlockSpentOutputsRef::from_ptr(self.inner as *const _) }
11571158
}
1159+
1160+
pub fn new(coins: &[Vec<Coin>]) -> Self {
1161+
struct CallbackContext<'a> {
1162+
coins: &'a [Vec<Coin>],
1163+
}
1164+
1165+
extern "C" fn coin_getter(
1166+
context: *mut c_void,
1167+
tx_index: usize,
1168+
coin_index: usize,
1169+
) -> *const btck_Coin {
1170+
let ctx = unsafe { &*(context as *const CallbackContext) };
1171+
ctx.coins[tx_index][coin_index].as_ptr()
1172+
}
1173+
1174+
extern "C" fn count_getter(context: *mut c_void, tx_index: usize) -> usize {
1175+
let ctx = unsafe { &*(context as *const CallbackContext) };
1176+
ctx.coins[tx_index].len()
1177+
}
1178+
1179+
let context = CallbackContext { coins };
1180+
unsafe {
1181+
BlockSpentOutputs::from_ptr(btck_block_spent_outputs_create(
1182+
&context as *const CallbackContext as *mut c_void,
1183+
Some(coin_getter),
1184+
Some(count_getter),
1185+
coins.len(),
1186+
))
1187+
}
1188+
}
11581189
}
11591190

11601191
impl FromMutPtr<btck_BlockSpentOutputs> for BlockSpentOutputs {
@@ -1710,6 +1741,10 @@ unsafe impl Send for Coin {}
17101741
unsafe impl Sync for Coin {}
17111742

17121743
impl Coin {
1744+
pub fn new(output: &TxOut) -> Coin {
1745+
unsafe { Coin::from_ptr(btck_coin_create(output.as_ptr(), 0, 0)) }
1746+
}
1747+
17131748
/// Creates a borrowed reference to this coin.
17141749
///
17151750
/// 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: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ mod tests {
77
BlockValidationStateRef, ChainParams, ChainType, ChainstateManager,
88
ChainstateManagerBuilder, Coin, Context, ContextBuilder, KernelError, Log, Logger,
99
PrecomputedTransactionData, ScriptPubkey, ScriptVerifyError, Transaction,
10-
TransactionSpentOutputs, TxIn, TxOut, ValidationMode, VERIFY_ALL, VERIFY_ALL_PRE_TAPROOT,
11-
VERIFY_TAPROOT, VERIFY_WITNESS,
10+
TransactionSpentOutputs, TxIn, TxOut, TxOutPointRef, ValidationMode, VERIFY_ALL,
11+
VERIFY_ALL_PRE_TAPROOT, VERIFY_TAPROOT, VERIFY_WITNESS,
1212
};
1313
use libbitcoinkernel_sys::btck_ScriptVerificationFlags;
1414
use std::fs::File;
@@ -385,6 +385,68 @@ mod tests {
385385
}
386386
}
387387

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()) {
436+
let result = chainman.process_block_header(&block.header());
437+
match result {
438+
ProcessBlockHeaderResult::Success(state) => {
439+
assert_eq!(state.mode(), ValidationMode::Valid);
440+
}
441+
_ => assert!(false),
442+
};
443+
444+
let (result, state) = chainman.validate_block(block, &block_spent_outputs);
445+
assert!(result);
446+
assert_eq!(state.mode(), ValidationMode::Valid);
447+
}
448+
}
449+
388450
#[test]
389451
fn test_chain_operations() {
390452
let (context, data_dir) = testing_setup();

0 commit comments

Comments
 (0)