Skip to content

Commit 67a9c0d

Browse files
committed
Ensure ChannelManager methods are idempotent
During event handling, ChannelManager methods may need to be called as indicated in the Event documentation. Ensure that these calls are idempotent for the same event rather than panicking. This allows users to persist events for later handling without needing to worry about processing the same event twice (e.g., if ChannelManager is not persisted but the events were, the restarted ChannelManager would return some of the same events).
1 parent a3e4af0 commit 67a9c0d

File tree

3 files changed

+43
-11
lines changed

3 files changed

+43
-11
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2443,7 +2443,8 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
24432443
/// Returns an [`APIError::APIMisuseError`] if the funding_transaction spent non-SegWit outputs
24442444
/// or if no output was found which matches the parameters in [`Event::FundingGenerationReady`].
24452445
///
2446-
/// Panics if a funding transaction has already been provided for this channel.
2446+
/// Returns [`APIError::ChannelUnavailable`] if a funding transaction has already been provided
2447+
/// for the channel or if the channel has been closed as indicated by [`Event::ChannelClosed`].
24472448
///
24482449
/// May panic if the output found in the funding transaction is duplicative with some other
24492450
/// channel (note that this should be trivially prevented by using unique funding transaction
@@ -2458,6 +2459,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
24582459
/// create a new channel with a conflicting funding transaction.
24592460
///
24602461
/// [`Event::FundingGenerationReady`]: crate::util::events::Event::FundingGenerationReady
2462+
/// [`Event::ChannelClosed`]: crate::util::events::Event::ChannelClosed
24612463
pub fn funding_transaction_generated(&self, temporary_channel_id: &[u8; 32], funding_transaction: Transaction) -> Result<(), APIError> {
24622464
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&self.total_consistency_lock, &self.persistence_notifier);
24632465

@@ -3353,17 +3355,19 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
33533355
}
33543356
}
33553357

3356-
/// Provides a payment preimage in response to a PaymentReceived event, returning true and
3357-
/// generating message events for the net layer to claim the payment, if possible. Thus, you
3358-
/// should probably kick the net layer to go send messages if this returns true!
3358+
/// Provides a payment preimage in response to [`Event::PaymentReceived`], generating any
3359+
/// [`MessageSendEvent`]s needed to claim the payment.
3360+
///
3361+
/// Returns whether any HTLCs were claimed. If true, you must have the network layer process any
3362+
/// [`MessageSendEvent`]s returned by [`get_and_clear_pending_msg_events`].
33593363
///
33603364
/// Note that if you did not set an `amount_msat` when calling [`create_inbound_payment`] or
33613365
/// [`create_inbound_payment_for_hash`] you must check that the amount in the `PaymentReceived`
33623366
/// event matches your expectation. If you fail to do so and call this method, you may provide
33633367
/// the sender "proof-of-payment" when they did not fulfill the full expected payment.
33643368
///
3365-
/// May panic if called except in response to a PaymentReceived event.
3366-
///
3369+
/// [`Event::PaymentReceived`]: crate::util::events::Event::PaymentReceived
3370+
/// [`get_and_clear_pending_msg_events`]: MessageSendEventsProvider::get_and_clear_pending_msg_events
33673371
/// [`create_inbound_payment`]: Self::create_inbound_payment
33683372
/// [`create_inbound_payment_for_hash`]: Self::create_inbound_payment_for_hash
33693373
pub fn claim_funds(&self, payment_preimage: PaymentPreimage) -> bool {

lightning/src/ln/functional_test_utils.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,7 @@ pub fn sign_funding_transaction<'a, 'b, 'c>(node_a: &Node<'a, 'b, 'c>, node_b: &
530530
let (temporary_channel_id, tx, funding_output) = create_funding_transaction(node_a, channel_value, 42);
531531
assert_eq!(temporary_channel_id, expected_temporary_channel_id);
532532

533-
node_a.node.funding_transaction_generated(&temporary_channel_id, tx.clone()).unwrap();
533+
assert!(node_a.node.funding_transaction_generated(&temporary_channel_id, tx.clone()).is_ok());
534534
check_added_monitors!(node_a, 0);
535535

536536
let funding_created_msg = get_event_msg!(node_a, MessageSendEvent::SendFundingCreated, node_b.node.get_our_node_id());
@@ -558,6 +558,11 @@ pub fn sign_funding_transaction<'a, 'b, 'c>(node_a: &Node<'a, 'b, 'c>, node_b: &
558558
assert_eq!(node_a.tx_broadcaster.txn_broadcasted.lock().unwrap()[0], tx);
559559
node_a.tx_broadcaster.txn_broadcasted.lock().unwrap().clear();
560560

561+
// Ensure that funding_transaction_generated is idempotent.
562+
assert!(node_a.node.funding_transaction_generated(&temporary_channel_id, tx.clone()).is_err());
563+
assert!(node_a.node.get_and_clear_pending_msg_events().is_empty());
564+
check_added_monitors!(node_a, 0);
565+
561566
tx
562567
}
563568

