Skip to content

Commit 9001935

Browse files
feat(core): Add zero-copy as_bytes to ScriptPubkeyExt
to_bytes allocates a Vec<u8> on every call. as_bytes returns a slice directly into kernel-managed memory by capturing the pointer handed to the writer callback, which btck_script_pubkey_to_bytes invokes synchronously before returning.
1 parent e22be3a commit 9001935

2 files changed

Lines changed: 97 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111
- Added `Block::check` to perform context-free validation of a block (size, weight, coinbase, transactions, sigops), with optional proof-of-work and merkle-root checks toggled via the `BLOCK_CHECK_BASE` / `_POW` / `_MERKLE` / `_ALL` flags. Returns a `BlockCheckResult` enum carrying the validation state on failure.
12+
- Added `ScriptPubkeyExt::as_bytes` to return a zero-copy slice into kernel-managed memory. Unlike `to_bytes`, this does not allocate.
1213

1314
### Changed
1415
- The `verify` function's `flags` parameter now uses `ScriptVerificationFlags` instead of `u32`, making the type explicit in the public API.

src/core/script.rs

Lines changed: 96 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
//! let p2wpkh = ScriptPubkey::new(&hex::decode(p2wpkh_hex).unwrap()).unwrap();
4949
//! ```
5050
51-
use std::{ffi::c_void, marker::PhantomData};
51+
use std::{ffi::c_void, marker::PhantomData, panic};
5252

5353
use libbitcoinkernel_sys::{
5454
btck_ScriptPubkey, btck_script_pubkey_copy, btck_script_pubkey_create,
@@ -57,7 +57,10 @@ use libbitcoinkernel_sys::{
5757

5858
use crate::{
5959
c_serialize,
60-
ffi::sealed::{AsPtr, FromMutPtr, FromPtr},
60+
ffi::{
61+
c_helpers,
62+
sealed::{AsPtr, FromMutPtr, FromPtr},
63+
},
6164
KernelError,
6265
};
6366

@@ -84,6 +87,60 @@ pub trait ScriptPubkeyExt: AsPtr<btck_ScriptPubkey> {
8487
})
8588
.expect("Script pubkey to_bytes should never fail")
8689
}
90+
91+
/// Returns a zero-copy view of the script's raw bytes.
92+
/// Unlike [`to_bytes`](ScriptPubkeyExt::to_bytes), this does not allocate.
93+
/// The returned slice borrows directly from kernel-managed memory and is
94+
/// valid for the lifetime of `self`.
95+
///
96+
/// # Examples
97+
///
98+
/// ```no_run
99+
/// # use bitcoinkernel::{prelude::*, ScriptPubkey};
100+
/// let script = ScriptPubkey::new(&[0x76, 0xa9]).unwrap();
101+
/// assert_eq!(script.as_bytes(), &[0x76, 0xa9]);
102+
/// ```
103+
fn as_bytes(&self) -> &[u8] {
104+
struct BytesOut {
105+
ptr: *const u8,
106+
len: usize,
107+
}
108+
109+
unsafe extern "C" fn writer(
110+
data: *const c_void,
111+
len: usize,
112+
user_data: *mut c_void,
113+
) -> i32 {
114+
panic::catch_unwind(|| {
115+
let out = &mut *(user_data as *mut BytesOut);
116+
out.ptr = data as *const u8;
117+
out.len = len;
118+
c_helpers::to_c_result(true)
119+
})
120+
.unwrap_or_else(|_| c_helpers::to_c_result(false))
121+
}
122+
123+
let mut out = BytesOut {
124+
ptr: std::ptr::null(),
125+
len: 0,
126+
};
127+
let ret = unsafe {
128+
btck_script_pubkey_to_bytes(
129+
self.as_ptr(),
130+
Some(writer),
131+
&mut out as *mut BytesOut as *mut c_void,
132+
)
133+
};
134+
assert!(
135+
c_helpers::success(ret),
136+
"btck_script_pubkey_to_bytes should never fail for a valid ScriptPubkey"
137+
);
138+
139+
if out.ptr.is_null() {
140+
return &[];
141+
}
142+
unsafe { std::slice::from_raw_parts(out.ptr, out.len) }
143+
}
87144
}
88145

89146
/// A single script pubkey containing spending conditions for a [`crate::TxOut`].
@@ -349,6 +406,43 @@ mod tests {
349406
assert_eq!(bytes, script_data);
350407
}
351408

409+
#[test]
410+
fn test_scriptpubkey_as_bytes_empty() {
411+
let script = ScriptPubkey::new(&[]).unwrap();
412+
assert_eq!(script.as_bytes(), &[]);
413+
}
414+
415+
#[test]
416+
fn test_scriptpubkey_as_bytes() {
417+
let script_data = vec![0x76, 0xa9, 0x14];
418+
let script = ScriptPubkey::new(&script_data).unwrap();
419+
assert_eq!(script.as_bytes(), script_data.as_slice());
420+
}
421+
422+
#[test]
423+
fn test_scriptpubkey_ref_as_bytes() {
424+
let script_data = vec![0x76, 0xa9, 0x14];
425+
let script = ScriptPubkey::new(&script_data).unwrap();
426+
let script_ref = script.as_ref();
427+
assert_eq!(script_ref.as_bytes(), script_data.as_slice());
428+
}
429+
430+
#[test]
431+
fn test_as_bytes_matches_to_bytes() {
432+
let script_data = vec![0x76, 0xa9, 0x14];
433+
let script = ScriptPubkey::new(&script_data).unwrap();
434+
assert_eq!(script.as_bytes(), script.to_bytes().as_slice());
435+
}
436+
437+
#[test]
438+
fn test_as_bytes_no_copy() {
439+
let script_data = vec![0x76, 0xa9, 0x14];
440+
let script = ScriptPubkey::new(&script_data).unwrap();
441+
let bytes1 = script.as_bytes();
442+
let bytes2 = script.as_bytes();
443+
assert_eq!(bytes1.as_ptr(), bytes2.as_ptr());
444+
}
445+
352446
#[test]
353447
fn test_scriptpubkey_into_vec() {
354448
let script_data = vec![0x76, 0xa9, 0x14];

0 commit comments

Comments
 (0)