use crate::ln::msgs::{ChannelMessageHandler, DecodeError, LightningError};
#[cfg(test)]
use crate::ln::outbound_payment;
-use crate::ln::outbound_payment::{Bolt12PaymentError, OutboundPayments, PaymentAttempts, PendingOutboundPayment, SendAlongPathArgs, StaleExpiration};
+use crate::ln::outbound_payment::{OutboundPayments, PaymentAttempts, PendingOutboundPayment, SendAlongPathArgs, StaleExpiration};
use crate::ln::wire::Encode;
use crate::offers::invoice::{BlindedPayInfo, Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder, UnsignedBolt12Invoice};
use crate::offers::invoice_error::InvoiceError;
use core::ops::Deref;
// Re-export this for use in the public API.
-pub use crate::ln::outbound_payment::{PaymentSendFailure, ProbeSendFailure, Retry, RetryableSendFailure, RecipientOnionFields};
+pub use crate::ln::outbound_payment::{Bolt12PaymentError, PaymentSendFailure, ProbeSendFailure, Retry, RetryableSendFailure, RecipientOnionFields};
use crate::ln::script::ShutdownScript;
// We hold various information about HTLC relay in the HTLC objects in Channel itself:
err: msg,
action: msgs::ErrorAction::IgnoreError,
},
- ChannelError::Close(msg) => LightningError {
+ ChannelError::Close((msg, _reason)) => LightningError {
err: msg.clone(),
action: msgs::ErrorAction::SendErrorMessage {
msg: msgs::ErrorMessage {
/// accepted. An unaccepted channel that exceeds this limit will be abandoned.
const UNACCEPTED_INBOUND_CHANNEL_AGE_LIMIT_TICKS: i32 = 2;
+/// The number of blocks of historical feerate estimates we keep around and consider when deciding
+/// to force-close a channel for having too-low fees. Also the number of blocks we have to see
+/// after startup before we consider force-closing channels for having too-low fees.
+pub(super) const FEERATE_TRACKING_BLOCKS: usize = 144;
+
/// Stores a PaymentSecret and any other data we may need to validate an inbound payment is
/// actually ours and not some duplicate HTLC sent to us by a node along the route.
///
/// Tracks the message events that are to be broadcasted when we are connected to some peer.
pending_broadcast_messages: Mutex<Vec<MessageSendEvent>>,
+ /// We only want to force-close our channels on peers based on stale feerates when we're
+ /// confident the feerate on the channel is *really* stale, not just became stale recently.
+ /// Thus, we store the fee estimates we had as of the last [`FEERATE_TRACKING_BLOCKS`] blocks
+ /// (after startup completed) here, and only force-close when channels have a lower feerate
+ /// than we predicted any time in the last [`FEERATE_TRACKING_BLOCKS`] blocks.
+ ///
+ /// We only keep this in memory as we assume any feerates we receive immediately after startup
+ /// may be bunk (as they often are if Bitcoin Core crashes) and want to delay taking any
+ /// actions for a day anyway.
+ ///
+ /// The first element in the pair is the
+ /// [`ConfirmationTarget::MinAllowedAnchorChannelRemoteFee`] estimate, the second the
+ /// [`ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee`] estimate.
+ last_days_feerates: Mutex<VecDeque<(u32, u32)>>,
+
entropy_source: ES,
node_signer: NS,
signer_provider: SP,
ChannelError::Ignore(msg) => {
(false, MsgHandleErrInternal::from_chan_no_close(ChannelError::Ignore(msg), *$channel_id))
},
- ChannelError::Close(msg) => {
+ ChannelError::Close((msg, reason)) => {
let logger = WithChannelContext::from(&$self.logger, &$channel.context, None);
log_error!(logger, "Closing channel {} due to close-required error: {}", $channel_id, msg);
update_maps_on_chan_removal!($self, $channel.context);
- let reason = ClosureReason::ProcessingError { err: msg.clone() };
let shutdown_res = $channel.context.force_shutdown(true, reason);
let err =
MsgHandleErrInternal::from_finish_shutdown(msg, *$channel_id, shutdown_res, $channel_update);
pending_offers_messages: Mutex::new(Vec::new()),
pending_broadcast_messages: Mutex::new(Vec::new()),
+ last_days_feerates: Mutex::new(VecDeque::new()),
+
entropy_source,
node_signer,
signer_provider,
}
} else {
let mut chan_phase = remove_channel_phase!(self, chan_phase_entry);
- shutdown_result = Some(chan_phase.context_mut().force_shutdown(false, ClosureReason::HolderForceClosed));
+ shutdown_result = Some(chan_phase.context_mut().force_shutdown(false, ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }));
}
},
hash_map::Entry::Vacant(_) => {
let closure_reason = if let Some(peer_msg) = peer_msg {
ClosureReason::CounterpartyForceClosed { peer_msg: UntrustedString(peer_msg.to_string()) }
} else {
- ClosureReason::HolderForceClosed
+ ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(broadcast) }
};
let logger = WithContext::from(&self.logger, Some(*peer_node_id), Some(*channel_id), None);
if let hash_map::Entry::Occupied(chan_phase_entry) = peer_state.channel_by_id.entry(channel_id.clone()) {
self.pending_outbound_payments.test_set_payment_metadata(payment_id, new_payment_metadata);
}
- pub(super) fn send_payment_for_bolt12_invoice(&self, invoice: &Bolt12Invoice, payment_id: PaymentId) -> Result<(), Bolt12PaymentError> {
+ /// Pays the [`Bolt12Invoice`] associated with the `payment_id` encoded in its `payer_metadata`.
+ ///
+ /// The invoice's `payer_metadata` is used to authenticate that the invoice was indeed requested
+ /// before attempting a payment. [`Bolt12PaymentError::UnexpectedInvoice`] is returned if this
+ /// fails or if the encoded `payment_id` is not recognized. The latter may happen once the
+ /// payment is no longer tracked because the payment was attempted after:
+ /// - an invoice for the `payment_id` was already paid,
+ /// - one full [timer tick] has elapsed since initially requesting the invoice when paying an
+ /// offer, or
+ /// - the refund corresponding to the invoice has already expired.
+ ///
+ /// To retry the payment, request another invoice using a new `payment_id`.
+ ///
+ /// Attempting to pay the same invoice twice while the first payment is still pending will
+ /// result in a [`Bolt12PaymentError::DuplicateInvoice`].
+ ///
+ /// Otherwise, either [`Event::PaymentSent`] or [`Event::PaymentFailed`] are used to indicate
+ /// whether or not the payment was successful.
+ ///
+ /// [timer tick]: Self::timer_tick_occurred
+ pub fn send_payment_for_bolt12_invoice(&self, invoice: &Bolt12Invoice) -> Result<(), Bolt12PaymentError> {
+ let secp_ctx = &self.secp_ctx;
+ let expanded_key = &self.inbound_payment_key;
+ match invoice.verify(expanded_key, secp_ctx) {
+ Ok(payment_id) => self.send_payment_for_verified_bolt12_invoice(invoice, payment_id),
+ Err(()) => Err(Bolt12PaymentError::UnexpectedInvoice),
+ }
+ }
+
+ fn send_payment_for_verified_bolt12_invoice(&self, invoice: &Bolt12Invoice, payment_id: PaymentId) -> Result<(), Bolt12PaymentError> {
let best_block_height = self.best_block.read().unwrap().height;
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
self.pending_outbound_payments
Some(ChannelPhase::UnfundedOutboundV1(mut chan)) => {
macro_rules! close_chan { ($err: expr, $api_err: expr, $chan: expr) => { {
let counterparty;
- let err = if let ChannelError::Close(msg) = $err {
+ let err = if let ChannelError::Close((msg, reason)) = $err {
let channel_id = $chan.context.channel_id();
counterparty = chan.context.get_counterparty_node_id();
- let reason = ClosureReason::ProcessingError { err: msg.clone() };
let shutdown_res = $chan.context.force_shutdown(false, reason);
MsgHandleErrInternal::from_finish_shutdown(msg, channel_id, shutdown_res, None)
} else { unreachable!(); };
match find_funding_output(&chan, &funding_transaction) {
Ok(found_funding_txo) => funding_txo = found_funding_txo,
Err(err) => {
- let chan_err = ChannelError::Close(err.to_owned());
+ let chan_err = ChannelError::close(err.to_owned());
let api_err = APIError::APIMisuseError { err: err.to_owned() };
return close_chan!(chan_err, api_err, chan);
},
log_error!(logger,
"Force-closing pending channel with ID {} for not establishing in a timely manner", chan_id);
update_maps_on_chan_removal!(self, &context);
- shutdown_channels.push(context.force_shutdown(false, ClosureReason::HolderForceClosed));
+ shutdown_channels.push(context.force_shutdown(false, ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }));
pending_msg_events.push(MessageSendEvent::HandleError {
node_id: counterparty_node_id,
action: msgs::ErrorAction::SendErrorMessage {
log_trace!(logger, "ChannelMonitor updated to {}. Current highest is {}. {} pending in-flight updates.",
highest_applied_update_id, channel.context.get_latest_monitor_update_id(),
remaining_in_flight);
- if !channel.is_awaiting_monitor_update() || channel.context.get_latest_monitor_update_id() != highest_applied_update_id {
+ if !channel.is_awaiting_monitor_update() || remaining_in_flight != 0 {
return;
}
handle_monitor_update_completion!(self, peer_state_lock, peer_state, per_peer_state, channel);
},
Some(mut phase) => {
let err_msg = format!("Got an unexpected funding_created message from peer with counterparty_node_id {}", counterparty_node_id);
- let err = ChannelError::Close(err_msg);
+ let err = ChannelError::close(err_msg);
return Err(convert_chan_phase_err!(self, err, &mut phase, &msg.temporary_channel_id).1);
},
None => return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", counterparty_node_id), msg.temporary_channel_id))
// `update_maps_on_chan_removal`), we'll remove the existing channel
// from `outpoint_to_peer`. Thus, we must first unset the funding outpoint
// on the channel.
- let err = ChannelError::Close($err.to_owned());
+ let err = ChannelError::close($err.to_owned());
chan.unset_funding_info(msg.temporary_channel_id);
return Err(convert_chan_phase_err!(self, err, chan, &funded_channel_id, UNFUNDED_CHANNEL).1);
} } }
} else { unreachable!(); }
Ok(())
} else {
- let e = ChannelError::Close("Channel funding outpoint was a duplicate".to_owned());
+ let e = ChannelError::close("Channel funding outpoint was a duplicate".to_owned());
// We weren't able to watch the channel to begin with, so no
// updates should be made on it. Previously, full_stack_target
// found an (unreachable) panic when the monitor update contained
Ok(())
} else {
- try_chan_phase_entry!(self, Err(ChannelError::Close(
+ try_chan_phase_entry!(self, Err(ChannelError::close(
"Got a channel_ready message for an unfunded channel!".into())), chan_phase_entry)
}
},
(tx, Some(remove_channel_phase!(self, chan_phase_entry)), shutdown_result)
} else { (tx, None, shutdown_result) }
} else {
- return try_chan_phase_entry!(self, Err(ChannelError::Close(
+ return try_chan_phase_entry!(self, Err(ChannelError::close(
"Got a closing_signed message for an unfunded channel!".into())), chan_phase_entry);
}
},
}
try_chan_phase_entry!(self, chan.update_add_htlc(&msg, pending_forward_info, &self.fee_estimator), chan_phase_entry);
} else {
- return try_chan_phase_entry!(self, Err(ChannelError::Close(
+ return try_chan_phase_entry!(self, Err(ChannelError::close(
"Got an update_add_htlc message for an unfunded channel!".into())), chan_phase_entry);
}
},
next_user_channel_id = chan.context.get_user_id();
res
} else {
- return try_chan_phase_entry!(self, Err(ChannelError::Close(
+ return try_chan_phase_entry!(self, Err(ChannelError::close(
"Got an update_fulfill_htlc message for an unfunded channel!".into())), chan_phase_entry);
}
},
if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() {
try_chan_phase_entry!(self, chan.update_fail_htlc(&msg, HTLCFailReason::from_msg(msg)), chan_phase_entry);
} else {
- return try_chan_phase_entry!(self, Err(ChannelError::Close(
+ return try_chan_phase_entry!(self, Err(ChannelError::close(
"Got an update_fail_htlc message for an unfunded channel!".into())), chan_phase_entry);
}
},
match peer_state.channel_by_id.entry(msg.channel_id) {
hash_map::Entry::Occupied(mut chan_phase_entry) => {
if (msg.failure_code & 0x8000) == 0 {
- let chan_err: ChannelError = ChannelError::Close("Got update_fail_malformed_htlc with BADONION not set".to_owned());
+ let chan_err = ChannelError::close("Got update_fail_malformed_htlc with BADONION not set".to_owned());
try_chan_phase_entry!(self, Err(chan_err), chan_phase_entry);
}
if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() {
try_chan_phase_entry!(self, chan.update_fail_malformed_htlc(&msg, HTLCFailReason::reason(msg.failure_code, msg.sha256_of_onion.to_vec())), chan_phase_entry);
} else {
- return try_chan_phase_entry!(self, Err(ChannelError::Close(
+ return try_chan_phase_entry!(self, Err(ChannelError::close(
"Got an update_fail_malformed_htlc message for an unfunded channel!".into())), chan_phase_entry);
}
Ok(())
}
Ok(())
} else {
- return try_chan_phase_entry!(self, Err(ChannelError::Close(
+ return try_chan_phase_entry!(self, Err(ChannelError::close(
"Got a commitment_signed message for an unfunded channel!".into())), chan_phase_entry);
}
},
}
htlcs_to_fail
} else {
- return try_chan_phase_entry!(self, Err(ChannelError::Close(
+ return try_chan_phase_entry!(self, Err(ChannelError::close(
"Got a revoke_and_ack message for an unfunded channel!".into())), chan_phase_entry);
}
},
let logger = WithChannelContext::from(&self.logger, &chan.context, None);
try_chan_phase_entry!(self, chan.update_fee(&self.fee_estimator, &msg, &&logger), chan_phase_entry);
} else {
- return try_chan_phase_entry!(self, Err(ChannelError::Close(
+ return try_chan_phase_entry!(self, Err(ChannelError::close(
"Got an update_fee message for an unfunded channel!".into())), chan_phase_entry);
}
},
update_msg: Some(self.get_channel_update_for_broadcast(chan).unwrap()),
});
} else {
- return try_chan_phase_entry!(self, Err(ChannelError::Close(
+ return try_chan_phase_entry!(self, Err(ChannelError::close(
"Got an announcement_signatures message for an unfunded channel!".into())), chan_phase_entry);
}
},
}
}
} else {
- return try_chan_phase_entry!(self, Err(ChannelError::Close(
+ return try_chan_phase_entry!(self, Err(ChannelError::close(
"Got a channel_update for an unfunded channel!".into())), chan_phase_entry);
}
},
}
need_lnd_workaround
} else {
- return try_chan_phase_entry!(self, Err(ChannelError::Close(
+ return try_chan_phase_entry!(self, Err(ChannelError::close(
"Got a channel_reestablish message for an unfunded channel!".into())), chan_phase_entry);
}
},
let reason = if let MonitorEvent::HolderForceClosedWithInfo { reason, .. } = monitor_event {
reason
} else {
- ClosureReason::HolderForceClosed
+ ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(true) }
};
failed_channels.push(chan.context.force_shutdown(false, reason.clone()));
if let Ok(update) = self.get_channel_update_for_broadcast(&chan) {
///
/// Uses [`MessageRouter`] to construct a [`BlindedPath`] for the offer based on the given
/// `absolute_expiry` according to [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`]. See those docs for
- /// privacy implications. However, if one is not found, uses a one-hop [`BlindedPath`] with
- /// [`ChannelManager::get_our_node_id`] as the introduction node instead. In the latter case,
- /// the node must be announced, otherwise, there is no way to find a path to the introduction
- /// node in order to send the [`InvoiceRequest`].
+ /// privacy implications as well as those of the parameterized [`Router`], which implements
+ /// [`MessageRouter`].
///
/// Also, uses a derived signing pubkey in the offer for recipient privacy.
///
///
/// Uses [`MessageRouter`] to construct a [`BlindedPath`] for the refund based on the given
/// `absolute_expiry` according to [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`]. See those docs for
- /// privacy implications. However, if one is not found, uses a one-hop [`BlindedPath`] with
- /// [`ChannelManager::get_our_node_id`] as the introduction node instead. In the latter case,
- /// the node must be announced, otherwise, there is no way to find a path to the introduction
- /// node in order to send the [`Bolt12Invoice`].
+ /// privacy implications as well as those of the parameterized [`Router`], which implements
+ /// [`MessageRouter`].
///
/// Also, uses a derived payer id in the refund for payer privacy.
///
///
/// # Privacy
///
- /// Uses a one-hop [`BlindedPath`] for the reply path with [`ChannelManager::get_our_node_id`]
- /// as the introduction node and a derived payer id for payer privacy. As such, currently, the
- /// node must be announced. Otherwise, there is no way to find a path to the introduction node
- /// in order to send the [`Bolt12Invoice`].
+ /// For payer privacy, uses a derived payer id and uses [`MessageRouter::create_blinded_paths`]
+ /// to construct a [`BlindedPath`] for the reply path. For further privacy implications, see the
+ /// docs of the parameterized [`Router`], which implements [`MessageRouter`].
///
/// # Limitations
///
let peers = self.per_peer_state.read().unwrap()
.iter()
- .filter(|(_, peer)| peer.lock().unwrap().latest_features.supports_onion_messages())
+ .map(|(node_id, peer_state)| (node_id, peer_state.lock().unwrap()))
+ .filter(|(_, peer)| peer.is_connected)
+ .filter(|(_, peer)| peer.latest_features.supports_onion_messages())
.map(|(node_id, _)| *node_id)
.collect::<Vec<_>>();
let peers = self.per_peer_state.read().unwrap()
.iter()
.map(|(node_id, peer_state)| (node_id, peer_state.lock().unwrap()))
+ .filter(|(_, peer)| peer.is_connected)
.filter(|(_, peer)| peer.latest_features.supports_onion_messages())
.map(|(node_id, peer)| ForwardNode {
node_id: *node_id,
self, || -> NotifyOption { NotifyOption::DoPersist });
*self.best_block.write().unwrap() = BestBlock::new(block_hash, height);
- self.do_chain_event(Some(height), |channel| channel.best_block_updated(height, header.time, self.chain_hash, &self.node_signer, &self.default_configuration, &&WithChannelContext::from(&self.logger, &channel.context, None)));
+ let mut min_anchor_feerate = None;
+ let mut min_non_anchor_feerate = None;
+ if self.background_events_processed_since_startup.load(Ordering::Relaxed) {
+ // If we're past the startup phase, update our feerate cache
+ let mut last_days_feerates = self.last_days_feerates.lock().unwrap();
+ if last_days_feerates.len() >= FEERATE_TRACKING_BLOCKS {
+ last_days_feerates.pop_front();
+ }
+ let anchor_feerate = self.fee_estimator
+ .bounded_sat_per_1000_weight(ConfirmationTarget::MinAllowedAnchorChannelRemoteFee);
+ let non_anchor_feerate = self.fee_estimator
+ .bounded_sat_per_1000_weight(ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee);
+ last_days_feerates.push_back((anchor_feerate, non_anchor_feerate));
+ if last_days_feerates.len() >= FEERATE_TRACKING_BLOCKS {
+ min_anchor_feerate = last_days_feerates.iter().map(|(f, _)| f).min().copied();
+ min_non_anchor_feerate = last_days_feerates.iter().map(|(_, f)| f).min().copied();
+ }
+ }
+
+ self.do_chain_event(Some(height), |channel| {
+ let logger = WithChannelContext::from(&self.logger, &channel.context, None);
+ if channel.context.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
+ if let Some(feerate) = min_anchor_feerate {
+ channel.check_for_stale_feerate(&logger, feerate)?;
+ }
+ } else {
+ if let Some(feerate) = min_non_anchor_feerate {
+ channel.check_for_stale_feerate(&logger, feerate)?;
+ }
+ }
+ channel.best_block_updated(height, header.time, self.chain_hash, &self.node_signer, &self.default_configuration, &&WithChannelContext::from(&self.logger, &channel.context, None))
+ });
macro_rules! max_time {
($timestamp: expr) => {
};
match response {
- Ok(invoice) => return responder.respond(OffersMessage::Invoice(invoice)),
- Err(error) => return responder.respond(OffersMessage::InvoiceError(error.into())),
+ Ok(invoice) => responder.respond(OffersMessage::Invoice(invoice)),
+ Err(error) => responder.respond(OffersMessage::InvoiceError(error.into())),
}
},
OffersMessage::Invoice(invoice) => {
- let response = invoice
- .verify(expanded_key, secp_ctx)
- .map_err(|()| InvoiceError::from_string("Unrecognized invoice".to_owned()))
- .and_then(|payment_id| {
+ let result = match invoice.verify(expanded_key, secp_ctx) {
+ Ok(payment_id) => {
let features = self.bolt12_invoice_features();
if invoice.invoice_features().requires_unknown_bits_from(&features) {
Err(InvoiceError::from(Bolt12SemanticError::UnknownRequiredFeatures))
+ } else if self.default_configuration.manually_handle_bolt12_invoices {
+ let event = Event::InvoiceReceived { payment_id, invoice, responder };
+ self.pending_events.lock().unwrap().push_back((event, None));
+ return ResponseInstruction::NoResponse;
} else {
- self.send_payment_for_bolt12_invoice(&invoice, payment_id)
+ self.send_payment_for_verified_bolt12_invoice(&invoice, payment_id)
.map_err(|e| {
log_trace!(self.logger, "Failed paying invoice: {:?}", e);
InvoiceError::from_string(format!("{:?}", e))
})
}
- });
+ },
+ Err(()) => Err(InvoiceError::from_string("Unrecognized invoice".to_owned())),
+ };
- match (responder, response) {
- (Some(responder), Err(e)) => responder.respond(OffersMessage::InvoiceError(e)),
- (None, Err(_)) => {
- log_trace!(
- self.logger,
- "A response was generated, but there is no reply_path specified for sending the response."
- );
- return ResponseInstruction::NoResponse;
- }
- _ => return ResponseInstruction::NoResponse,
+ match result {
+ Ok(()) => ResponseInstruction::NoResponse,
+ Err(e) => match responder {
+ Some(responder) => responder.respond(OffersMessage::InvoiceError(e)),
+ None => {
+ log_trace!(self.logger, "No reply path for sending invoice error: {:?}", e);
+ ResponseInstruction::NoResponse
+ },
+ },
}
},
OffersMessage::InvoiceError(invoice_error) => {
log_trace!(self.logger, "Received invoice_error: {}", invoice_error);
- return ResponseInstruction::NoResponse;
+ ResponseInstruction::NoResponse
},
}
}
node_signer: args.node_signer,
signer_provider: args.signer_provider,
+ last_days_feerates: Mutex::new(VecDeque::new()),
+
logger: args.logger,
default_configuration: args.default_config,
};
nodes[0].node.force_close_channel_with_peer(&chan.2, &nodes[1].node.get_our_node_id(), None, true).unwrap();
check_added_monitors!(nodes[0], 1);
- check_closed_event!(nodes[0], 1, ClosureReason::HolderForceClosed, [nodes[1].node.get_our_node_id()], 100000);
+ check_closed_event!(nodes[0], 1, ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(true) }, [nodes[1].node.get_our_node_id()], 100000);
// Confirm that the channel_update was not sent immediately to node[1] but was cached.
let node_1_events = nodes[1].node.get_and_clear_pending_msg_events();
nodes[0].node.force_close_broadcasting_latest_txn(&chan.2, &nodes[1].node.get_our_node_id(), error_message.to_string()).unwrap();
check_closed_broadcast!(nodes[0], true);
check_added_monitors!(nodes[0], 1);
- check_closed_event!(nodes[0], 1, ClosureReason::HolderForceClosed, [nodes[1].node.get_our_node_id()], 100000);
+ check_closed_event!(nodes[0], 1, ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(true) }, [nodes[1].node.get_our_node_id()], 100000);
{
// Assert that nodes[1] is awaiting removal for nodes[0] once nodes[1] has been
nodes[0].node.force_close_broadcasting_latest_txn(&chan_id, &nodes[1].node.get_our_node_id(), error_message.to_string()).unwrap();
check_closed_broadcast(&nodes[0], 1, true);
check_added_monitors(&nodes[0], 1);
- check_closed_event!(nodes[0], 1, ClosureReason::HolderForceClosed, [nodes[1].node.get_our_node_id()], 100000);
+ check_closed_event!(nodes[0], 1, ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(true) }, [nodes[1].node.get_our_node_id()], 100000);
{
let txn = nodes[0].tx_broadcaster.txn_broadcast();
assert_eq!(txn.len(), 1);