Skip to content

Commit beff5be

Browse files
refactor: establish core library structure with FFI and Bitcoin
primitives Organize codebase into logical modules: - ffi/: Foreign Function Interface with C bindings and helpers - core/: Bitcoin data types (blocks, trasnactions, scripts) and verification - notifications/: Callback interfaces for kernel and validation events - log/: Logging utilities and helpers Add from_ptr methods to types to enable cross-module construction where private fields prevented inline instantiation. Configure Windows CI to run tests sequentially due to Rust's multithreaded test execution conflicting with shared state.
1 parent 97262a3 commit beff5be

File tree

21 files changed

+2635
-1635
lines changed

21 files changed

+2635
-1635
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ jobs:
118118
echo "BOOST_LIBRARYDIR=$env:GITHUB_WORKSPACE\vcpkg\installed\x64-windows-static\lib" | Out-File -FilePath $env:GITHUB_ENV -Append
119119
120120
- name: Run tests
121-
run: cargo test -vv
121+
run: cargo test -vv -- --test-threads=1
122122

123123
linux-aarch64:
124124
name: Build and Test on Linux ARM64

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ Examples for the usage of the library can be found in the `examples/` directory
3636
and the `tests`. For now, the example binary implements a bare-bones silent
3737
payments scanner.
3838

39+
## Testing
40+
41+
Due to the underlying Bitcoin Core logging system using global state on Windows, tests must be run sequentially:
42+
43+
```bash
44+
cargo test -- --test-threads=1
45+
```
46+
3947
## Fuzzing
4048

