Skip to content

Commit

Permalink
feat(wallet): BIP 329 import/export labels
Browse files Browse the repository at this point in the history
Adds BIP 329 label import export functionality
into the `bdk_wallet` crate.

This is feature-gated to the `"labels"` feature.
  • Loading branch information
storopoli committed Jul 28, 2024
1 parent 82141a8 commit f3a5cbb
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 0 deletions.
1 change: 1 addition & 0 deletions crates/wallet/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ all-keys = ["keys-bip39"]
keys-bip39 = ["bip39"]
rusqlite = ["bdk_chain/rusqlite"]
file_store = ["bdk_file_store"]
labels = []

[dev-dependencies]
lazy_static = "1.4"
Expand Down
42 changes: 42 additions & 0 deletions crates/wallet/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
// licenses.

use alloc::boxed::Box;
#[cfg(feature = "labels")]
use alloc::string::String;
use core::convert::AsRef;

use bdk_chain::ConfirmationTime;
Expand Down Expand Up @@ -63,6 +65,46 @@ pub struct LocalOutput {
pub derivation_index: u32,
/// The confirmation time for transaction containing this utxo
pub confirmation_time: ConfirmationTime,
#[cfg(feature = "labels")]
/// The label for this UTXO according to
/// [BIP 329](https://github.com/bitcoin/bips/blob/master/bip-0329.mediawiki)
/// format
pub label: Option<Label>,
}

#[cfg(feature = "labels")]
/// A label for a [`LocalOutput`], used to identify the purpose of the UTXO.
///
/// # Note
///
/// The labels follow the [BIP 329](https://github.com/bitcoin/bips/blob/master/bip-0329.mediawiki)
/// export/import format.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive] // We might add more types in the future
pub enum Label {
/// Input Outpoint: Transaction id and input index separated by a colon
Input(String),
/// Output Outpoint: Transaction id and input index separated by a colon
Output(String),
}

#[cfg(feature = "labels")]
impl Label {
/// Gets the [`Label`]
pub fn label(&self) -> &str {
match self {
Label::Input(s) => s,
Label::Output(s) => s,
}
}

/// Edits the [`Label`]
pub fn edit(&mut self, new_label: String) {
match self {
Label::Input(s) => *s = new_label,
Label::Output(s) => *s = new_label,
}
}
}

/// A [`Utxo`] with its `satisfaction_weight`.
Expand Down
93 changes: 93 additions & 0 deletions crates/wallet/src/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ use chain::Staged;
use core::fmt;
use core::mem;
use core::ops::Deref;
#[cfg(feature = "labels")]
use core::str::FromStr;
use rand_core::RngCore;

use descriptor::error::Error as DescriptorError;
Expand Down Expand Up @@ -297,6 +299,32 @@ impl fmt::Display for ApplyBlockError {
#[cfg(feature = "std")]
impl std::error::Error for ApplyBlockError {}

#[cfg(feature = "labels")]
/// The error type when importing [`Label`]s into a [`Wallet`].
#[derive(Debug, PartialEq)]
pub enum LabelError {
/// There was a problem with the [`Label::Input`] type.
Input(String),
/// There was a problem with the [`Label::Output`] type.
Output(String),
/// There was a problem with the [`Label::Address`] type.
Address(String),
}

#[cfg(feature = "labels")]
impl fmt::Display for LabelError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
LabelError::Input(l) => write!(f, "input label error: {l}"),
LabelError::Output(l) => write!(f, "output label error: {l}"),
LabelError::Address(l) => write!(f, "address label error: {l}"),
}
}
}

#[cfg(all(feature = "std", feature = "labels"))]
impl std::error::Error for LabelError {}

impl Wallet {
/// Build a new [`Wallet`].
///
Expand Down Expand Up @@ -1570,6 +1598,8 @@ impl Wallet {
is_spent: true,
derivation_index,
confirmation_time,
#[cfg(feature = "labels")]
label: None,
}),
satisfaction_weight,
}
Expand Down Expand Up @@ -2310,6 +2340,67 @@ impl Wallet {
.batch_insert_relevant_unconfirmed(unconfirmed_txs);
self.stage.merge(indexed_graph_changeset.into());
}

#[cfg(feature = "labels")]
/// Exports the labels of all the wallet's UTXOs.
///
/// # Note
///
/// The labels follow the [BIP 329](https://github.com/bitcoin/bips/blob/master/bip-0329.mediawiki)
/// export/import format.
pub fn export_labels(&self) -> Vec<Option<Label>> {
let labels = self.list_output().map(|utxo| utxo.label).collect();
labels
}

#[cfg(feature = "labels")]
/// Imports labels into a wallet
///
/// # Note
///
/// The labels follow the [BIP 329](https://github.com/bitcoin/bips/blob/master/bip-0329.mediawiki)
/// export/import format.
pub fn import_labels(&self, labels: Vec<Label>) -> Result<(), LabelError> {
labels.iter().try_for_each(|label| {
match label {
Label::Input(l) => {
// parse the string into an OutPoint and then match
let parts = l.split(':').collect::<Vec<_>>();
if parts.len() != 2 {
return Err(LabelError::Input(l.to_string()));
} else {
let outpoint = OutPoint::new(
// TODO: deal with the Error types in expect
Txid::from_str(parts[0]).expect("valid txid"),
u32::from_str(parts[1]).expect("valid vout"),
);
let local_output = self.get_utxo(outpoint);
if let Some(mut local_output) = local_output {
local_output.label = Some(label.clone());
};
}
}
Label::Output(l) => {
// parse the string into an OutPoint and then match
let parts = l.split(':').collect::<Vec<_>>();
if parts.len() != 2 {
return Err(LabelError::Input(l.to_string()));
} else {
let outpoint = OutPoint::new(
// TODO: deal with the Error types in expect
Txid::from_str(parts[0]).expect("valid txid"),
u32::from_str(parts[1]).expect("valid vout"),
);
let local_output = self.get_utxo(outpoint);
if let Some(mut local_output) = local_output {
local_output.label = Some(label.clone());
};
}
}
}
Ok(())
})
}
}

/// Methods to construct sync/full-scan requests for spk-based chain sources.
Expand Down Expand Up @@ -2386,6 +2477,8 @@ fn new_local_utxo(
confirmation_time: full_txo.chain_position.into(),
keychain,
derivation_index,
#[cfg(feature = "labels")]
label: None,
}
}

Expand Down

0 comments on commit f3a5cbb

Please sign in to comment.