Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 99 additions & 36 deletions fuzz/src/chanmon_consistency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ use lightning::ln::channel::{
use lightning::ln::channel_state::ChannelDetails;
use lightning::ln::channelmanager::{
ChainParameters, ChannelManager, ChannelManagerReadArgs, PaymentId, RecentPaymentDetails,
TrustedChannelFeatures,
};
use lightning::ln::functional_test_utils::*;
use lightning::ln::funding::{FundingContribution, FundingTemplate};
Expand Down Expand Up @@ -863,30 +864,41 @@ fn assert_action_timeout_awaiting_response(action: &msgs::ErrorAction) {
));
}

enum ChanType {
Legacy,
KeyedAnchors,
ZeroFeeCommitments,
}

#[inline]
pub fn do_test<Out: Output + MaybeSend + MaybeSync>(
data: &[u8], underlying_out: Out, anchors: bool,
) {
pub fn do_test<Out: Output + MaybeSend + MaybeSync>(data: &[u8], underlying_out: Out) {
let out = SearchingOutput::new(underlying_out);
let broadcast_a = Arc::new(TestBroadcaster { txn_broadcasted: RefCell::new(Vec::new()) });
let broadcast_b = Arc::new(TestBroadcaster { txn_broadcasted: RefCell::new(Vec::new()) });
let broadcast_c = Arc::new(TestBroadcaster { txn_broadcasted: RefCell::new(Vec::new()) });
let router = FuzzRouter {};

// Read initial monitor styles from fuzz input (1 byte: 2 bits per node)
let initial_mon_styles = if !data.is_empty() { data[0] } else { 0 };
// Read initial monitor styles and channel type from fuzz input byte 0:
// bits 0-2: monitor styles (1 bit per node)
// bits 3-4: channel type (0=Legacy, 1=KeyedAnchors, 2=ZeroFeeCommitments)
let config_byte = if !data.is_empty() { data[0] } else { 0 };
let chan_type = match (config_byte >> 3) & 0b11 {
0 => ChanType::Legacy,
1 => ChanType::KeyedAnchors,
_ => ChanType::ZeroFeeCommitments,
};
let mon_style = [
RefCell::new(if initial_mon_styles & 0b01 != 0 {
RefCell::new(if config_byte & 0b01 != 0 {
ChannelMonitorUpdateStatus::InProgress
} else {
ChannelMonitorUpdateStatus::Completed
}),
RefCell::new(if initial_mon_styles & 0b10 != 0 {
RefCell::new(if config_byte & 0b10 != 0 {
ChannelMonitorUpdateStatus::InProgress
} else {
ChannelMonitorUpdateStatus::Completed
}),
RefCell::new(if initial_mon_styles & 0b100 != 0 {
RefCell::new(if config_byte & 0b100 != 0 {
ChannelMonitorUpdateStatus::InProgress
} else {
ChannelMonitorUpdateStatus::Completed
Expand Down Expand Up @@ -926,8 +938,19 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(
config.channel_config.forwarding_fee_proportional_millionths = 0;
config.channel_handshake_config.announce_for_forwarding = true;
config.reject_inbound_splices = false;
if !anchors {
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = false;
match chan_type {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it is good to build out this way of increasing coverage. If chan type is part of the fuzz bytes, the fuzzer is able to zoom in on type-specific code paths.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for taking a look ! see the commit below

ChanType::Legacy => {
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = false;
config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = false;
},
ChanType::KeyedAnchors => {
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = false;
},
ChanType::ZeroFeeCommitments => {
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = false;
config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true;
},
}
let network = Network::Bitcoin;
let best_block_timestamp = genesis_block(network).header.time;
Expand Down Expand Up @@ -978,8 +1001,19 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(
config.channel_config.forwarding_fee_proportional_millionths = 0;
config.channel_handshake_config.announce_for_forwarding = true;
config.reject_inbound_splices = false;
if !anchors {
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = false;
match chan_type {
ChanType::Legacy => {
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = false;
config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = false;
},
ChanType::KeyedAnchors => {
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = false;
},
ChanType::ZeroFeeCommitments => {
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = false;
config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true;
},
}

let mut monitors = new_hash_map();
Expand Down Expand Up @@ -1078,8 +1112,23 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(
}};
}
macro_rules! make_channel {
($source: expr, $dest: expr, $source_monitor: expr, $dest_monitor: expr, $dest_keys_manager: expr, $chan_id: expr) => {{
$source.create_channel($dest.get_our_node_id(), 100_000, 42, 0, None, None).unwrap();
($source: expr, $dest: expr, $source_monitor: expr, $dest_monitor: expr, $dest_keys_manager: expr, $chan_id: expr, $trusted_open: expr, $trusted_accept: expr) => {{
if $trusted_open {
$source
.create_channel_to_trusted_peer_0reserve(
$dest.get_our_node_id(),
100_000,
42,
0,
None,
None,
)
.unwrap();
} else {
$source
.create_channel($dest.get_our_node_id(), 100_000, 42, 0, None, None)
.unwrap();
}
let open_channel = {
let events = $source.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 1);
Expand All @@ -1104,14 +1153,26 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(
random_bytes
.copy_from_slice(&$dest_keys_manager.get_secure_random_bytes()[..16]);
let user_channel_id = u128::from_be_bytes(random_bytes);
$dest
.accept_inbound_channel(
temporary_channel_id,
counterparty_node_id,
user_channel_id,
None,
)
.unwrap();
if $trusted_accept {
$dest
.accept_inbound_channel_from_trusted_peer(
temporary_channel_id,
counterparty_node_id,
user_channel_id,
TrustedChannelFeatures::ZeroReserve,
None,
)
.unwrap();
} else {
$dest
Comment on lines 1153 to +1167
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: The $trusted_accept branch calls accept_inbound_channel_from_trusted_peer with (accept_0conf=false, accept_0reserve=true), which means the zero-reserve acceptance path never exercises zero-conf. Consider also fuzzing the combination (true, true) on at least one channel to cover the zero-conf + zero-reserve interaction path that test_zero_reserve_zero_conf_combined tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leaving this to a follow-up to this PR

.accept_inbound_channel(
temporary_channel_id,
counterparty_node_id,
user_channel_id,
None,
)
.unwrap();
}
} else {
panic!("Wrong event type");
}
Expand Down Expand Up @@ -1287,12 +1348,16 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(
// Fuzz mode uses XOR-based hashing (all bytes XOR to one byte), and
// versions 0-5 cause collisions between A-B and B-C channel pairs
// (e.g., A-B with Version(1) collides with B-C with Version(3)).
make_channel!(nodes[0], nodes[1], monitor_a, monitor_b, keys_manager_b, 1);
make_channel!(nodes[0], nodes[1], monitor_a, monitor_b, keys_manager_b, 2);
make_channel!(nodes[0], nodes[1], monitor_a, monitor_b, keys_manager_b, 3);
make_channel!(nodes[1], nodes[2], monitor_b, monitor_c, keys_manager_c, 4);
make_channel!(nodes[1], nodes[2], monitor_b, monitor_c, keys_manager_c, 5);
make_channel!(nodes[1], nodes[2], monitor_b, monitor_c, keys_manager_c, 6);
// A-B: channel 2 A and B have 0-reserve (trusted open + trusted accept),
// channel 3 A has 0-reserve (trusted accept)
make_channel!(nodes[0], nodes[1], monitor_a, monitor_b, keys_manager_b, 1, false, false);
make_channel!(nodes[0], nodes[1], monitor_a, monitor_b, keys_manager_b, 2, true, true);
make_channel!(nodes[0], nodes[1], monitor_a, monitor_b, keys_manager_b, 3, false, true);
// B-C: channel 4 B has 0-reserve (via trusted accept),
// channel 5 C has 0-reserve (via trusted open)
make_channel!(nodes[1], nodes[2], monitor_b, monitor_c, keys_manager_c, 4, false, true);
make_channel!(nodes[1], nodes[2], monitor_b, monitor_c, keys_manager_c, 5, true, false);
make_channel!(nodes[1], nodes[2], monitor_b, monitor_c, keys_manager_c, 6, false, false);

// Wipe the transactions-broadcasted set to make sure we don't broadcast any transactions
// during normal operation in `test_return`.
Expand Down Expand Up @@ -1376,7 +1441,7 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(
}};
}

let mut read_pos = 1; // First byte was consumed for initial mon_style
let mut read_pos = 1; // First byte was consumed for initial config (mon_style + chan_type)
macro_rules! get_slice {
($len: expr) => {{
let slice_len = $len as usize;
Expand Down Expand Up @@ -2301,7 +2366,7 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(

0x80 => {
let mut max_feerate = last_htlc_clear_fee_a;
if !anchors {
if matches!(chan_type, ChanType::Legacy) {
max_feerate *= FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32;
}
if fee_est_a.ret_val.fetch_add(250, atomic::Ordering::AcqRel) + 250 > max_feerate {
Expand All @@ -2316,7 +2381,7 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(

0x84 => {
let mut max_feerate = last_htlc_clear_fee_b;
if !anchors {
if matches!(chan_type, ChanType::Legacy) {
max_feerate *= FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32;
}
if fee_est_b.ret_val.fetch_add(250, atomic::Ordering::AcqRel) + 250 > max_feerate {
Expand All @@ -2331,7 +2396,7 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(

0x88 => {
let mut max_feerate = last_htlc_clear_fee_c;
if !anchors {
if matches!(chan_type, ChanType::Legacy) {
max_feerate *= FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32;
}
if fee_est_c.ret_val.fetch_add(250, atomic::Ordering::AcqRel) + 250 > max_feerate {
Expand Down Expand Up @@ -2783,12 +2848,10 @@ impl<O: Output> SearchingOutput<O> {
}

pub fn chanmon_consistency_test<Out: Output + MaybeSend + MaybeSync>(data: &[u8], out: Out) {
do_test(data, out.clone(), false);
do_test(data, out, true);
do_test(data, out);
}

#[no_mangle]
pub extern "C" fn chanmon_consistency_run(data: *const u8, datalen: usize) {
do_test(unsafe { std::slice::from_raw_parts(data, datalen) }, test_logger::DevNull {}, false);
do_test(unsafe { std::slice::from_raw_parts(data, datalen) }, test_logger::DevNull {}, true);
do_test(unsafe { std::slice::from_raw_parts(data, datalen) }, test_logger::DevNull {});
}
7 changes: 5 additions & 2 deletions lightning-liquidity/tests/lsps2_integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ use common::{

use lightning::events::{ClosureReason, Event};
use lightning::get_event_msg;
use lightning::ln::channelmanager::{OptionalBolt11PaymentParams, PaymentId};
use lightning::ln::channelmanager::{
OptionalBolt11PaymentParams, PaymentId, TrustedChannelFeatures,
};
use lightning::ln::functional_test_utils::*;
use lightning::ln::msgs::BaseMessageHandler;
use lightning::ln::msgs::ChannelMessageHandler;
Expand Down Expand Up @@ -1513,10 +1515,11 @@ fn create_channel_with_manual_broadcast(
Event::OpenChannelRequest { temporary_channel_id, .. } => {
client_node
.node
.accept_inbound_channel_from_trusted_peer_0conf(
.accept_inbound_channel_from_trusted_peer(
&temporary_channel_id,
&service_node_id,
user_channel_id,
TrustedChannelFeatures::ZeroConf,
None,
)
.unwrap();
Expand Down
2 changes: 1 addition & 1 deletion lightning/src/events/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1643,7 +1643,7 @@ pub enum Event {
/// Furthermore, note that if [`ChannelTypeFeatures::supports_zero_conf`] returns true on this type,
/// the resulting [`ChannelManager`] will not be readable by versions of LDK prior to
/// 0.0.107. Channels setting this type also need to get manually accepted via
/// [`crate::ln::channelmanager::ChannelManager::accept_inbound_channel_from_trusted_peer_0conf`],
/// [`crate::ln::channelmanager::ChannelManager::accept_inbound_channel_from_trusted_peer`],
/// or will be rejected otherwise.
///
/// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager
Expand Down
8 changes: 5 additions & 3 deletions lightning/src/ln/async_signer_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use crate::events::{ClosureReason, Event};
use crate::ln::chan_utils::ClosingTransaction;
use crate::ln::channel::DISCONNECT_PEER_AWAITING_RESPONSE_TICKS;
use crate::ln::channel_state::{ChannelDetails, ChannelShutdownState};
use crate::ln::channelmanager::{PaymentId, RAACommitmentOrder};
use crate::ln::channelmanager::{PaymentId, RAACommitmentOrder, TrustedChannelFeatures};
use crate::ln::msgs::{BaseMessageHandler, ChannelMessageHandler, ErrorAction, MessageSendEvent};
use crate::ln::outbound_payment::RecipientOnionFields;
use crate::ln::{functional_test_utils::*, msgs};
Expand Down Expand Up @@ -78,10 +78,11 @@ fn do_test_open_channel(zero_conf: bool) {
Event::OpenChannelRequest { temporary_channel_id, .. } => {
nodes[1]
.node
.accept_inbound_channel_from_trusted_peer_0conf(
.accept_inbound_channel_from_trusted_peer(
temporary_channel_id,
&node_a_id,
0,
TrustedChannelFeatures::ZeroConf,
None,
)
.expect("Unable to accept inbound zero-conf channel");
Expand Down Expand Up @@ -383,10 +384,11 @@ fn do_test_funding_signed_0conf(signer_ops: Vec<SignerOp>) {
Event::OpenChannelRequest { temporary_channel_id, .. } => {
nodes[1]
.node
.accept_inbound_channel_from_trusted_peer_0conf(
.accept_inbound_channel_from_trusted_peer(
temporary_channel_id,
&node_a_id,
0,
TrustedChannelFeatures::ZeroConf,
None,
)
.expect("Unable to accept inbound zero-conf channel");
Expand Down
18 changes: 15 additions & 3 deletions lightning/src/ln/chanmon_update_fail_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use crate::chain::transaction::OutPoint;
use crate::chain::{ChannelMonitorUpdateStatus, Listen, Watch};
use crate::events::{ClosureReason, Event, HTLCHandlingFailureType, PaymentPurpose};
use crate::ln::channel::AnnouncementSigsState;
use crate::ln::channelmanager::{PaymentId, RAACommitmentOrder};
use crate::ln::channelmanager::{PaymentId, RAACommitmentOrder, TrustedChannelFeatures};
use crate::ln::msgs;
use crate::ln::msgs::{
BaseMessageHandler, ChannelMessageHandler, MessageSendEvent, RoutingMessageHandler,
Expand Down Expand Up @@ -3235,7 +3235,13 @@ fn do_test_outbound_reload_without_init_mon(use_0conf: bool) {
if use_0conf {
nodes[1]
.node
.accept_inbound_channel_from_trusted_peer_0conf(&chan_id, &node_a_id, 0, None)
.accept_inbound_channel_from_trusted_peer(
&chan_id,
&node_a_id,
0,
TrustedChannelFeatures::ZeroConf,
None,
)
.unwrap();
} else {
nodes[1].node.accept_inbound_channel(&chan_id, &node_a_id, 0, None).unwrap();
Expand Down Expand Up @@ -3344,7 +3350,13 @@ fn do_test_inbound_reload_without_init_mon(use_0conf: bool, lock_commitment: boo
if use_0conf {
nodes[1]
.node
.accept_inbound_channel_from_trusted_peer_0conf(&chan_id, &node_a_id, 0, None)
.accept_inbound_channel_from_trusted_peer(
&chan_id,
&node_a_id,
0,
TrustedChannelFeatures::ZeroConf,
None,
)
.unwrap();
} else {
nodes[1].node.accept_inbound_channel(&chan_id, &node_a_id, 0, None).unwrap();
Expand Down
Loading