From 45f5e27552d075b4b44afe7400e1ba936e157ffc Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Tue, 9 May 2023 14:14:51 +0200 Subject: [PATCH 1/5] Break builder pattern and expose setters via bindings Unfortunately there is no good way of exposing the builder pattern via Uniffi bindings currenlty. We therefore resort to internal mutability and using setters on the `Builder` object. --- README.md | 9 ++-- bindings/ldk_node.udl | 13 +++++ src/error.rs | 3 ++ src/lib.rs | 123 ++++++++++++++++++++++-------------------- src/types.rs | 16 +++++- 5 files changed, 99 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index 5f978410a..ae67af6b8 100644 --- a/README.md +++ b/README.md @@ -11,14 +11,15 @@ The primary abstraction of the library is the `Node`, which can be retrieved by use ldk_node::{Builder, NetAddress}; use ldk_node::lightning_invoice::Invoice; use ldk_node::bitcoin::secp256k1::PublicKey; +use ldk_node::bitcoin::Network; use std::str::FromStr; fn main() { - let node = Builder::new() - .set_network("testnet") - .set_esplora_server_url("https://blockstream.info/testnet/api".to_string()) - .build(); + let mut builder = Builder::new(); + builder.set_network(Network::Testnet); + builder.set_esplora_server_url("https://blockstream.info/testnet/api".to_string()); + let node = builder.build(); node.start().unwrap(); let _funding_address = node.new_funding_address(); diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 4162e9f15..312ffab77 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -13,6 +13,15 @@ interface Builder { constructor(); [Name=from_config] constructor(Config config); + void set_entropy_seed_path(string seed_path); + void set_entropy_seed_bytes(sequence seed_bytes); + void set_entropy_bip39_mnemonic(Mnemonic mnemonic, string? passphrase); + void set_gossip_source_p2p(); + void set_gossip_source_rgs(string rgs_server_url); + void set_storage_dir_path(string storage_dir_path); + void set_esplora_server_url(string esplora_server_url); + void set_network(Network network); + void set_listening_address(NetAddress listening_address); LDKNode build(); }; @@ -85,6 +94,7 @@ enum NodeError { "InvalidAddress", "InvalidNetAddress", "InvalidPublicKey", + "InvalidSecretKey", "InvalidPaymentHash", "InvalidPaymentPreimage", "InvalidPaymentSecret", @@ -188,3 +198,6 @@ typedef string UserChannelId; [Custom] typedef string Network; + +[Custom] +typedef string Mnemonic; diff --git a/src/error.rs b/src/error.rs index 1d59fc01d..4d95535a7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -37,6 +37,8 @@ pub enum Error { InvalidNetAddress, /// The given public key is invalid. InvalidPublicKey, + /// The given secret key is invalid. + InvalidSecretKey, /// The given payment hash is invalid. InvalidPaymentHash, /// The given payment preimage is invalid. @@ -79,6 +81,7 @@ impl fmt::Display for Error { Self::InvalidAddress => write!(f, "The given address is invalid."), Self::InvalidNetAddress => write!(f, "The given network address is invalid."), Self::InvalidPublicKey => write!(f, "The given public key is invalid."), + Self::InvalidSecretKey => write!(f, "The given secret key is invalid."), Self::InvalidPaymentHash => write!(f, "The given payment hash is invalid."), Self::InvalidPaymentPreimage => write!(f, "The given payment preimage is invalid."), Self::InvalidPaymentSecret => write!(f, "The given payment secret is invalid."), diff --git a/src/lib.rs b/src/lib.rs index 086701371..a17f44735 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,14 +29,15 @@ //! use ldk_node::{Builder, NetAddress}; //! use ldk_node::lightning_invoice::Invoice; //! use ldk_node::bitcoin::secp256k1::PublicKey; +//! use ldk_node::bitcoin::Network; //! use std::str::FromStr; //! //! fn main() { -//! let node = Builder::new() -//! .set_network("testnet") -//! .set_esplora_server_url("https://blockstream.info/testnet/api".to_string()) -//! .build(); +//! let mut builder = Builder::new(); +//! builder.set_network(Network::Testnet); +//! builder.set_esplora_server_url("https://blockstream.info/testnet/api".to_string()); //! +//! let node = builder.build(); //! node.start().unwrap(); //! //! let _funding_address = node.new_funding_address(); @@ -144,6 +145,8 @@ use bitcoin::hashes::Hash; use bitcoin::secp256k1::PublicKey; use bitcoin::Network; +use bip39::Mnemonic; + use bitcoin::{Address, BlockHash, OutPoint, Txid}; use rand::Rng; @@ -152,7 +155,6 @@ use std::convert::TryInto; use std::default::Default; use std::fs; use std::net::ToSocketAddrs; -use std::str::FromStr; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex, RwLock}; use std::time::{Duration, Instant, SystemTime}; @@ -212,7 +214,7 @@ impl Default for Config { enum EntropySourceConfig { SeedFile(String), SeedBytes([u8; WALLET_KEYS_SEED_LEN]), - Bip39Mnemonic { mnemonic: bip39::Mnemonic, passphrase: Option }, + Bip39Mnemonic { mnemonic: Mnemonic, passphrase: Option }, } #[derive(Debug, Clone)] @@ -223,26 +225,27 @@ enum GossipSourceConfig { /// A builder for an [`Node`] instance, allowing to set some configuration and module choices from /// the getgo. -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct Builder { - config: Config, - entropy_source_config: Option, - gossip_source_config: Option, + config: RwLock, + entropy_source_config: RwLock>, + gossip_source_config: RwLock>, } impl Builder { /// Creates a new builder instance with the default configuration. pub fn new() -> Self { - let config = Config::default(); - let entropy_source_config = None; - let gossip_source_config = None; + let config = RwLock::new(Config::default()); + let entropy_source_config = RwLock::new(None); + let gossip_source_config = RwLock::new(None); Self { config, entropy_source_config, gossip_source_config } } /// Creates a new builder instance from an [`Config`]. pub fn from_config(config: Config) -> Self { - let entropy_source_config = None; - let gossip_source_config = None; + let config = RwLock::new(config); + let entropy_source_config = RwLock::new(None); + let gossip_source_config = RwLock::new(None); Self { config, entropy_source_config, gossip_source_config } } @@ -250,80 +253,79 @@ impl Builder { /// /// If the given file does not exist a new random seed file will be generated and /// stored at the given location. - pub fn set_entropy_seed_path(&mut self, seed_path: String) -> &mut Self { - self.entropy_source_config = Some(EntropySourceConfig::SeedFile(seed_path)); - self + pub fn set_entropy_seed_path(&self, seed_path: String) { + *self.entropy_source_config.write().unwrap() = + Some(EntropySourceConfig::SeedFile(seed_path)); + } + + /// Configures the [`Node`] instance to source its wallet entropy from the given 64 seed bytes. + /// + /// **Note:** Panics if the length of the given `seed_bytes` differs from 64. + pub fn set_entropy_seed_bytes(&self, seed_bytes: Vec) { + if seed_bytes.len() != WALLET_KEYS_SEED_LEN { + panic!("Failed to set seed due to invalid length."); + } + let mut bytes = [0u8; WALLET_KEYS_SEED_LEN]; + bytes.copy_from_slice(&seed_bytes); + *self.entropy_source_config.write().unwrap() = Some(EntropySourceConfig::SeedBytes(bytes)); } - /// Configures the [`Node`] instance to source its wallet entropy from the given seed bytes. - pub fn set_entropy_seed_bytes(&mut self, seed_bytes: [u8; WALLET_KEYS_SEED_LEN]) -> &mut Self { - self.entropy_source_config = Some(EntropySourceConfig::SeedBytes(seed_bytes)); - self + /// Configures the [`Node`] instance to source its wallet entropy from a [BIP 39] mnemonic. + /// + /// [BIP 39]: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki + pub fn set_entropy_bip39_mnemonic(&self, mnemonic: Mnemonic, passphrase: Option) { + *self.entropy_source_config.write().unwrap() = + Some(EntropySourceConfig::Bip39Mnemonic { mnemonic, passphrase }); } /// Configures the [`Node`] instance to source its gossip data from the Lightning peer-to-peer /// network. - pub fn set_gossip_source_p2p(&mut self) -> &mut Self { - self.gossip_source_config = Some(GossipSourceConfig::P2PNetwork); - self + pub fn set_gossip_source_p2p(&self) { + *self.gossip_source_config.write().unwrap() = Some(GossipSourceConfig::P2PNetwork); } /// Configures the [`Node`] instance to source its gossip data from the given RapidGossipSync /// server. - pub fn set_gossip_source_rgs(&mut self, rgs_server_url: String) -> &mut Self { - self.gossip_source_config = Some(GossipSourceConfig::RapidGossipSync(rgs_server_url)); - self - } - - /// Configures the [`Node`] instance to source its wallet entropy from a [BIP 39] mnemonic. - /// - /// [BIP 39]: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki - pub fn set_entropy_bip39_mnemonic( - &mut self, mnemonic: bip39::Mnemonic, passphrase: Option, - ) -> &mut Self { - self.entropy_source_config = - Some(EntropySourceConfig::Bip39Mnemonic { mnemonic, passphrase }); - self + pub fn set_gossip_source_rgs(&self, rgs_server_url: String) { + *self.gossip_source_config.write().unwrap() = + Some(GossipSourceConfig::RapidGossipSync(rgs_server_url)); } /// Sets the used storage directory path. /// /// Default: `/tmp/ldk_node/` - pub fn set_storage_dir_path(&mut self, storage_dir_path: String) -> &mut Self { - self.config.storage_dir_path = storage_dir_path; - self + pub fn set_storage_dir_path(&self, storage_dir_path: String) { + let mut config = self.config.write().unwrap(); + config.storage_dir_path = storage_dir_path; } /// Sets the Esplora server URL. /// /// Default: `https://blockstream.info/api` - pub fn set_esplora_server_url(&mut self, esplora_server_url: String) -> &mut Self { - self.config.esplora_server_url = esplora_server_url; - self + pub fn set_esplora_server_url(&self, esplora_server_url: String) { + let mut config = self.config.write().unwrap(); + config.esplora_server_url = esplora_server_url; } /// Sets the Bitcoin network used. - /// - /// Options: `mainnet`/`bitcoin`, `testnet`, `regtest`, `signet` - /// - /// Default: `regtest` - pub fn set_network(&mut self, network: &str) -> &mut Self { - self.config.network = Network::from_str(network).unwrap_or(Network::Regtest); - self + pub fn set_network(&self, network: Network) { + let mut config = self.config.write().unwrap(); + config.network = network; } /// Sets the IP address and TCP port on which [`Node`] will listen for incoming network connections. /// /// Default: `0.0.0.0:9735` - pub fn set_listening_address(&mut self, listening_address: NetAddress) -> &mut Self { - self.config.listening_address = Some(listening_address); - self + pub fn set_listening_address(&self, listening_address: NetAddress) { + let mut config = self.config.write().unwrap(); + config.listening_address = Some(listening_address); } /// Builds a [`Node`] instance with a [`FilesystemStore`] backend and according to the options /// previously configured. pub fn build(&self) -> Arc> { - let ldk_data_dir = format!("{}/ldk", self.config.storage_dir_path); + let config = self.config.read().unwrap(); + let ldk_data_dir = format!("{}/ldk", config.storage_dir_path); let kv_store = Arc::new(FilesystemStore::new(ldk_data_dir.clone().into())); self.build_with_store(kv_store) } @@ -332,7 +334,7 @@ impl Builder { pub fn build_with_store( &self, kv_store: Arc, ) -> Arc> { - let config = Arc::new(self.config.clone()); + let config = Arc::new(self.config.read().unwrap().clone()); let ldk_data_dir = format!("{}/ldk", config.storage_dir_path); fs::create_dir_all(ldk_data_dir.clone()).expect("Failed to create LDK data directory"); @@ -345,7 +347,9 @@ impl Builder { let logger = Arc::new(FilesystemLogger::new(log_file_path)); // Initialize the on-chain wallet and chain access - let seed_bytes = if let Some(entropy_source_config) = &self.entropy_source_config { + let seed_bytes = if let Some(entropy_source_config) = + &*self.entropy_source_config.read().unwrap() + { // Use the configured entropy source, if the user set one. match entropy_source_config { EntropySourceConfig::SeedBytes(bytes) => bytes.clone(), @@ -550,8 +554,9 @@ impl Builder { // Initialize the GossipSource // Use the configured gossip source, if the user set one, otherwise default to P2PNetwork. + let gossip_source_config_lock = self.gossip_source_config.read().unwrap(); let gossip_source_config = - self.gossip_source_config.as_ref().unwrap_or(&GossipSourceConfig::P2PNetwork); + gossip_source_config_lock.as_ref().unwrap_or(&GossipSourceConfig::P2PNetwork); let gossip_source = match gossip_source_config { GossipSourceConfig::P2PNetwork => { diff --git a/src/types.rs b/src/types.rs index 92d957246..52067cee3 100644 --- a/src/types.rs +++ b/src/types.rs @@ -24,8 +24,9 @@ use bitcoin::hashes::Hash; use bitcoin::secp256k1::PublicKey; use bitcoin::{Address, Network, OutPoint, Txid}; -use core::convert::TryFrom; -use std::convert::TryInto; +use bip39::Mnemonic; + +use std::convert::{TryFrom, TryInto}; use std::fmt::Display; use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs}; use std::str::FromStr; @@ -436,6 +437,17 @@ impl Display for NetAddress { } } +impl UniffiCustomTypeConverter for Mnemonic { + type Builtin = String; + fn into_custom(val: Self::Builtin) -> uniffi::Result { + Ok(Mnemonic::from_str(&val).map_err(|_| Error::InvalidSecretKey)?) + } + + fn from_custom(obj: Self) -> Self::Builtin { + obj.to_string() + } +} + impl UniffiCustomTypeConverter for NetAddress { type Builtin = String; fn into_custom(val: Self::Builtin) -> uniffi::Result { From 704e2db54b3f65319c383c61345cf3d293a45a3b Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Wed, 10 May 2023 10:09:00 +0200 Subject: [PATCH 2/5] Expose `Network` as enum type rather than via `string` --- .../org/lightningdevkit/ldknode/LibraryTest.kt | 3 +-- bindings/ldk_node.udl | 10 +++++++--- src/types.rs | 14 +------------- 3 files changed, 9 insertions(+), 18 deletions(-) diff --git a/bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt b/bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt index 433411870..b004b0da4 100644 --- a/bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt +++ b/bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt @@ -61,8 +61,7 @@ class LibraryTest { @Test fun fullCycle() { setup() - val network: Network = "regtest" - assertEquals(network, "regtest") + val network = Network.REGTEST val tmpDir1 = createTempDirectory("ldk_node").toString() println("Random dir 1: $tmpDir1") diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 312ffab77..e77916fec 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -127,6 +127,13 @@ enum PaymentStatus { "Failed", }; +enum Network { + "Bitcoin", + "Testnet", + "Signet", + "Regtest", +}; + dictionary PaymentDetails { PaymentHash hash; PaymentPreimage? preimage; @@ -196,8 +203,5 @@ typedef string ChannelId; [Custom] typedef string UserChannelId; -[Custom] -typedef string Network; - [Custom] typedef string Mnemonic; diff --git a/src/types.rs b/src/types.rs index 52067cee3..79e4b6ba6 100644 --- a/src/types.rs +++ b/src/types.rs @@ -22,7 +22,7 @@ use lightning_transaction_sync::EsploraSyncClient; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::Hash; use bitcoin::secp256k1::PublicKey; -use bitcoin::{Address, Network, OutPoint, Txid}; +use bitcoin::{Address, OutPoint, Txid}; use bip39::Mnemonic; @@ -270,18 +270,6 @@ impl Readable for UserChannelId { } } -impl UniffiCustomTypeConverter for Network { - type Builtin = String; - - fn into_custom(val: Self::Builtin) -> uniffi::Result { - Ok(Network::from_str(&val).map_err(|_| Error::InvalidNetwork)?) - } - - fn from_custom(obj: Self) -> Self::Builtin { - obj.to_string() - } -} - impl UniffiCustomTypeConverter for Txid { type Builtin = String; fn into_custom(val: Self::Builtin) -> uniffi::Result { From 4b52e2fd687edb75603ff0960ee7b27368d1fdf3 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Thu, 18 May 2023 10:46:28 +0200 Subject: [PATCH 3/5] Move esplora config to `ChainDataSourceConfig` After we already have `EntropySourceConfig` and `GossipSourceConfig`, we here move the esplora server URL to a corresponding `enum ChainDataSourceConfig`, which will us to add further variants (e.g., `Electrum`) in the future without breaking the API. Additionally, we document the set default values. --- bindings/ldk_node.udl | 3 +- src/lib.rs | 122 +++++++++++++++++++++-------------- src/test/functional_tests.rs | 70 ++++++++++++-------- src/test/utils.rs | 8 +-- 4 files changed, 124 insertions(+), 79 deletions(-) diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index e77916fec..9ceae441b 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -3,7 +3,6 @@ namespace ldk_node { dictionary Config { string storage_dir_path; - string esplora_server_url; Network network; NetAddress? listening_address; u32 default_cltv_expiry_delta; @@ -16,10 +15,10 @@ interface Builder { void set_entropy_seed_path(string seed_path); void set_entropy_seed_bytes(sequence seed_bytes); void set_entropy_bip39_mnemonic(Mnemonic mnemonic, string? passphrase); + void set_esplora_server(string esplora_server_url); void set_gossip_source_p2p(); void set_gossip_source_rgs(string rgs_server_url); void set_storage_dir_path(string storage_dir_path); - void set_esplora_server_url(string esplora_server_url); void set_network(Network network); void set_listening_address(NetAddress listening_address); LDKNode build(); diff --git a/src/lib.rs b/src/lib.rs index a17f44735..5aedd91c3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,7 +35,7 @@ //! fn main() { //! let mut builder = Builder::new(); //! builder.set_network(Network::Testnet); -//! builder.set_esplora_server_url("https://blockstream.info/testnet/api".to_string()); +//! builder.set_esplora_server("https://blockstream.info/testnet/api".to_string()); //! //! let node = builder.build(); //! node.start().unwrap(); @@ -161,6 +161,13 @@ use std::time::{Duration, Instant, SystemTime}; uniffi::include_scaffolding!("ldk_node"); +// Config defaults +const DEFAULT_STORAGE_DIR_PATH: &str = "/tmp/ldk_node/"; +const DEFAULT_NETWORK: Network = Network::Regtest; +const DEFAULT_LISTENING_ADDR: &str = "0.0.0.0:9735"; +const DEFAULT_CLTV_EXPIRY_DELTA: u32 = 144; +const DEFAULT_ESPLORA_SERVER_URL: &str = "https://blockstream.info/api"; + // The '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; @@ -185,11 +192,19 @@ const WALLET_KEYS_SEED_LEN: usize = 64; #[derive(Debug, Clone)] /// Represents the configuration of an [`Node`] instance. +/// +/// ### Defaults +/// +/// | Parameter | Value | +/// |-----------------------------|------------------| +/// | `storage_dir_path` | /tmp/ldk_node/ | +/// | `network` | Network::Regtest | +/// | `listening_address` | 0.0.0.0:9735 | +/// | `default_cltv_expiry_delta` | 144 | +/// pub struct Config { /// The path where the underlying LDK and BDK persist their data. pub storage_dir_path: String, - /// The URL of the utilized Esplora server. - pub esplora_server_url: String, /// The used Bitcoin network. pub network: Network, /// The IP address and TCP port the node will listen on. @@ -201,15 +216,19 @@ pub struct Config { impl Default for Config { fn default() -> Self { Self { - storage_dir_path: "/tmp/ldk_node/".to_string(), - esplora_server_url: "http://localhost:3002".to_string(), - network: Network::Regtest, - listening_address: Some("0.0.0.0:9735".parse().unwrap()), - default_cltv_expiry_delta: 144, + storage_dir_path: DEFAULT_STORAGE_DIR_PATH.to_string(), + network: DEFAULT_NETWORK, + listening_address: Some(DEFAULT_LISTENING_ADDR.parse().unwrap()), + default_cltv_expiry_delta: DEFAULT_CLTV_EXPIRY_DELTA, } } } +#[derive(Debug, Clone)] +enum ChainDataSourceConfig { + Esplora(String), +} + #[derive(Debug, Clone)] enum EntropySourceConfig { SeedFile(String), @@ -225,10 +244,16 @@ enum GossipSourceConfig { /// A builder for an [`Node`] instance, allowing to set some configuration and module choices from /// the getgo. +/// +/// ### Defaults +/// - Wallet entropy is sourced from a `keys_seed` file located under [`Config::storage_dir_path`] +/// - Chain data is sourced from the Esplora endpoint `https://blockstream.info/api` +/// - Gossip data is sourced via the peer-to-peer network #[derive(Debug)] pub struct Builder { config: RwLock, entropy_source_config: RwLock>, + chain_data_source_config: RwLock>, gossip_source_config: RwLock>, } @@ -237,16 +262,18 @@ impl Builder { pub fn new() -> Self { let config = RwLock::new(Config::default()); let entropy_source_config = RwLock::new(None); + let chain_data_source_config = RwLock::new(None); let gossip_source_config = RwLock::new(None); - Self { config, entropy_source_config, gossip_source_config } + Self { config, entropy_source_config, chain_data_source_config, gossip_source_config } } /// Creates a new builder instance from an [`Config`]. pub fn from_config(config: Config) -> Self { let config = RwLock::new(config); let entropy_source_config = RwLock::new(None); + let chain_data_source_config = RwLock::new(None); let gossip_source_config = RwLock::new(None); - Self { config, entropy_source_config, gossip_source_config } + Self { config, entropy_source_config, chain_data_source_config, gossip_source_config } } /// Configures the [`Node`] instance to source its wallet entropy from a seed file on disk. @@ -278,6 +305,12 @@ impl Builder { Some(EntropySourceConfig::Bip39Mnemonic { mnemonic, passphrase }); } + /// Configures the [`Node`] instance to source its chain data from the given Esplora server. + pub fn set_esplora_server(&self, esplora_server_url: String) { + *self.chain_data_source_config.write().unwrap() = + Some(ChainDataSourceConfig::Esplora(esplora_server_url)); + } + /// Configures the [`Node`] instance to source its gossip data from the Lightning peer-to-peer /// network. pub fn set_gossip_source_p2p(&self) { @@ -292,21 +325,11 @@ impl Builder { } /// Sets the used storage directory path. - /// - /// Default: `/tmp/ldk_node/` pub fn set_storage_dir_path(&self, storage_dir_path: String) { let mut config = self.config.write().unwrap(); config.storage_dir_path = storage_dir_path; } - /// Sets the Esplora server URL. - /// - /// Default: `https://blockstream.info/api` - pub fn set_esplora_server_url(&self, esplora_server_url: String) { - let mut config = self.config.write().unwrap(); - config.esplora_server_url = esplora_server_url; - } - /// Sets the Bitcoin network used. pub fn set_network(&self, network: Network) { let mut config = self.config.write().unwrap(); @@ -314,8 +337,6 @@ impl Builder { } /// Sets the IP address and TCP port on which [`Node`] will listen for incoming network connections. - /// - /// Default: `0.0.0.0:9735` pub fn set_listening_address(&self, listening_address: NetAddress) { let mut config = self.config.write().unwrap(); config.listening_address = Some(listening_address); @@ -347,24 +368,20 @@ impl Builder { let logger = Arc::new(FilesystemLogger::new(log_file_path)); // Initialize the on-chain wallet and chain access - let seed_bytes = if let Some(entropy_source_config) = - &*self.entropy_source_config.read().unwrap() - { - // Use the configured entropy source, if the user set one. - match entropy_source_config { - EntropySourceConfig::SeedBytes(bytes) => bytes.clone(), - EntropySourceConfig::SeedFile(seed_path) => { - io::utils::read_or_generate_seed_file(seed_path) - } - EntropySourceConfig::Bip39Mnemonic { mnemonic, passphrase } => match passphrase { - Some(passphrase) => mnemonic.to_seed(passphrase), - None => mnemonic.to_seed(""), - }, + let seed_bytes = match &*self.entropy_source_config.read().unwrap() { + Some(EntropySourceConfig::SeedBytes(bytes)) => bytes.clone(), + Some(EntropySourceConfig::SeedFile(seed_path)) => { + io::utils::read_or_generate_seed_file(seed_path) + } + Some(EntropySourceConfig::Bip39Mnemonic { mnemonic, passphrase }) => match passphrase { + Some(passphrase) => mnemonic.to_seed(passphrase), + None => mnemonic.to_seed(""), + }, + None => { + // Default to read or generate from the default location generate a seed file. + let seed_path = format!("{}/keys_seed", config.storage_dir_path); + io::utils::read_or_generate_seed_file(&seed_path) } - } else { - // Default to read or generate from the default location generate a seed file. - let seed_path = format!("{}/keys_seed", config.storage_dir_path); - io::utils::read_or_generate_seed_file(&seed_path) }; let xprv = bitcoin::util::bip32::ExtendedPrivKey::new_master(config.network, &seed_bytes) @@ -389,14 +406,25 @@ impl Builder { ) .expect("Failed to set up on-chain wallet"); - let tx_sync = Arc::new(EsploraSyncClient::new( - config.esplora_server_url.clone(), - Arc::clone(&logger), - )); - - let blockchain = - EsploraBlockchain::from_client(tx_sync.client().clone(), BDK_CLIENT_STOP_GAP) - .with_concurrency(BDK_CLIENT_CONCURRENCY); + let (blockchain, tx_sync) = match &*self.chain_data_source_config.read().unwrap() { + Some(ChainDataSourceConfig::Esplora(server_url)) => { + let tx_sync = + Arc::new(EsploraSyncClient::new(server_url.clone(), Arc::clone(&logger))); + let blockchain = + EsploraBlockchain::from_client(tx_sync.client().clone(), BDK_CLIENT_STOP_GAP) + .with_concurrency(BDK_CLIENT_CONCURRENCY); + (blockchain, tx_sync) + } + None => { + // Default to Esplora client. + let server_url = DEFAULT_ESPLORA_SERVER_URL.to_string(); + let tx_sync = Arc::new(EsploraSyncClient::new(server_url, Arc::clone(&logger))); + let blockchain = + EsploraBlockchain::from_client(tx_sync.client().clone(), BDK_CLIENT_STOP_GAP) + .with_concurrency(BDK_CLIENT_CONCURRENCY); + (blockchain, tx_sync) + } + }; let runtime = Arc::new(RwLock::new(None)); let wallet = Arc::new(Wallet::new( diff --git a/src/test/functional_tests.rs b/src/test/functional_tests.rs index 4d00d41ba..39da1a634 100644 --- a/src/test/functional_tests.rs +++ b/src/test/functional_tests.rs @@ -8,15 +8,19 @@ use bitcoin::Amount; fn channel_full_cycle() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); println!("== Node A =="); - let esplora_url = electrsd.esplora_url.as_ref().unwrap(); - let config_a = random_config(esplora_url); - let node_a = Builder::from_config(config_a).build(); + let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap()); + let config_a = random_config(); + let builder_a = Builder::from_config(config_a); + builder_a.set_esplora_server(esplora_url.clone()); + let node_a = builder_a.build(); node_a.start().unwrap(); let addr_a = node_a.new_funding_address().unwrap(); println!("\n== Node B =="); - let config_b = random_config(esplora_url); - let node_b = Builder::from_config(config_b).build(); + let config_b = random_config(); + let builder_b = Builder::from_config(config_b); + builder_b.set_esplora_server(esplora_url); + let node_b = builder_b.build(); node_b.start().unwrap(); let addr_b = node_b.new_funding_address().unwrap(); @@ -221,15 +225,19 @@ fn channel_full_cycle() { fn channel_open_fails_when_funds_insufficient() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); println!("== Node A =="); - let esplora_url = electrsd.esplora_url.as_ref().unwrap(); - let config_a = random_config(&esplora_url); - let node_a = Builder::from_config(config_a).build(); + let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap()); + let config_a = random_config(); + let builder_a = Builder::from_config(config_a); + builder_a.set_esplora_server(esplora_url.clone()); + let node_a = builder_a.build(); node_a.start().unwrap(); let addr_a = node_a.new_funding_address().unwrap(); println!("\n== Node B =="); - let config_b = random_config(&esplora_url); - let node_b = Builder::from_config(config_b).build(); + let config_b = random_config(); + let builder_b = Builder::from_config(config_b); + builder_b.set_esplora_server(esplora_url); + let node_b = builder_b.build(); node_b.start().unwrap(); let addr_b = node_b.new_funding_address().unwrap(); @@ -261,12 +269,11 @@ fn channel_open_fails_when_funds_insufficient() { #[test] fn connect_to_public_testnet_esplora() { - let (_bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let esplora_url = electrsd.esplora_url.as_ref().unwrap(); - let mut config = random_config(&esplora_url); - config.esplora_server_url = "https://blockstream.info/testnet/api".to_string(); + let mut config = random_config(); config.network = bitcoin::Network::Testnet; - let node = Builder::from_config(config).build(); + let builder = Builder::from_config(config); + builder.set_esplora_server("https://blockstream.info/testnet/api".to_string()); + let node = builder.build(); node.start().unwrap(); node.sync_wallets().unwrap(); node.stop().unwrap(); @@ -275,9 +282,11 @@ fn connect_to_public_testnet_esplora() { #[test] fn start_stop_reinit() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let esplora_url = electrsd.esplora_url.as_ref().unwrap(); - let config = random_config(&esplora_url); - let node = Builder::from_config(config.clone()).build(); + let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap()); + let config = random_config(); + let builder = Builder::from_config(config.clone()); + builder.set_esplora_server(esplora_url.clone()); + let node = builder.build(); let expected_node_id = node.node_id(); let funding_address = node.new_funding_address().unwrap(); @@ -302,7 +311,9 @@ fn start_stop_reinit() { assert_eq!(node.stop(), Err(Error::NotRunning)); drop(node); - let reinitialized_node = Builder::from_config(config).build(); + let new_builder = Builder::from_config(config); + new_builder.set_esplora_server(esplora_url); + let reinitialized_node = builder.build(); assert_eq!(reinitialized_node.node_id(), expected_node_id); reinitialized_node.start().unwrap(); @@ -324,15 +335,19 @@ fn start_stop_reinit() { #[test] fn onchain_spend_receive() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let esplora_url = electrsd.esplora_url.as_ref().unwrap(); + let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap()); - let config_a = random_config(esplora_url); - let node_a = Builder::from_config(config_a).build(); + let config_a = random_config(); + let builder_a = Builder::from_config(config_a); + builder_a.set_esplora_server(esplora_url.clone()); + let node_a = builder_a.build(); node_a.start().unwrap(); let addr_a = node_a.new_funding_address().unwrap(); - let config_b = random_config(esplora_url); - let node_b = Builder::from_config(config_b).build(); + let config_b = random_config(); + let builder_b = Builder::from_config(config_b); + builder_b.set_esplora_server(esplora_url); + let node_b = builder_b.build(); node_b.start().unwrap(); let addr_b = node_b.new_funding_address().unwrap(); @@ -376,8 +391,11 @@ fn onchain_spend_receive() { #[test] fn sign_verify_msg() { let (_, electrsd) = setup_bitcoind_and_electrsd(); - let esplora_url = electrsd.esplora_url.as_ref().unwrap(); - let node = Builder::from_config(random_config(esplora_url)).build(); + let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap()); + let config = random_config(); + let builder = Builder::from_config(config.clone()); + builder.set_esplora_server(esplora_url.clone()); + let node = builder.build(); node.start().unwrap(); diff --git a/src/test/utils.rs b/src/test/utils.rs index c396b21a8..4c65eb21c 100644 --- a/src/test/utils.rs +++ b/src/test/utils.rs @@ -4,7 +4,7 @@ use lightning::util::logger::{Level, Logger, Record}; use lightning::util::persist::KVStorePersister; use lightning::util::ser::Writeable; -use bitcoin::{Address, Amount, OutPoint, Txid}; +use bitcoin::{Address, Amount, Network, OutPoint, Txid}; use bitcoind::bitcoincore_rpc::RpcApi; use electrsd::bitcoind::bitcoincore_rpc::bitcoincore_rpc_json::AddressType; @@ -251,11 +251,11 @@ pub fn random_port() -> u16 { rng.gen_range(5000..65535) } -pub fn random_config(esplora_url: &str) -> Config { +pub fn random_config() -> Config { let mut config = Config::default(); - println!("Setting esplora server URL: {}", esplora_url); - config.esplora_server_url = format!("http://{}", esplora_url); + config.network = Network::Regtest; + println!("Setting network: {}", config.network); let rand_dir = random_storage_path(); println!("Setting random LDK storage dir: {}", rand_dir); From 44dab656e106bbea140de1f5c0a17f914c87b690 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 26 May 2023 10:30:33 +0200 Subject: [PATCH 4/5] Switch `DEFAULT_NETWORK` over to `Bitcoin` mainnet As we're getting ready for release --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5aedd91c3..9a8ce2267 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -163,7 +163,7 @@ uniffi::include_scaffolding!("ldk_node"); // Config defaults const DEFAULT_STORAGE_DIR_PATH: &str = "/tmp/ldk_node/"; -const DEFAULT_NETWORK: Network = Network::Regtest; +const DEFAULT_NETWORK: Network = Network::Bitcoin; const DEFAULT_LISTENING_ADDR: &str = "0.0.0.0:9735"; const DEFAULT_CLTV_EXPIRY_DELTA: u32 = 144; const DEFAULT_ESPLORA_SERVER_URL: &str = "https://blockstream.info/api"; @@ -198,7 +198,7 @@ const WALLET_KEYS_SEED_LEN: usize = 64; /// | Parameter | Value | /// |-----------------------------|------------------| /// | `storage_dir_path` | /tmp/ldk_node/ | -/// | `network` | Network::Regtest | +/// | `network` | Network::Bitcoin | /// | `listening_address` | 0.0.0.0:9735 | /// | `default_cltv_expiry_delta` | 144 | /// From de94a54f14a470f705b0bf8fa8e475e37e1ab6ba Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 26 May 2023 10:27:09 +0200 Subject: [PATCH 5/5] Move UniFFI types to own, feature-gated module While we need quite a bit of dependencies and custom impls for UniFFI support, there is no reason to pull them in for Rust-only builds. Here we move the UniFFI-specific types and trait impls to a dedicated module and hide it behind a new `uniffi` feature. --- .github/workflows/build.yml | 10 +- Cargo.toml | 9 +- build.rs | 1 + scripts/uniffi_bindgen_generate_kotlin.sh | 2 +- .../uniffi_bindgen_generate_kotlin_android.sh | 7 +- scripts/uniffi_bindgen_generate_python.sh | 2 +- scripts/uniffi_bindgen_generate_swift.sh | 12 +- src/hex_utils.rs | 1 + src/lib.rs | 14 +- src/types.rs | 179 +---------------- src/uniffi_types.rs | 188 ++++++++++++++++++ uniffi-bindgen.rs | 1 + 12 files changed, 225 insertions(+), 201 deletions(-) create mode 100644 src/uniffi_types.rs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8e0be61c1..d7a889264 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,10 +22,16 @@ jobs: profile: minimal - name: Build on Rust ${{ matrix.toolchain }} run: cargo build --verbose --color always + - name: Build with UniFFI support on Rust ${{ matrix.toolchain }} + run: cargo build --features uniffi --verbose --color always - name: Check release build on Rust ${{ matrix.toolchain }} run: cargo check --release --verbose --color always + - name: Check release build with UniFFI support on Rust ${{ matrix.toolchain }} + run: cargo check --release --features uniffi --verbose --color always + - name: Test on Rust ${{ matrix.toolchain }} + run: cargo test + - name: Test with UniFFI support on Rust ${{ matrix.toolchain }} + run: cargo test --features uniffi - name: Check formatting on Rust ${{ matrix.toolchain }} if: matrix.check-fmt run: rustup component add rustfmt && cargo fmt --all -- --check - - name: Test on Rust ${{ matrix.toolchain }} - run: cargo test diff --git a/Cargo.toml b/Cargo.toml index d1116d28b..0be56137d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,9 @@ opt-level = 'z' # Optimize for size. lto = true # Enable Link Time Optimization codegen-units = 1 # Reduce number of codegen units to increase optimizations. panic = 'abort' # Abort on panic -strip = true # Strip symbols from binary* + +[features] +default = [] [dependencies] lightning = { version = "0.0.115", features = ["max_level_trace", "std"] } @@ -67,7 +69,7 @@ serde_json = { version = "1.0" } tokio = { version = "1", default-features = false, features = [ "rt-multi-thread", "time", "sync" ] } esplora-client = { version = "0.4", default-features = false } libc = "0.2" -uniffi = { version = "0.23.0", features = ["build"] } +uniffi = { version = "0.23.0", features = ["build"], optional = true } [dev-dependencies] electrsd = { version = "0.22.0", features = ["legacy", "esplora_a33e97e1", "bitcoind_23_0"] } @@ -76,8 +78,7 @@ proptest = "1.0.0" regex = "1.5.6" [build-dependencies] -uniffi = { version = "0.23.0", features = ["build", "cli"] } - +uniffi = { version = "0.23.0", features = ["build", "cli"], optional = true } [profile.release] panic = "abort" diff --git a/build.rs b/build.rs index f5dd351c1..087855111 100644 --- a/build.rs +++ b/build.rs @@ -1,3 +1,4 @@ fn main() { + #[cfg(feature = "uniffi")] uniffi::generate_scaffolding("bindings/ldk_node.udl").unwrap(); } diff --git a/scripts/uniffi_bindgen_generate_kotlin.sh b/scripts/uniffi_bindgen_generate_kotlin.sh index 72dd5cef3..f889c1f85 100755 --- a/scripts/uniffi_bindgen_generate_kotlin.sh +++ b/scripts/uniffi_bindgen_generate_kotlin.sh @@ -12,7 +12,7 @@ fi #rustup target add aarch64-apple-darwin #cargo build --target aarch64-apple-darwin || exit 1 -cargo build --release || exit 1 +cargo build --release --features uniffi || exit 1 $UNIFFI_BINDGEN_BIN generate bindings/ldk_node.udl --language kotlin -o "$TARGET_DIR" || exit 1 mkdir -p "$BINDINGS_DIR"/"$PROJECT_DIR"/lib/src/main/kotlin/"$PACKAGE_DIR" || exit 1 diff --git a/scripts/uniffi_bindgen_generate_kotlin_android.sh b/scripts/uniffi_bindgen_generate_kotlin_android.sh index 71734b94e..30a867181 100755 --- a/scripts/uniffi_bindgen_generate_kotlin_android.sh +++ b/scripts/uniffi_bindgen_generate_kotlin_android.sh @@ -9,10 +9,9 @@ LLVM_ARCH_PATH="darwin-x86_64" PATH="$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/$LLVM_ARCH_PATH/bin:$PATH" rustup +nightly target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi -#cargo build --release || exit 1 -CFLAGS="-D__ANDROID_MIN_SDK_VERSION__=21" AR=llvm-ar CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="x86_64-linux-android21-clang" CC="x86_64-linux-android21-clang" cargo +nightly build --profile release-smaller --target x86_64-linux-android || exit 1 -CFLAGS="-D__ANDROID_MIN_SDK_VERSION__=21" AR=llvm-ar CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="armv7a-linux-androideabi21-clang" CC="armv7a-linux-androideabi21-clang" cargo +nightly build --profile release-smaller --target armv7-linux-androideabi || exit 1 -CFLAGS="-D__ANDROID_MIN_SDK_VERSION__=21" AR=llvm-ar CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="aarch64-linux-android21-clang" CC="aarch64-linux-android21-clang" cargo +nightly build --profile release-smaller --target aarch64-linux-android || exit 1 +CFLAGS="-D__ANDROID_MIN_SDK_VERSION__=21" AR=llvm-ar CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="x86_64-linux-android21-clang" CC="x86_64-linux-android21-clang" cargo +nightly build --profile release-smaller --features uniffi --target x86_64-linux-android || exit 1 +CFLAGS="-D__ANDROID_MIN_SDK_VERSION__=21" AR=llvm-ar CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="armv7a-linux-androideabi21-clang" CC="armv7a-linux-androideabi21-clang" cargo +nightly build --profile release-smaller --features uniffi --target armv7-linux-androideabi || exit 1 +CFLAGS="-D__ANDROID_MIN_SDK_VERSION__=21" AR=llvm-ar CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="aarch64-linux-android21-clang" CC="aarch64-linux-android21-clang" cargo +nightly build --profile release-smaller --features uniffi --target aarch64-linux-android || exit 1 $UNIFFI_BINDGEN_BIN generate bindings/ldk_node.udl --language kotlin -o "$BINDINGS_DIR"/"$PROJECT_DIR"/lib/src/main/kotlin || exit 1 JNI_LIB_DIR="$BINDINGS_DIR"/"$PROJECT_DIR"/lib/src/main/jniLibs/ diff --git a/scripts/uniffi_bindgen_generate_python.sh b/scripts/uniffi_bindgen_generate_python.sh index 8dd937fba..a517fb329 100755 --- a/scripts/uniffi_bindgen_generate_python.sh +++ b/scripts/uniffi_bindgen_generate_python.sh @@ -2,6 +2,6 @@ BINDINGS_DIR="./bindings/python" UNIFFI_BINDGEN_BIN="cargo +nightly run --features=uniffi/cli --bin uniffi-bindgen" -cargo +nightly build --release || exit 1 +cargo +nightly build --release --features uniffi || exit 1 $UNIFFI_BINDGEN_BIN generate bindings/ldk_node.udl --language python -o "$BINDINGS_DIR" || exit 1 cp ./target/release/libldk_node.dylib "$BINDINGS_DIR"/libldk_node.dylib || exit 1 diff --git a/scripts/uniffi_bindgen_generate_swift.sh b/scripts/uniffi_bindgen_generate_swift.sh index d821581e2..042e157be 100755 --- a/scripts/uniffi_bindgen_generate_swift.sh +++ b/scripts/uniffi_bindgen_generate_swift.sh @@ -15,12 +15,12 @@ rustup target add aarch64-apple-ios-sim --toolchain nightly rustup target add aarch64-apple-darwin x86_64-apple-darwin # Build rust target libs -cargo build --profile release-smaller || exit 1 -cargo build --profile release-smaller --target x86_64-apple-darwin || exit 1 -cargo build --profile release-smaller --target aarch64-apple-darwin || exit 1 -cargo build --profile release-smaller --target x86_64-apple-ios || exit 1 -cargo build --profile release-smaller --target aarch64-apple-ios || exit 1 -cargo +nightly build --release --target aarch64-apple-ios-sim || exit 1 +cargo build --profile release-smaller --features uniffi || exit 1 +cargo build --profile release-smaller --features uniffi --target x86_64-apple-darwin || exit 1 +cargo build --profile release-smaller --features uniffi --target aarch64-apple-darwin || exit 1 +cargo build --profile release-smaller --features uniffi --target x86_64-apple-ios || exit 1 +cargo build --profile release-smaller --features uniffi --target aarch64-apple-ios || exit 1 +cargo +nightly build --release --features uniffi --target aarch64-apple-ios-sim || exit 1 # Combine ios-sim and apple-darwin (macos) libs for x86_64 and aarch64 (m1) mkdir -p target/lipo-ios-sim/release-smaller || exit 1 diff --git a/src/hex_utils.rs b/src/hex_utils.rs index 89803c9bb..1b50c5647 100644 --- a/src/hex_utils.rs +++ b/src/hex_utils.rs @@ -1,5 +1,6 @@ use std::fmt::Write; +#[cfg(feature = "uniffi")] pub fn to_vec(hex: &str) -> Option> { let mut out = Vec::with_capacity(hex.len() / 2); diff --git a/src/lib.rs b/src/lib.rs index 9a8ce2267..3a0526f23 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -86,6 +86,8 @@ mod peer_store; #[cfg(test)] mod test; mod types; +#[cfg(feature = "uniffi")] +mod uniffi_types; mod wallet; pub use bip39; @@ -100,6 +102,9 @@ use error::Error; pub use event::Event; pub use types::NetAddress; +#[cfg(feature = "uniffi")] +use {bitcoin::OutPoint, lightning::ln::PaymentSecret, uniffi_types::*}; + use event::{EventHandler, EventQueue}; use gossip::GossipSource; use io::fs_store::FilesystemStore; @@ -122,7 +127,7 @@ use lightning::ln::channelmanager::{ self, ChainParameters, ChannelManagerReadArgs, PaymentId, RecipientOnionFields, Retry, }; use lightning::ln::peer_handler::{IgnoringMessageHandler, MessageHandler}; -use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret}; +use lightning::ln::{PaymentHash, PaymentPreimage}; use lightning::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringParameters}; use lightning::util::config::{ChannelHandshakeConfig, ChannelHandshakeLimits, UserConfig}; @@ -147,7 +152,7 @@ use bitcoin::Network; use bip39::Mnemonic; -use bitcoin::{Address, BlockHash, OutPoint, Txid}; +use bitcoin::{Address, BlockHash, Txid}; use rand::Rng; @@ -159,6 +164,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex, RwLock}; use std::time::{Duration, Instant, SystemTime}; +#[cfg(feature = "uniffi")] uniffi::include_scaffolding!("ldk_node"); // Config defaults @@ -703,10 +709,6 @@ impl Builder { } } -/// This type alias is required as Uniffi doesn't support generics, i.e., we can only expose the -/// concretized types via this aliasing hack. -type LDKNode = Node; - /// The main interface object of LDK Node, wrapping the necessary LDK and BDK functionalities. /// /// Needs to be initialized and instantiated through [`Builder::build`]. diff --git a/src/types.rs b/src/types.rs index 79e4b6ba6..f164eb70d 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,8 +1,5 @@ -use crate::error::Error; -use crate::hex_utils; use crate::logger::FilesystemLogger; use crate::wallet::{Wallet, WalletKeysManager}; -use crate::UniffiCustomTypeConverter; use lightning::chain::chainmonitor; use lightning::chain::keysinterface::InMemorySigner; @@ -10,23 +7,17 @@ use lightning::ln::channelmanager::ChannelDetails as LdkChannelDetails; use lightning::ln::msgs::NetAddress as LdkNetAddress; use lightning::ln::msgs::RoutingMessageHandler; use lightning::ln::peer_handler::IgnoringMessageHandler; -use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret}; use lightning::routing::gossip; use lightning::routing::router::DefaultRouter; use lightning::routing::scoring::ProbabilisticScorer; use lightning::util::ser::{Hostname, Readable, Writeable, Writer}; -use lightning_invoice::{Invoice, SignedRawInvoice}; use lightning_net_tokio::SocketDescriptor; use lightning_transaction_sync::EsploraSyncClient; -use bitcoin::hashes::sha256::Hash as Sha256; -use bitcoin::hashes::Hash; use bitcoin::secp256k1::PublicKey; -use bitcoin::{Address, OutPoint, Txid}; +use bitcoin::OutPoint; -use bip39::Mnemonic; - -use std::convert::{TryFrom, TryInto}; +use std::convert::TryFrom; use std::fmt::Display; use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs}; use std::str::FromStr; @@ -95,108 +86,6 @@ pub(crate) type OnionMessenger = lightning::onion_message::OnionMessenger< IgnoringMessageHandler, >; -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::InvalidPublicKey.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::InvalidAddress.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::InvalidInvoice.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::InvalidPaymentHash.into()) - } - } - - fn from_custom(obj: Self) -> Self::Builtin { - Sha256::from_slice(&obj.0).unwrap().to_string() - } -} - -impl UniffiCustomTypeConverter for PaymentPreimage { - type Builtin = String; - - fn into_custom(val: Self::Builtin) -> uniffi::Result { - if let Some(bytes_vec) = hex_utils::to_vec(&val) { - let bytes_res = bytes_vec.try_into(); - if let Ok(bytes) = bytes_res { - return Ok(PaymentPreimage(bytes)); - } - } - Err(Error::InvalidPaymentPreimage.into()) - } - - fn from_custom(obj: Self) -> Self::Builtin { - hex_utils::to_string(&obj.0) - } -} - -impl UniffiCustomTypeConverter for PaymentSecret { - type Builtin = String; - - fn into_custom(val: Self::Builtin) -> uniffi::Result { - if let Some(bytes_vec) = hex_utils::to_vec(&val) { - let bytes_res = bytes_vec.try_into(); - if let Ok(bytes) = bytes_res { - return Ok(PaymentSecret(bytes)); - } - } - Err(Error::InvalidPaymentSecret.into()) - } - - fn from_custom(obj: Self) -> Self::Builtin { - hex_utils::to_string(&obj.0) - } -} - /// The global identifier of a channel. /// /// Note that this will start out to be a temporary ID until channel funding negotiation is @@ -205,25 +94,6 @@ impl UniffiCustomTypeConverter for PaymentSecret { #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct ChannelId(pub [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::InvalidChannelId.into()) - } - - fn from_custom(obj: Self) -> Self::Builtin { - hex_utils::to_string(&obj.0) - } -} - impl Writeable for ChannelId { fn write(&self, writer: &mut W) -> Result<(), lightning::io::Error> { Ok(self.0.write(writer)?) @@ -244,18 +114,6 @@ impl Readable for ChannelId { #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct UserChannelId(pub 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::InvalidChannelId)?)) - } - - fn from_custom(obj: Self) -> Self::Builtin { - obj.0.to_string() - } -} - impl Writeable for UserChannelId { fn write(&self, writer: &mut W) -> Result<(), lightning::io::Error> { Ok(self.0.write(writer)?) @@ -270,17 +128,6 @@ impl Readable for UserChannelId { } } -impl UniffiCustomTypeConverter for Txid { - type Builtin = String; - fn into_custom(val: Self::Builtin) -> uniffi::Result { - Ok(Txid::from_str(&val)?) - } - - fn from_custom(obj: Self) -> Self::Builtin { - obj.to_string() - } -} - /// Details of a channel as returned by [`Node::list_channels`]. /// /// [`Node::list_channels`]: [`crate::Node::list_channels`] @@ -425,28 +272,6 @@ impl Display for NetAddress { } } -impl UniffiCustomTypeConverter for Mnemonic { - type Builtin = String; - fn into_custom(val: Self::Builtin) -> uniffi::Result { - Ok(Mnemonic::from_str(&val).map_err(|_| Error::InvalidSecretKey)?) - } - - fn from_custom(obj: Self) -> Self::Builtin { - obj.to_string() - } -} - -impl UniffiCustomTypeConverter for NetAddress { - type Builtin = String; - fn into_custom(val: Self::Builtin) -> uniffi::Result { - Ok(NetAddress::from_str(&val).map_err(|_| Error::InvalidNetAddress)?) - } - - fn from_custom(obj: Self) -> Self::Builtin { - obj.to_string() - } -} - impl FromStr for NetAddress { type Err = (); diff --git a/src/uniffi_types.rs b/src/uniffi_types.rs new file mode 100644 index 000000000..6e4e4888c --- /dev/null +++ b/src/uniffi_types.rs @@ -0,0 +1,188 @@ +use crate::UniffiCustomTypeConverter; + +use crate::error::Error; +use crate::hex_utils; +use crate::io::FilesystemStore; +use crate::{ChannelId, NetAddress, Node, UserChannelId}; + +use bitcoin::hashes::sha256::Hash as Sha256; +use bitcoin::hashes::Hash; +use bitcoin::secp256k1::PublicKey; +use bitcoin::{Address, Txid}; +use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret}; +use lightning_invoice::{Invoice, SignedRawInvoice}; + +use bip39::Mnemonic; + +use std::convert::TryInto; +use std::str::FromStr; + +/// This type alias is required as Uniffi doesn't support generics, i.e., we can only expose the +/// concretized types via this aliasing hack. +pub type LDKNode = Node; + +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::InvalidPublicKey.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::InvalidAddress.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::InvalidInvoice.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::InvalidPaymentHash.into()) + } + } + + fn from_custom(obj: Self) -> Self::Builtin { + Sha256::from_slice(&obj.0).unwrap().to_string() + } +} + +impl UniffiCustomTypeConverter for PaymentPreimage { + type Builtin = String; + + fn into_custom(val: Self::Builtin) -> uniffi::Result { + if let Some(bytes_vec) = hex_utils::to_vec(&val) { + let bytes_res = bytes_vec.try_into(); + if let Ok(bytes) = bytes_res { + return Ok(PaymentPreimage(bytes)); + } + } + Err(Error::InvalidPaymentPreimage.into()) + } + + fn from_custom(obj: Self) -> Self::Builtin { + hex_utils::to_string(&obj.0) + } +} + +impl UniffiCustomTypeConverter for PaymentSecret { + type Builtin = String; + + fn into_custom(val: Self::Builtin) -> uniffi::Result { + if let Some(bytes_vec) = hex_utils::to_vec(&val) { + let bytes_res = bytes_vec.try_into(); + if let Ok(bytes) = bytes_res { + return Ok(PaymentSecret(bytes)); + } + } + Err(Error::InvalidPaymentSecret.into()) + } + + fn from_custom(obj: Self) -> Self::Builtin { + hex_utils::to_string(&obj.0) + } +} + +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::InvalidChannelId.into()) + } + + fn from_custom(obj: Self) -> Self::Builtin { + hex_utils::to_string(&obj.0) + } +} + +impl UniffiCustomTypeConverter for UserChannelId { + type Builtin = String; + + fn into_custom(val: Self::Builtin) -> uniffi::Result { + Ok(UserChannelId(u128::from_str(&val).map_err(|_| Error::InvalidChannelId)?)) + } + + fn from_custom(obj: Self) -> Self::Builtin { + obj.0.to_string() + } +} + +impl UniffiCustomTypeConverter for Txid { + type Builtin = String; + fn into_custom(val: Self::Builtin) -> uniffi::Result { + Ok(Txid::from_str(&val)?) + } + + fn from_custom(obj: Self) -> Self::Builtin { + obj.to_string() + } +} + +impl UniffiCustomTypeConverter for Mnemonic { + type Builtin = String; + fn into_custom(val: Self::Builtin) -> uniffi::Result { + Ok(Mnemonic::from_str(&val).map_err(|_| Error::InvalidSecretKey)?) + } + + fn from_custom(obj: Self) -> Self::Builtin { + obj.to_string() + } +} + +impl UniffiCustomTypeConverter for NetAddress { + type Builtin = String; + fn into_custom(val: Self::Builtin) -> uniffi::Result { + Ok(NetAddress::from_str(&val).map_err(|_| Error::InvalidNetAddress)?) + } + + fn from_custom(obj: Self) -> Self::Builtin { + obj.to_string() + } +} diff --git a/uniffi-bindgen.rs b/uniffi-bindgen.rs index 2aea96784..cb5b60e20 100644 --- a/uniffi-bindgen.rs +++ b/uniffi-bindgen.rs @@ -1,3 +1,4 @@ fn main() { + #[cfg(feature = "uniffi")] uniffi::uniffi_bindgen_main() }