Skip to content

Commit 9ee6616

Browse files
committed
feat(bitcoin): introduce hash-like types
Types like "Txid", "BlockHash", "DescriptorId" are all just 32 byte arrays that represent hashes with different meanings. Currently they are represented as strings at the FFI layer, but they are also meaningful arrays of bytes. Particularly if a user wants to implement persistence over the FFI layer, they would want to efficiently serialize these types. Here I am introducing a new group of types that all implement display, allow serialization to bytes, and may be constructed from an array of bytes. I went with a "rule of 3s" here, and also introduced a macro to do these implementations because there was a lot of boilerplate involved. Note that all of these are included in the wallet changeset, which is required to represent in-full for FFI-layer custom persistence.
1 parent 1d8ee94 commit 9ee6616

File tree

3 files changed

+72
-5
lines changed

3 files changed

+72
-5
lines changed

bdk-ffi/src/bitcoin.rs

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,31 @@
11
use crate::error::{
2-
AddressParseError, ExtractTxError, FeeRateError, FromScriptError, PsbtError, PsbtParseError,
3-
TransactionError,
2+
AddressParseError, ExtractTxError, FeeRateError, FromScriptError, HashParseError, PsbtError,
3+
PsbtParseError, TransactionError,
44
};
55
use crate::error::{ParseAmountError, PsbtFinalizeError};
6-
use crate::{impl_from_core_type, impl_into_core_type};
6+
use crate::{impl_from_core_type, impl_hash_like, impl_into_core_type};
77

88
use bdk_wallet::bitcoin::address::NetworkChecked;
99
use bdk_wallet::bitcoin::address::NetworkUnchecked;
1010
use bdk_wallet::bitcoin::address::{Address as BdkAddress, AddressData as BdkAddressData};
1111
use bdk_wallet::bitcoin::blockdata::block::Header as BdkHeader;
12+
use bdk_wallet::bitcoin::consensus::encode::deserialize;
1213
use bdk_wallet::bitcoin::consensus::encode::serialize;
1314
use bdk_wallet::bitcoin::consensus::Decodable;
15+
use bdk_wallet::bitcoin::hashes::sha256::Hash as BitcoinSha256Hash;
1416
use bdk_wallet::bitcoin::io::Cursor;
1517
use bdk_wallet::bitcoin::secp256k1::Secp256k1;
1618
use bdk_wallet::bitcoin::Amount as BdkAmount;
19+
use bdk_wallet::bitcoin::BlockHash as BitcoinBlockHash;
1720
use bdk_wallet::bitcoin::FeeRate as BdkFeeRate;
1821
use bdk_wallet::bitcoin::Network;
22+
use bdk_wallet::bitcoin::OutPoint as BdkOutPoint;
1923
use bdk_wallet::bitcoin::Psbt as BdkPsbt;
2024
use bdk_wallet::bitcoin::ScriptBuf as BdkScriptBuf;
2125
use bdk_wallet::bitcoin::Transaction as BdkTransaction;
2226
use bdk_wallet::bitcoin::TxIn as BdkTxIn;
2327
use bdk_wallet::bitcoin::TxOut as BdkTxOut;
24-
use bdk_wallet::bitcoin::{OutPoint as BdkOutPoint, Txid};
28+
use bdk_wallet::bitcoin::Txid as BitcoinTxid;
2529
use bdk_wallet::miniscript::psbt::PsbtExt;
2630
use bdk_wallet::serde_json;
2731

@@ -51,7 +55,7 @@ impl From<&BdkOutPoint> for OutPoint {
5155
impl From<OutPoint> for BdkOutPoint {
5256
fn from(outpoint: OutPoint) -> Self {
5357
BdkOutPoint {
54-
txid: Txid::from_str(&outpoint.txid).unwrap(),
58+
txid: BitcoinTxid::from_str(&outpoint.txid).unwrap(),
5559
vout: outpoint.vout,
5660
}
5761
}
@@ -575,6 +579,34 @@ impl From<&BdkTxOut> for TxOut {
575579
}
576580
}
577581

582+
/// A bitcoin Block hash
583+
#[derive(uniffi::Object)]
584+
#[uniffi::export(Display)]
585+
pub struct BlockHash(BitcoinBlockHash);
586+
587+
impl_hash_like!(BlockHash, BitcoinBlockHash);
588+
589+
/// A bitcoin transaction identifier
590+
#[derive(uniffi::Object)]
591+
#[uniffi::export(Display)]
592+
pub struct Txid(BitcoinTxid);
593+
594+
impl_hash_like!(Txid, BitcoinTxid);
595+
596+
/// A collision-proof unique identifier for a descriptor.
597+
#[derive(uniffi::Object)]
598+
#[uniffi::export(Display)]
599+
pub struct DescriptorId(BitcoinSha256Hash);
600+
601+
impl_hash_like!(DescriptorId, BitcoinSha256Hash);
602+
603+
/// The merkle root of the merkle tree corresponding to a block's transactions.
604+
#[derive(uniffi::Object)]
605+
#[uniffi::export(Display)]
606+
pub struct TxMerkleNode(BitcoinSha256Hash);
607+
608+
impl_hash_like!(TxMerkleNode, BitcoinSha256Hash);
609+
578610
#[cfg(test)]
579611
mod tests {
580612
use crate::bitcoin::Address;

bdk-ffi/src/error.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1507,6 +1507,12 @@ impl From<BdkEncodeError> for TransactionError {
15071507
}
15081508
}
15091509

1510+
#[derive(Debug, thiserror::Error, uniffi::Error)]
1511+
pub enum HashParseError {
1512+
#[error("invalid hash: expected length 32 bytes, got {len} bytes")]
1513+
InvalidHash { len: u32 },
1514+
}
1515+
15101516
impl From<BdkSqliteError> for SqliteError {
15111517
fn from(error: BdkSqliteError) -> Self {
15121518
SqliteError::Sqlite {

bdk-ffi/src/macros.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,32 @@ macro_rules! impl_into_core_type {
1919
}
2020
};
2121
}
22+
23+
#[macro_export]
24+
macro_rules! impl_hash_like {
25+
($ffi_type:ident, $core_type:ident) => {
26+
#[uniffi::export]
27+
impl $ffi_type {
28+
/// Construct a hash-like type from 32 bytes.
29+
#[uniffi::constructor]
30+
fn from_bytes(bytes: Vec<u8>) -> Result<Self, HashParseError> {
31+
let hash_like: $core_type = deserialize(&bytes).map_err(|_| {
32+
let len = bytes.len() as u32;
33+
HashParseError::InvalidHash { len }
34+
})?;
35+
Ok(Self(hash_like))
36+
}
37+
38+
/// Serialize this type into a 32 byte array.
39+
fn serialize(&self) -> Vec<u8> {
40+
serialize(&self.0)
41+
}
42+
}
43+
44+
impl std::fmt::Display for $ffi_type {
45+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46+
self.0.fmt(f)
47+
}
48+
}
49+
};
50+
}

0 commit comments

Comments
 (0)