Merge pull request #3010 from shaavan/issue2836

Introduce Retry InvoiceRequest Flow
This commit is contained in:
Matt Corallo 2024-09-12 15:12:39 +00:00 committed by GitHub
commit 1059f5ffc5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 223 additions and 35 deletions

View file

@ -786,6 +786,7 @@ mod tests {
fn get_chain_hashes(&self) -> Option<Vec<ChainHash>> {
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<MessageSendEvent> {

View file

@ -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<BlindedMessagePath>,
) -> 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<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>
@ -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

View file

@ -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<Vec<ChainHash>>;
/// 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.

View file

@ -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() {

View file

@ -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<u64>,
retryable_invoice_request: Option<RetryableInvoiceRequest>
},
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<HashMap<PaymentId, PendingOutboundPayment>>,
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<PaymentId, PendingOutboundPayment>) -> 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<u64>
max_total_routing_fee_msat: Option<u64>, retryable_invoice_request: Option<RetryableInvoiceRequest>
) -> 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());

View file

@ -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<Descriptor: SocketDescriptor, CM: Deref, RM: Deref, OM: Deref, L: Deref, CM
let their_node_id = peer_lock.their_node_id.expect("We know the peer's public key by the time we receive messages").0;
let logger = WithContext::from(&self.logger, Some(their_node_id), None, None);
self.message_handler.chan_handler.message_received();
let message = match self.do_handle_message_holding_peer_lock(peer_lock, message, their_node_id, &logger)? {
Some(processed_message) => processed_message,
None => return Ok(None),

View file

@ -1039,6 +1039,13 @@ impl Writeable for InvoiceRequestContents {
}
}
impl Readable for InvoiceRequest {
fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
let bytes: WithoutLength<Vec<u8>> = 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<u64> = 80..160;

View file

@ -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 {