@@ -1057,6 +1062,9 @@ macro_rules! expect_pending_htlcs_forwardable {
10571062
($node: expr) => {{
10581063
expect_pending_htlcs_forwardable_ignore!($node);
10591064
$node.node.process_pending_htlc_forwards();
1065+
1066+
// Ensure process_pending_htlc_forwards is idempotent.
1067+
$node.node.process_pending_htlc_forwards();
10601068
}}
10611069
}
10621070

@@ -1070,6 +1078,9 @@ macro_rules! expect_pending_htlcs_forwardable_from_events {
10701078
};
10711079
if $ignore {
10721080
$node.node.process_pending_htlc_forwards();
1081+
1082+
// Ensure process_pending_htlc_forwards is idempotent.
1083+
$node.node.process_pending_htlc_forwards();
10731084
}
10741085
}}
10751086
}
@@ -1392,6 +1403,12 @@ pub fn do_claim_payment_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>,
13921403
last_update_fulfill_dance!(origin_node, expected_route.first().unwrap());
13931404
}
13941405
}
1406+
1407+
// Ensure that claim_funds is idempotent.
1408+
assert!(!expected_paths[0].last().unwrap().node.claim_funds(our_payment_preimage));
1409+
assert!(expected_paths[0].last().unwrap().node.get_and_clear_pending_msg_events().is_empty());
1410+
check_added_monitors!(expected_paths[0].last().unwrap(), 0);
1411+
13951412
expected_total_fee_msat
13961413
}
13971414
pub fn claim_payment_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_paths: &[&[&Node<'a, 'b, 'c>]], skip_last: bool, our_payment_preimage: PaymentPreimage) {
@@ -1536,6 +1553,12 @@ pub fn fail_payment_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expe
15361553
}
15371554
}
15381555
}
1556+
1557+
// Ensure that fail_htlc_backwards is idempotent.
1558+
assert!(!expected_paths[0].last().unwrap().node.fail_htlc_backwards(&our_payment_hash));
1559+
assert!(expected_paths[0].last().unwrap().node.get_and_clear_pending_events().is_empty());
1560+
assert!(expected_paths[0].last().unwrap().node.get_and_clear_pending_msg_events().is_empty());
1561+
check_added_monitors!(expected_paths[0].last().unwrap(), 0);
15391562
}
15401563

15411564
pub fn fail_payment<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_path: &[&Node<'a, 'b, 'c>], our_payment_hash: PaymentHash) {

lightning/src/util/events.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,13 @@ impl_writeable_tlv_based_enum_upgradable!(ClosureReason,
153153
#[derive(Clone, Debug)]
154154
pub enum Event {
155155
/// Used to indicate that the client should generate a funding transaction with the given
156-
/// parameters and then call ChannelManager::funding_transaction_generated.
157-
/// Generated in ChannelManager message handling.
156+
/// parameters and then call [`ChannelManager::funding_transaction_generated`].
157+
/// Generated in [`ChannelManager`] message handling.
158158
/// Note that *all inputs* in the funding transaction must spend SegWit outputs or your
159159
/// counterparty can steal your funds!
160+
///
161+
/// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager
162+
/// [`ChannelManager::funding_transaction_generated`]: crate::ln::channelmanager::ChannelManager::funding_transaction_generated
160163
FundingGenerationReady {
161164
/// The random channel_id we picked which you'll need to pass into
162165
/// ChannelManager::funding_transaction_generated.
@@ -271,8 +274,10 @@ pub enum Event {
271274
#[cfg(test)]
272275
error_data: Option<Vec<u8>>,
273276
},
274-
/// Used to indicate that ChannelManager::process_pending_htlc_forwards should be called at a
275-
/// time in the future.
277+
/// Used to indicate that [`ChannelManager::process_pending_htlc_forwards`] should be called at
278+
/// a time in the future.
279+
///
280+
/// [`ChannelManager::process_pending_htlc_forwards`]: crate::ln::channelmanager::ChannelManager::process_pending_htlc_forwards
276281
PendingHTLCsForwardable {
277282
/// The minimum amount of time that should be waited prior to calling
278283
/// process_pending_htlc_forwards. To increase the effort required to correlate payments,

0 commit comments

Comments
 (0)