Skip to content

Express the wallet changeset over the FFI layer #756

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions bdk-ffi/src/bdk.udl
Original file line number Diff line number Diff line change
Expand Up @@ -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
// ------------------------------------------------------------------------
Expand Down
51 changes: 50 additions & 1 deletion bdk-ffi/src/bitcoin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Txid>,
Expand All @@ -54,6 +54,15 @@ impl From<&BdkOutPoint> for OutPoint {
}
}

impl From<BdkOutPoint> for OutPoint {
fn from(value: BdkOutPoint) -> Self {
Self {
txid: Arc::new(Txid(value.txid)),
vout: value.vout,
}
}
}

impl From<OutPoint> for BdkOutPoint {
fn from(outpoint: OutPoint) -> Self {
BdkOutPoint {
Expand All @@ -63,6 +72,28 @@ impl From<OutPoint> 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
Expand Down Expand Up @@ -588,6 +619,24 @@ impl From<&BdkTxOut> for TxOut {
}
}

impl From<BdkTxOut> 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<TxOut> 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)]
Expand Down
1 change: 0 additions & 1 deletion bdk-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
246 changes: 242 additions & 4 deletions bdk-ffi/src/types.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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};

Expand Down Expand Up @@ -85,16 +89,34 @@ impl From<BdkChainPosition<BdkConfirmationBlockTime>> 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,
/// The confirmation time of the transaction being anchored.
pub confirmation_time: u64,
}

impl From<BdkConfirmationBlockTime> for ConfirmationBlockTime {
fn from(value: BdkConfirmationBlockTime) -> Self {
Self {
block_id: value.block_id.into(),
confirmation_time: value.confirmation_time,
}
}
}

impl From<ConfirmationBlockTime> 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,
Expand All @@ -111,6 +133,15 @@ impl From<BdkBlockId> for BlockId {
}
}

impl From<BlockId> 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 {
Expand Down Expand Up @@ -725,3 +756,210 @@ pub struct UnconfirmedTx {
pub tx: Arc<Transaction>,
pub last_seen: u64,
}

/// Mapping of descriptors to their last revealed index.
#[derive(Debug, Clone, uniffi::Record)]
pub struct IndexerChangeSet {
pub last_revealed: HashMap<Arc<DescriptorId>, u32>,
}

impl From<bdk_wallet::chain::indexer::keychain_txout::ChangeSet> 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<IndexerChangeSet> 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<Arc<BlockHash>>,
}

/// Changes to the local chain
#[derive(Debug, Clone, uniffi::Record)]
pub struct LocalChainChangeSet {
pub changes: Vec<ChainChange>,
}

impl From<bdk_wallet::chain::local_chain::ChangeSet> 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<LocalChainChangeSet> 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<Txid>,
}

#[derive(Debug, Clone, uniffi::Record)]
pub struct TxGraphChangeSet {
pub txs: Vec<Arc<Transaction>>,
pub txouts: HashMap<Arc<HashableOutPoint>, TxOut>,
pub anchors: Vec<Anchor>,
pub last_seen: HashMap<Arc<Txid>, u64>,
}

impl From<bdk_wallet::chain::tx_graph::ChangeSet<BdkConfirmationBlockTime>> for TxGraphChangeSet {
fn from(mut value: bdk_wallet::chain::tx_graph::ChangeSet<BdkConfirmationBlockTime>) -> 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::<Vec<Arc<Transaction>>>();
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<TxGraphChangeSet> for bdk_wallet::chain::tx_graph::ChangeSet<BdkConfirmationBlockTime> {
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<Arc<Descriptor>>,
pub change_descriptor: Option<Arc<Descriptor>>,
pub network: Option<bdk_wallet::bitcoin::Network>,
pub local_chain: LocalChainChangeSet,
pub tx_graph: TxGraphChangeSet,
pub indexer: IndexerChangeSet,
}

impl From<ChangeSet> 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<bdk_wallet::ChangeSet> 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,
}
}
}
1 change: 0 additions & 1 deletion bdk-ffi/tests/bindings/test.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
*/

import org.bitcoindevkit.bitcoin.Network
import org.bitcoindevkit.BlockId

// A type from bitcoin-ffi
val network = Network.TESTNET
1 change: 0 additions & 1 deletion bdk-ffi/tests/bindings/test.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from bdkpython import BlockId
from bdkpython.bitcoin import Network

import unittest
Expand Down
Loading