Skip to content

Commit 3a83a4e

Browse files
committed
fuzz: add force close actions to chanmon_consistency
Add explicit force-close fuzz actions for the A-B and B-C channels. Enable holder commitment and holder HTLC signing together so on-chain cleanup retries do not split the paired monitor-side signer operations. The all-node holder-signing byte remains as a compatibility alias for existing fuzz inputs. The harness records dust HTLC paths before closing so later payment resolution checks can account for claims blocked by dust outputs.
1 parent c2c76d2 commit 3a83a4e

1 file changed

Lines changed: 93 additions & 22 deletions

File tree

fuzz/src/chanmon_consistency.rs

Lines changed: 93 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
//! actions such as sending payments, handling events, or changing monitor update return values on
1616
//! a per-node basis. This should allow it to find any cases where the ordering of actions results
1717
//! in us getting out of sync with ourselves, and, assuming at least one of our recieve- or
18-
//! send-side handling is correct, other peers.
18+
//! send-side handling is correct, other peers. The fuzzer also exercises user-initiated
19+
//! force-closes with on-chain commitment transaction confirmation.
1920
2021
use bitcoin::amount::Amount;
2122
use bitcoin::constants::genesis_block;
@@ -49,7 +50,7 @@ use lightning::events::{self, EventsProvider};
4950
use lightning::ln::channel::{
5051
FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE, MAX_STD_OUTPUT_DUST_LIMIT_SATOSHIS,
5152
};
52-
use lightning::ln::channel_state::ChannelDetails;
53+
use lightning::ln::channel_state::{ChannelDetails, InboundHTLCDetails, OutboundHTLCDetails};
5354
use lightning::ln::channelmanager::{
5455
ChainParameters, ChannelManager, ChannelManagerReadArgs, PaymentId, RecentPaymentDetails,
5556
TrustedChannelFeatures,
@@ -765,10 +766,12 @@ impl SignerProvider for KeyProvider {
765766
}
766767
}
767768

