From 3153d05f3fe07d1b5fedca5307f44691a35b7758 Mon Sep 17 00:00:00 2001 From: DanGould Date: Wed, 13 Nov 2024 13:57:42 -0500 Subject: [PATCH 1/3] Bind to uniffi using proc macros Proc macros let rust-analyzer check that types are compatible at rust compile time, and thus let downstream projects more easily depend on bitcoin-ffi. The `Network` enum is defined in the UDL file, but we need to define it in rust with `uniffi::Enum` in order to check UniFFI bindings at rust compile time. "It is permitted to use this [`uniffi::Enum`] macro on a type that is also defined in the UDL file as long as the two definitions are equal in the names and ordering of variants and variant fields" see: https://mozilla.github.io/uniffi-rs/latest/proc_macro/index.html#the-uniffienum-derive --- src/bitcoin.udl | 151 ++---------------------------------------------- src/error.rs | 10 ++-- src/lib.rs | 103 +++++++++++++++++++++++++++------ 3 files changed, 95 insertions(+), 169 deletions(-) diff --git a/src/bitcoin.udl b/src/bitcoin.udl index cb817d4..ba5e9c8 100644 --- a/src/bitcoin.udl +++ b/src/bitcoin.udl @@ -1,152 +1,9 @@ namespace bitcoin {}; -// ------------------------------------------------------------------------ -// Core types -// ------------------------------------------------------------------------ - -interface Script { - constructor(sequence raw_output_script); - - sequence to_bytes(); -}; - -interface Amount { - [Name=from_sat] - constructor(u64 from_sat); - - [Name=from_btc, Throws=ParseAmountError] - constructor(f64 from_btc); - - u64 to_sat(); - - f64 to_btc(); -}; - -interface FeeRate { - [Name=from_sat_per_vb, Throws=FeeRateError] - constructor(u64 sat_per_vb); - - [Name=from_sat_per_kwu] - constructor(u64 sat_per_kwu); - - u64 to_sat_per_vb_ceil(); - - u64 to_sat_per_vb_floor(); - - u64 to_sat_per_kwu(); -}; - -[Custom] -typedef string Txid; - -[Custom] -typedef string BlockHash; - -dictionary OutPoint { - Txid txid; - u32 vout; -}; - -dictionary TxIn { - OutPoint previous_output; - Script script_sig; - u32 sequence; - sequence> witness; -}; - [NonExhaustive] enum Network { - "Bitcoin", - "Testnet", - "Signet", - "Regtest", -}; - -[Traits=(Display)] -interface Address { - [Throws=AddressParseError] - constructor(string address, Network network); - - [Name=from_script, Throws=FromScriptError] - constructor(Script script, Network network); - - Script script_pubkey(); - - string to_qr_uri(); - - boolean is_valid_for_network(Network network); -}; - -dictionary TxOut { - Amount value; - Script script_pubkey; -}; - -interface Transaction { - [Name=deserialize, Throws=EncodeError] - constructor([ByRef] bytes transaction_bytes); - bytes serialize(); - string compute_txid(); - u64 total_size(); - u64 vsize(); - boolean is_coinbase(); - boolean is_explicitly_rbf(); - boolean is_lock_time_enabled(); - i32 version(); - u64 weight(); - sequence input(); - sequence output(); - u32 lock_time(); -}; - -// ------------------------------------------------------------------------ -// Errors -// ------------------------------------------------------------------------ - -[Error] -enum AddressParseError { - "Base58", - "Bech32", - "WitnessVersion", - "WitnessProgram", - "UnknownHrp", - "LegacyAddressTooLong", - "InvalidBase58PayloadLength", - "InvalidLegacyPrefix", - "NetworkValidation", - "OtherAddressParseErr", -}; - -[Error] -interface FromScriptError { - UnrecognizedScript(); - WitnessProgram(string error_message); - WitnessVersion(string error_message); - OtherFromScriptErr(); -}; - -[Error] -interface ParseAmountError { - OutOfRange(); - TooPrecise(); - MissingDigits(); - InputTooLarge(); - InvalidCharacter(string error_message); - OtherParseAmountErr(); -}; - -[Error] -interface FeeRateError { - ArithmeticOverflow(); -}; - -[Error] -interface EncodeError { - Io(); - OversizedVectorAllocation(); - InvalidChecksum(string expected, string actual); - NonMinimalVarInt(); - ParseFailed(); - UnsupportedSegwitFlag(u8 flag); - OtherEncodeErr(); + "Bitcoin", + "Testnet", + "Signet", + "Regtest", }; \ No newline at end of file diff --git a/src/error.rs b/src/error.rs index 3303104..ded9785 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,7 +4,7 @@ use bitcoin::amount::ParseAmountError as BitcoinParseAmountError; use bitcoin::consensus::encode::Error as BitcoinEncodeError; use bitcoin::hex::DisplayHex; -#[derive(Debug, thiserror::Error)] +#[derive(Debug, thiserror::Error, uniffi::Error)] pub enum AddressParseError { #[error("base58 address encoding error")] Base58, @@ -51,13 +51,13 @@ impl From for AddressParseError { } } -#[derive(Debug, thiserror::Error)] +#[derive(Debug, thiserror::Error, uniffi::Error)] pub enum FeeRateError { #[error("arithmetic overflow on feerate")] ArithmeticOverflow, } -#[derive(Debug, thiserror::Error)] +#[derive(Debug, thiserror::Error, uniffi::Error)] pub enum FromScriptError { #[error("script is not a p2pkh, p2sh or witness program")] UnrecognizedScript, @@ -88,7 +88,7 @@ impl From for FromScriptError { } } -#[derive(Debug, thiserror::Error)] +#[derive(Debug, thiserror::Error, uniffi::Error)] pub enum ParseAmountError { #[error("amount out of range")] OutOfRange, @@ -125,7 +125,7 @@ impl From for ParseAmountError { } } -#[derive(Debug, thiserror::Error)] +#[derive(Debug, thiserror::Error, uniffi::Error)] pub enum EncodeError { #[error("io error")] Io, diff --git a/src/lib.rs b/src/lib.rs index b4901b9..3538ca5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,6 @@ use bitcoin::TxIn as BitcoinTxIn; use bitcoin::TxOut as BitcoinTxOut; pub use bitcoin::BlockHash; -pub use bitcoin::OutPoint; pub use bitcoin::Txid; use error::AddressParseError; @@ -26,20 +25,25 @@ use std::sync::Arc; #[macro_use] mod macros; pub mod error; -pub use bitcoin::Network; -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, uniffi::Object)] pub struct Address(BitcoinAddress); +#[uniffi::export] impl Address { + #[uniffi::constructor] pub fn new(address: String, network: Network) -> Result { let parsed_address = BitcoinAddress::from_str(&address).map_err(AddressParseError::from)?; - let network_checked_address = parsed_address.require_network(network)?; + let network_checked_address = parsed_address.require_network(network.into())?; Ok(Address(network_checked_address)) } + #[uniffi::constructor] pub fn from_script(script: Arc