Skip to content
130 changes: 102 additions & 28 deletions fuzz/src/chanmon_consistency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -863,9 +863,15 @@ fn assert_action_timeout_awaiting_response(action: &msgs::ErrorAction) {
));
}

pub enum ChanType {
Legacy,
KeyedAnchors,
ZeroFeeCommitments,
}

#[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 +932,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 @@ -978,8 +995,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 +1106,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 +1147,27 @@ 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,
false,
true,
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 @@ -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,
);
}
4 changes: 3 additions & 1 deletion lightning-liquidity/tests/lsps2_integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1513,10 +1513,12 @@ 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,
true,
false,
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: 6 additions & 2 deletions lightning/src/ln/async_signer_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,12 @@ 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,
true,
false,
None,
)
.expect("Unable to accept inbound zero-conf channel");
Expand Down Expand Up @@ -383,10 +385,12 @@ 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,
true,
false,
None,
)
.expect("Unable to accept inbound zero-conf channel");
Expand Down
8 changes: 6 additions & 2 deletions lightning/src/ln/chanmon_update_fail_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3235,7 +3235,9 @@ 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, true, false, None,
)
.unwrap();
} else {
nodes[1].node.accept_inbound_channel(&chan_id, &node_a_id, 0, None).unwrap();
Expand Down Expand Up @@ -3344,7 +3346,9 @@ 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, true, false, None,
)
.unwrap();
} else {
nodes[1].node.accept_inbound_channel(&chan_id, &node_a_id, 0, None).unwrap();
Expand Down
Loading
Loading