Skip to content

Commit 2667e5e

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 and enable the holder signing operations needed to exercise them. The harness records dust HTLC paths before closing so later payment resolution checks can account for claims blocked by dust outputs.
1 parent f95343e commit 2667e5e

1 file changed

Lines changed: 100 additions & 11 deletions

File tree

fuzz/src/chanmon_consistency.rs

Lines changed: 100 additions & 11 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,
@@ -648,10 +649,12 @@ impl SignerProvider for KeyProvider {
648649
}
649650
}
650651

651-
const SUPPORTED_SIGNER_OPS: [SignerOp; 3] = [
652+
const SUPPORTED_SIGNER_OPS: [SignerOp; 5] = [
652653
SignerOp::SignCounterpartyCommitment,
653654
SignerOp::GetPerCommitmentPoint,
654655
SignerOp::ReleaseCommitmentSecret,
656+
SignerOp::SignHolderCommitment,
657+
SignerOp::SignHolderHtlcTransaction,
655658
];
656659

657660
impl KeyProvider {
@@ -1432,6 +1435,16 @@ impl<'a> HarnessNode<'a> {
14321435
}
14331436
}
14341437

1438+
#[inline]
1439+
fn inbound_dust_blocks_path(htlc: &InboundHTLCDetails) -> bool {
1440+
htlc.is_dust
1441+
}
1442+
1443+
#[inline]
1444+
fn outbound_dust_blocks_path(htlc: &OutboundHTLCDetails) -> bool {
1445+
htlc.is_dust
1446+
}
1447+
14351448
struct EventQueues {
14361449
ab: Vec<MessageSendEvent>,
14371450
ba: Vec<MessageSendEvent>,
@@ -3150,6 +3163,65 @@ impl<'a, 'd, Out: Output + MaybeSend + MaybeSync> Harness<'a, 'd, Out> {
31503163
assert!(settled, "message-only settle exceeded budget: {}", self.pending_work_summary(),);
31513164
}
31523165

3166+
fn record_force_close_dust(&self, closer_idx: usize, channel_id: ChannelId) {
3167+
if let Some(channel) = self.nodes[closer_idx]
3168+
.node
3169+
.list_channels()
3170+
.into_iter()
3171+
.find(|chan| chan.channel_id == channel_id)
3172+
{
3173+
let mut dust_parts = channel
3174+
.pending_inbound_htlcs
3175+
.iter()
3176+
.filter(|htlc| inbound_dust_blocks_path(htlc))
3177+
.map(|htlc| (htlc.payment_hash, htlc.amount_msat))
3178+
.chain(
3179+
channel
3180+
.pending_outbound_htlcs
3181+
.iter()
3182+
.filter(|htlc| outbound_dust_blocks_path(htlc))
3183+
.map(|htlc| (htlc.payment_hash, htlc.amount_msat)),
3184+
)
3185+
.collect::<Vec<_>>();
3186+
let payment_paths = self.payments.payment_paths_by_hash.borrow();
3187+
let mut blocked_paths = self.payments.blocked_dust_paths_by_hash.borrow_mut();
3188+
for (payment_hash, amount_msat) in dust_parts.drain(..) {
3189+
let Some(paths) = payment_paths.get(&payment_hash) else {
3190+
continue;
3191+
};
3192+
let blocked_for_hash =
3193+
blocked_paths.entry(payment_hash).or_insert_with(HashSet::new);
3194+
if let Some((path_idx, _)) = paths.iter().enumerate().find(|(path_idx, path)| {
3195+
!blocked_for_hash.contains(path_idx)
3196+
&& path.iter().any(|(chan_id, part_amt)| {
3197+
*chan_id == channel_id && *part_amt == amount_msat
3198+
})
3199+
}) {
3200+
blocked_for_hash.insert(path_idx);
3201+
}
3202+
}
3203+
}
3204+
}
3205+
3206+
fn force_close(
3207+
&mut self, closer_idx: usize, channel_id: ChannelId, counterparty_idx: usize, reason: &str,
3208+
) {
3209+
self.flush_progress(32);
3210+
self.record_force_close_dust(closer_idx, channel_id);
3211+
if self.nodes[closer_idx]
3212+
.node
3213+
.force_close_broadcasting_latest_txn(
3214+
&channel_id,
3215+
&self.nodes[counterparty_idx].our_node_id(),
3216+
reason.to_string(),
3217+
)
3218+
.is_ok()
3219+
{
3220+
self.payments.closed_channels.borrow_mut().insert(channel_id);
3221+
self.flush_progress(32);
3222+
}
3223+
}
3224+
31533225
fn probe_amount_for_direction(
31543226
&self, source_idx: usize, dest_chan_id: ChannelId,
31553227
) -> Option<u64> {
@@ -4092,25 +4164,42 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(data: &[u8], out: Out) {
40924164
harness.nodes[2].node.signer_unblocked(None);
40934165
},
40944166
0xcc => {
4095-
harness.nodes[1]
4167+
harness.nodes[0]
40964168
.keys_manager
4097-
.enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret);
4098-
let filter = Some((harness.nodes[0].our_node_id(), harness.chan_a_id()));
4099-
harness.nodes[1].node.signer_unblocked(filter);
4169+
.enable_op_for_all_signers(SignerOp::SignHolderCommitment);
4170+
harness.nodes[0].node.signer_unblocked(None);
41004171
},
41014172
0xcd => {
41024173
harness.nodes[1]
41034174
.keys_manager
4104-
.enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret);
4105-
let filter = Some((harness.nodes[2].our_node_id(), harness.chan_b_id()));
4106-
harness.nodes[1].node.signer_unblocked(filter);
4175+
.enable_op_for_all_signers(SignerOp::SignHolderCommitment);
4176+
harness.nodes[1].node.signer_unblocked(None);
41074177
},
41084178
0xce => {
41094179
harness.nodes[2]
41104180
.keys_manager
4111-
.enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret);
4181+
.enable_op_for_all_signers(SignerOp::SignHolderCommitment);
41124182
harness.nodes[2].node.signer_unblocked(None);
41134183
},
4184+
0xcf => {
4185+
harness.nodes[0]
4186+
.keys_manager
4187+
.enable_op_for_all_signers(SignerOp::SignHolderHtlcTransaction);
4188+
harness.nodes[1]
4189+
.keys_manager
4190+
.enable_op_for_all_signers(SignerOp::SignHolderHtlcTransaction);
4191+
harness.nodes[2]
4192+
.keys_manager
4193+
.enable_op_for_all_signers(SignerOp::SignHolderHtlcTransaction);
4194+
harness.nodes[0].node.signer_unblocked(None);
4195+
harness.nodes[1].node.signer_unblocked(None);
4196+
harness.nodes[2].node.signer_unblocked(None);
4197+
},
4198+
4199+
0xd0 => harness.force_close(0, harness.chan_a_id(), 1, "]]]]]]]]]"),
4200+
0xd1 => harness.force_close(1, harness.chan_b_id(), 2, "]]]]]]]]"),
4201+
0xd2 => harness.force_close(1, harness.chan_a_id(), 0, "]]]]]]]"),
4202+
0xd3 => harness.force_close(2, harness.chan_b_id(), 1, "]]]]]"),
41144203

41154204
0xd8 => harness.confirm_broadcasts_for_node(0),
41164205
0xd9 => harness.confirm_broadcasts_for_node(1),

0 commit comments

Comments
 (0)