Skip to content

Contribute funding inputs on accepting dual-funded channel #3735

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
Draft
15 changes: 12 additions & 3 deletions lightning-liquidity/src/lsps2/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use alloc::string::{String, ToString};
use alloc::vec::Vec;

use core::cmp::Ordering as CmpOrdering;
use core::ops::Deref;
use core::sync::atomic::{AtomicUsize, Ordering};

Expand Down Expand Up @@ -645,13 +646,21 @@ where

match self.remove_pending_request(&mut peer_state_lock, &request_id) {
Some(LSPS2Request::GetInfo(_)) => {
let response = LSPS2Response::GetInfo(LSPS2GetInfoResponse {
opening_fee_params_menu: opening_fee_params_menu
let mut opening_fee_params_menu: Vec<LSPS2OpeningFeeParams> =
opening_fee_params_menu
.into_iter()
.map(|param| {
param.into_opening_fee_params(&self.config.promise_secret)
})
.collect(),
.collect();
opening_fee_params_menu.sort_by(|a, b| {
match a.min_fee_msat.cmp(&b.min_fee_msat) {
CmpOrdering::Equal => a.proportional.cmp(&b.proportional),
other => other,
}
});
let response = LSPS2Response::GetInfo(LSPS2GetInfoResponse {
opening_fee_params_menu,
});
(Ok(()), Some(response))
},
Expand Down
72 changes: 72 additions & 0 deletions lightning-liquidity/tests/lsps2_integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -746,3 +746,75 @@ fn invalid_token_flow() {
panic!("Expected LSPS2ClientEvent::GetInfoFailed event");
}
}

#[test]
fn opening_fee_params_menu_is_sorted_by_spec() {
let (service_node_id, client_node_id, service_node, client_node, _secret) =
setup_test_lsps2("opening_fee_params_menu_is_sorted_by_spec");

let client_handler = client_node.liquidity_manager.lsps2_client_handler().unwrap();
let service_handler = service_node.liquidity_manager.lsps2_service_handler().unwrap();

let _ = client_handler.request_opening_params(service_node_id, None);
let get_info_request = get_lsps_message!(client_node, service_node_id);
service_node.liquidity_manager.handle_custom_message(get_info_request, client_node_id).unwrap();

let get_info_event = service_node.liquidity_manager.next_event().unwrap();
let request_id = match get_info_event {
LiquidityEvent::LSPS2Service(LSPS2ServiceEvent::GetInfo { request_id, .. }) => request_id,
_ => panic!("Unexpected event"),
};

let raw_params_generator = |min_fee_msat: u64, proportional: u32| LSPS2RawOpeningFeeParams {
min_fee_msat,
proportional,
valid_until: LSPSDateTime::from_str("2035-05-20T08:30:45Z").unwrap(),
min_lifetime: 144,
max_client_to_self_delay: 128,
min_payment_size_msat: 1,
max_payment_size_msat: 100_000_000,
};

let raw_params = vec![
raw_params_generator(200, 20), // Will be sorted to position 2
raw_params_generator(100, 10), // Will be sorted to position 0 (lowest min_fee, lowest proportional)
raw_params_generator(300, 30), // Will be sorted to position 4 (highest min_fee, highest proportional)
raw_params_generator(100, 20), // Will be sorted to position 1 (same min_fee as 0, higher proportional)
raw_params_generator(200, 30), // Will be sorted to position 3 (higher min_fee than 2, higher proportional)
];

service_handler
.opening_fee_params_generated(&client_node_id, request_id.clone(), raw_params)
.unwrap();

let get_info_response = get_lsps_message!(service_node, client_node_id);
client_node
.liquidity_manager
.handle_custom_message(get_info_response, service_node_id)
.unwrap();

let event = client_node.liquidity_manager.next_event().unwrap();
if let LiquidityEvent::LSPS2Client(LSPS2ClientEvent::OpeningParametersReady {
opening_fee_params_menu,
..
}) = event
{
// The LSP, when ordering the opening_fee_params_menu array, MUST order by the following rules:
// The 0th item MAY have any parameters.
// Each succeeding item MUST, compared to the previous item, obey any one of the following:
// Have a larger min_fee_msat, and equal proportional.
// Have a larger proportional, and equal min_fee_msat.
// Have a larger min_fee_msat, AND larger proportional.
for (cur, next) in
opening_fee_params_menu.iter().zip(opening_fee_params_menu.iter().skip(1))
{
let valid = (next.min_fee_msat > cur.min_fee_msat
&& next.proportional == cur.proportional)
|| (next.proportional > cur.proportional && next.min_fee_msat == cur.min_fee_msat)
|| (next.min_fee_msat > cur.min_fee_msat && next.proportional > cur.proportional);
assert!(valid, "Params not sorted as per spec");
}
} else {
panic!("Unexpected event");
}
}
60 changes: 60 additions & 0 deletions lightning/src/events/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1572,6 +1572,55 @@ pub enum Event {
/// onion messages.
peer_node_id: PublicKey,
},
/// Indicates that a funding transaction constructed via interactive transaction construction for a
/// channel is ready to be signed by the client. This event will only be triggered
/// if at least one input was contributed by the holder and needs to be signed.
///
/// The transaction contains all inputs provided by both parties along with the channel's funding
/// output and a change output if applicable.
///
/// No part of the transaction should be changed before signing as the content of the transaction
/// has already been negotiated with the counterparty.
///
/// Each signature MUST use the SIGHASH_ALL flag to avoid invalidation of the initial commitment and
/// hence possible loss of funds.
///
/// After signing, call [`ChannelManager::funding_transaction_signed`] with the (partially) signed
/// funding transaction.
///
/// Generated in [`ChannelManager`] message handling.
///
/// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager
/// [`ChannelManager::funding_transaction_signed`]: crate::ln::channelmanager::ChannelManager::funding_transaction_signed
FundingTransactionReadyForSigning {
/// The channel_id of the channel which you'll need to pass back into
/// [`ChannelManager::funding_transaction_signed`].
///
/// [`ChannelManager::funding_transaction_signed`]: crate::ln::channelmanager::ChannelManager::funding_transaction_signed
channel_id: ChannelId,
/// The counterparty's node_id, which you'll need to pass back into
/// [`ChannelManager::funding_transaction_signed`].
///
/// [`ChannelManager::funding_transaction_signed`]: crate::ln::channelmanager::ChannelManager::funding_transaction_signed
counterparty_node_id: PublicKey,
// TODO(dual_funding): Enable links when methods are implemented
/// The `user_channel_id` value passed in to `ChannelManager::create_dual_funded_channel` for outbound
/// channels, or to [`ChannelManager::accept_inbound_channel`] or `ChannelManager::accept_inbound_channel_with_contribution`
/// for inbound channels if [`UserConfig::manually_accept_inbound_channels`] config flag is set to true.
/// Otherwise `user_channel_id` will be randomized for an inbound channel.
/// This may be zero for objects serialized with LDK versions prior to 0.0.113.
///
/// [`ChannelManager::accept_inbound_channel`]: crate::ln::channelmanager::ChannelManager::accept_inbound_channel
/// [`UserConfig::manually_accept_inbound_channels`]: crate::util::config::UserConfig::manually_accept_inbound_channels
// [`ChannelManager::create_dual_funded_channel`]: crate::ln::channelmanager::ChannelManager::create_dual_funded_channel
// [`ChannelManager::accept_inbound_channel_with_contribution`]: crate::ln::channelmanager::ChannelManager::accept_inbound_channel_with_contribution
user_channel_id: u128,
/// The unsigned transaction to be signed and passed back to
/// [`ChannelManager::funding_transaction_signed`].
///
/// [`ChannelManager::funding_transaction_signed`]: crate::ln::channelmanager::ChannelManager::funding_transaction_signed
unsigned_transaction: Transaction,
},
}

impl Writeable for Event {
Expand Down Expand Up @@ -1996,6 +2045,13 @@ impl Writeable for Event {
(8, former_temporary_channel_id, required),
});
},
&Event::FundingTransactionReadyForSigning { .. } => {
45u8.write(writer)?;
// We never write out FundingTransactionReadyForSigning events as, upon disconnection, peers
// drop any V2-established/spliced channels which have not yet exchanged the initial `commitment_signed`.
// We only exhange the initial `commitment_signed` after the client calls
// `ChannelManager::funding_transaction_signed` and ALWAYS before we send a `tx_signatures`
},
// Note that, going forward, all new events must only write data inside of
// `write_tlv_fields`. Versions 0.0.101+ will ignore odd-numbered events that write
// data via `write_tlv_fields`.
Expand Down Expand Up @@ -2560,6 +2616,10 @@ impl MaybeReadable for Event {
former_temporary_channel_id: former_temporary_channel_id.0.unwrap(),
}))
},
45u8 => {
// Value 45 is used for `Event::FundingTransactionReadyForSigning`.
Ok(None)
},
// Versions prior to 0.0.100 did not ignore odd types, instead returning InvalidValue.
// Version 0.0.100 failed to properly ignore odd types, possibly resulting in corrupt
// reads.
Expand Down
Loading
Loading