diff --git a/lightning-net-tokio/src/lib.rs b/lightning-net-tokio/src/lib.rs index 4306e192180..89ac7a52ec2 100644 --- a/lightning-net-tokio/src/lib.rs +++ b/lightning-net-tokio/src/lib.rs @@ -786,6 +786,7 @@ mod tests { fn get_chain_hashes(&self) -> Option> { Some(vec![ChainHash::using_genesis_block(Network::Testnet)]) } + fn message_received(&self) {} } impl MessageSendEventsProvider for MsgHandler { fn get_and_clear_pending_msg_events(&self) -> Vec { diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 6414d8a2836..5f8dc1e5541 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -61,11 +61,11 @@ use crate::ln::onion_utils::{HTLCFailReason, INVALID_ONION_BLINDING}; use crate::ln::msgs::{ChannelMessageHandler, DecodeError, LightningError}; #[cfg(test)] use crate::ln::outbound_payment; -use crate::ln::outbound_payment::{OutboundPayments, PaymentAttempts, PendingOutboundPayment, SendAlongPathArgs, StaleExpiration}; +use crate::ln::outbound_payment::{OutboundPayments, PaymentAttempts, PendingOutboundPayment, RetryableInvoiceRequest, SendAlongPathArgs, StaleExpiration}; use crate::ln::wire::Encode; use crate::offers::invoice::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder, UnsignedBolt12Invoice}; use crate::offers::invoice_error::InvoiceError; -use crate::offers::invoice_request::{DerivedPayerId, InvoiceRequestBuilder}; +use crate::offers::invoice_request::{DerivedPayerId, InvoiceRequest, InvoiceRequestBuilder}; use crate::offers::nonce::Nonce; use crate::offers::offer::{Offer, OfferBuilder}; use crate::offers::parse::Bolt12SemanticError; @@ -3105,7 +3105,7 @@ where outbound_scid_aliases: Mutex::new(new_hash_set()), pending_inbound_payments: Mutex::new(new_hash_map()), - pending_outbound_payments: OutboundPayments::new(), + pending_outbound_payments: OutboundPayments::new(new_hash_map()), forward_htlcs: Mutex::new(new_hash_map()), decode_update_add_htlcs: Mutex::new(new_hash_map()), claimable_payments: Mutex::new(ClaimablePayments { claimable_payments: new_hash_map(), pending_claiming_payments: new_hash_map() }), @@ -9005,7 +9005,7 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => { let expiration = StaleExpiration::AbsoluteTimeout(absolute_expiry); $self.pending_outbound_payments .add_new_awaiting_invoice( - payment_id, expiration, retry_strategy, max_total_routing_fee_msat, + payment_id, expiration, retry_strategy, max_total_routing_fee_msat, None, ) .map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?; @@ -9131,17 +9131,30 @@ where let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); let expiration = StaleExpiration::TimerTicks(1); + let retryable_invoice_request = RetryableInvoiceRequest { + invoice_request: invoice_request.clone(), + nonce, + }; self.pending_outbound_payments .add_new_awaiting_invoice( - payment_id, expiration, retry_strategy, max_total_routing_fee_msat + payment_id, expiration, retry_strategy, max_total_routing_fee_msat, + Some(retryable_invoice_request) ) .map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?; + self.enqueue_invoice_request(invoice_request, reply_paths) + } + + fn enqueue_invoice_request( + &self, + invoice_request: InvoiceRequest, + reply_paths: Vec, + ) -> Result<(), Bolt12SemanticError> { let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap(); - if !offer.paths().is_empty() { + if !invoice_request.paths().is_empty() { reply_paths .iter() - .flat_map(|reply_path| offer.paths().iter().map(move |path| (path, reply_path))) + .flat_map(|reply_path| invoice_request.paths().iter().map(move |path| (path, reply_path))) .take(OFFERS_MESSAGE_REQUEST_LIMIT) .for_each(|(path, reply_path)| { let instructions = MessageSendInstructions::WithSpecifiedReplyPath { @@ -9151,7 +9164,7 @@ where let message = OffersMessage::InvoiceRequest(invoice_request.clone()); pending_offers_messages.push((message, instructions)); }); - } else if let Some(signing_pubkey) = offer.signing_pubkey() { + } else if let Some(signing_pubkey) = invoice_request.signing_pubkey() { for reply_path in reply_paths { let instructions = MessageSendInstructions::WithSpecifiedReplyPath { destination: Destination::Node(signing_pubkey), @@ -10811,6 +10824,39 @@ where "Dual-funded channels not supported".to_owned(), msg.channel_id.clone())), counterparty_node_id); } + + fn message_received(&self) { + for (payment_id, retryable_invoice_request) in self + .pending_outbound_payments + .release_invoice_requests_awaiting_invoice() + { + let RetryableInvoiceRequest { invoice_request, nonce } = retryable_invoice_request; + let hmac = payment_id.hmac_for_offer_payment(nonce, &self.inbound_payment_key); + let context = OffersContext::OutboundPayment { + payment_id, + nonce, + hmac: Some(hmac) + }; + match self.create_blinded_paths(context) { + Ok(reply_paths) => match self.enqueue_invoice_request(invoice_request, reply_paths) { + Ok(_) => {} + Err(_) => { + log_warn!(self.logger, + "Retry failed for an invoice request with payment_id: {}", + payment_id + ); + } + }, + Err(_) => { + log_warn!(self.logger, + "Retry failed for an invoice request with payment_id: {}. \ + Reason: router could not find a blinded path to include as the reply path", + payment_id + ); + } + } + } + } } impl @@ -12227,10 +12273,7 @@ where } pending_outbound_payments = Some(outbounds); } - let pending_outbounds = OutboundPayments { - pending_outbound_payments: Mutex::new(pending_outbound_payments.unwrap()), - retry_lock: Mutex::new(()) - }; + let pending_outbounds = OutboundPayments::new(pending_outbound_payments.unwrap()); // We have to replay (or skip, if they were completed after we wrote the `ChannelManager`) // each `ChannelMonitorUpdate` in `in_flight_monitor_updates`. After doing so, we have to diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 72646bf5d4a..5c536a09dc4 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -1605,6 +1605,14 @@ pub trait ChannelMessageHandler : MessageSendEventsProvider { /// If it's `None`, then no particular network chain hash compatibility will be enforced when /// connecting to peers. fn get_chain_hashes(&self) -> Option>; + + /// Indicates that a message was received from any peer for any handler. + /// Called before the message is passed to the appropriate handler. + /// Useful for indicating that a network connection is active. + /// + /// Note: Since this function is called frequently, it should be as + /// efficient as possible for its intended purpose. + fn message_received(&self); } /// A trait to describe an object which can receive routing messages. diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index 689441b81fa..3b9317e4674 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -1070,6 +1070,78 @@ fn send_invoice_for_refund_with_distinct_reply_path() { assert_eq!(reply_path.introduction_node(), &IntroductionNode::NodeId(nodes[6].node.get_our_node_id())); } +/// Verifies that the invoice request message can be retried if it fails to reach the +/// payee on the first attempt. +#[test] +fn creates_and_pays_for_offer_with_retry() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + + let alice = &nodes[0]; + let alice_id = alice.node.get_our_node_id(); + let bob = &nodes[1]; + let bob_id = bob.node.get_our_node_id(); + + let offer = alice.node + .create_offer_builder(None).unwrap() + .amount_msats(10_000_000) + .build().unwrap(); + assert_ne!(offer.signing_pubkey(), Some(alice_id)); + assert!(!offer.paths().is_empty()); + for path in offer.paths() { + assert_eq!(path.introduction_node(), &IntroductionNode::NodeId(alice_id)); + } + let payment_id = PaymentId([1; 32]); + bob.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None).unwrap(); + expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); + + let _lost_onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); + + // Simulate a scenario where the original onion_message is lost before reaching Alice. + // Use handle_message_received to regenerate the message. + bob.node.message_received(); + let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); + + alice.onion_messenger.handle_onion_message(bob_id, &onion_message); + + let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message); + let payment_context = PaymentContext::Bolt12Offer(Bolt12OfferContext { + offer_id: offer.id(), + invoice_request: InvoiceRequestFields { + payer_id: invoice_request.payer_id(), + quantity: None, + payer_note_truncated: None, + }, + }); + assert_eq!(invoice_request.amount_msats(), None); + assert_ne!(invoice_request.payer_id(), bob_id); + assert_eq!(reply_path.introduction_node(), &IntroductionNode::NodeId(bob_id)); + let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap(); + bob.onion_messenger.handle_onion_message(alice_id, &onion_message); + + // Expect no more OffersMessage to be enqueued by this point, even after calling + // handle_message_received. + bob.node.message_received(); + + assert!(bob.onion_messenger.next_onion_message_for_peer(alice_id).is_none()); + + let (invoice, _) = extract_invoice(bob, &onion_message); + assert_eq!(invoice.amount_msats(), 10_000_000); + assert_ne!(invoice.signing_pubkey(), alice_id); + assert!(!invoice.payment_paths().is_empty()); + for path in invoice.payment_paths() { + assert_eq!(path.introduction_node(), &IntroductionNode::NodeId(alice_id)); + } + route_bolt12_payment(bob, &[alice], &invoice); + expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id); + claim_bolt12_payment(bob, &[alice], payment_context); + expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id); +} + /// Checks that a deferred invoice can be paid asynchronously from an Event::InvoiceReceived. #[test] fn pays_bolt12_invoice_asynchronously() { diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index 69e97aa3573..ca0d7c17d99 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -22,6 +22,8 @@ use crate::ln::features::Bolt12InvoiceFeatures; use crate::ln::onion_utils; use crate::ln::onion_utils::{DecodedOnionFailure, HTLCFailReason}; use crate::offers::invoice::Bolt12Invoice; +use crate::offers::invoice_request::InvoiceRequest; +use crate::offers::nonce::Nonce; use crate::routing::router::{BlindedTail, InFlightHtlcs, Path, PaymentParameters, Route, RouteParameters, Router}; use crate::sign::{EntropySource, NodeSigner, Recipient}; use crate::util::errors::APIError; @@ -32,6 +34,7 @@ use crate::util::ser::ReadableArgs; use core::fmt::{self, Display, Formatter}; use core::ops::Deref; +use core::sync::atomic::{AtomicBool, Ordering}; use core::time::Duration; use crate::prelude::*; @@ -53,6 +56,7 @@ pub(crate) enum PendingOutboundPayment { expiration: StaleExpiration, retry_strategy: Retry, max_total_routing_fee_msat: Option, + retryable_invoice_request: Option }, InvoiceReceived { payment_hash: PaymentHash, @@ -100,6 +104,16 @@ pub(crate) enum PendingOutboundPayment { }, } +pub(crate) struct RetryableInvoiceRequest { + pub(crate) invoice_request: InvoiceRequest, + pub(crate) nonce: Nonce, +} + +impl_writeable_tlv_based!(RetryableInvoiceRequest, { + (0, invoice_request, required), + (2, nonce, required), +}); + impl PendingOutboundPayment { fn increment_attempts(&mut self) { if let PendingOutboundPayment::Retryable { attempts, .. } = self { @@ -666,13 +680,19 @@ pub(super) struct SendAlongPathArgs<'a> { pub(super) struct OutboundPayments { pub(super) pending_outbound_payments: Mutex>, - pub(super) retry_lock: Mutex<()>, + awaiting_invoice: AtomicBool, + retry_lock: Mutex<()>, } impl OutboundPayments { - pub(super) fn new() -> Self { + pub(super) fn new(pending_outbound_payments: HashMap) -> Self { + let has_invoice_requests = pending_outbound_payments.values().any(|payment| { + matches!(payment, PendingOutboundPayment::AwaitingInvoice { retryable_invoice_request: Some(_), .. }) + }); + Self { - pending_outbound_payments: Mutex::new(new_hash_map()), + pending_outbound_payments: Mutex::new(pending_outbound_payments), + awaiting_invoice: AtomicBool::new(has_invoice_requests), retry_lock: Mutex::new(()), } } @@ -1393,16 +1413,20 @@ impl OutboundPayments { pub(super) fn add_new_awaiting_invoice( &self, payment_id: PaymentId, expiration: StaleExpiration, retry_strategy: Retry, - max_total_routing_fee_msat: Option + max_total_routing_fee_msat: Option, retryable_invoice_request: Option ) -> Result<(), ()> { let mut pending_outbounds = self.pending_outbound_payments.lock().unwrap(); match pending_outbounds.entry(payment_id) { hash_map::Entry::Occupied(_) => Err(()), hash_map::Entry::Vacant(entry) => { + if retryable_invoice_request.is_some() { + self.awaiting_invoice.store(true, Ordering::Release); + } entry.insert(PendingOutboundPayment::AwaitingInvoice { expiration, retry_strategy, max_total_routing_fee_msat, + retryable_invoice_request, }); Ok(()) @@ -1874,6 +1898,31 @@ impl OutboundPayments { pub fn clear_pending_payments(&self) { self.pending_outbound_payments.lock().unwrap().clear() } + + pub fn release_invoice_requests_awaiting_invoice(&self) -> Vec<(PaymentId, RetryableInvoiceRequest)> { + if !self.awaiting_invoice.load(Ordering::Acquire) { + return vec![]; + } + + let mut pending_outbound_payments = self.pending_outbound_payments.lock().unwrap(); + let invoice_requests = pending_outbound_payments + .iter_mut() + .filter_map(|(payment_id, payment)| { + if let PendingOutboundPayment::AwaitingInvoice { + retryable_invoice_request, .. + } = payment { + retryable_invoice_request.take().map(|retryable_invoice_request| { + (*payment_id, retryable_invoice_request) + }) + } else { + None + } + }) + .collect(); + + self.awaiting_invoice.store(false, Ordering::Release); + invoice_requests + } } /// Returns whether a payment with the given [`PaymentHash`] and [`PaymentId`] is, in fact, a @@ -1929,6 +1978,7 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment, (0, expiration, required), (2, retry_strategy, required), (4, max_total_routing_fee_msat, option), + (5, retryable_invoice_request, option), }, (7, InvoiceReceived) => { (0, payment_hash, required), @@ -1959,6 +2009,7 @@ mod tests { use crate::routing::router::{InFlightHtlcs, Path, PaymentParameters, Route, RouteHop, RouteParameters}; use crate::sync::{Arc, Mutex, RwLock}; use crate::util::errors::APIError; + use crate::util::hash_tables::new_hash_map; use crate::util::test_utils; use alloc::collections::VecDeque; @@ -1993,7 +2044,7 @@ mod tests { } #[cfg(feature = "std")] fn do_fails_paying_after_expiration(on_retry: bool) { - let outbound_payments = OutboundPayments::new(); + let outbound_payments = OutboundPayments::new(new_hash_map()); let logger = test_utils::TestLogger::new(); let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger)); let scorer = RwLock::new(test_utils::TestScorer::new()); @@ -2037,7 +2088,7 @@ mod tests { do_find_route_error(true); } fn do_find_route_error(on_retry: bool) { - let outbound_payments = OutboundPayments::new(); + let outbound_payments = OutboundPayments::new(new_hash_map()); let logger = test_utils::TestLogger::new(); let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger)); let scorer = RwLock::new(test_utils::TestScorer::new()); @@ -2076,7 +2127,7 @@ mod tests { #[test] fn initial_send_payment_path_failed_evs() { - let outbound_payments = OutboundPayments::new(); + let outbound_payments = OutboundPayments::new(new_hash_map()); let logger = test_utils::TestLogger::new(); let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger)); let scorer = RwLock::new(test_utils::TestScorer::new()); @@ -2158,7 +2209,7 @@ mod tests { #[test] fn removes_stale_awaiting_invoice_using_absolute_timeout() { let pending_events = Mutex::new(VecDeque::new()); - let outbound_payments = OutboundPayments::new(); + let outbound_payments = OutboundPayments::new(new_hash_map()); let payment_id = PaymentId([0; 32]); let absolute_expiry = 100; let tick_interval = 10; @@ -2167,7 +2218,7 @@ mod tests { assert!(!outbound_payments.has_pending_payments()); assert!( outbound_payments.add_new_awaiting_invoice( - payment_id, expiration, Retry::Attempts(0), None + payment_id, expiration, Retry::Attempts(0), None, None, ).is_ok() ); assert!(outbound_payments.has_pending_payments()); @@ -2197,14 +2248,14 @@ mod tests { assert!( outbound_payments.add_new_awaiting_invoice( - payment_id, expiration, Retry::Attempts(0), None + payment_id, expiration, Retry::Attempts(0), None, None, ).is_ok() ); assert!(outbound_payments.has_pending_payments()); assert!( outbound_payments.add_new_awaiting_invoice( - payment_id, expiration, Retry::Attempts(0), None + payment_id, expiration, Retry::Attempts(0), None, None, ).is_err() ); } @@ -2212,7 +2263,7 @@ mod tests { #[test] fn removes_stale_awaiting_invoice_using_timer_ticks() { let pending_events = Mutex::new(VecDeque::new()); - let outbound_payments = OutboundPayments::new(); + let outbound_payments = OutboundPayments::new(new_hash_map()); let payment_id = PaymentId([0; 32]); let timer_ticks = 3; let expiration = StaleExpiration::TimerTicks(timer_ticks); @@ -2220,7 +2271,7 @@ mod tests { assert!(!outbound_payments.has_pending_payments()); assert!( outbound_payments.add_new_awaiting_invoice( - payment_id, expiration, Retry::Attempts(0), None + payment_id, expiration, Retry::Attempts(0), None, None, ).is_ok() ); assert!(outbound_payments.has_pending_payments()); @@ -2250,14 +2301,14 @@ mod tests { assert!( outbound_payments.add_new_awaiting_invoice( - payment_id, expiration, Retry::Attempts(0), None + payment_id, expiration, Retry::Attempts(0), None, None, ).is_ok() ); assert!(outbound_payments.has_pending_payments()); assert!( outbound_payments.add_new_awaiting_invoice( - payment_id, expiration, Retry::Attempts(0), None + payment_id, expiration, Retry::Attempts(0), None, None, ).is_err() ); } @@ -2265,14 +2316,14 @@ mod tests { #[test] fn removes_abandoned_awaiting_invoice() { let pending_events = Mutex::new(VecDeque::new()); - let outbound_payments = OutboundPayments::new(); + let outbound_payments = OutboundPayments::new(new_hash_map()); let payment_id = PaymentId([0; 32]); let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(100)); assert!(!outbound_payments.has_pending_payments()); assert!( outbound_payments.add_new_awaiting_invoice( - payment_id, expiration, Retry::Attempts(0), None + payment_id, expiration, Retry::Attempts(0), None, None, ).is_ok() ); assert!(outbound_payments.has_pending_payments()); @@ -2302,13 +2353,13 @@ mod tests { let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet); let pending_events = Mutex::new(VecDeque::new()); - let outbound_payments = OutboundPayments::new(); + let outbound_payments = OutboundPayments::new(new_hash_map()); let payment_id = PaymentId([0; 32]); let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(100)); assert!( outbound_payments.add_new_awaiting_invoice( - payment_id, expiration, Retry::Attempts(0), None + payment_id, expiration, Retry::Attempts(0), None, None, ).is_ok() ); assert!(outbound_payments.has_pending_payments()); @@ -2355,7 +2406,7 @@ mod tests { let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet); let pending_events = Mutex::new(VecDeque::new()); - let outbound_payments = OutboundPayments::new(); + let outbound_payments = OutboundPayments::new(new_hash_map()); let payment_id = PaymentId([0; 32]); let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(100)); @@ -2372,7 +2423,7 @@ mod tests { assert!( outbound_payments.add_new_awaiting_invoice( payment_id, expiration, Retry::Attempts(0), - Some(invoice.amount_msats() / 100 + 50_000) + Some(invoice.amount_msats() / 100 + 50_000), None, ).is_ok() ); assert!(outbound_payments.has_pending_payments()); @@ -2416,7 +2467,7 @@ mod tests { let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet); let pending_events = Mutex::new(VecDeque::new()); - let outbound_payments = OutboundPayments::new(); + let outbound_payments = OutboundPayments::new(new_hash_map()); let payment_id = PaymentId([0; 32]); let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(100)); @@ -2472,7 +2523,7 @@ mod tests { assert!( outbound_payments.add_new_awaiting_invoice( - payment_id, expiration, Retry::Attempts(0), Some(1234) + payment_id, expiration, Retry::Attempts(0), Some(1234), None, ).is_ok() ); assert!(outbound_payments.has_pending_payments()); diff --git a/lightning/src/ln/peer_handler.rs b/lightning/src/ln/peer_handler.rs index fa26600250e..3c0d724ad94 100644 --- a/lightning/src/ln/peer_handler.rs +++ b/lightning/src/ln/peer_handler.rs @@ -388,6 +388,8 @@ impl ChannelMessageHandler for ErroringMessageHandler { fn handle_tx_abort(&self, their_node_id: PublicKey, msg: &msgs::TxAbort) { ErroringMessageHandler::push_error(self, their_node_id, msg.channel_id); } + + fn message_received(&self) {} } impl Deref for ErroringMessageHandler { @@ -1616,6 +1618,8 @@ impl processed_message, None => return Ok(None), diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index fa3d9161b82..32d05249cfa 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -1039,6 +1039,13 @@ impl Writeable for InvoiceRequestContents { } } +impl Readable for InvoiceRequest { + fn read(reader: &mut R) -> Result { + let bytes: WithoutLength> = Readable::read(reader)?; + Self::try_from(bytes.0).map_err(|_| DecodeError::InvalidValue) + } +} + /// Valid type range for invoice_request TLV records. pub(super) const INVOICE_REQUEST_TYPES: core::ops::Range = 80..160; diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index e41ba359b3b..a15584c9178 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -942,6 +942,8 @@ impl msgs::ChannelMessageHandler for TestChannelMessageHandler { fn handle_tx_abort(&self, _their_node_id: PublicKey, msg: &msgs::TxAbort) { self.received_msg(wire::Message::TxAbort(msg.clone())); } + + fn message_received(&self) {} } impl events::MessageSendEventsProvider for TestChannelMessageHandler {