diff --git a/bdk-ffi/src/bdk.udl b/bdk-ffi/src/bdk.udl index 6d6fadd5..86b3b77e 100644 --- a/bdk-ffi/src/bdk.udl +++ b/bdk-ffi/src/bdk.udl @@ -304,10 +304,6 @@ interface FullScanScriptInspector { void inspect(KeychainKind keychain, u32 index, Script script); }; -/// A changeset for [`Wallet`]. -[Remote] -interface ChangeSet {}; - // ------------------------------------------------------------------------ // bdk_wallet crate - wallet module // ------------------------------------------------------------------------ diff --git a/bdk-ffi/src/bitcoin.rs b/bdk-ffi/src/bitcoin.rs index aabbbb8f..bc76e1d3 100644 --- a/bdk-ffi/src/bitcoin.rs +++ b/bdk-ffi/src/bitcoin.rs @@ -37,10 +37,10 @@ use std::str::FromStr; use std::sync::{Arc, Mutex}; /// A reference to an unspent output by TXID and output index. -#[derive(Debug, Clone, Eq, PartialEq, uniffi:: Record)] +#[derive(Debug, Clone, Eq, PartialEq, std::hash::Hash, uniffi:: Record)] pub struct OutPoint { /// The transaction. - pub txid: String, + pub txid: Arc, /// The index of the output in the transaction. pub vout: u32, } @@ -48,21 +48,43 @@ pub struct OutPoint { impl From<&BdkOutPoint> for OutPoint { fn from(outpoint: &BdkOutPoint) -> Self { OutPoint { - txid: outpoint.txid.to_string(), + txid: Arc::new(Txid(outpoint.txid)), vout: outpoint.vout, } } } +impl From for OutPoint { + fn from(value: BdkOutPoint) -> Self { + Self { + txid: Arc::new(Txid(value.txid)), + vout: value.vout, + } + } +} + impl From for BdkOutPoint { fn from(outpoint: OutPoint) -> Self { BdkOutPoint { - txid: BitcoinTxid::from_str(&outpoint.txid).unwrap(), + txid: BitcoinTxid::from_raw_hash(outpoint.txid.0.into()), vout: outpoint.vout, } } } +/// An [`OutPoint`] suitable as a key in a hash map. +#[derive(Debug, PartialEq, Eq, std::hash::Hash, uniffi::Object)] +#[uniffi::export(Debug, Eq, Hash)] +pub struct HashableOutPoint(pub(crate) OutPoint); + +#[uniffi::export] +impl HashableOutPoint { + /// Get the internal [`OutPoint`] + pub fn outpoint(&self) -> OutPoint { + self.0.clone() + } +} + /// Represents fee rate. /// /// This is an integer type representing fee rate in sat/kwu. It provides protection against mixing @@ -549,7 +571,7 @@ impl From<&BdkTxIn> for TxIn { fn from(tx_in: &BdkTxIn) -> Self { TxIn { previous_output: OutPoint { - txid: tx_in.previous_output.txid.to_string(), + txid: Arc::new(Txid(tx_in.previous_output.txid)), vout: tx_in.previous_output.vout, }, script_sig: Arc::new(Script(tx_in.script_sig.clone())), @@ -583,6 +605,24 @@ impl From<&BdkTxOut> for TxOut { } } +impl From for TxOut { + fn from(tx_out: BdkTxOut) -> Self { + Self { + value: tx_out.value.to_sat(), + script_pubkey: Arc::new(Script(tx_out.script_pubkey)), + } + } +} + +impl From for BdkTxOut { + fn from(tx_out: TxOut) -> Self { + Self { + value: BdkAmount::from_sat(tx_out.value), + script_pubkey: tx_out.script_pubkey.0.clone(), + } + } +} + /// A bitcoin Block hash #[derive(Debug, Clone, Copy, PartialEq, Eq, std::hash::Hash, uniffi::Object)] #[uniffi::export(Display, Eq, Hash)] diff --git a/bdk-ffi/src/lib.rs b/bdk-ffi/src/lib.rs index a00b727e..6bdef52e 100644 --- a/bdk-ffi/src/lib.rs +++ b/bdk-ffi/src/lib.rs @@ -47,7 +47,6 @@ use crate::types::SyncScriptInspector; use bdk_wallet::bitcoin::Network; use bdk_wallet::keys::bip39::WordCount; -use bdk_wallet::ChangeSet; use bdk_wallet::KeychainKind; uniffi::include_scaffolding!("bdk"); diff --git a/bdk-ffi/src/types.rs b/bdk-ffi/src/types.rs index 05a030bb..97076570 100644 --- a/bdk-ffi/src/types.rs +++ b/bdk-ffi/src/types.rs @@ -1,5 +1,9 @@ -use crate::bitcoin::{Address, Amount, OutPoint, Script, Transaction, TxOut}; +use crate::bitcoin::{ + Address, Amount, BlockHash, DescriptorId, HashableOutPoint, OutPoint, Script, Transaction, + TxOut, Txid, +}; use crate::error::{CreateTxError, RequestBuilderError}; +use crate::keys::DescriptorPublicKey; use bdk_core::bitcoin::absolute::LockTime as BdkLockTime; use bdk_core::spk_client::SyncItem; @@ -23,7 +27,7 @@ use bdk_wallet::Balance as BdkBalance; use bdk_wallet::LocalOutput as BdkLocalOutput; use bdk_wallet::Update as BdkUpdate; -use std::collections::HashMap; +use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::convert::TryFrom; use std::sync::{Arc, Mutex}; @@ -66,7 +70,7 @@ impl From> for ChainPosition { } => { let block_id = BlockId { height: anchor.block_id.height, - hash: anchor.block_id.hash.to_string(), + hash: Arc::new(BlockHash(anchor.block_id.hash)), }; ChainPosition::Confirmed { confirmation_block_time: ConfirmationBlockTime { @@ -84,7 +88,7 @@ impl From> for ChainPosition { } /// Represents the confirmation block and time of a transaction. -#[derive(Debug, uniffi::Record)] +#[derive(Debug, Clone, PartialEq, Eq, std::hash::Hash, uniffi::Record)] pub struct ConfirmationBlockTime { /// The anchor block. pub block_id: BlockId, @@ -92,13 +96,49 @@ pub struct ConfirmationBlockTime { pub confirmation_time: u64, } +impl From for ConfirmationBlockTime { + fn from(value: BdkConfirmationBlockTime) -> Self { + Self { + block_id: value.block_id.into(), + confirmation_time: value.confirmation_time, + } + } +} + +impl From for BdkConfirmationBlockTime { + fn from(value: ConfirmationBlockTime) -> Self { + Self { + block_id: value.block_id.into(), + confirmation_time: value.confirmation_time, + } + } +} + /// A reference to a block in the canonical chain. -#[derive(Debug, uniffi::Record)] +#[derive(Debug, Clone, PartialEq, Eq, std::hash::Hash, uniffi::Record)] pub struct BlockId { /// The height of the block. pub height: u32, /// The hash of the block. - pub hash: String, + pub hash: Arc, +} + +impl From for BlockId { + fn from(value: bdk_wallet::chain::BlockId) -> Self { + Self { + height: value.height, + hash: Arc::new(BlockHash(value.hash)), + } + } +} + +impl From for bdk_wallet::chain::BlockId { + fn from(value: BlockId) -> Self { + Self { + height: value.height, + hash: value.hash.0, + } + } } /// A transaction that is deemed to be part of the canonical history. @@ -203,7 +243,7 @@ impl From for LocalOutput { fn from(local_utxo: BdkLocalOutput) -> Self { LocalOutput { outpoint: OutPoint { - txid: local_utxo.outpoint.txid.to_string(), + txid: Arc::new(Txid(local_utxo.outpoint.txid)), vout: local_utxo.outpoint.vout, }, txout: TxOut { @@ -714,3 +754,208 @@ pub struct UnconfirmedTx { pub tx: Arc, pub last_seen: u64, } + +/// Mapping of descriptors to their last revealed index. +#[derive(Debug, Clone, uniffi::Record)] +pub struct IndexerChangeSet { + pub last_revealed: HashMap, u32>, +} + +impl From for IndexerChangeSet { + fn from(mut value: bdk_wallet::chain::indexer::keychain_txout::ChangeSet) -> Self { + let mut changes = HashMap::new(); + for (id, index) in core::mem::take(&mut value.last_revealed) { + changes.insert(Arc::new(DescriptorId(id.0)), index); + } + Self { + last_revealed: changes, + } + } +} + +impl From for bdk_wallet::chain::indexer::keychain_txout::ChangeSet { + fn from(mut value: IndexerChangeSet) -> Self { + let mut changes = BTreeMap::new(); + for (id, index) in core::mem::take(&mut value.last_revealed) { + let descriptor_id = bdk_wallet::chain::DescriptorId(id.0); + changes.insert(descriptor_id, index); + } + Self { + last_revealed: changes, + } + } +} + +/// The hash added or removed at the given height. +#[derive(Debug, Clone, uniffi::Record)] +pub struct ChainChange { + /// Effected height + pub height: u32, + /// A hash was added or must be removed. + pub hash: Option>, +} + +/// Changes to the local chain +#[derive(Debug, Clone, uniffi::Record)] +pub struct LocalChainChangeSet { + pub changes: Vec, +} + +impl From for LocalChainChangeSet { + fn from(mut value: bdk_wallet::chain::local_chain::ChangeSet) -> Self { + let mut changes = Vec::with_capacity(value.blocks.len()); + for (height, hash) in core::mem::take(&mut value.blocks) { + let hash = hash.map(|h| Arc::new(BlockHash(h))); + let change = ChainChange { height, hash }; + changes.push(change); + } + Self { changes } + } +} + +impl From for bdk_wallet::chain::local_chain::ChangeSet { + fn from(mut value: LocalChainChangeSet) -> Self { + let mut changes = BTreeMap::new(); + for change in core::mem::take(&mut value.changes) { + let height = change.height; + let hash = change.hash.map(|h| h.0); + changes.insert(height, hash); + } + Self { blocks: changes } + } +} + +#[derive(Debug, Clone, uniffi::Record)] +pub struct Anchor { + pub confirmation_block_time: ConfirmationBlockTime, + pub txid: Arc, +} + +#[derive(Debug, Clone, uniffi::Record)] +pub struct TxGraphChangeSet { + pub txs: Vec>, + pub txouts: HashMap, TxOut>, + pub anchors: Vec, + pub last_seen: HashMap, u64>, +} + +impl From> for TxGraphChangeSet { + fn from(mut value: bdk_wallet::chain::tx_graph::ChangeSet) -> Self { + let btree_txs = core::mem::take(&mut value.txs); + let txs = btree_txs + .into_iter() + .map(|tx| Arc::new(tx.as_ref().into())) + .collect::>>(); + let mut txouts = HashMap::new(); + for (outpoint, txout) in core::mem::take(&mut value.txouts) { + txouts.insert(Arc::new(HashableOutPoint(outpoint.into())), txout.into()); + } + let mut anchors = Vec::new(); + for anchor in core::mem::take(&mut value.anchors) { + let confirmation_block_time = anchor.0.into(); + let txid = Arc::new(Txid(anchor.1)); + let anchor = Anchor { + confirmation_block_time, + txid, + }; + anchors.push(anchor); + } + let mut last_seens = HashMap::new(); + for (txid, time) in core::mem::take(&mut value.last_seen) { + last_seens.insert(Arc::new(Txid(txid)), time); + } + TxGraphChangeSet { + txs, + txouts, + anchors, + last_seen: last_seens, + } + } +} + +impl From for bdk_wallet::chain::tx_graph::ChangeSet { + fn from(mut value: TxGraphChangeSet) -> Self { + let mut txs = BTreeSet::new(); + for tx in core::mem::take(&mut value.txs) { + let tx = Arc::new(tx.as_ref().into()); + txs.insert(tx); + } + let mut txouts = BTreeMap::new(); + for txout in core::mem::take(&mut value.txouts) { + txouts.insert(txout.0.outpoint().into(), txout.1.into()); + } + let mut anchors = BTreeSet::new(); + for anchor in core::mem::take(&mut value.anchors) { + let txid = anchor.txid.0; + anchors.insert((anchor.confirmation_block_time.into(), txid)); + } + let mut last_seen = BTreeMap::new(); + for (txid, time) in core::mem::take(&mut value.last_seen) { + last_seen.insert(txid.0, time); + } + Self { + txs, + txouts, + anchors, + last_seen, + } + } +} + +#[derive(Debug, Clone, uniffi::Record)] +pub struct ChangeSet { + pub descriptor: Option>, + pub change_descriptor: Option>, + pub network: Option, + pub local_chain: LocalChainChangeSet, + pub tx_graph: TxGraphChangeSet, + pub indexer: IndexerChangeSet, +} + +impl From for bdk_wallet::ChangeSet { + fn from(value: ChangeSet) -> Self { + let descriptor = value.descriptor.map(|d| { + let str_repr = d.to_string(); + str_repr.parse::>().unwrap() + }); + let change_descriptor = value.change_descriptor.map(|d| { + let str_repr = d.to_string(); + str_repr.parse::>().unwrap() + }); + let network = value.network; + let local_chain = value.local_chain.into(); + let tx_graph = value.tx_graph.into(); + let indexer = value.indexer.into(); + Self { + descriptor, + change_descriptor, + network, + local_chain, + tx_graph, + indexer, + } + } +} + +impl From for ChangeSet { + fn from(value: bdk_wallet::ChangeSet) -> Self { + let descriptor = value + .descriptor + .map(|d| Arc::new(DescriptorPublicKey::from_string(d.to_string()).unwrap())); + let change_descriptor = value + .change_descriptor + .map(|d| Arc::new(DescriptorPublicKey::from_string(d.to_string()).unwrap())); + let network = value.network; + let local_chain = value.local_chain.into(); + let tx_graph = value.tx_graph.into(); + let indexer = value.indexer.into(); + Self { + descriptor, + change_descriptor, + network, + local_chain, + tx_graph, + indexer, + } + } +} diff --git a/bdk-ffi/tests/bindings/test.kts b/bdk-ffi/tests/bindings/test.kts index ffb52291..4f7f2d1b 100644 --- a/bdk-ffi/tests/bindings/test.kts +++ b/bdk-ffi/tests/bindings/test.kts @@ -4,10 +4,10 @@ */ import org.bitcoindevkit.bitcoin.Network -import org.bitcoindevkit.BlockId +import org.bitcoindevkit.Condition // A type from bitcoin-ffi val network = Network.TESTNET // A type from bdk-ffi -val blockId = BlockId(0uL, "abcd") +val condition = Condition(null, null) diff --git a/bdk-ffi/tests/bindings/test.py b/bdk-ffi/tests/bindings/test.py index 4077ca8e..d3f9494b 100644 --- a/bdk-ffi/tests/bindings/test.py +++ b/bdk-ffi/tests/bindings/test.py @@ -1,4 +1,4 @@ -from bdkpython import BlockId +from bdkpython import Condition from bdkpython.bitcoin import Network import unittest @@ -11,7 +11,7 @@ def test_some_enum(self): # A type from the bdk-ffi library def test_some_dict(self): - block_id = BlockId(height=0, hash="abcd") + condition = Condition(csv = None, timelock = None) if __name__=='__main__': unittest.main() diff --git a/bdk-ffi/tests/bindings/test.swift b/bdk-ffi/tests/bindings/test.swift index 8e9ea599..68cb3b1d 100644 --- a/bdk-ffi/tests/bindings/test.swift +++ b/bdk-ffi/tests/bindings/test.swift @@ -10,4 +10,4 @@ import BitcoinDevKit let network = Network.testnet // A type from the bdk-ffi library -let blockId = BlockId(height: 32, hash: "abcd") +let condition = Condition(csv: nil, timelock: nil)