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/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/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 4162e9f15..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; @@ -13,6 +12,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_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_network(Network network); + void set_listening_address(NetAddress listening_address); LDKNode build(); }; @@ -85,6 +93,7 @@ enum NodeError { "InvalidAddress", "InvalidNetAddress", "InvalidPublicKey", + "InvalidSecretKey", "InvalidPaymentHash", "InvalidPaymentPreimage", "InvalidPaymentSecret", @@ -117,6 +126,13 @@ enum PaymentStatus { "Failed", }; +enum Network { + "Bitcoin", + "Testnet", + "Signet", + "Regtest", +}; + dictionary PaymentDetails { PaymentHash hash; PaymentPreimage? preimage; @@ -187,4 +203,4 @@ typedef string ChannelId; typedef string UserChannelId; [Custom] -typedef string Network; +typedef string Mnemonic; 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/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/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 086701371..3a0526f23 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("https://blockstream.info/testnet/api".to_string()); //! +//! let node = builder.build(); //! node.start().unwrap(); //! //! let _funding_address = node.new_funding_address(); @@ -85,6 +86,8 @@ mod peer_store; #[cfg(test)] mod test; mod types; +#[cfg(feature = "uniffi")] +mod uniffi_types; mod wallet; pub use bip39; @@ -99,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; @@ -121,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}; @@ -144,7 +150,9 @@ use bitcoin::hashes::Hash; use bitcoin::secp256k1::PublicKey; use bitcoin::Network; -use bitcoin::{Address, BlockHash, OutPoint, Txid}; +use bip39::Mnemonic; + +use bitcoin::{Address, BlockHash, Txid}; use rand::Rng; @@ -152,13 +160,20 @@ 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}; +#[cfg(feature = "uniffi")] uniffi::include_scaffolding!("ldk_node"); +// Config defaults +const DEFAULT_STORAGE_DIR_PATH: &str = "/tmp/ldk_node/"; +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"; + // 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; @@ -183,11 +198,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::Bitcoin | +/// | `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. @@ -199,20 +222,24 @@ 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), SeedBytes([u8; WALLET_KEYS_SEED_LEN]), - Bip39Mnemonic { mnemonic: bip39::Mnemonic, passphrase: Option }, + Bip39Mnemonic { mnemonic: Mnemonic, passphrase: Option }, } #[derive(Debug, Clone)] @@ -223,107 +250,109 @@ enum GossipSourceConfig { /// A builder for an [`Node`] instance, allowing to set some configuration and module choices from /// the getgo. -#[derive(Debug, Clone)] +/// +/// ### 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: Config, - entropy_source_config: Option, - gossip_source_config: Option, + config: RwLock, + entropy_source_config: RwLock>, + chain_data_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; - Self { config, entropy_source_config, gossip_source_config } + 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, chain_data_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; - Self { config, entropy_source_config, gossip_source_config } + 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, chain_data_source_config, gossip_source_config } } /// Configures the [`Node`] instance to source its wallet entropy from a seed file on disk. /// /// 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 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 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 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 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(&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 - } - - /// 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_storage_dir_path(&self, storage_dir_path: String) { + let mut config = self.config.write().unwrap(); + config.storage_dir_path = storage_dir_path; } /// 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 +361,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,22 +374,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 { - // 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) @@ -385,14 +412,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( @@ -550,8 +588,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 => { @@ -670,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/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); diff --git a/src/types.rs b/src/types.rs index 92d957246..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,22 +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, Network, OutPoint, Txid}; +use bitcoin::OutPoint; -use core::convert::TryFrom; -use std::convert::TryInto; +use std::convert::TryFrom; use std::fmt::Display; use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs}; use std::str::FromStr; @@ -94,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 @@ -204,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)?) @@ -243,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)?) @@ -269,29 +128,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 { - 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`] @@ -436,17 +272,6 @@ impl Display for NetAddress { } } -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() }