diff --git a/api-docs/kotlin/src/main/kotlin/org/bitcoindevkit/bdk.kt b/api-docs/kotlin/src/main/kotlin/org/bitcoindevkit/bdk.kt index 4401ffc..1413a35 100644 --- a/api-docs/kotlin/src/main/kotlin/org/bitcoindevkit/bdk.kt +++ b/api-docs/kotlin/src/main/kotlin/org/bitcoindevkit/bdk.kt @@ -248,7 +248,7 @@ class Blockchain( config: BlockchainConfig ) { /** Broadcast a transaction. */ - fun broadcast(psbt: PartiallySignedBitcoinTransaction) {} + fun broadcast(transaction: Transaction) {} /** Estimate the fee rate required to confirm a transaction in a given target of blocks. */ fun estimateFee(target: ULong): FeeRate {} @@ -260,6 +260,18 @@ class Blockchain( fun getBlockHash(height: UInt): String {} } +/** + * A bitcoin transaction. + * + * @constructor Build a new Bitcoin Transaction. + * + * @param transactionBytes The transaction bytes, bitcoin consensus encoded. + */ +class Transaction(transactionBytes: List) { + /** Return the transaction bytes, bitcoin consensus encoded. */ + fun serialize(): List {} +} + /** * A partially signed bitcoin transaction. * @@ -274,8 +286,8 @@ class PartiallySignedBitcoinTransaction(psbtBase64: String) { /** Get the txid of the PSBT. */ fun txid(): String {} - /** Return the transaction as bytes. */ - fun extractTx(): List + /** Extract the transaction. */ + fun extractTx(): Transaction {} /** * Combines this PartiallySignedTransaction with another PSBT as described by BIP 174. diff --git a/bdk-ffi/src/bdk.udl b/bdk-ffi/src/bdk.udl index 25f65eb..f9c38b2 100644 --- a/bdk-ffi/src/bdk.udl +++ b/bdk-ffi/src/bdk.udl @@ -162,7 +162,7 @@ interface Blockchain { constructor(BlockchainConfig config); [Throws=BdkError] - void broadcast([ByRef] PartiallySignedTransaction psbt); + void broadcast([ByRef] Transaction transaction); [Throws=BdkError] FeeRate estimate_fee(u64 target); @@ -237,6 +237,13 @@ interface FeeRate { float as_sat_per_vb(); }; +interface Transaction { + [Throws=BdkError] + constructor(sequence transaction_bytes); + + sequence serialize(); +}; + interface PartiallySignedTransaction { [Throws=BdkError] constructor(string psbt_base64); @@ -245,7 +252,7 @@ interface PartiallySignedTransaction { string txid(); - sequence extract_tx(); + Transaction extract_tx(); [Throws=BdkError] PartiallySignedTransaction combine(PartiallySignedTransaction other); diff --git a/bdk-ffi/src/blockchain.rs b/bdk-ffi/src/blockchain.rs index 2d8e9f5..9d206c4 100644 --- a/bdk-ffi/src/blockchain.rs +++ b/bdk-ffi/src/blockchain.rs @@ -1,5 +1,5 @@ // use crate::BlockchainConfig; -use crate::{BdkError, PartiallySignedTransaction}; +use crate::{BdkError, Transaction}; use bdk::bitcoin::Network; use bdk::blockchain::any::{AnyBlockchain, AnyBlockchainConfig}; use bdk::blockchain::rpc::Auth as BdkAuth; @@ -60,9 +60,9 @@ impl Blockchain { self.blockchain_mutex.lock().expect("blockchain") } - pub(crate) fn broadcast(&self, psbt: &PartiallySignedTransaction) -> Result<(), BdkError> { - let tx = psbt.internal.lock().unwrap().clone().extract_tx(); - self.get_blockchain().broadcast(&tx) + pub(crate) fn broadcast(&self, transaction: &Transaction) -> Result<(), BdkError> { + let tx = &transaction.internal; + self.get_blockchain().broadcast(tx) } pub(crate) fn estimate_fee(&self, target: u64) -> Result, BdkError> { diff --git a/bdk-ffi/src/lib.rs b/bdk-ffi/src/lib.rs index 3f2f976..21a9db2 100644 --- a/bdk-ffi/src/lib.rs +++ b/bdk-ffi/src/lib.rs @@ -15,7 +15,11 @@ use crate::keys::{DescriptorPublicKey, DescriptorSecretKey, Mnemonic}; use crate::psbt::PartiallySignedTransaction; use crate::wallet::{BumpFeeTxBuilder, TxBuilder, Wallet}; use bdk::bitcoin::blockdata::script::Script as BdkScript; -use bdk::bitcoin::{Address as BdkAddress, Network, OutPoint as BdkOutPoint, Txid}; +use bdk::bitcoin::consensus::Decodable; +use bdk::bitcoin::psbt::serialize::Serialize; +use bdk::bitcoin::{ + Address as BdkAddress, Network, OutPoint as BdkOutPoint, Transaction as BdkTransaction, Txid, +}; use bdk::blockchain::Progress as BdkProgress; use bdk::database::any::{SledDbConfiguration, SqliteDbConfiguration}; use bdk::keys::bip39::WordCount; @@ -24,6 +28,7 @@ use bdk::wallet::AddressInfo as BdkAddressInfo; use bdk::{Balance as BdkBalance, BlockTime, Error as BdkError, FeeRate, KeychainKind}; use std::convert::From; use std::fmt; +use std::io::Cursor; use std::str::FromStr; use std::sync::Arc; @@ -218,6 +223,24 @@ impl fmt::Debug for ProgressHolder { } } +/// A Bitcoin transaction. +#[derive(Debug)] +pub struct Transaction { + internal: BdkTransaction, +} + +impl Transaction { + fn new(transaction_bytes: Vec) -> Result { + let mut decoder = Cursor::new(transaction_bytes); + let tx: BdkTransaction = BdkTransaction::consensus_decode(&mut decoder)?; + Ok(Transaction { internal: tx }) + } + + fn serialize(&self) -> Vec { + self.internal.serialize() + } +} + /// A Bitcoin address. struct Address { address: BdkAddress, @@ -264,3 +287,21 @@ pub struct TxBuilderResult { } uniffi::deps::static_assertions::assert_impl_all!(Wallet: Sync, Send); + +// The goal of these tests to to ensure `bdk-ffi` intermediate code correctly calls `bdk` APIs. +// These tests should not be used to verify `bdk` behavior that is already tested in the `bdk` +// crate. +#[cfg(test)] +mod test { + use super::Transaction; + use bdk::bitcoin::hashes::hex::FromHex; + + // Verify that bdk-ffi Transaction can be created from valid bytes and serialized back into the same bytes. + #[test] + fn test_transaction_serde() { + let test_tx_bytes = Vec::from_hex("020000000001031cfbc8f54fbfa4a33a30068841371f80dbfe166211242213188428f437445c91000000006a47304402206fbcec8d2d2e740d824d3d36cc345b37d9f65d665a99f5bd5c9e8d42270a03a8022013959632492332200c2908459547bf8dbf97c65ab1a28dec377d6f1d41d3d63e012103d7279dfb90ce17fe139ba60a7c41ddf605b25e1c07a4ddcb9dfef4e7d6710f48feffffff476222484f5e35b3f0e43f65fc76e21d8be7818dd6a989c160b1e5039b7835fc00000000171600140914414d3c94af70ac7e25407b0689e0baa10c77feffffffa83d954a62568bbc99cc644c62eb7383d7c2a2563041a0aeb891a6a4055895570000000017160014795d04cc2d4f31480d9a3710993fbd80d04301dffeffffff06fef72f000000000017a91476fd7035cd26f1a32a5ab979e056713aac25796887a5000f00000000001976a914b8332d502a529571c6af4be66399cd33379071c588ac3fda0500000000001976a914fc1d692f8de10ae33295f090bea5fe49527d975c88ac522e1b00000000001976a914808406b54d1044c429ac54c0e189b0d8061667e088ac6eb68501000000001976a914dfab6085f3a8fb3e6710206a5a959313c5618f4d88acbba20000000000001976a914eb3026552d7e3f3073457d0bee5d4757de48160d88ac0002483045022100bee24b63212939d33d513e767bc79300051f7a0d433c3fcf1e0e3bf03b9eb1d70220588dc45a9ce3a939103b4459ce47500b64e23ab118dfc03c9caa7d6bfc32b9c601210354fd80328da0f9ae6eef2b3a81f74f9a6f66761fadf96f1d1d22b1fd6845876402483045022100e29c7e3a5efc10da6269e5fc20b6a1cb8beb92130cc52c67e46ef40aaa5cac5f0220644dd1b049727d991aece98a105563416e10a5ac4221abac7d16931842d5c322012103960b87412d6e169f30e12106bdf70122aabb9eb61f455518322a18b920a4dfa887d30700").unwrap(); + let new_tx_from_bytes = Transaction::new(test_tx_bytes.clone()).unwrap(); + let serialized_tx_to_bytes = new_tx_from_bytes.serialize(); + assert_eq!(test_tx_bytes, serialized_tx_to_bytes); + } +} diff --git a/bdk-ffi/src/psbt.rs b/bdk-ffi/src/psbt.rs index 272fae2..80f36af 100644 --- a/bdk-ffi/src/psbt.rs +++ b/bdk-ffi/src/psbt.rs @@ -1,11 +1,10 @@ use bdk::bitcoin::hashes::hex::ToHex; -use bdk::bitcoin::psbt::serialize::Serialize; use bdk::bitcoin::util::psbt::PartiallySignedTransaction as BdkPartiallySignedTransaction; use bdk::psbt::PsbtUtils; use std::str::FromStr; use std::sync::{Arc, Mutex}; -use crate::{BdkError, FeeRate}; +use crate::{BdkError, FeeRate, Transaction}; #[derive(Debug)] pub(crate) struct PartiallySignedTransaction { @@ -32,14 +31,10 @@ impl PartiallySignedTransaction { txid.to_hex() } - /// Return the transaction as bytes. - pub(crate) fn extract_tx(&self) -> Vec { - self.internal - .lock() - .unwrap() - .clone() - .extract_tx() - .serialize() + /// Return the transaction. + pub(crate) fn extract_tx(&self) -> Arc { + let tx = self.internal.lock().unwrap().clone().extract_tx(); + Arc::new(Transaction { internal: tx }) } /// Combines this PartiallySignedTransaction with other PSBT as described by BIP 174.