diff --git a/src/builder.rs b/src/builder.rs index 2dd542ba3e..73f9dd17d4 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -2743,6 +2743,100 @@ mod tests { assert!(matches!(result, Err(BuildError::ReadFailed))); } + #[test] + fn unstarted_node_rejects_live_node_apis() { + use std::str::FromStr; + + use bitcoin::secp256k1::PublicKey; + use lightning::ln::channelmanager::PaymentId; + use lightning::ln::msgs::SocketAddress; + use lightning_types::payment::PaymentPreimage; + + use crate::{Error, UserChannelId}; + + let builder = NodeBuilder::new(); + let logger = Arc::new(Logger::new_log_facade()); + #[cfg(not(feature = "uniffi"))] + let node_entropy = NodeEntropy::from_seed_bytes([42; 64]); + #[cfg(feature = "uniffi")] + let node_entropy = NodeEntropy::from_seed_bytes(vec![42; 64]).unwrap(); + let node = builder + .build_with_store_and_logger(node_entropy, InMemoryStore::new(), logger) + .unwrap(); + + let node_id = PublicKey::from_str( + "0276607124ebe6a6c9338517b6f485825b27c2dcc0b9fc2aa6a4c0df91194e5993", + ) + .unwrap(); + let payment_id = PaymentId([0; 32]); + let user_channel_id = UserChannelId(0); + + assert_eq!(node.onchain_payment().bump_fee_rbf(payment_id, None), Err(Error::NotRunning)); + assert_eq!(node.close_channel(&user_channel_id, node_id), Err(Error::NotRunning)); + assert_eq!( + node.force_close_channel(&user_channel_id, node_id, None), + Err(Error::NotRunning) + ); + assert_eq!( + node.update_channel_config(&user_channel_id, node_id, Default::default()), + Err(Error::NotRunning) + ); + assert_eq!(node.splice_in(&user_channel_id, node_id, 1_000), Err(Error::NotRunning)); + assert_eq!( + node.bump_channel_funding_fee(&user_channel_id, node_id), + Err(Error::NotRunning) + ); + assert_eq!(node.bolt11_payment().fail_for_id(payment_id), Err(Error::NotRunning)); + assert_eq!( + node.bolt11_payment().claim_for_id(payment_id, 0, PaymentPreimage([0; 32])), + Err(Error::NotRunning) + ); + + let address = SocketAddress::from_str("127.0.0.1:9735").unwrap(); + assert_eq!( + node.open_channel(node_id, address.clone(), 1_000, None, None, None), + Err(Error::NotRunning) + ); + assert_eq!( + node.open_channel_with_all(node_id, address.clone(), None, None, None), + Err(Error::NotRunning) + ); + assert_eq!( + node.open_announced_channel(node_id, address.clone(), 1_000, None, None, None), + Err(Error::NotRunning) + ); + assert_eq!( + node.open_announced_channel_with_all(node_id, address.clone(), None, None, None), + Err(Error::NotRunning) + ); + assert_eq!( + node.open_0reserve_channel(node_id, address.clone(), 1_000, None, None, None), + Err(Error::NotRunning) + ); + assert_eq!( + node.open_0reserve_channel_with_all(node_id, address, None, None, None), + Err(Error::NotRunning) + ); + + // Async receive state is persisted via the background processor. + assert_eq!(node.bolt12_payment().receive_async().err(), Some(Error::NotRunning)); + #[cfg(not(feature = "uniffi"))] + let paths = Vec::new(); + #[cfg(feature = "uniffi")] + let paths = Vec::::new(); + assert_eq!( + node.bolt12_payment().set_paths_to_static_invoice_server(paths), + Err(Error::NotRunning) + ); + + // The running state is checked before any URI or HRN resolution happens. + #[cfg(feature = "unified-payments")] + { + let res = node.runtime.block_on(node.unified_payment().send("not a uri", None, None)); + assert_eq!(res.err(), Some(Error::NotRunning)); + } + } + #[test] fn sanitize_empty_node_alias() { // Empty node alias diff --git a/src/lib.rs b/src/lib.rs index e738c27207..eb5d9d9491 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1175,6 +1175,7 @@ impl Node { self.bolt11_payment().into(), self.bolt12_payment().into(), Arc::clone(&self.config), + Arc::clone(&self.is_running), Arc::clone(&self.logger), self.hrn_resolver.clone(), ) @@ -1199,6 +1200,7 @@ impl Node { self.bolt11_payment(), self.bolt12_payment(), Arc::clone(&self.config), + Arc::clone(&self.is_running), Arc::clone(&self.logger), self.hrn_resolver.clone(), )) @@ -1254,6 +1256,7 @@ impl Node { Arc::clone(&self.wallet), Arc::clone(&self.connection_manager), Arc::clone(&self.liquidity_source), + Arc::clone(&self.is_running), Arc::clone(&self.logger), ) } @@ -1266,6 +1269,7 @@ impl Node { Arc::clone(&self.wallet), Arc::clone(&self.connection_manager), Arc::clone(&self.liquidity_source), + Arc::clone(&self.is_running), Arc::clone(&self.logger), )) } @@ -1538,6 +1542,10 @@ impl Node { push_to_counterparty_msat: Option, fee_rate: Option, channel_config: Option, ) -> Result { + if !*self.is_running.read().expect("lock") { + return Err(Error::NotRunning); + } + let fee_rate_opt = maybe_map_fee_rate_opt(fee_rate); self.open_channel_inner( node_id, @@ -1580,6 +1588,10 @@ impl Node { push_to_counterparty_msat: Option, fee_rate: Option, channel_config: Option, ) -> Result { + if !*self.is_running.read().expect("lock") { + return Err(Error::NotRunning); + } + if let Err(err) = may_announce_channel(&self.config) { log_error!(self.logger, "Failed to open announced channel as the node hasn't been sufficiently configured to act as a forwarding node: {}", err); return Err(Error::ChannelCreationFailed); @@ -1619,6 +1631,10 @@ impl Node { &self, node_id: PublicKey, address: SocketAddress, push_to_counterparty_msat: Option, fee_rate: Option, channel_config: Option, ) -> Result { + if !*self.is_running.read().expect("lock") { + return Err(Error::NotRunning); + } + let fee_rate_opt = maybe_map_fee_rate_opt(fee_rate); self.open_channel_inner( node_id, @@ -1657,6 +1673,10 @@ impl Node { &self, node_id: PublicKey, address: SocketAddress, push_to_counterparty_msat: Option, fee_rate: Option, channel_config: Option, ) -> Result { + if !*self.is_running.read().expect("lock") { + return Err(Error::NotRunning); + } + if let Err(err) = may_announce_channel(&self.config) { log_error!(self.logger, "Failed to open announced channel as the node hasn't been sufficiently configured to act as a forwarding node: {err}"); return Err(Error::ChannelCreationFailed); @@ -1702,6 +1722,10 @@ impl Node { push_to_counterparty_msat: Option, fee_rate: Option, channel_config: Option, ) -> Result { + if !*self.is_running.read().expect("lock") { + return Err(Error::NotRunning); + } + let fee_rate_opt = maybe_map_fee_rate_opt(fee_rate); self.open_channel_inner( node_id, @@ -1736,6 +1760,10 @@ impl Node { &self, node_id: PublicKey, address: SocketAddress, push_to_counterparty_msat: Option, fee_rate: Option, channel_config: Option, ) -> Result { + if !*self.is_running.read().expect("lock") { + return Err(Error::NotRunning); + } + let fee_rate_opt = maybe_map_fee_rate_opt(fee_rate); self.open_channel_inner( node_id, @@ -1893,6 +1921,10 @@ impl Node { &self, user_channel_id: &UserChannelId, counterparty_node_id: PublicKey, splice_amount_sats: u64, ) -> Result<(), Error> { + if !*self.is_running.read().expect("lock") { + return Err(Error::NotRunning); + } + self.splice_in_inner( user_channel_id, counterparty_node_id, @@ -1916,6 +1948,9 @@ impl Node { pub fn splice_in_with_all( &self, user_channel_id: &UserChannelId, counterparty_node_id: PublicKey, ) -> Result<(), Error> { + if !*self.is_running.read().expect("lock") { + return Err(Error::NotRunning); + } self.splice_in_inner(user_channel_id, counterparty_node_id, FundingAmount::Max) } @@ -1934,6 +1969,10 @@ impl Node { &self, user_channel_id: &UserChannelId, counterparty_node_id: PublicKey, address: &Address, splice_amount_sats: u64, ) -> Result<(), Error> { + if !*self.is_running.read().expect("lock") { + return Err(Error::NotRunning); + } + let open_channels = self.channel_manager.list_channels_with_counterparty(&counterparty_node_id); if let Some(channel_details) = @@ -2015,6 +2054,10 @@ impl Node { pub fn bump_channel_funding_fee( &self, user_channel_id: &UserChannelId, counterparty_node_id: PublicKey, ) -> Result<(), Error> { + if !*self.is_running.read().expect("lock") { + return Err(Error::NotRunning); + } + let open_channels = self.channel_manager.list_channels_with_counterparty(&counterparty_node_id); if let Some(channel_details) = @@ -2130,6 +2173,9 @@ impl Node { pub fn close_channel( &self, user_channel_id: &UserChannelId, counterparty_node_id: PublicKey, ) -> Result<(), Error> { + if !*self.is_running.read().expect("lock") { + return Err(Error::NotRunning); + } self.close_channel_internal(user_channel_id, counterparty_node_id, false, None) } @@ -2150,6 +2196,9 @@ impl Node { &self, user_channel_id: &UserChannelId, counterparty_node_id: PublicKey, reason: Option, ) -> Result<(), Error> { + if !*self.is_running.read().expect("lock") { + return Err(Error::NotRunning); + } self.close_channel_internal(user_channel_id, counterparty_node_id, true, reason) } @@ -2210,6 +2259,10 @@ impl Node { &self, user_channel_id: &UserChannelId, counterparty_node_id: PublicKey, channel_config: ChannelConfig, ) -> Result<(), Error> { + if !*self.is_running.read().expect("lock") { + return Err(Error::NotRunning); + } + let open_channels: Vec = self.channel_manager.list_channels_with_counterparty(&counterparty_node_id); if let Some(channel_details) = diff --git a/src/liquidity/client/lsps1.rs b/src/liquidity/client/lsps1.rs index d6549ccbf8..79dc655fe6 100644 --- a/src/liquidity/client/lsps1.rs +++ b/src/liquidity/client/lsps1.rs @@ -463,6 +463,7 @@ pub struct LSPS1Liquidity { wallet: Arc, connection_manager: Arc>>, liquidity_source: Arc>>, + is_running: Arc>, logger: Arc, } @@ -470,9 +471,10 @@ impl LSPS1Liquidity { pub(crate) fn new( runtime: Arc, wallet: Arc, connection_manager: Arc>>, - liquidity_source: Arc>>, logger: Arc, + liquidity_source: Arc>>, is_running: Arc>, + logger: Arc, ) -> Self { - Self { runtime, wallet, connection_manager, liquidity_source, logger } + Self { runtime, wallet, connection_manager, liquidity_source, is_running, logger } } } @@ -489,6 +491,10 @@ impl LSPS1Liquidity { &self, lsp_balance_sat: u64, client_balance_sat: u64, channel_expiry_blocks: u32, announce_channel: bool, node_id: Option, ) -> Result { + if !*self.is_running.read().expect("lock") { + return Err(Error::NotRunning); + } + let lsps1_node = self .runtime .block_on(async { self.liquidity_source.get_lsps1_node(node_id.as_ref()).await })?; @@ -528,6 +534,10 @@ impl LSPS1Liquidity { pub fn check_order_status( &self, order_id: LSPS1OrderId, lsp_node_id: PublicKey, ) -> Result { + if !*self.is_running.read().expect("lock") { + return Err(Error::NotRunning); + } + let lsps1_node = self .runtime .block_on(async { self.liquidity_source.get_lsps1_node(Some(&lsp_node_id)).await })?; diff --git a/src/liquidity/mod.rs b/src/liquidity/mod.rs index ffc1f878bc..c1442b0fc9 100644 --- a/src/liquidity/mod.rs +++ b/src/liquidity/mod.rs @@ -134,6 +134,7 @@ pub struct Liquidity { wallet: Arc, connection_manager: Arc>>, liquidity_source: Arc>>, + is_running: Arc>, logger: Arc, } @@ -141,9 +142,10 @@ impl Liquidity { pub(crate) fn new( runtime: Arc, wallet: Arc, connection_manager: Arc>>, - liquidity_source: Arc>>, logger: Arc, + liquidity_source: Arc>>, is_running: Arc>, + logger: Arc, ) -> Self { - Self { runtime, wallet, connection_manager, liquidity_source, logger } + Self { runtime, wallet, connection_manager, liquidity_source, is_running, logger } } } @@ -159,6 +161,10 @@ impl Liquidity { &self, node_id: PublicKey, address: SocketAddress, token: Option, trust_peer_0conf: bool, ) -> Result<(), Error> { + if !*self.is_running.read().expect("lock") { + return Err(Error::NotRunning); + } + { let mut lsp_nodes = self.liquidity_source.lsp_nodes.write().expect("lock"); if lsp_nodes.iter().any(|n| n.node_id == node_id) { @@ -212,6 +218,7 @@ impl Liquidity { Arc::clone(&self.wallet), Arc::clone(&self.connection_manager), self.liquidity_source.lsps1_client(), + Arc::clone(&self.is_running), Arc::clone(&self.logger), ) } diff --git a/src/payment/bolt11.rs b/src/payment/bolt11.rs index 5f0a70c2c6..4d2d210a7f 100644 --- a/src/payment/bolt11.rs +++ b/src/payment/bolt11.rs @@ -423,6 +423,10 @@ impl Bolt11Payment { pub fn claim_for_id( &self, payment_id: PaymentId, claimable_amount_msat: u64, preimage: PaymentPreimage, ) -> Result<(), Error> { + if !*self.is_running.read().expect("lock") { + return Err(Error::NotRunning); + } + let details = self.runtime.block_on(self.payment_store.get(&payment_id))?.ok_or_else(|| { log_error!( @@ -489,6 +493,10 @@ impl Bolt11Payment { /// /// [`PaymentClaimable`]: crate::Event::PaymentClaimable pub fn fail_for_id(&self, payment_id: PaymentId) -> Result<(), Error> { + if !*self.is_running.read().expect("lock") { + return Err(Error::NotRunning); + } + let details = self.runtime.block_on(self.payment_store.get(&payment_id))?.ok_or_else(|| { log_error!( @@ -637,6 +645,9 @@ impl Bolt11Payment { &self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32, max_total_lsp_fee_limit_msat: Option, ) -> Result { + if !*self.is_running.read().expect("lock") { + return Err(Error::NotRunning); + } let description = maybe_try_convert_enum(description)?; let invoice = self.receive_via_jit_channel_inner( Some(amount_msat), @@ -682,6 +693,9 @@ impl Bolt11Payment { &self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32, max_total_lsp_fee_limit_msat: Option, payment_hash: PaymentHash, ) -> Result { + if !*self.is_running.read().expect("lock") { + return Err(Error::NotRunning); + } let description = maybe_try_convert_enum(description)?; let invoice = self.receive_via_jit_channel_inner( Some(amount_msat), @@ -709,6 +723,9 @@ impl Bolt11Payment { &self, description: &Bolt11InvoiceDescription, expiry_secs: u32, max_proportional_lsp_fee_limit_ppm_msat: Option, ) -> Result { + if !*self.is_running.read().expect("lock") { + return Err(Error::NotRunning); + } let description = maybe_try_convert_enum(description)?; let invoice = self.receive_via_jit_channel_inner( None, @@ -755,6 +772,9 @@ impl Bolt11Payment { &self, description: &Bolt11InvoiceDescription, expiry_secs: u32, max_proportional_lsp_fee_limit_ppm_msat: Option, payment_hash: PaymentHash, ) -> Result { + if !*self.is_running.read().expect("lock") { + return Err(Error::NotRunning); + } let description = maybe_try_convert_enum(description)?; let invoice = self.receive_via_jit_channel_inner( None, diff --git a/src/payment/bolt12.rs b/src/payment/bolt12.rs index 0e994d67ac..dcac54c172 100644 --- a/src/payment/bolt12.rs +++ b/src/payment/bolt12.rs @@ -569,6 +569,10 @@ impl Bolt12Payment { &self, amount_msat: u64, expiry_secs: u32, quantity: Option, payer_note: Option, route_parameters: Option, ) -> Result { + if !*self.is_running.read().expect("lock") { + return Err(Error::NotRunning); + } + let payment_id = PaymentId(self.keys_manager.get_secure_random_bytes()); let absolute_expiry = (SystemTime::now() + Duration::from_secs(expiry_secs as u64)) @@ -640,6 +644,10 @@ impl Bolt12Payment { /// [`StaticInvoice`]: lightning::offers::static_invoice::StaticInvoice /// [`Offer`]: lightning::offers::offer::Offer pub fn receive_async(&self) -> Result { + if !*self.is_running.read().expect("lock") { + return Err(Error::NotRunning); + } + self.channel_manager .get_async_receive_offer() .map(maybe_wrap) @@ -659,6 +667,10 @@ impl Bolt12Payment { pub fn set_paths_to_static_invoice_server( &self, paths: Vec, ) -> Result<(), Error> { + if !*self.is_running.read().expect("lock") { + return Err(Error::NotRunning); + } + self.channel_manager .set_paths_to_static_invoice_server(paths) .or(Err(Error::InvalidBlindedPaths)) @@ -689,6 +701,10 @@ impl Bolt12Payment { /// [`Offer`]: lightning::offers::offer::Offer /// [`StaticInvoice`]: lightning::offers::static_invoice::StaticInvoice pub fn set_paths_to_static_invoice_server(&self, paths: Vec) -> Result<(), Error> { + if !*self.is_running.read().expect("lock") { + return Err(Error::NotRunning); + } + let decoded_paths = as Readable>::read(&mut &paths[..]) .or(Err(Error::InvalidBlindedPaths))?; diff --git a/src/payment/onchain.rs b/src/payment/onchain.rs index 141b2e3b23..98f5240220 100644 --- a/src/payment/onchain.rs +++ b/src/payment/onchain.rs @@ -79,6 +79,10 @@ impl OnchainPayment { pub fn send_to_address( &self, address: &bitcoin::Address, amount_sats: u64, fee_rate: Option, ) -> Result { + if !*self.is_running.read().expect("lock") { + return Err(Error::NotRunning); + } + let fee_rate_opt = maybe_map_fee_rate_opt(fee_rate); self.runtime.block_on(self.send_to_address_inner(address, amount_sats, fee_rate_opt)) } @@ -134,6 +138,10 @@ impl OnchainPayment { pub fn bump_fee_rbf( &self, payment_id: PaymentId, fee_rate: Option, ) -> Result { + if !*self.is_running.read().expect("lock") { + return Err(Error::NotRunning); + } + let cur_anchor_reserve_sats = crate::total_anchor_channels_reserve_sats(&self.channel_manager, &self.config); let fee_rate_opt = maybe_map_fee_rate_opt(fee_rate); diff --git a/src/payment/unified.rs b/src/payment/unified.rs index 0eb4f41a28..1595427f9e 100644 --- a/src/payment/unified.rs +++ b/src/payment/unified.rs @@ -14,7 +14,7 @@ //! [BIP 353]: https://github.com/bitcoin/bips/blob/master/bip-0353.mediawiki //! [BOLT 11]: https://github.com/lightning/bolts/blob/master/11-payment-encoding.md //! [BOLT 12]: https://github.com/lightning/bolts/blob/master/12-offer-encoding.md -use std::sync::Arc; +use std::sync::{Arc, RwLock}; use std::time::Duration; use std::vec::IntoIter; @@ -73,6 +73,7 @@ pub struct UnifiedPayment { bolt12_payment: Arc, #[cfg(not(hrn_tests))] config: Arc, + is_running: Arc>, logger: Arc, hrn_resolver: HRNResolver, #[cfg(hrn_tests)] @@ -82,8 +83,8 @@ pub struct UnifiedPayment { impl UnifiedPayment { pub(crate) fn new( onchain_payment: Arc, bolt11_invoice: Arc, - bolt12_payment: Arc, config: Arc, logger: Arc, - hrn_resolver: HRNResolver, + bolt12_payment: Arc, config: Arc, is_running: Arc>, + logger: Arc, hrn_resolver: HRNResolver, ) -> Self { #[cfg(hrn_tests)] let _ = config; @@ -94,6 +95,7 @@ impl UnifiedPayment { bolt12_payment, #[cfg(not(hrn_tests))] config, + is_running, logger, hrn_resolver, #[cfg(hrn_tests)] @@ -189,6 +191,10 @@ impl UnifiedPayment { &self, uri_str: &str, amount_msat: Option, route_parameters: Option, ) -> Result { + if !*self.is_running.read().expect("lock") { + return Err(Error::NotRunning); + } + let target_network; #[cfg(hrn_tests)] @@ -321,6 +327,10 @@ impl UnifiedPayment { log_error!(self.logger, "Failed to send BOLT12 offer: DuplicatePayment. This is part of a unified payment. Aborting to avoid duplicate payment."); return Err(Error::DuplicatePayment); }, + // There is nothing to fall back to if the node isn't running. + Err(Error::NotRunning) => { + return Err(Error::NotRunning); + }, // A persistence failure may occur after the Lightning payment has // already been initiated with the ChannelManager. Falling back to // the BOLT11 invoice in that case would double-pay, so we abort @@ -348,6 +358,10 @@ impl UnifiedPayment { log_error!(self.logger, "Failed to send BOLT11 invoice: DuplicatePayment. This is part of a unified payment. Aborting to avoid duplicate payment."); return Err(Error::DuplicatePayment); }, + // There is nothing to fall back to if the node isn't running. + Err(Error::NotRunning) => { + return Err(Error::NotRunning); + }, // A persistence failure may occur after the Lightning payment has // already been initiated with the ChannelManager. Falling back to // the on-chain method in that case would double-pay, so we abort diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index e33682dea0..b21b749acf 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -4289,6 +4289,108 @@ async fn spontaneous_send_with_custom_preimage() { } } +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn live_node_apis_return_not_running_after_stop() { + let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); + let chain_source = random_chain_source(&bitcoind, &electrsd); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, false); + + let address_a = node_a.onchain_payment().new_address().unwrap(); + premine_and_distribute_funds( + &bitcoind.client, + &electrsd.client, + vec![address_a], + Amount::from_sat(1_000_000), + ) + .await; + node_a.sync_wallets().unwrap(); + + let description = + Bolt11InvoiceDescription::Direct(Description::new(String::from("test")).unwrap()); + let invoice = node_b.bolt11_payment().receive(1_000_000, &description.clone().into(), 3600); + let invoice = invoice.unwrap(); + let node_b_id = node_b.node_id(); + let node_b_addr = node_b.listening_addresses().unwrap().first().unwrap().clone(); + let btc_address = node_b.onchain_payment().new_address().unwrap(); + + node_a.stop().unwrap(); + + let payment_id = PaymentId([0; 32]); + let preimage = PaymentPreimage([0; 32]); + let user_channel_id = ldk_node::UserChannelId(0); + + // On-chain. + assert_eq!( + node_a.onchain_payment().send_to_address(&btc_address, 10_000, None), + Err(NodeError::NotRunning) + ); + assert_eq!( + node_a.onchain_payment().send_all_to_address(&btc_address, true, None), + Err(NodeError::NotRunning) + ); + assert_eq!(node_a.onchain_payment().bump_fee_rbf(payment_id, None), Err(NodeError::NotRunning)); + + // Channels. + assert_eq!( + node_a.open_channel(node_b_id, node_b_addr.clone(), 100_000, None, None, None), + Err(NodeError::NotRunning) + ); + assert_eq!(node_a.splice_in(&user_channel_id, node_b_id, 10_000), Err(NodeError::NotRunning)); + assert_eq!( + node_a.splice_out(&user_channel_id, node_b_id, &btc_address, 10_000), + Err(NodeError::NotRunning) + ); + assert_eq!( + node_a.bump_channel_funding_fee(&user_channel_id, node_b_id), + Err(NodeError::NotRunning) + ); + assert_eq!(node_a.close_channel(&user_channel_id, node_b_id), Err(NodeError::NotRunning)); + assert_eq!( + node_a.force_close_channel(&user_channel_id, node_b_id, None), + Err(NodeError::NotRunning) + ); + assert_eq!( + node_a.update_channel_config(&user_channel_id, node_b_id, Default::default()), + Err(NodeError::NotRunning) + ); + assert_eq!(node_a.connect(node_b_id, node_b_addr.clone(), false), Err(NodeError::NotRunning)); + assert_eq!(node_a.sync_wallets(), Err(NodeError::NotRunning)); + + // Payments. + assert_eq!(node_a.bolt11_payment().send(&invoice, None), Err(NodeError::NotRunning)); + assert_eq!( + node_a.bolt11_payment().claim_for_id(payment_id, 0, preimage), + Err(NodeError::NotRunning) + ); + assert_eq!(node_a.bolt11_payment().fail_for_id(payment_id), Err(NodeError::NotRunning)); + assert_eq!( + node_a.bolt11_payment().receive_via_jit_channel(1_000_000, &description.into(), 3600, None), + Err(NodeError::NotRunning) + ); + assert_eq!( + node_a.bolt12_payment().initiate_refund(1_000, 3600, None, None, None).map(|_| ()), + Err(NodeError::NotRunning) + ); + assert_eq!( + node_a.spontaneous_payment().send(1_000, node_b_id, None), + Err(NodeError::NotRunning) + ); + + // Liquidity. + assert_eq!( + node_a.liquidity().add_liquidity_source(node_b_id, node_b_addr, None, false), + Err(NodeError::NotRunning) + ); + + // The node is fully functional again after a restart. + node_a.start().unwrap(); + node_a.sync_wallets().unwrap(); + assert!(node_a.onchain_payment().send_to_address(&btc_address, 10_000, None).is_ok()); + + node_a.stop().unwrap(); + node_b.stop().unwrap(); +} + #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn drop_in_async_context() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();