Skip to content
Open
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 @@ -862,30 +863,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 @@ -925,8 +937,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 network = Network::Bitcoin;
let best_block_timestamp = genesis_block(network).header.time;
Expand Down Expand Up @@ -977,8 +1000,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 @@ -1077,8 +1111,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 @@ -1103,14 +1152,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
.accept_inbound_channel(
temporary_channel_id,
counterparty_node_id,
user_channel_id,
None,
)
.unwrap();
}
} else {
panic!("Wrong event type");
}
Expand Down Expand Up @@ -1286,12 +1347,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 @@ -1375,7 +1440,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 @@ -2332,7 +2397,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 @@ -2347,7 +2412,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 @@ -2362,7 +2427,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 @@ -2832,12 +2897,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 @@ -1503,10 +1505,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 @@ -1657,7 +1657,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, Confirm, 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 @@ -3241,7 +3241,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 @@ -3350,7 +3356,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
Loading