Skip to content

Commit b57a8b7

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 3cf90ac commit b57a8b7

1 file changed

Lines changed: 94 additions & 3 deletions

File tree

fuzz/src/chanmon_consistency.rs

Lines changed: 94 additions & 3 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 {
@@ -1080,6 +1083,12 @@ impl<'a> HarnessNode<'a> {
10801083
self.node.timer_tick_occurred();
10811084
}
10821085

1086+
fn enable_holder_signer_ops(&self) {
1087+
self.keys_manager.enable_op_for_all_signers(SignerOp::SignHolderCommitment);
1088+
self.keys_manager.enable_op_for_all_signers(SignerOp::SignHolderHtlcTransaction);
1089+
self.node.signer_unblocked(None);
1090+
}
1091+
10831092
fn current_feerate_sat_per_kw(&self) -> FeeRate {
10841093
self.fee_estimator.feerate_sat_per_kw()
10851094
}
@@ -1238,6 +1247,16 @@ impl<'a> HarnessNode<'a> {
12381247
}
12391248
}
12401249

1250+
#[inline]
1251+
fn inbound_dust_blocks_path(htlc: &InboundHTLCDetails) -> bool {
1252+
htlc.is_dust
1253+
}
1254+
1255+
#[inline]
1256+
fn outbound_dust_blocks_path(htlc: &OutboundHTLCDetails) -> bool {
1257+
htlc.is_dust
1258+
}
1259+
12411260
#[derive(Copy, Clone)]
12421261
enum MonitorReloadSelector {
12431262
Persisted,
@@ -3663,6 +3682,65 @@ impl<'a, Out: Output + MaybeSend + MaybeSync> Harness<'a, Out> {
36633682
assert!(settled, "message-only settle exceeded budget: {}", self.pending_work_summary(),);
36643683
}
36653684

3685+
fn record_force_close_dust(&self, closer_idx: usize, channel_id: ChannelId) {
3686+
if let Some(channel) = self.nodes[closer_idx]
3687+
.node
3688+
.list_channels()
3689+
.into_iter()
3690+
.find(|chan| chan.channel_id == channel_id)
3691+
{
3692+
let mut dust_parts = channel
3693+
.pending_inbound_htlcs
3694+
.iter()
3695+
.filter(|htlc| inbound_dust_blocks_path(htlc))
3696+
.map(|htlc| (htlc.payment_hash, htlc.amount_msat))
3697+
.chain(
3698+
channel
3699+
.pending_outbound_htlcs
3700+
.iter()
3701+
.filter(|htlc| outbound_dust_blocks_path(htlc))
3702+
.map(|htlc| (htlc.payment_hash, htlc.amount_msat)),
3703+
)
3704+
.collect::<Vec<_>>();
3705+
let payment_paths = self.payments.payment_paths_by_hash.borrow();
3706+
let mut blocked_paths = self.payments.blocked_dust_paths_by_hash.borrow_mut();
3707+
for (payment_hash, amount_msat) in dust_parts.drain(..) {
3708+
let Some(paths) = payment_paths.get(&payment_hash) else {
3709+
continue;
3710+
};
3711+
let blocked_for_hash =
3712+
blocked_paths.entry(payment_hash).or_insert_with(HashSet::new);
3713+
if let Some((path_idx, _)) = paths.iter().enumerate().find(|(path_idx, path)| {
3714+
!blocked_for_hash.contains(path_idx)
3715+
&& path.iter().any(|(chan_id, part_amt)| {
3716+
*chan_id == channel_id && *part_amt == amount_msat
3717+
})
3718+
}) {
3719+
blocked_for_hash.insert(path_idx);
3720+
}
3721+
}
3722+
}
3723+
}
3724+
3725+
fn force_close(
3726+
&mut self, closer_idx: usize, channel_id: ChannelId, counterparty_idx: usize, reason: &str,
3727+
) {
3728+
self.flush_progress(32);
3729+
self.record_force_close_dust(closer_idx, channel_id);
3730+
if self.nodes[closer_idx]
3731+
.node
3732+
.force_close_broadcasting_latest_txn(
3733+
&channel_id,
3734+
&self.nodes[counterparty_idx].get_our_node_id(),
3735+
reason.to_string(),
3736+
)
3737+
.is_ok()
3738+
{
3739+
self.payments.closed_channels.borrow_mut().insert(channel_id);
3740+
self.flush_progress(32);
3741+
}
3742+
}
3743+
36663744
fn probe_amount_for_direction(
36673745
&self, source_idx: usize, dest_chan_id: ChannelId,
36683746
) -> Option<u64> {
@@ -4101,6 +4179,19 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(data: &[u8], out: Out) {
41014179
harness.nodes[2].signer_unblocked(None);
41024180
},
41034181

4182+
0xd0 => harness.force_close(0, harness.chan_a_id(), 1, "]]]]]]]]]"),
4183+
0xd1 => harness.force_close(1, harness.chan_b_id(), 2, "]]]]]]]]"),
4184+
0xd2 => harness.force_close(1, harness.chan_a_id(), 0, "]]]]]]]"),
4185+
0xd3 => harness.force_close(2, harness.chan_b_id(), 1, "]]]]]"),
4186+
0xd4 => harness.nodes[0].enable_holder_signer_ops(),
4187+
0xd5 => harness.nodes[1].enable_holder_signer_ops(),
4188+
0xd6 => harness.nodes[2].enable_holder_signer_ops(),
4189+
0xd7 => {
4190+
harness.nodes[0].enable_holder_signer_ops();
4191+
harness.nodes[1].enable_holder_signer_ops();
4192+
harness.nodes[2].enable_holder_signer_ops();
4193+
},
4194+
41044195
0xd8 => harness.confirm_broadcasts_for_node(0),
41054196
0xd9 => harness.confirm_broadcasts_for_node(1),
41064197
0xda => harness.confirm_broadcasts_for_node(2),

0 commit comments

Comments
 (0)