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
130 changes: 102 additions & 28 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,9 +864,15 @@ fn assert_action_timeout_awaiting_response(action: &msgs::ErrorAction) {
));
}

pub enum ChanType {
Legacy,
KeyedAnchors,
ZeroFeeCommitments,
}
Comment on lines +867 to +871
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: Consider adding #[derive(Clone, Copy)] (or at least Copy) on this fieldless enum. While Rust allows matching without Copy on fieldless enums (it reads only the discriminant), having Copy makes the intent explicit and avoids confusion for future maintainers. It also enables patterns like let ct = chan_type; if ever needed.


#[inline]
pub fn do_test<Out: Output + MaybeSend + MaybeSync>(
data: &[u8], underlying_out: Out, anchors: bool,
data: &[u8], underlying_out: Out, chan_type: ChanType,
) {
let out = SearchingOutput::new(underlying_out);
let broadcast_a = Arc::new(TestBroadcaster { txn_broadcasted: RefCell::new(Vec::new()) });
Expand Down Expand Up @@ -926,8 +933,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.

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 +996,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 +1107,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 +1148,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 1148 to +1162
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 +1343,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 @@ -2301,7 +2361,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 +2376,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 +2391,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 +2843,26 @@ 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.clone(), ChanType::Legacy);
do_test(data, out.clone(), ChanType::KeyedAnchors);
do_test(data, out, ChanType::ZeroFeeCommitments);
}

#[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 {},
ChanType::Legacy,
);
do_test(
unsafe { std::slice::from_raw_parts(data, datalen) },
test_logger::DevNull {},
ChanType::KeyedAnchors,
);
do_test(
unsafe { std::slice::from_raw_parts(data, datalen) },
test_logger::DevNull {},
ChanType::ZeroFeeCommitments,
);
}
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