From dd64758191b68372b6ec54a438d23d0f2f654682 Mon Sep 17 00:00:00 2001 From: Camillarhi Date: Sat, 5 Apr 2025 00:41:09 +0100 Subject: [PATCH 1/9] feat: add onchain address validation for network-specific addresses --- src/error.rs | 10 ++++++++++ src/payment/onchain.rs | 27 +++++++++++++++++++++++++-- tests/integration_tests_rust.rs | 15 ++++++++++++++- 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/src/error.rs b/src/error.rs index 2cb71186d..72157268e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -10,6 +10,7 @@ use bdk_chain::local_chain::CannotConnectError as BdkChainConnectionError; use bdk_chain::tx_graph::CalculateFeeError as BdkChainCalculateFeeError; use bdk_wallet::error::CreateTxError as BdkCreateTxError; use bdk_wallet::signer::SignerError as BdkSignerError; +use bitcoin::Network; use std::fmt; @@ -120,6 +121,13 @@ pub enum Error { LiquiditySourceUnavailable, /// The given operation failed due to the LSP's required opening fee being too high. LiquidityFeeTooHigh, + /// The given address is invalid. + InvalidAddressFormat, + ///Address belongs to the wrong network + InvalidNetworkAddress { + /// The expected network. + expected: Network + }, } impl fmt::Display for Error { @@ -193,6 +201,8 @@ impl fmt::Display for Error { Self::LiquidityFeeTooHigh => { write!(f, "The given operation failed due to the LSP's required opening fee being too high.") }, + Self::InvalidAddressFormat => write!(f, "The given address is invalid."), + Self::InvalidNetworkAddress { expected } => write!(f, "The given address is invalid. Expected network: {:?}", expected), } } } diff --git a/src/payment/onchain.rs b/src/payment/onchain.rs index 046d66c69..793074352 100644 --- a/src/payment/onchain.rs +++ b/src/payment/onchain.rs @@ -13,8 +13,10 @@ use crate::logger::{log_info, LdkLogger, Logger}; use crate::types::{ChannelManager, Wallet}; use crate::wallet::OnchainSendAmount; -use bitcoin::{Address, Txid}; +use bitcoin::address::NetworkUnchecked; +use bitcoin::{Address, Network, Txid}; +use std::str::FromStr; use std::sync::{Arc, RwLock}; #[cfg(not(feature = "uniffi"))] @@ -80,12 +82,33 @@ impl OnchainPayment { return Err(Error::NotRunning); } + let validated_address = self.parse_and_validate_address(self.config.network, &address.to_string())?; + let cur_anchor_reserve_sats = crate::total_anchor_channels_reserve_sats(&self.channel_manager, &self.config); let send_amount = OnchainSendAmount::ExactRetainingReserve { amount_sats, cur_anchor_reserve_sats }; let fee_rate_opt = maybe_map_fee_rate_opt!(fee_rate); - self.wallet.send_to_address(address, send_amount, fee_rate_opt) + self.wallet.send_to_address(&validated_address, send_amount, fee_rate_opt) + } + + /// Validates a Bitcoin address is properly formatted and matches the expected network. + /// + /// Returns `Ok(Address)` if valid, or: + /// - `InvalidAddressFormat` if malformed + /// - `InvalidNetworkAddress` if valid but wrong network + /// + /// # Example + /// ``` + /// let address = "tb1q..."; // Testnet address + /// parse_and_validate_address(Network::Testnet, address)?; + pub fn parse_and_validate_address(&self, network: Network, address: &str) -> Result { + Address::::from_str(address) + .map_err(|_| Error::InvalidAddressFormat)? + .require_network(network) + .map_err(|_| Error::InvalidNetworkAddress { + expected: network, + }) } /// Send an on-chain payment to the given address, draining the available funds. diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index f2dfa4b5e..bdb38278c 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -32,9 +32,12 @@ use lightning_invoice::{Bolt11InvoiceDescription, Description}; use bitcoin::hashes::Hash; use bitcoin::Amount; - +use bitcoin::address::NetworkUnchecked; +use bitcoin::Address; +use lightning_invoice::{Bolt11InvoiceDescription, Description}; use log::LevelFilter; +use std::str::FromStr; use std::sync::Arc; #[test] @@ -302,6 +305,9 @@ fn onchain_send_receive() { let addr_a = node_a.onchain_payment().new_address().unwrap(); let addr_b = node_b.onchain_payment().new_address().unwrap(); + let static_address = "tb1q0d40e5rta4fty63z64gztf8c3v20cvet6v2jdh"; + let unchecked_address = Address::::from_str(static_address).unwrap(); + let addr_c = unchecked_address.assume_checked(); let premine_amount_sat = 1_100_000; premine_and_distribute_funds( @@ -366,6 +372,13 @@ fn onchain_send_receive() { node_a.onchain_payment().send_to_address(&addr_b, expected_node_a_balance + 1, None) ); + assert_eq!( + Err(NodeError::InvalidNetworkAddress { + expected: node_a.config().network, + }), + node_a.onchain_payment().send_to_address(&addr_c, expected_node_a_balance + 1, None) + ); + let amount_to_send_sats = 54321; let txid = node_b.onchain_payment().send_to_address(&addr_a, amount_to_send_sats, None).unwrap(); From dd9dd5c5471f2d575d534cd53bd52cc0deb1702b Mon Sep 17 00:00:00 2001 From: Camillarhi Date: Wed, 9 Apr 2025 13:52:11 +0100 Subject: [PATCH 2/9] feat: add onchain address validation for network-specific addresses on `sending all to address` --- src/error.rs | 9 --- src/main.rs | 128 ++++++++++++++++++++++++++++++++ src/payment/onchain.rs | 44 +++++------ tests/integration_tests_rust.rs | 13 ++-- 4 files changed, 159 insertions(+), 35 deletions(-) create mode 100644 src/main.rs diff --git a/src/error.rs b/src/error.rs index 72157268e..9304b73b8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -121,13 +121,6 @@ pub enum Error { LiquiditySourceUnavailable, /// The given operation failed due to the LSP's required opening fee being too high. LiquidityFeeTooHigh, - /// The given address is invalid. - InvalidAddressFormat, - ///Address belongs to the wrong network - InvalidNetworkAddress { - /// The expected network. - expected: Network - }, } impl fmt::Display for Error { @@ -201,8 +194,6 @@ impl fmt::Display for Error { Self::LiquidityFeeTooHigh => { write!(f, "The given operation failed due to the LSP's required opening fee being too high.") }, - Self::InvalidAddressFormat => write!(f, "The given address is invalid."), - Self::InvalidNetworkAddress { expected } => write!(f, "The given address is invalid. Expected network: {:?}", expected), } } } diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 000000000..227fa1a16 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,128 @@ +use bitcoin::address::{NetworkUnchecked, ParseError}; +use bitcoin::Address; +use ldk_node::bitcoin::secp256k1::PublicKey; +use ldk_node::bitcoin::Network; +use ldk_node::lightning::ln::msgs::SocketAddress; +use ldk_node::lightning_invoice::Bolt11Invoice; +use ldk_node::Builder; +use std::str::FromStr; +mod error; + +pub use error::Error as NodeError; +use error::Error; + +fn main() { + let mut builder = Builder::new(); + builder.set_network(Network::Testnet); + builder.set_chain_source_esplora("https://blockstream.info/testnet/api".to_string(), None); + builder.set_gossip_source_rgs( + "https://rapidsync.lightningdevkit.org/testnet/snapshot".to_string(), + ); + + let node = builder.build().unwrap(); + + node.start().unwrap(); + + let funding_address = node.onchain_payment().new_address(); + // let funding_address = "tb1q0d40e5rta4fty63z64gztf8c3v20cvet6v2jdh"; // Testnet address + println!("Funding address: {:?}", funding_address); + // .. fund address .. + // Fund the wallet using the printed address before proceeding + println!("Please fund the wallet with at least 11000 sats (including fees)."); + println!("Press Enter to continue after funding..."); + // let mut input = String::new(); + // std::io::stdin().read_line(&mut input).unwrap(); + + // Wait for synchronization + println!("Waiting for wallet to sync with the blockchain..."); + // std::thread::sleep(std::time::Duration::from_secs(600)); // Wait for 10 minutes (600 seconds) for the wallet to sync + + // let funding_address = funding_address.unwrap(); + // node.onchain_payment().send_to_address(&funding_address, 10000, None).unwrap(); + // println!("Sent 10000 sat to funding address"); + + let balance = node.list_balances(); + println!("Wallet balance: {:?}", balance); + + let config = node.config(); + println!("Node config: {:?}", config); + + let node_id = node.node_id(); + println!("Node ID: {:?}", node_id); + + let listnening_address = node.listening_addresses(); + println!("Listening address: {:?}", listnening_address); + + //todo: delay for 10 min or more to allow real funding of a generated address + + // let node_id = PublicKey::from_str("026fa208407265cddd6f8184803e71480c3ba1d8885d9e322dba1c57bb414d1ed1").unwrap(); + // let node_addr = SocketAddress::from_str("127.0.0.1:9735").unwrap(); + let channels = node.list_channels(); + for channel in channels { + println!("Channel: {:?}", channel); + } + + let send_to_address = get_sendto_address().unwrap(); + + const ADDR: &str = "tb1q0d40e5rta4fty63z64gztf8c3v20cvet6v2jdh"; + // let val = parse_and_validate_address(node.config().network, &send_to_address.to_string()).unwrap(); + // println!("Parsed and validated address: {:?}", val); + + // let validated_address = + // unchecked_address.require_network(node.config().network).map_err(|_| Error::InvalidNetwork); + + // let tx_id = node.onchain_payment().send_to_address(&send_to_address, 300, None)?; + // println!("Transaction ID: {:?}", tx_id); + match node.onchain_payment().send_to_address(&send_to_address, 300, None) { + Ok(txid) => { + println!("Successfully sent transaction: {}", txid); + // Continue with success case + }, + // Err(Error::InvalidNetworkAddress { expected, received }) => { + // eprintln!("Error: Please use a {} address (this was a {} address)", expected, actual); + // // Handle network mismatch case + // }, + // Err(Error::InvalidAddressFormat(e)) => { + // eprintln!("Error: Invalid address format: {}", e); + // // Handle bad address case + // }, + Err(e) => { + eprintln!("Error: {}", e); + node.stop().unwrap(); + // Handle other errors + }, + } + // node.open_channel(node_id, node_addr, 10000, None, None).unwrap(); + node.stop().unwrap(); + + let event = node.wait_next_event(); + println!("EVENT: {:?}", event); + node.event_handled(); + + let invoice = Bolt11Invoice::from_str("lnbcrt1m1pn7cpr8pp57czqam62fenlx7dq9gd0zj53kymydl6kgmd8j76qeawszeagcvjqdqqcqzzsxqyz5vqsp57kzl3xlsvkx68jeegrt548tp02vrkcu7ku7x5eszwx36825kgshs9qxpqysgqlyfja29vh9m3jsu6uxrzz7htg84qygc3kgahxw2njzrmfh8htaqs0sscr86wfywq934w8sxzqwhq6hpemg7c4ufnmqz559kzr0sswfgq7cs35c").unwrap(); + node.bolt11_payment().send(&invoice, None).unwrap(); + + node.stop().unwrap(); +} + +fn get_sendto_address() -> Result { + let static_address = "bc1qmhwps3pz9ff0ms8gvte66l59g0zx7cvx3fw0nn"; + // let static_address = "tb1q0d40e5rta4fty63z64gztf8c3v20cvet6v2jdh"; + let unchecked_address = Address::::from_str(static_address)?; + let send_to_address = unchecked_address.assume_checked(); + + Ok(send_to_address) +} + +fn parse_and_validate_address(network: Network, addr: &str) -> Result { + // Parse the address into an unchecked state + let unchecked_address = Address::::from_str(addr)?; + + // Validate the network and convert to a checked address + let validated_address = unchecked_address.require_network(network)?; + + Ok(validated_address) + + // let address = ADDR.parse::>()?.require_network(network)?; + // Ok(address) +} diff --git a/src/payment/onchain.rs b/src/payment/onchain.rs index 793074352..b0f105681 100644 --- a/src/payment/onchain.rs +++ b/src/payment/onchain.rs @@ -82,7 +82,7 @@ impl OnchainPayment { return Err(Error::NotRunning); } - let validated_address = self.parse_and_validate_address(self.config.network, &address.to_string())?; + let validated_address = self.parse_and_validate_address(self.config.network, &address)?; let cur_anchor_reserve_sats = crate::total_anchor_channels_reserve_sats(&self.channel_manager, &self.config); @@ -92,25 +92,6 @@ impl OnchainPayment { self.wallet.send_to_address(&validated_address, send_amount, fee_rate_opt) } - /// Validates a Bitcoin address is properly formatted and matches the expected network. - /// - /// Returns `Ok(Address)` if valid, or: - /// - `InvalidAddressFormat` if malformed - /// - `InvalidNetworkAddress` if valid but wrong network - /// - /// # Example - /// ``` - /// let address = "tb1q..."; // Testnet address - /// parse_and_validate_address(Network::Testnet, address)?; - pub fn parse_and_validate_address(&self, network: Network, address: &str) -> Result { - Address::::from_str(address) - .map_err(|_| Error::InvalidAddressFormat)? - .require_network(network) - .map_err(|_| Error::InvalidNetworkAddress { - expected: network, - }) - } - /// Send an on-chain payment to the given address, draining the available funds. /// /// This is useful if you have closed all channels and want to migrate funds to another @@ -134,6 +115,8 @@ impl OnchainPayment { return Err(Error::NotRunning); } + let validated_address = self.parse_and_validate_address(self.config.network, &address)?; + let send_amount = if retain_reserves { let cur_anchor_reserve_sats = crate::total_anchor_channels_reserve_sats(&self.channel_manager, &self.config); @@ -143,6 +126,25 @@ impl OnchainPayment { }; let fee_rate_opt = maybe_map_fee_rate_opt!(fee_rate); - self.wallet.send_to_address(address, send_amount, fee_rate_opt) + self.wallet.send_to_address(&validated_address, send_amount, fee_rate_opt) + } + + /// Validates a Bitcoin address is properly formatted and matches the expected network. + /// + /// Returns `Ok(Address)` if valid, or: + /// - `InvalidAddressFormat` if malformed + /// - `InvalidNetworkAddress` if valid but wrong network + /// + /// # Example + /// ``` + /// let address = "tb1q..."; // Testnet address + /// parse_and_validate_address(Network::Testnet, address)?; + pub fn parse_and_validate_address( + &self, network: Network, address: &Address, + ) -> Result { + Address::::from_str(address.to_string().as_str()) + .map_err(|_| Error::InvalidAddress)? + .require_network(network) + .map_err(|_| Error::InvalidAddress) } } diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index bdb38278c..f9080e603 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -30,10 +30,10 @@ use lightning::util::persist::KVStore; use lightning_invoice::{Bolt11InvoiceDescription, Description}; -use bitcoin::hashes::Hash; -use bitcoin::Amount; use bitcoin::address::NetworkUnchecked; +use bitcoin::hashes::Hash; use bitcoin::Address; +use bitcoin::Amount; use lightning_invoice::{Bolt11InvoiceDescription, Description}; use log::LevelFilter; @@ -373,12 +373,15 @@ fn onchain_send_receive() { ); assert_eq!( - Err(NodeError::InvalidNetworkAddress { - expected: node_a.config().network, - }), + Err(NodeError::InvalidAddress), node_a.onchain_payment().send_to_address(&addr_c, expected_node_a_balance + 1, None) ); + assert_eq!( + Err(NodeError::InvalidAddress), + node_a.onchain_payment().send_all_to_address(&addr_c, true, None) + ); + let amount_to_send_sats = 54321; let txid = node_b.onchain_payment().send_to_address(&addr_a, amount_to_send_sats, None).unwrap(); From 636dd107f108b2b91c371ae00174c9ee2313700a Mon Sep 17 00:00:00 2001 From: Camillarhi Date: Sun, 13 Apr 2025 18:06:16 +0100 Subject: [PATCH 3/9] remove `main.rs` file --- src/main.rs | 128 ---------------------------------------------------- 1 file changed, 128 deletions(-) delete mode 100644 src/main.rs diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 227fa1a16..000000000 --- a/src/main.rs +++ /dev/null @@ -1,128 +0,0 @@ -use bitcoin::address::{NetworkUnchecked, ParseError}; -use bitcoin::Address; -use ldk_node::bitcoin::secp256k1::PublicKey; -use ldk_node::bitcoin::Network; -use ldk_node::lightning::ln::msgs::SocketAddress; -use ldk_node::lightning_invoice::Bolt11Invoice; -use ldk_node::Builder; -use std::str::FromStr; -mod error; - -pub use error::Error as NodeError; -use error::Error; - -fn main() { - let mut builder = Builder::new(); - builder.set_network(Network::Testnet); - builder.set_chain_source_esplora("https://blockstream.info/testnet/api".to_string(), None); - builder.set_gossip_source_rgs( - "https://rapidsync.lightningdevkit.org/testnet/snapshot".to_string(), - ); - - let node = builder.build().unwrap(); - - node.start().unwrap(); - - let funding_address = node.onchain_payment().new_address(); - // let funding_address = "tb1q0d40e5rta4fty63z64gztf8c3v20cvet6v2jdh"; // Testnet address - println!("Funding address: {:?}", funding_address); - // .. fund address .. - // Fund the wallet using the printed address before proceeding - println!("Please fund the wallet with at least 11000 sats (including fees)."); - println!("Press Enter to continue after funding..."); - // let mut input = String::new(); - // std::io::stdin().read_line(&mut input).unwrap(); - - // Wait for synchronization - println!("Waiting for wallet to sync with the blockchain..."); - // std::thread::sleep(std::time::Duration::from_secs(600)); // Wait for 10 minutes (600 seconds) for the wallet to sync - - // let funding_address = funding_address.unwrap(); - // node.onchain_payment().send_to_address(&funding_address, 10000, None).unwrap(); - // println!("Sent 10000 sat to funding address"); - - let balance = node.list_balances(); - println!("Wallet balance: {:?}", balance); - - let config = node.config(); - println!("Node config: {:?}", config); - - let node_id = node.node_id(); - println!("Node ID: {:?}", node_id); - - let listnening_address = node.listening_addresses(); - println!("Listening address: {:?}", listnening_address); - - //todo: delay for 10 min or more to allow real funding of a generated address - - // let node_id = PublicKey::from_str("026fa208407265cddd6f8184803e71480c3ba1d8885d9e322dba1c57bb414d1ed1").unwrap(); - // let node_addr = SocketAddress::from_str("127.0.0.1:9735").unwrap(); - let channels = node.list_channels(); - for channel in channels { - println!("Channel: {:?}", channel); - } - - let send_to_address = get_sendto_address().unwrap(); - - const ADDR: &str = "tb1q0d40e5rta4fty63z64gztf8c3v20cvet6v2jdh"; - // let val = parse_and_validate_address(node.config().network, &send_to_address.to_string()).unwrap(); - // println!("Parsed and validated address: {:?}", val); - - // let validated_address = - // unchecked_address.require_network(node.config().network).map_err(|_| Error::InvalidNetwork); - - // let tx_id = node.onchain_payment().send_to_address(&send_to_address, 300, None)?; - // println!("Transaction ID: {:?}", tx_id); - match node.onchain_payment().send_to_address(&send_to_address, 300, None) { - Ok(txid) => { - println!("Successfully sent transaction: {}", txid); - // Continue with success case - }, - // Err(Error::InvalidNetworkAddress { expected, received }) => { - // eprintln!("Error: Please use a {} address (this was a {} address)", expected, actual); - // // Handle network mismatch case - // }, - // Err(Error::InvalidAddressFormat(e)) => { - // eprintln!("Error: Invalid address format: {}", e); - // // Handle bad address case - // }, - Err(e) => { - eprintln!("Error: {}", e); - node.stop().unwrap(); - // Handle other errors - }, - } - // node.open_channel(node_id, node_addr, 10000, None, None).unwrap(); - node.stop().unwrap(); - - let event = node.wait_next_event(); - println!("EVENT: {:?}", event); - node.event_handled(); - - let invoice = Bolt11Invoice::from_str("lnbcrt1m1pn7cpr8pp57czqam62fenlx7dq9gd0zj53kymydl6kgmd8j76qeawszeagcvjqdqqcqzzsxqyz5vqsp57kzl3xlsvkx68jeegrt548tp02vrkcu7ku7x5eszwx36825kgshs9qxpqysgqlyfja29vh9m3jsu6uxrzz7htg84qygc3kgahxw2njzrmfh8htaqs0sscr86wfywq934w8sxzqwhq6hpemg7c4ufnmqz559kzr0sswfgq7cs35c").unwrap(); - node.bolt11_payment().send(&invoice, None).unwrap(); - - node.stop().unwrap(); -} - -fn get_sendto_address() -> Result { - let static_address = "bc1qmhwps3pz9ff0ms8gvte66l59g0zx7cvx3fw0nn"; - // let static_address = "tb1q0d40e5rta4fty63z64gztf8c3v20cvet6v2jdh"; - let unchecked_address = Address::::from_str(static_address)?; - let send_to_address = unchecked_address.assume_checked(); - - Ok(send_to_address) -} - -fn parse_and_validate_address(network: Network, addr: &str) -> Result { - // Parse the address into an unchecked state - let unchecked_address = Address::::from_str(addr)?; - - // Validate the network and convert to a checked address - let validated_address = unchecked_address.require_network(network)?; - - Ok(validated_address) - - // let address = ADDR.parse::>()?.require_network(network)?; - // Ok(address) -} From 2a938be9c78575b1b3f39c8367a2e0df66b21512 Mon Sep 17 00:00:00 2001 From: Camillarhi Date: Sun, 13 Apr 2025 18:06:39 +0100 Subject: [PATCH 4/9] Update error reference --- src/error.rs | 1 - src/payment/onchain.rs | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/error.rs b/src/error.rs index 9304b73b8..2cb71186d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -10,7 +10,6 @@ use bdk_chain::local_chain::CannotConnectError as BdkChainConnectionError; use bdk_chain::tx_graph::CalculateFeeError as BdkChainCalculateFeeError; use bdk_wallet::error::CreateTxError as BdkCreateTxError; use bdk_wallet::signer::SignerError as BdkSignerError; -use bitcoin::Network; use std::fmt; diff --git a/src/payment/onchain.rs b/src/payment/onchain.rs index b0f105681..ec12d1c42 100644 --- a/src/payment/onchain.rs +++ b/src/payment/onchain.rs @@ -132,8 +132,7 @@ impl OnchainPayment { /// Validates a Bitcoin address is properly formatted and matches the expected network. /// /// Returns `Ok(Address)` if valid, or: - /// - `InvalidAddressFormat` if malformed - /// - `InvalidNetworkAddress` if valid but wrong network + /// - `InvalidAddress` if malformed or for a different network /// /// # Example /// ``` From 24ee5d4f4d82289f0a921c2441423c1ac1bfd565 Mon Sep 17 00:00:00 2001 From: Camillarhi Date: Mon, 21 Apr 2025 21:12:36 +0100 Subject: [PATCH 5/9] move onchain address validation for network-specific addresses to `Wallet::send_to_address` --- src/builder.rs | 1 + src/payment/onchain.rs | 30 ++++-------------------------- src/wallet/mod.rs | 33 +++++++++++++++++++++++++++++---- 3 files changed, 34 insertions(+), 30 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 224cc9fa7..d48b71c7c 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1029,6 +1029,7 @@ fn build_with_store_internal( Arc::clone(&tx_broadcaster), Arc::clone(&fee_estimator), Arc::clone(&payment_store), + Arc::clone(&config), Arc::clone(&logger), )); diff --git a/src/payment/onchain.rs b/src/payment/onchain.rs index ec12d1c42..69ed1b365 100644 --- a/src/payment/onchain.rs +++ b/src/payment/onchain.rs @@ -13,10 +13,8 @@ use crate::logger::{log_info, LdkLogger, Logger}; use crate::types::{ChannelManager, Wallet}; use crate::wallet::OnchainSendAmount; -use bitcoin::address::NetworkUnchecked; -use bitcoin::{Address, Network, Txid}; +use bitcoin::{Address, Txid}; -use std::str::FromStr; use std::sync::{Arc, RwLock}; #[cfg(not(feature = "uniffi"))] @@ -82,14 +80,14 @@ impl OnchainPayment { return Err(Error::NotRunning); } - let validated_address = self.parse_and_validate_address(self.config.network, &address)?; + // let validated_address = self.parse_and_validate_address(self.config.network, &address)?; let cur_anchor_reserve_sats = crate::total_anchor_channels_reserve_sats(&self.channel_manager, &self.config); let send_amount = OnchainSendAmount::ExactRetainingReserve { amount_sats, cur_anchor_reserve_sats }; let fee_rate_opt = maybe_map_fee_rate_opt!(fee_rate); - self.wallet.send_to_address(&validated_address, send_amount, fee_rate_opt) + self.wallet.send_to_address(&address, send_amount, fee_rate_opt) } /// Send an on-chain payment to the given address, draining the available funds. @@ -115,8 +113,6 @@ impl OnchainPayment { return Err(Error::NotRunning); } - let validated_address = self.parse_and_validate_address(self.config.network, &address)?; - let send_amount = if retain_reserves { let cur_anchor_reserve_sats = crate::total_anchor_channels_reserve_sats(&self.channel_manager, &self.config); @@ -126,24 +122,6 @@ impl OnchainPayment { }; let fee_rate_opt = maybe_map_fee_rate_opt!(fee_rate); - self.wallet.send_to_address(&validated_address, send_amount, fee_rate_opt) - } - - /// Validates a Bitcoin address is properly formatted and matches the expected network. - /// - /// Returns `Ok(Address)` if valid, or: - /// - `InvalidAddress` if malformed or for a different network - /// - /// # Example - /// ``` - /// let address = "tb1q..."; // Testnet address - /// parse_and_validate_address(Network::Testnet, address)?; - pub fn parse_and_validate_address( - &self, network: Network, address: &Address, - ) -> Result { - Address::::from_str(address.to_string().as_str()) - .map_err(|_| Error::InvalidAddress)? - .require_network(network) - .map_err(|_| Error::InvalidAddress) + self.wallet.send_to_address(&address, send_amount, fee_rate_opt) } } diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 53de4b8d5..9ff7aa030 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -5,8 +5,10 @@ // http://opensource.org/licenses/MIT>, at your option. You may not use this file except in // accordance with one or both of these licenses. +use bitcoin::address::NetworkUnchecked; use persist::KVStoreWalletPersister; +use crate::config::Config; use crate::logger::{log_debug, log_error, log_info, log_trace, LdkLogger, Logger}; use crate::fee_estimator::{ConfirmationTarget, FeeEstimator}; @@ -43,11 +45,11 @@ use bitcoin::secp256k1::ecdh::SharedSecret; use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature}; use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey, Signing}; use bitcoin::{ - Amount, FeeRate, ScriptBuf, Transaction, TxOut, Txid, WPubkeyHash, WitnessProgram, - WitnessVersion, + Address, Amount, FeeRate, Network, ScriptBuf, Transaction, TxOut, Txid, WPubkeyHash, WitnessProgram, WitnessVersion }; use std::ops::Deref; +use std::str::FromStr; use std::sync::{Arc, Mutex}; pub(crate) enum OnchainSendAmount { @@ -71,6 +73,7 @@ where broadcaster: B, fee_estimator: E, payment_store: Arc>>, + config: Arc, logger: L, } @@ -83,11 +86,11 @@ where pub(crate) fn new( wallet: bdk_wallet::PersistedWallet, wallet_persister: KVStoreWalletPersister, broadcaster: B, fee_estimator: E, - payment_store: Arc>>, logger: L, + payment_store: Arc>>, config: Arc, logger: L, ) -> Self { let inner = Mutex::new(wallet); let persister = Mutex::new(wallet_persister); - Self { inner, persister, broadcaster, fee_estimator, payment_store, logger } + Self { inner, persister, broadcaster, fee_estimator, payment_store, config, logger } } pub(crate) fn get_full_scan_request(&self) -> FullScanRequest { @@ -327,10 +330,32 @@ where self.get_balances(total_anchor_channels_reserve_sats).map(|(_, s)| s) } + /// Validates a Bitcoin address is properly formatted and matches the expected network. + /// + /// Returns `Ok(Address)` if valid, or: + /// - `InvalidAddress` if malformed or for a different network + /// + /// # Example + /// ``` + /// let address = "tb1q..."; // Testnet address + /// parse_and_validate_address(Network::Testnet, address)?; + pub fn parse_and_validate_address( + &self, network: Network, address: &Address, + ) -> Result { + Address::::from_str(address.to_string().as_str()) + .map_err(|_| Error::InvalidAddress)? + .require_network(network) + .map_err(|_| Error::InvalidAddress) + } + + + pub(crate) fn send_to_address( &self, address: &bitcoin::Address, send_amount: OnchainSendAmount, fee_rate: Option, ) -> Result { + self.parse_and_validate_address(self.config.network, &address)?; + // Use the set fee_rate or default to fee estimation. let confirmation_target = ConfirmationTarget::OnchainPayment; let fee_rate = From 98ae1b1d00aae0c98fa7b3584b7c2da583c69884 Mon Sep 17 00:00:00 2001 From: Camillarhi Date: Mon, 21 Apr 2025 21:13:33 +0100 Subject: [PATCH 6/9] update code format --- src/wallet/mod.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 9ff7aa030..e39ed0476 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -45,7 +45,8 @@ use bitcoin::secp256k1::ecdh::SharedSecret; use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature}; use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey, Signing}; use bitcoin::{ - Address, Amount, FeeRate, Network, ScriptBuf, Transaction, TxOut, Txid, WPubkeyHash, WitnessProgram, WitnessVersion + Address, Amount, FeeRate, Network, ScriptBuf, Transaction, TxOut, Txid, WPubkeyHash, + WitnessProgram, WitnessVersion, }; use std::ops::Deref; @@ -348,8 +349,6 @@ where .map_err(|_| Error::InvalidAddress) } - - pub(crate) fn send_to_address( &self, address: &bitcoin::Address, send_amount: OnchainSendAmount, fee_rate: Option, From 8273f070e5d19c8e3da57ff7d844c7695b720965 Mon Sep 17 00:00:00 2001 From: Camillarhi Date: Mon, 21 Apr 2025 21:17:17 +0100 Subject: [PATCH 7/9] revert to original method --- src/payment/onchain.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/payment/onchain.rs b/src/payment/onchain.rs index 69ed1b365..046d66c69 100644 --- a/src/payment/onchain.rs +++ b/src/payment/onchain.rs @@ -80,14 +80,12 @@ impl OnchainPayment { return Err(Error::NotRunning); } - // let validated_address = self.parse_and_validate_address(self.config.network, &address)?; - let cur_anchor_reserve_sats = crate::total_anchor_channels_reserve_sats(&self.channel_manager, &self.config); let send_amount = OnchainSendAmount::ExactRetainingReserve { amount_sats, cur_anchor_reserve_sats }; let fee_rate_opt = maybe_map_fee_rate_opt!(fee_rate); - self.wallet.send_to_address(&address, send_amount, fee_rate_opt) + self.wallet.send_to_address(address, send_amount, fee_rate_opt) } /// Send an on-chain payment to the given address, draining the available funds. @@ -122,6 +120,6 @@ impl OnchainPayment { }; let fee_rate_opt = maybe_map_fee_rate_opt!(fee_rate); - self.wallet.send_to_address(&address, send_amount, fee_rate_opt) + self.wallet.send_to_address(address, send_amount, fee_rate_opt) } } From 1dab2349935470cb3c8504ea23ea69a3049b453b Mon Sep 17 00:00:00 2001 From: Camillarhi Date: Tue, 29 Apr 2025 15:39:36 +0100 Subject: [PATCH 8/9] Add a comment addressing invalid address --- tests/integration_tests_rust.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index f9080e603..04318f7e0 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -34,7 +34,6 @@ use bitcoin::address::NetworkUnchecked; use bitcoin::hashes::Hash; use bitcoin::Address; use bitcoin::Amount; -use lightning_invoice::{Bolt11InvoiceDescription, Description}; use log::LevelFilter; use std::str::FromStr; @@ -305,6 +304,7 @@ fn onchain_send_receive() { let addr_a = node_a.onchain_payment().new_address().unwrap(); let addr_b = node_b.onchain_payment().new_address().unwrap(); + // This is a Bitcoin Testnet address. Sending funds to this address from the Regtest network will fail let static_address = "tb1q0d40e5rta4fty63z64gztf8c3v20cvet6v2jdh"; let unchecked_address = Address::::from_str(static_address).unwrap(); let addr_c = unchecked_address.assume_checked(); From 479ef75c49e9c8a89415117432eefe7ccb21790d Mon Sep 17 00:00:00 2001 From: Camillarhi Date: Tue, 29 Apr 2025 15:41:35 +0100 Subject: [PATCH 9/9] remove `pub` maker and doc comment --- src/wallet/mod.rs | 11 +---------- tests/integration_tests_rust.rs | 2 +- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index e39ed0476..cd6217688 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -331,16 +331,7 @@ where self.get_balances(total_anchor_channels_reserve_sats).map(|(_, s)| s) } - /// Validates a Bitcoin address is properly formatted and matches the expected network. - /// - /// Returns `Ok(Address)` if valid, or: - /// - `InvalidAddress` if malformed or for a different network - /// - /// # Example - /// ``` - /// let address = "tb1q..."; // Testnet address - /// parse_and_validate_address(Network::Testnet, address)?; - pub fn parse_and_validate_address( + fn parse_and_validate_address( &self, network: Network, address: &Address, ) -> Result { Address::::from_str(address.to_string().as_str()) diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index 04318f7e0..ded88d35c 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -304,7 +304,7 @@ fn onchain_send_receive() { let addr_a = node_a.onchain_payment().new_address().unwrap(); let addr_b = node_b.onchain_payment().new_address().unwrap(); - // This is a Bitcoin Testnet address. Sending funds to this address from the Regtest network will fail + // This is a Bitcoin Testnet address. Sending funds to this address from the Regtest network will fail let static_address = "tb1q0d40e5rta4fty63z64gztf8c3v20cvet6v2jdh"; let unchecked_address = Address::::from_str(static_address).unwrap(); let addr_c = unchecked_address.assume_checked();