From 45c77246f5b6428d04a8184626cd32208b0005a6 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Mon, 19 Sep 2022 18:46:38 +0200 Subject: [PATCH] Generate UniFFI scaffolding We generate the scaffolding from an UDL file and include it in `lib.rs`. Furthermore, we add a bindings generation shell script for convenience. --- Cargo.toml | 9 +++ build.rs | 3 + src/error.rs | 16 ++++- src/event.rs | 38 +++++++----- src/lib.rs | 123 ++++++++++++++++++++++++++++++++++--- uniffi/ldk_lite.udl | 79 ++++++++++++++++++++++++ uniffi_bindgen_generate.sh | 4 ++ 7 files changed, 244 insertions(+), 28 deletions(-) create mode 100644 build.rs create mode 100644 uniffi/ldk_lite.udl create mode 100644 uniffi_bindgen_generate.sh diff --git a/Cargo.toml b/Cargo.toml index 07814a1ff..7b18df672 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,10 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +crate-type = ["staticlib", "cdylib"] +name = "ldk_lite" + [dependencies] #lightning = { version = "0.0.112", features = ["max_level_trace", "std"] } #lightning-invoice = { version = "0.20" } @@ -46,6 +50,8 @@ chrono = "0.4" futures = "0.3" serde_json = { version = "1.0" } tokio = { version = "1", features = [ "full" ] } +uniffi = { version = "0.21.0", features = ["builtin-bindgen"] } +uniffi_macros = { version = "0.21.0", features = ["builtin-bindgen"] } [dev-dependencies] electrsd = { version = "0.22.0", features = ["legacy", "esplora_a33e97e1", "bitcoind_23_0"] } @@ -53,6 +59,9 @@ electrum-client = "0.12.0" lazy_static = "1.4.0" once_cell = "1.16.0" +[build-dependencies] +uniffi_build = "0.21.0" + [profile.release] panic = "abort" diff --git a/build.rs b/build.rs new file mode 100644 index 000000000..c829aff23 --- /dev/null +++ b/build.rs @@ -0,0 +1,3 @@ +fn main() { + uniffi_build::generate_scaffolding("uniffi/ldk_lite.udl").unwrap(); +} diff --git a/src/error.rs b/src/error.rs index 3ec8e4da8..53766549d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -11,12 +11,20 @@ pub enum Error { FundingTxCreationFailed, /// A network connection has been closed. ConnectionFailed, + /// The given address is invalid. + AddressInvalid, + /// The given public key is invalid. + PublicKeyInvalid, + /// The given payment hash is invalid. + PaymentHashInvalid, /// Payment of the given invoice has already been intiated. NonUniquePaymentHash, /// The given invoice is invalid. InvoiceInvalid, /// Invoice creation failed. InvoiceCreationFailed, + /// The given channel ID is invalid. + ChannelIdInvalid, /// No route for the given target could be found. RoutingFailed, /// A given peer info could not be parsed. @@ -40,13 +48,15 @@ impl fmt::Display for Error { match *self { Self::AlreadyRunning => write!(f, "LDKLite is already running."), Self::NotRunning => write!(f, "LDKLite is not running."), - Self::FundingTxCreationFailed => { - write!(f, "Funding transaction could not be created.") - } + Self::FundingTxCreationFailed => write!(f, "Funding transaction could not be created."), Self::ConnectionFailed => write!(f, "Network connection closed."), + Self::AddressInvalid => write!(f, "The given address is invalid."), + Self::PublicKeyInvalid => write!(f, "The given public key is invalid."), + Self::PaymentHashInvalid => write!(f, "The given payment hash is invalid."), Self::NonUniquePaymentHash => write!(f, "An invoice must not get payed twice."), Self::InvoiceInvalid => write!(f, "The given invoice is invalid."), Self::InvoiceCreationFailed => write!(f, "Failed to create invoice."), + Self::ChannelIdInvalid => write!(f, "The given channel ID is invalid."), Self::RoutingFailed => write!(f, "Failed to find route."), Self::PeerInfoParseFailed => write!(f, "Failed to parse the given peer information."), Self::ChannelCreationFailed => write!(f, "Failed to create channel."), diff --git a/src/event.rs b/src/event.rs index 11e79f19c..fdf2f78ba 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1,6 +1,6 @@ use crate::{ - hex_utils, ChannelManager, Config, Error, NetworkGraph, PaymentInfo, PaymentInfoStorage, - PaymentStatus, Wallet, + hex_utils, ChannelId, ChannelManager, Config, Error, NetworkGraph, PaymentInfo, + PaymentInfoStorage, PaymentStatus, UserChannelId, Wallet, }; use crate::logger::{log_error, log_given_level, log_info, log_internal, Logger}; @@ -50,16 +50,16 @@ pub enum Event { /// A channel is ready to be used. ChannelReady { /// The `channel_id` of the channel. - channel_id: [u8; 32], + channel_id: ChannelId, /// The `user_channel_id` of the channel. - user_channel_id: u128, + user_channel_id: UserChannelId, }, /// A channel has been closed. ChannelClosed { /// The `channel_id` of the channel. - channel_id: [u8; 32], + channel_id: ChannelId, /// The `user_channel_id` of the channel. - user_channel_id: u128, + user_channel_id: UserChannelId, }, } @@ -83,13 +83,13 @@ impl Readable for Event { Ok(Self::PaymentReceived { payment_hash, amount_msat }) } 3u8 => { - let channel_id: [u8; 32] = Readable::read(reader)?; - let user_channel_id: u128 = Readable::read(reader)?; + let channel_id = ChannelId(Readable::read(reader)?); + let user_channel_id = UserChannelId(Readable::read(reader)?); Ok(Self::ChannelReady { channel_id, user_channel_id }) } 4u8 => { - let channel_id: [u8; 32] = Readable::read(reader)?; - let user_channel_id: u128 = Readable::read(reader)?; + let channel_id = ChannelId(Readable::read(reader)?); + let user_channel_id = UserChannelId(Readable::read(reader)?); Ok(Self::ChannelClosed { channel_id, user_channel_id }) } _ => Err(lightning::ln::msgs::DecodeError::InvalidValue), @@ -118,14 +118,14 @@ impl Writeable for Event { } Self::ChannelReady { channel_id, user_channel_id } => { 3u8.write(writer)?; - channel_id.write(writer)?; - user_channel_id.write(writer)?; + channel_id.0.write(writer)?; + user_channel_id.0.write(writer)?; Ok(()) } Self::ChannelClosed { channel_id, user_channel_id } => { 4u8.write(writer)?; - channel_id.write(writer)?; - user_channel_id.write(writer)?; + channel_id.0.write(writer)?; + user_channel_id.0.write(writer)?; Ok(()) } } @@ -559,7 +559,10 @@ where counterparty_node_id, ); self.event_queue - .add_event(Event::ChannelReady { channel_id, user_channel_id }) + .add_event(Event::ChannelReady { + channel_id: ChannelId(channel_id), + user_channel_id: UserChannelId(user_channel_id), + }) .expect("Failed to push to event queue"); } LdkEvent::ChannelClosed { channel_id, reason, user_channel_id } => { @@ -570,7 +573,10 @@ where reason ); self.event_queue - .add_event(Event::ChannelClosed { channel_id, user_channel_id }) + .add_event(Event::ChannelClosed { + channel_id: ChannelId(channel_id), + user_channel_id: UserChannelId(user_channel_id), + }) .expect("Failed to push to event queue"); } LdkEvent::DiscardFunding { .. } => {} diff --git a/src/lib.rs b/src/lib.rs index df40f315a..13aebd94a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,7 +17,6 @@ //! - Wallet and channel states are persisted to disk. //! - Gossip is retrieved over the P2P network. -#![deny(missing_docs)] #![deny(broken_intra_doc_links)] #![deny(private_intra_doc_links)] #![allow(bare_trait_objects)] @@ -67,7 +66,7 @@ use lightning_transaction_sync::EsploraSyncClient; use lightning_net_tokio::SocketDescriptor; use lightning::routing::router::DefaultRouter; -use lightning_invoice::{payment, Currency, Invoice}; +use lightning_invoice::{payment, Currency, Invoice, SignedRawInvoice}; use bdk::bitcoin::secp256k1::Secp256k1; use bdk::blockchain::esplora::EsploraBlockchain; @@ -77,10 +76,11 @@ use bdk::template::Bip84; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::Hash; use bitcoin::secp256k1::PublicKey; -use bitcoin::BlockHash; +use bitcoin::{Address, BlockHash}; use rand::Rng; +use core::str::FromStr; use std::collections::HashMap; use std::convert::{TryFrom, TryInto}; use std::default::Default; @@ -90,6 +90,8 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex, RwLock}; use std::time::{Duration, Instant, SystemTime}; +uniffi_macros::include_scaffolding!("ldk_lite"); + // The used 'stop gap' parameter used by BDK's wallet sync. This seems to configure the threshold // number of blocks after which BDK stops looking for scripts belonging to the wallet. const BDK_CLIENT_STOP_GAP: usize = 20; @@ -197,7 +199,7 @@ impl Builder { } /// Builds an [`LdkLite`] instance according to the options previously configured. - pub fn build(&self) -> LdkLite { + pub fn build(&self) -> Arc { let config = Arc::new(self.config.clone()); let ldk_data_dir = format!("{}/ldk", &config.storage_dir_path.clone()); @@ -426,7 +428,7 @@ impl Builder { let running = RwLock::new(None); - LdkLite { + Arc::new(LdkLite { running, config, wallet, @@ -445,7 +447,7 @@ impl Builder { inbound_payments, outbound_payments, peer_store, - } + }) } } @@ -487,7 +489,7 @@ impl LdkLite { /// Starts the necessary background tasks, such as handling events coming from user input, /// LDK/BDK, and the peer-to-peer network. After this returns, the [`LdkLite`] instance can be /// controlled via the provided API methods in a thread-safe manner. - pub fn start(&mut self) -> Result<(), Error> { + pub fn start(&self) -> Result<(), Error> { // Acquire a run lock and hold it until we're setup. let mut run_lock = self.running.write().unwrap(); if run_lock.is_some() { @@ -501,7 +503,7 @@ impl LdkLite { } /// Disconnects all peers, stops all running background tasks, and shuts down [`LdkLite`]. - pub fn stop(&mut self) -> Result<(), Error> { + pub fn stop(&self) -> Result<(), Error> { let mut run_lock = self.running.write().unwrap(); if run_lock.is_none() { return Err(Error::NotRunning); @@ -695,7 +697,7 @@ impl LdkLite { } /// Retrieve a new on-chain/funding address. - pub fn new_funding_address(&mut self) -> Result { + pub fn new_funding_address(&self) -> Result { let funding_address = self.wallet.get_new_address()?; log_info!(self.logger, "Generated new funding address: {}", funding_address); Ok(funding_address) @@ -1086,3 +1088,106 @@ pub(crate) type NetworkGraph = gossip::NetworkGraph>; pub(crate) type PaymentInfoStorage = Mutex>; pub(crate) type OnionMessenger = SimpleArcOnionMessenger; + +impl UniffiCustomTypeConverter for PublicKey { + type Builtin = String; + + fn into_custom(val: Self::Builtin) -> uniffi::Result { + if let Ok(key) = PublicKey::from_str(&val) { + return Ok(key); + } + + Err(Error::PublicKeyInvalid.into()) + } + + fn from_custom(obj: Self) -> Self::Builtin { + obj.to_string() + } +} + +impl UniffiCustomTypeConverter for Address { + type Builtin = String; + + fn into_custom(val: Self::Builtin) -> uniffi::Result { + if let Ok(addr) = Address::from_str(&val) { + return Ok(addr); + } + + Err(Error::AddressInvalid.into()) + } + + fn from_custom(obj: Self) -> Self::Builtin { + obj.to_string() + } +} + +impl UniffiCustomTypeConverter for Invoice { + type Builtin = String; + + fn into_custom(val: Self::Builtin) -> uniffi::Result { + if let Ok(signed) = val.parse::() { + if let Ok(invoice) = Invoice::from_signed(signed) { + return Ok(invoice); + } + } + + Err(Error::InvoiceInvalid.into()) + } + + fn from_custom(obj: Self) -> Self::Builtin { + obj.to_string() + } +} + +impl UniffiCustomTypeConverter for PaymentHash { + type Builtin = String; + + fn into_custom(val: Self::Builtin) -> uniffi::Result { + if let Ok(hash) = Sha256::from_str(&val) { + Ok(PaymentHash(hash.into_inner())) + } else { + Err(Error::PaymentHashInvalid.into()) + } + } + + fn from_custom(obj: Self) -> Self::Builtin { + Sha256::from_slice(&obj.0).unwrap().to_string() + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct ChannelId([u8; 32]); + +impl UniffiCustomTypeConverter for ChannelId { + type Builtin = String; + + fn into_custom(val: Self::Builtin) -> uniffi::Result { + if let Some(hex_vec) = hex_utils::to_vec(&val) { + if hex_vec.len() == 32 { + let mut channel_id = [0u8; 32]; + channel_id.copy_from_slice(&hex_vec[..]); + return Ok(Self(channel_id)); + } + } + Err(Error::ChannelIdInvalid.into()) + } + + fn from_custom(obj: Self) -> Self::Builtin { + hex_utils::to_string(&obj.0) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct UserChannelId(u128); + +impl UniffiCustomTypeConverter for UserChannelId { + type Builtin = String; + + fn into_custom(val: Self::Builtin) -> uniffi::Result { + Ok(UserChannelId(u128::from_str(&val).map_err(|_| Error::ChannelIdInvalid)?)) + } + + fn from_custom(obj: Self) -> Self::Builtin { + obj.0.to_string() + } +} diff --git a/uniffi/ldk_lite.udl b/uniffi/ldk_lite.udl new file mode 100644 index 000000000..dbc526456 --- /dev/null +++ b/uniffi/ldk_lite.udl @@ -0,0 +1,79 @@ +namespace ldk_lite { +}; + +interface Builder { + constructor(); + LdkLite build(); +}; + +interface LdkLite { + [Throws=Error] + void start(); + [Throws=Error] + void stop(); + Event next_event(); + void event_handled(); + [Throws=Error] + PublicKey node_id(); + [Throws=Error] + Address new_funding_address(); + [Throws=Error] + void connect_open_channel([ByRef]string node_pubkey_and_address, u64 channel_amount_sats, boolean announce_channel); + [Throws=Error] + PaymentHash send_payment(Invoice invoice); + [Throws=Error] + PaymentHash send_spontaneous_payment(u64 amount_msat, [ByRef]string node_id); + [Throws=Error] + Invoice receive_payment(u64? amount_msat, [ByRef]string description, u32 expiry_secs); + // TODO: payment_info() +}; + +[Error] +enum Error { + "AlreadyRunning", + "NotRunning", + "FundingTxCreationFailed", + "ConnectionFailed", + "AddressInvalid", + "PublicKeyInvalid", + "PaymentHashInvalid", + "NonUniquePaymentHash", + "InvoiceInvalid", + "InvoiceCreationFailed", + "ChannelIdInvalid", + "RoutingFailed", + "PeerInfoParseFailed", + "ChannelCreationFailed", + "ChannelClosingFailed", + "PersistenceFailed", + "WalletOperationFailed", + "WalletSigningFailed", + "TxSyncFailed", +}; + +[Enum] +interface Event { + PaymentSuccessful( PaymentHash payment_hash ); + PaymentFailed( PaymentHash payment_hash ); + PaymentReceived( PaymentHash payment_hash, u64 amount_msat); + ChannelReady ( ChannelId channel_id, UserChannelId user_channel_id ); + ChannelClosed ( ChannelId channel_id, UserChannelId user_channel_id ); +}; + +[Custom] +typedef string PublicKey; + +[Custom] +typedef string Address; + +[Custom] +typedef string Invoice; + +[Custom] +typedef string PaymentHash; + +[Custom] +typedef string ChannelId; + +[Custom] +typedef string UserChannelId; diff --git a/uniffi_bindgen_generate.sh b/uniffi_bindgen_generate.sh new file mode 100644 index 000000000..9a8650a03 --- /dev/null +++ b/uniffi_bindgen_generate.sh @@ -0,0 +1,4 @@ +#!/bin/bash +uniffi-bindgen generate uniffi/ldk_lite.udl --language swift +uniffi-bindgen generate uniffi/ldk_lite.udl --language python +uniffi-bindgen generate uniffi/ldk_lite.udl --language kotlin