768-
const SUPPORTED_SIGNER_OPS: [SignerOp; 3] = [
769+
const SUPPORTED_SIGNER_OPS: [SignerOp; 5] = [
769770
SignerOp::SignCounterpartyCommitment,
770771
SignerOp::GetPerCommitmentPoint,
771772
SignerOp::ReleaseCommitmentSecret,
773+
SignerOp::SignHolderCommitment,
774+
SignerOp::SignHolderHtlcTransaction,
772775
];
773776

774777
impl KeyProvider {
@@ -1054,6 +1057,12 @@ impl<'a> HarnessNode<'a> {
10541057
self.node.timer_tick_occurred();
10551058
}
10561059

1060+
fn enable_holder_signer_ops(&self) {
1061+
self.keys_manager.enable_op_for_all_signers(SignerOp::SignHolderCommitment);
1062+
self.keys_manager.enable_op_for_all_signers(SignerOp::SignHolderHtlcTransaction);
1063+
self.node.signer_unblocked(None);
1064+
}
1065+
10571066
fn current_feerate_sat_per_kw(&self) -> FeeRate {
10581067
self.fee_estimator.feerate_sat_per_kw()
10591068
}
@@ -1210,6 +1219,16 @@ impl<'a> HarnessNode<'a> {
12101219
}
12111220
}
12121221

1222+
#[inline]
1223+
fn inbound_dust_blocks_path(htlc: &InboundHTLCDetails) -> bool {
1224+
htlc.is_dust
1225+
}
1226+
1227+
#[inline]
1228+
fn outbound_dust_blocks_path(htlc: &OutboundHTLCDetails) -> bool {
1229+
htlc.is_dust
1230+
}
1231+
12131232
#[derive(Copy, Clone)]
12141233
enum MonitorReloadSelector {
12151234
Persisted,
@@ -3627,6 +3646,65 @@ impl<'a, Out: Output + MaybeSend + MaybeSync> Harness<'a, Out> {
36273646
assert!(settled, "message-only settle exceeded budget: {}", self.pending_work_summary(),);
36283647
}
36293648

3649+
fn record_force_close_dust(&self, closer_idx: usize, channel_id: ChannelId) {
3650+
if let Some(channel) = self.nodes[closer_idx]
3651+
.node
3652+
.list_channels()
3653+
.into_iter()
3654+
.find(|chan| chan.channel_id == channel_id)
3655+
{
3656+
let mut dust_parts = channel
3657+
.pending_inbound_htlcs
3658+
.iter()
3659+
.filter(|htlc| inbound_dust_blocks_path(htlc))
3660+
.map(|htlc| (htlc.payment_hash, htlc.amount_msat))
3661+
.chain(
3662+
channel
3663+
.pending_outbound_htlcs
3664+
.iter()
3665+
.filter(|htlc| outbound_dust_blocks_path(htlc))
3666+
.map(|htlc| (htlc.payment_hash, htlc.amount_msat)),
3667+
)
3668+
.collect::<Vec<_>>();
3669+
let payment_paths = self.payments.payment_paths_by_hash.borrow();
3670+
let mut blocked_paths = self.payments.blocked_dust_paths_by_hash.borrow_mut();
3671+
for (payment_hash, amount_msat) in dust_parts.drain(..) {
3672+
let Some(paths) = payment_paths.get(&payment_hash) else {
3673+
continue;
3674+
};
3675+
let blocked_for_hash =
3676+
blocked_paths.entry(payment_hash).or_insert_with(HashSet::new);
3677+
if let Some((path_idx, _)) = paths.iter().enumerate().find(|(path_idx, path)| {
3678+
!blocked_for_hash.contains(path_idx)
3679+
&& path.iter().any(|(chan_id, part_amt)| {
3680+
*chan_id == channel_id && *part_amt == amount_msat
3681+
})
3682+
}) {
3683+
blocked_for_hash.insert(path_idx);
3684+
}
3685+
}
3686+
}
3687+
}
3688+
3689+
fn force_close(
3690+
&mut self, closer_idx: usize, channel_id: ChannelId, counterparty_idx: usize, reason: &str,
3691+
) {
3692+
self.flush_progress(32);
3693+
self.record_force_close_dust(closer_idx, channel_id);
3694+
if self.nodes[closer_idx]
3695+
.node
3696+
.force_close_broadcasting_latest_txn(
3697+
&channel_id,
3698+
&self.nodes[counterparty_idx].get_our_node_id(),
3699+
reason.to_string(),
3700+
)
3701+
.is_ok()
3702+
{
3703+
self.payments.closed_channels.borrow_mut().insert(channel_id);
3704+
self.flush_progress(32);
3705+
}
3706+
}
3707+
36303708
fn probe_amount_for_direction(
36313709
&self, source_idx: usize, dest_chan_id: ChannelId,
36323710
) -> Option<u64> {
@@ -4036,27 +4114,20 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(data: &[u8], out: Out) {
40364114
.enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret);
40374115
harness.nodes[2].signer_unblocked(None);
40384116
},
4039-
0xcc => {
4040-
harness.nodes[1]
4041-
.keys_manager
4042-
.enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret);
4043-
let filter = Some((harness.nodes[0].get_our_node_id(), harness.chan_a_id()));
4044-
harness.nodes[1].signer_unblocked(filter);
4045-
},
4046-
0xcd => {
4047-
harness.nodes[1]
4048-
.keys_manager
4049-
.enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret);
4050-
let filter = Some((harness.nodes[2].get_our_node_id(), harness.chan_b_id()));
4051-
harness.nodes[1].signer_unblocked(filter);
4052-
},
4053-
0xce => {
4054-
harness.nodes[2]
4055-
.keys_manager
4056-
.enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret);
4057-
harness.nodes[2].signer_unblocked(None);
4117+
0xcc => harness.nodes[0].enable_holder_signer_ops(),
4118+
0xcd => harness.nodes[1].enable_holder_signer_ops(),
4119+
0xce => harness.nodes[2].enable_holder_signer_ops(),
4120+
0xcf => {
4121+
harness.nodes[0].enable_holder_signer_ops();
4122+
harness.nodes[1].enable_holder_signer_ops();
4123+
harness.nodes[2].enable_holder_signer_ops();
40584124
},
40594125

4126+
0xd0 => harness.force_close(0, harness.chan_a_id(), 1, "]]]]]]]]]"),
4127+
0xd1 => harness.force_close(1, harness.chan_b_id(), 2, "]]]]]]]]"),
4128+
0xd2 => harness.force_close(1, harness.chan_a_id(), 0, "]]]]]]]"),
4129+
0xd3 => harness.force_close(2, harness.chan_b_id(), 1, "]]]]]"),
4130+
40604131
0xd8 => harness.confirm_broadcasts_for_node(0),
40614132
0xd9 => harness.confirm_broadcasts_for_node(1),
40624133
0xda => harness.confirm_broadcasts_for_node(2),

0 commit comments

Comments
 (0)