diff --git a/bdk-ffi/src/bdk.udl b/bdk-ffi/src/bdk.udl index af6a7f44..dd667a93 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 e3f5ecb3..270c3f2f 100644 --- a/bdk-ffi/src/bitcoin.rs +++ b/bdk-ffi/src/bitcoin.rs @@ -37,7 +37,7 @@ 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: Arc, @@ -54,6 +54,15 @@ impl From<&BdkOutPoint> for OutPoint { } } +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 { @@ -63,6 +72,28 @@ impl From for BdkOutPoint { } } +/// An [`OutPoint`] used as a key in a hash map. +/// +/// Due to limitations in generating the foreign language bindings, we cannot use [`OutPoint`] as a +/// key for hash maps. +#[derive(Debug, PartialEq, Eq, std::hash::Hash, uniffi::Object)] +#[uniffi::export(Debug, Eq, Hash)] +pub struct HashableOutPoint(pub(crate) OutPoint); + +#[uniffi::export] +impl HashableOutPoint { + /// Create a key for a key-value store from an [`OutPoint`] + #[uniffi::constructor] + pub fn new(outpoint: OutPoint) -> Self { + Self(outpoint) + } + + /// 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 @@ -588,6 +619,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 9f469643..7fce491d 100644 --- a/bdk-ffi/src/lib.rs +++ b/bdk-ffi/src/lib.rs @@ -43,7 +43,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 ea30e259..afb2a972 100644 --- a/bdk-ffi/src/types.rs +++ b/bdk-ffi/src/types.rs @@ -1,4 +1,8 @@ -use crate::bitcoin::{Address, Amount, BlockHash, OutPoint, Script, Transaction, TxOut, Txid}; +use crate::bitcoin::{ + Address, Amount, BlockHash, DescriptorId, HashableOutPoint, OutPoint, Script, Transaction, + TxOut, Txid, +}; +use crate::descriptor::Descriptor; use crate::error::{CreateTxError, RequestBuilderError}; use bdk_core::bitcoin::absolute::LockTime as BdkLockTime; @@ -24,7 +28,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}; @@ -85,7 +89,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, @@ -93,8 +97,26 @@ 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, @@ -111,6 +133,15 @@ impl From for BlockId { } } +impl From for BdkBlockId { + 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. #[derive(uniffi::Record)] pub struct CanonicalTx { @@ -725,3 +756,210 @@ 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| d.extended_descriptor.clone()); + let change_descriptor = value + .change_descriptor + .map(|d| d.extended_descriptor.clone()); + 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(Descriptor { + extended_descriptor: d, + key_map: BTreeMap::new(), + }) + }); + let change_descriptor = value.change_descriptor.map(|d| { + Arc::new(Descriptor { + extended_descriptor: d, + key_map: BTreeMap::new(), + }) + }); + 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 041e9aae..b6b49cad 100644 --- a/bdk-ffi/tests/bindings/test.kts +++ b/bdk-ffi/tests/bindings/test.kts @@ -4,7 +4,6 @@ */ import org.bitcoindevkit.bitcoin.Network -import org.bitcoindevkit.BlockId // A type from bitcoin-ffi val network = Network.TESTNET diff --git a/bdk-ffi/tests/bindings/test.py b/bdk-ffi/tests/bindings/test.py index f35ca67c..c594d37a 100644 --- a/bdk-ffi/tests/bindings/test.py +++ b/bdk-ffi/tests/bindings/test.py @@ -1,4 +1,3 @@ -from bdkpython import BlockId from bdkpython.bitcoin import Network import unittest