4149
Fuzzing is done with [cargo fuzz](https://github.com/rust-fuzz/cargo-fuzz).

src/core/block.rs

Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
use std::{ffi::c_void, marker::PhantomData};
2+
3+
use libbitcoinkernel_sys::{
4+
btck_Block, btck_BlockSpentOutputs, btck_BlockTreeEntry, btck_TransactionSpentOutputs,
5+
btck_block_copy, btck_block_count_transactions, btck_block_create, btck_block_destroy,
6+
btck_block_get_hash, btck_block_get_transaction_at, btck_block_hash_destroy,
7+
btck_block_spent_outputs_copy, btck_block_spent_outputs_count,
8+
btck_block_spent_outputs_destroy, btck_block_spent_outputs_get_transaction_spent_outputs_at,
9+
btck_block_to_bytes, btck_block_tree_entry_destroy, btck_block_tree_entry_get_block_hash,
10+
btck_block_tree_entry_get_height, btck_block_tree_entry_get_previous,
11+
btck_transaction_spent_outputs_copy, btck_transaction_spent_outputs_count,
12+
btck_transaction_spent_outputs_destroy, btck_transaction_spent_outputs_get_coin_at,
13+
};
14+
15+
use crate::{c_serialize, state::ChainstateManager, KernelError, RefType};
16+
17+
use super::transaction::{Coin, Transaction};
18+
19+
/// A block tree entry that is tied to a specific [`ChainstateManager`].
20+
///
21+
/// Internally the [`ChainstateManager`] keeps an in-memory of the current block
22+
/// tree once it is loaded. The [`BlockTreeEntry`] points to an entry in this tree.
23+
/// It is only valid as long as the [`ChainstateManager`] it was retrieved from
24+
/// remains in scope.
25+
#[derive(Debug)]
26+
pub struct BlockTreeEntry {
27+
inner: *mut btck_BlockTreeEntry,
28+
marker: PhantomData<ChainstateManager>,
29+
}
30+
31+
unsafe impl Send for BlockTreeEntry {}
32+
unsafe impl Sync for BlockTreeEntry {}
33+
34+
impl BlockTreeEntry {
35+
/// Creates a BlockTreeEntry from an FFI pointer for internal library use.
36+
pub(crate) fn from_ptr(inner: *mut btck_BlockTreeEntry) -> Self {
37+
Self {
38+
inner,
39+
marker: PhantomData,
40+
}
41+
}
42+
43+
/// Move to the previous entry in the block tree. E.g. from height n to
44+
/// height n-1.
45+
pub fn prev(self) -> Result<BlockTreeEntry, KernelError> {
46+
let inner = unsafe { btck_block_tree_entry_get_previous(self.inner) };
47+
48+
if inner.is_null() {
49+
return Err(KernelError::OutOfBounds);
50+
}
51+
52+
Ok(BlockTreeEntry {
53+
inner,
54+
marker: self.marker,
55+
})
56+
}
57+
58+
/// Returns the current height associated with this BlockTreeEntry.
59+
pub fn height(&self) -> i32 {
60+
unsafe { btck_block_tree_entry_get_height(self.inner) }
61+
}
62+
63+
/// Returns the current block hash associated with this BlockTreeEntry.
64+
pub fn block_hash(&self) -> BlockHash {
65+
let hash = unsafe { btck_block_tree_entry_get_block_hash(self.inner) };
66+
let res = BlockHash {
67+
hash: unsafe { (&*hash).hash },
68+
};
69+
unsafe { btck_block_hash_destroy(hash) };
70+
res
71+
}
72+
73+
/// Get the inner FFI pointer for internal library use
74+
pub(crate) fn as_ptr(&self) -> *mut btck_BlockTreeEntry {
75+
self.inner
76+
}
77+
}
78+
79+
impl Drop for BlockTreeEntry {
80+
fn drop(&mut self) {
81+
unsafe { btck_block_tree_entry_destroy(self.inner) };
82+
}
83+
}
84+
85+
/// A type for a Block hash.
86+
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
87+
pub struct BlockHash {
88+
pub hash: [u8; 32],
89+
}
90+
91+
/// A Bitcoin block containing a header and transactions.
92+
///
93+
/// Blocks can be created from raw serialized data or retrieved from the blockchain.
94+
/// They represent the fundamental units of the Bitcoin blockchain structure.
95+
pub struct Block {
96+
inner: *mut btck_Block,
97+
}
98+
99+
unsafe impl Send for Block {}
100+
unsafe impl Sync for Block {}
101+
102+
impl Block {
103+
/// Creates a Block from an FFI pointer for internal library use.
104+
pub(crate) fn from_ptr(inner: *mut btck_Block) -> Self {
105+
Self { inner }
106+
}
107+
108+
/// Returns the hash of this block.
109+
///
110+
/// This is the double SHA256 hash of the block header, which serves as
111+
/// the block's unique identifier.
112+
pub fn hash(&self) -> BlockHash {
113+
let hash = unsafe { btck_block_get_hash(self.inner) };
114+
let res = BlockHash {
115+
hash: unsafe { (&*hash).hash },
116+
};
117+
unsafe { btck_block_hash_destroy(hash) };
118+
res
119+
}
120+
121+
/// Returns the number of transactions in this block.
122+
pub fn transaction_count(&self) -> usize {
123+
unsafe { btck_block_count_transactions(self.inner) as usize }
124+
}
125+
126+
/// Returns the transaction at the specified index.
127+
///
128+
/// # Arguments
129+
/// * `index` - The zero-based index of the transaction (0 is the coinbase)
130+
///
131+
/// # Errors
132+
/// Returns [`KernelError::OutOfBounds`] if the index is invalid.
133+
pub fn transaction(&self, index: usize) -> Result<Transaction, KernelError> {
134+
if index >= self.transaction_count() {
135+
return Err(KernelError::OutOfBounds);
136+
}
137+
let tx = unsafe { btck_block_get_transaction_at(self.inner, index) };
138+
Ok(Transaction::from_ptr(tx))
139+
}
140+
141+
/// Consensus encodes the block to Bitcoin wire format.
142+
pub fn consensus_encode(&self) -> Result<Vec<u8>, KernelError> {
143+
c_serialize(|callback, user_data| unsafe {
144+
btck_block_to_bytes(self.inner, Some(callback), user_data)
145+
})
146+
}
147+
148+
/// Get the inner FFI pointer for internal library use
149+
pub(crate) fn as_ptr(&self) -> *mut btck_Block {
150+
self.inner
151+
}
152+
}
153+
154+
impl TryFrom<Block> for Vec<u8> {
155+
type Error = KernelError;
156+
157+
fn try_from(block: Block) -> Result<Self, KernelError> {
158+
block.consensus_encode()
159+
}
160+
}
161+
162+
impl TryFrom<&[u8]> for Block {
163+
type Error = KernelError;
164+
165+
fn try_from(raw_block: &[u8]) -> Result<Self, Self::Error> {
166+
let inner =
167+
unsafe { btck_block_create(raw_block.as_ptr() as *const c_void, raw_block.len()) };
168+
if inner.is_null() {
169+
return Err(KernelError::Internal(
170+
"Failed to de-serialize Block.".to_string(),
171+
));
172+
}
173+
Ok(Block { inner })
174+
}
175+
}
176+
177+
impl Clone for Block {
178+
fn clone(&self) -> Self {
179+
Block {
180+
inner: unsafe { btck_block_copy(self.inner) },
181+
}
182+
}
183+
}
184+
185+
impl Drop for Block {
186+
fn drop(&mut self) {
187+
unsafe { btck_block_destroy(self.inner) };
188+
}
189+
}
190+
191+
/// Spent output data for all transactions in a block.
192+
///
193+
/// This contains the previous outputs that were consumed by all transactions
194+
/// in a specific block.
195+
pub struct BlockSpentOutputs {
196+
inner: *mut btck_BlockSpentOutputs,
197+
}
198+
199+
unsafe impl Send for BlockSpentOutputs {}
200+
unsafe impl Sync for BlockSpentOutputs {}
201+
202+
impl BlockSpentOutputs {
203+
/// Creates BlockSpentOutputs from an FFI pointer for internal library use.
204+
pub(crate) fn from_ptr(inner: *mut btck_BlockSpentOutputs) -> Self {
205+
Self { inner }
206+
}
207+
208+
/// Returns the number of transactions that have spent output data.
209+
///
210+
/// Note: This excludes the coinbase transaction, which has no inputs.
211+
pub fn count(&self) -> usize {
212+
unsafe { btck_block_spent_outputs_count(self.inner) }
213+
}
214+
215+
/// Returns a reference to the spent outputs for a specific transaction in the block.
216+
///
217+
/// # Arguments
218+
/// * `transaction_index` - The index of the transaction (0-based, excluding coinbase)
219+
///
220+
/// # Returns
221+
/// * `Ok(RefType<TransactionSpentOutputs, BlockSpentOutputs>)` - A reference to the transaction's spent outputs
222+
/// * `Err(KernelError::OutOfBounds)` - If the index is invalid
223+
pub fn transaction_spent_outputs(
224+
&self,
225+
transaction_index: usize,
226+
) -> Result<RefType<'_, TransactionSpentOutputs, BlockSpentOutputs>, KernelError> {
227+
let tx_out_ptr = unsafe {
228+
btck_block_spent_outputs_get_transaction_spent_outputs_at(self.inner, transaction_index)
229+
};
230+
if tx_out_ptr.is_null() {
231+
return Err(KernelError::OutOfBounds);
232+
}
233+
Ok(RefType::new(TransactionSpentOutputs { inner: tx_out_ptr }))
234+
}
235+
}
236+
237+
impl Clone for BlockSpentOutputs {
238+
fn clone(&self) -> Self {
239+
BlockSpentOutputs {
240+
inner: unsafe { btck_block_spent_outputs_copy(self.inner) },
241+
}
242+
}
243+
}
244+
245+
impl Drop for BlockSpentOutputs {
246+
fn drop(&mut self) {
247+
unsafe { btck_block_spent_outputs_destroy(self.inner) };
248+
}
249+
}
250+
251+
/// Spent output data for a single transaction.
252+
///
253+
/// Contains all the coins (UTXOs) that were consumed by a specific transaction's
254+
/// inputs, in the same order as the transaction's inputs.
255+
pub struct TransactionSpentOutputs {
256+
inner: *mut btck_TransactionSpentOutputs,
257+
}
258+
259+
unsafe impl Send for TransactionSpentOutputs {}
260+
unsafe impl Sync for TransactionSpentOutputs {}
261+
262+
impl TransactionSpentOutputs {
263+
/// Returns the number of coins spent by this transaction.
264+
pub fn count(&self) -> usize {
265+
unsafe { btck_transaction_spent_outputs_count(self.inner) }
266+
}
267+
268+
/// Returns a reference to the coin at the specified input index.
269+
///
270+
/// # Arguments
271+
/// * `coin_index` - The index corresponding to the transaction input
272+
///
273+
/// # Returns
274+
/// * `Ok(RefType<Coin, TransactionSpentOutputs>)` - A reference to the coin
275+
/// * `Err(KernelError::OutOfBounds)` - If the index is invalid
276+
pub fn coin(
277+
&self,
278+
coin_index: usize,
279+
) -> Result<RefType<'_, Coin, TransactionSpentOutputs>, KernelError> {
280+
let coin_ptr = unsafe {
281+
btck_transaction_spent_outputs_get_coin_at(self.inner as *const _, coin_index)
282+
};
283+
if coin_ptr.is_null() {
284+
return Err(KernelError::OutOfBounds);
285+
}
286+
287+
Ok(RefType::new(Coin::from_ptr(coin_ptr)))
288+
}
289+
}
290+
291+
impl Clone for TransactionSpentOutputs {
292+
fn clone(&self) -> Self {
293+
TransactionSpentOutputs {
294+
inner: unsafe { btck_transaction_spent_outputs_copy(self.inner) },
295+
}
296+
}
297+
}
298+
299+
impl Drop for TransactionSpentOutputs {
300+
fn drop(&mut self) {
301+
unsafe { btck_transaction_spent_outputs_destroy(self.inner) };
302+
}
303+
}

src/core/mod.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
pub mod block;
2+
pub mod script;
3+
pub mod transaction;
4+
pub mod verify;
5+
6+
pub use block::{Block, BlockHash, BlockSpentOutputs, BlockTreeEntry, TransactionSpentOutputs};
7+
pub use script::ScriptPubkey;
8+
pub use transaction::{Coin, Transaction, TxOut};
9+
pub use verify::{
10+
verify, ScriptVerifyError, ScriptVerifyStatus, VERIFY_ALL, VERIFY_ALL_PRE_TAPROOT,
11+
VERIFY_CHECKLOCKTIMEVERIFY, VERIFY_CHECKSEQUENCEVERIFY, VERIFY_DERSIG, VERIFY_NONE,
12+
VERIFY_NULLDUMMY, VERIFY_P2SH, VERIFY_TAPROOT, VERIFY_WITNESS,
13+
};

0 commit comments

Comments
 (0)