Skip to content

Commit 88cc043

Browse files
committed
[bindings] Give Transaction objects a buffer-is-owned flag.
A lot of our container mapping depends on the `is_owned` flag which we have for in-crate mapped objects to map references and non-references into the same container type. Transaction was mapped to two completely different types (a slice and a Vec type), which led to a number of edge cases in the bindings generation. Specifically, I spent a few days trying to map `[(A, &Transaction)]` properly and came up empty - we map slices into the same types as Vecs (and rely on the `is_owned` flag to avoid double-free) and the lack of one for `Transaction` would have required a special-case in numerous functions. Instead, we just add a flag in `Transaction` to mirror what we do for in-crate types and check it before free-ing any underlying memory. Note that, sadly, because the c_types objects aren't mapped as a part of our C++ bindings generation, you have to manually call `Transaction_free()` even in C++.
1 parent b4f0f78 commit 88cc043

File tree

4 files changed

+37
-16
lines changed

4 files changed

+37
-16
lines changed

c-bindings-gen/src/types.rs

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -340,8 +340,7 @@ impl<'a, 'c: 'a> TypeResolver<'a, 'c> {
340340
"bitcoin::blockdata::script::Script" if is_ref => Some("crate::c_types::u8slice"),
341341
"bitcoin::blockdata::script::Script" if !is_ref => Some("crate::c_types::derived::CVec_u8Z"),
342342
"bitcoin::blockdata::transaction::OutPoint" if is_ref => Some("crate::chain::transaction::OutPoint"),
343-
"bitcoin::blockdata::transaction::Transaction" if is_ref && !ptr_for_ref => Some("crate::c_types::Transaction"),
344-
"bitcoin::blockdata::transaction::Transaction" => Some("crate::c_types::derived::CVec_u8Z"),
343+
"bitcoin::blockdata::transaction::Transaction" => Some("crate::c_types::Transaction"),
345344
"bitcoin::blockdata::transaction::TxOut" if !is_ref => Some("crate::c_types::TxOut"),
346345
"bitcoin::OutPoint" => Some("crate::chain::transaction::OutPoint"),
347346
"bitcoin::network::constants::Network" => Some("crate::bitcoin::network::Network"),
@@ -413,7 +412,7 @@ impl<'a, 'c: 'a> TypeResolver<'a, 'c> {
413412
"bitcoin::blockdata::script::Script" if is_ref => Some("&::bitcoin::blockdata::script::Script::from(Vec::from("),
414413
"bitcoin::blockdata::script::Script" if !is_ref => Some("::bitcoin::blockdata::script::Script::from("),
415414
"bitcoin::blockdata::transaction::Transaction" if is_ref => Some("&"),
416-
"bitcoin::blockdata::transaction::Transaction" => Some("::bitcoin::consensus::encode::deserialize(&"),
415+
"bitcoin::blockdata::transaction::Transaction" => Some(""),
417416
"bitcoin::blockdata::transaction::TxOut" if !is_ref => Some(""),
418417
"bitcoin::network::constants::Network" => Some(""),
419418
"bitcoin::blockdata::block::BlockHeader" => Some("&::bitcoin::consensus::encode::deserialize(unsafe { &*"),
@@ -471,8 +470,7 @@ impl<'a, 'c: 'a> TypeResolver<'a, 'c> {
471470
"bitcoin::secp256k1::key::SecretKey" if is_ref => Some("}[..]).unwrap()"),
472471
"bitcoin::blockdata::script::Script" if is_ref => Some(".to_slice()))"),
473472
"bitcoin::blockdata::script::Script" if !is_ref => Some(".into_rust())"),
474-
"bitcoin::blockdata::transaction::Transaction" if is_ref => Some(".into_bitcoin()"),
475-
"bitcoin::blockdata::transaction::Transaction" => Some(".into_rust()[..]).unwrap()"),
473+
"bitcoin::blockdata::transaction::Transaction" => Some(".into_bitcoin()"),
476474
"bitcoin::blockdata::transaction::TxOut" if !is_ref => Some(".into_rust()"),
477475
"bitcoin::network::constants::Network" => Some(".into_bitcoin()"),
478476
"bitcoin::blockdata::block::BlockHeader" => Some(" }).unwrap()"),
@@ -553,8 +551,7 @@ impl<'a, 'c: 'a> TypeResolver<'a, 'c> {
553551
"bitcoin::secp256k1::Error" if !is_ref => Some("crate::c_types::Secp256k1Error::from_rust("),
554552
"bitcoin::blockdata::script::Script" if is_ref => Some("crate::c_types::u8slice::from_slice(&"),
555553
"bitcoin::blockdata::script::Script" if !is_ref => Some(""),
556-
"bitcoin::blockdata::transaction::Transaction" if is_ref && !ptr_for_ref => Some("crate::c_types::Transaction::from_slice(&local_"),
557-
"bitcoin::blockdata::transaction::Transaction" => Some("local_"),
554+
"bitcoin::blockdata::transaction::Transaction" => Some("crate::c_types::Transaction::from_vec(local_"),
558555
"bitcoin::blockdata::transaction::TxOut" if !is_ref => Some("crate::c_types::TxOut::from_rust("),
559556
"bitcoin::blockdata::block::BlockHeader" if is_ref => Some("&local_"),
560557
"bitcoin::blockdata::block::Block" if is_ref => Some("crate::c_types::u8slice::from_slice(&local_"),
@@ -617,8 +614,7 @@ impl<'a, 'c: 'a> TypeResolver<'a, 'c> {
617614
"bitcoin::secp256k1::Error" if !is_ref => Some(")"),
618615
"bitcoin::blockdata::script::Script" if is_ref => Some("[..])"),
619616
"bitcoin::blockdata::script::Script" if !is_ref => Some(".into_bytes().into()"),
620-
"bitcoin::blockdata::transaction::Transaction" if is_ref && !ptr_for_ref => Some(")"),
621-
"bitcoin::blockdata::transaction::Transaction" => Some(".into()"),
617+
"bitcoin::blockdata::transaction::Transaction" => Some(")"),
622618
"bitcoin::blockdata::transaction::TxOut" if !is_ref => Some(")"),
623619
"bitcoin::blockdata::block::BlockHeader" if is_ref => Some(""),
624620
"bitcoin::blockdata::block::Block" if is_ref => Some(")"),

lightning-c-bindings/demo.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ uint32_t get_fee(const void *this_arg, LDKConfirmationTarget target) {
1919

2020
void broadcast_tx(const void *this_arg, LDKTransaction tx) {
2121
//TODO
22+
Transaction_free(tx);
2223
}
2324

2425
LDKCResult_NoneChannelMonitorUpdateErrZ add_channel_monitor(const void *this_arg, LDKOutPoint funding_txo, LDKChannelMonitor monitor) {

lightning-c-bindings/demo.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ static int num_txs_broadcasted = 0; // Technically a race, but ints are atomic o
115115
void broadcast_tx(const void *this_arg, LDKTransaction tx) {
116116
num_txs_broadcasted += 1;
117117
//TODO
118+
Transaction_free(tx);
118119
}
119120

120121
struct NodeMonitors {

lightning-c-bindings/src/c_types/mod.rs

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,25 +86,48 @@ impl Secp256k1Error {
8686
}
8787

8888
#[repr(C)]
89-
/// A reference to a serialized transaction, in (pointer, length) form.
90-
/// This type does *not* own its own memory, so access to it after, eg, the call in which it was
91-
/// provided to you are invalid.
89+
/// A serialized transaction, in (pointer, length) form.
90+
///
91+
/// This type optionally owns its own memory, and thus the semantics around access change based on
92+
/// the data_is_owned flag. If data_is_owned is set, you must call Transaction_free to free the
93+
/// underlying buffer before the object goes out of scope. If data_is_owned is not set, any access
94+
/// to the buffer after the scope in which the object was provided to you is invalid. eg, access
95+
/// after you return from the call in which a !data_is_owned Transaction is provided to you would
96+
/// be invalid.
97+
///
98+
/// Note that, while it may change in the future, because transactions on the Rust side are stored
99+
/// in a deserialized form, all `Transaction`s generated on the Rust side will have `data_is_owned`
100+
/// set and all `Transaction`s you pass to Rust may have `data_is_owned` either set or unset at
101+
/// your discretion.
92102
pub struct Transaction {
93-
pub data: *const u8,
103+
pub data: *mut u8,
94104
pub datalen: usize,
105+
pub data_is_owned: bool,
95106
}
96107
impl Transaction {
97108
pub(crate) fn into_bitcoin(&self) -> BitcoinTransaction {
98109
if self.datalen == 0 { panic!("0-length buffer can never represent a valid Transaction"); }
99110
::bitcoin::consensus::encode::deserialize(unsafe { std::slice::from_raw_parts(self.data, self.datalen) }).unwrap()
100111
}
101-
pub(crate) fn from_slice(s: &[u8]) -> Self {
112+
pub(crate) fn from_vec(v: Vec<u8>) -> Self {
113+
let datalen = v.len();
114+
let data = Box::into_raw(v.into_boxed_slice());
102115
Self {
103-
data: s.as_ptr(),
104-
datalen: s.len(),
116+
data: unsafe { (*data).as_mut_ptr() },
117+
datalen,
118+
data_is_owned: true,
105119
}
106120
}
107121
}
122+
impl Drop for Transaction {
123+
fn drop(&mut self) {
124+
if self.data_is_owned && self.datalen != 0 {
125+
let _ = CVecTempl { data: self.data, datalen: self.datalen };
126+
}
127+
}
128+
}
129+
#[no_mangle]
130+
pub extern "C" fn Transaction_free(_res: Transaction) { }
108131

109132
#[repr(C)]
110133
#[derive(Clone)]

0 commit comments

Comments
 (0)