From c5f5b9224ff5b547ff8c1d6b07345b59fd54f363 Mon Sep 17 00:00:00 2001 From: Duncan Dean Date: Tue, 12 Sep 2023 10:52:50 +0200 Subject: [PATCH 1/9] Add V2 constructors to `ChannelId` --- lightning/src/ln/channel_id.rs | 66 +++++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/lightning/src/ln/channel_id.rs b/lightning/src/ln/channel_id.rs index 19003961ff1..90efe3c94be 100644 --- a/lightning/src/ln/channel_id.rs +++ b/lightning/src/ln/channel_id.rs @@ -14,8 +14,13 @@ use crate::io; use crate::ln::msgs::DecodeError; use crate::sign::EntropySource; use crate::util::ser::{Readable, Writeable, Writer}; +use super::channel_keys::RevocationBasepoint; -use bitcoin::hashes::Hash as _; +use bitcoin::hashes::{ + Hash as _, + HashEngine as _, + sha256::Hash as Sha256, +}; use core::fmt; use core::ops::Deref; @@ -68,6 +73,32 @@ impl ChannelId { pub fn is_zero(&self) -> bool { self.0[..] == [0; 32] } + + /// Create _v2_ channel ID by concatenating the holder revocation basepoint with the counterparty + /// revocation basepoint and hashing the result. The basepoints will be concatenated in increasing + /// sorted order. + pub fn v2_from_revocation_basepoints( + ours: &RevocationBasepoint, + theirs: &RevocationBasepoint, + ) -> Self { + let ours = ours.0.serialize(); + let theirs = theirs.0.serialize(); + let (lesser, greater) = if ours < theirs { + (ours, theirs) + } else { + (theirs, ours) + }; + let mut engine = Sha256::engine(); + engine.input(&lesser[..]); + engine.input(&greater[..]); + Self(Sha256::from_engine(engine).to_byte_array()) + } + + /// Create temporary _v2_ channel ID by concatenating a zeroed out basepoint with the holder + /// revocation basepoint and hashing the result. + pub fn temporary_v2_from_revocation_basepoint(our_revocation_basepoint: &RevocationBasepoint) -> Self { + Self(Sha256::hash(&[[0u8; 33], our_revocation_basepoint.0.serialize()].concat()).to_byte_array()) + } } impl Writeable for ChannelId { @@ -91,9 +122,17 @@ impl fmt::Display for ChannelId { #[cfg(test)] mod tests { + use bitcoin::hashes::{ + Hash as _, + HashEngine as _, + hex::FromHex as _, + sha256::Hash as Sha256, + }; + use bitcoin::secp256k1::PublicKey; use hex::DisplayHex; use crate::ln::ChannelId; + use crate::ln::channel_keys::RevocationBasepoint; use crate::util::ser::{Readable, Writeable}; use crate::util::test_utils; use crate::prelude::*; @@ -139,4 +178,29 @@ mod tests { let channel_id = ChannelId::v1_from_funding_txid(&[2; 32], 1); assert_eq!(format!("{}", &channel_id), "0202020202020202020202020202020202020202020202020202020202020203"); } + + #[test] + fn test_channel_id_v2_from_basepoints() { + // Ours greater than theirs + let ours = RevocationBasepoint(PublicKey::from_slice(&>::from_hex("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()[..]).unwrap()); + let theirs = RevocationBasepoint(PublicKey::from_slice(&>::from_hex("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap()); + + let mut engine = Sha256::engine(); + engine.input(&theirs.0.serialize()); + engine.input(&ours.0.serialize()); + let expected_id = ChannelId(Sha256::from_engine(engine).to_byte_array()); + + assert_eq!(ChannelId::v2_from_revocation_basepoints(&ours, &theirs), expected_id); + + // Theirs greater than ours + let ours = RevocationBasepoint(PublicKey::from_slice(&>::from_hex("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007").unwrap()[..]).unwrap()); + let theirs = RevocationBasepoint(PublicKey::from_slice(&>::from_hex("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap()); + + let mut engine = Sha256::engine(); + engine.input(&ours.0.serialize()); + engine.input(&theirs.0.serialize()); + let expected_id = ChannelId(Sha256::from_engine(engine).to_byte_array()); + + assert_eq!(ChannelId::v2_from_revocation_basepoints(&ours, &theirs), expected_id); + } } From 47f7dfe9ee41deaf12468674e0aa4b2a793cc981 Mon Sep 17 00:00:00 2001 From: Duncan Dean Date: Mon, 4 Dec 2023 09:22:14 +0200 Subject: [PATCH 2/9] Test builds with #[cfg(dual_funding)] enabled --- ci/check-cfg-flags.py | 2 ++ ci/ci-tests.sh | 1 + 2 files changed, 3 insertions(+) diff --git a/ci/check-cfg-flags.py b/ci/check-cfg-flags.py index 0b3a4d569ad..0cfa2023ee2 100755 --- a/ci/check-cfg-flags.py +++ b/ci/check-cfg-flags.py @@ -96,6 +96,8 @@ def check_cfg_tag(cfg): pass elif cfg == "require_route_graph_test": pass + elif cfg == "dual_funding": + pass else: print("Bad cfg tag: " + cfg) assert False diff --git a/ci/ci-tests.sh b/ci/ci-tests.sh index baec6910130..59acb833f1f 100755 --- a/ci/ci-tests.sh +++ b/ci/ci-tests.sh @@ -174,3 +174,4 @@ fi echo -e "\n\nTest cfg-flag builds" RUSTFLAGS="--cfg=taproot" cargo test --verbose --color always -p lightning RUSTFLAGS="--cfg=async_signing" cargo test --verbose --color always -p lightning +RUSTFLAGS="--cfg=dual_funding" cargo test --verbose --color always -p lightning From 740bb3188c0ec94e509e68e559f9690a1f79fe35 Mon Sep 17 00:00:00 2001 From: Duncan Dean Date: Tue, 12 Sep 2023 21:18:14 +0200 Subject: [PATCH 3/9] Add `DualFundingChannelContext` struct --- lightning/src/ln/channel.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index a82f4d41be4..2ae44fb0eb0 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -2874,6 +2874,20 @@ pub(crate) fn commit_tx_fee_msat(feerate_per_kw: u32, num_htlcs: usize, channel_ (commitment_tx_base_weight(channel_type_features) + num_htlcs as u64 * COMMITMENT_TX_WEIGHT_PER_HTLC) * feerate_per_kw as u64 / 1000 * 1000 } +/// Context for dual-funded channels. +#[cfg(dual_funding)] +pub(super) struct DualFundingChannelContext { + /// The amount in satoshis we will be contributing to the channel. + pub our_funding_satoshis: u64, + /// The amount in satoshis our counterparty will be contributing to the channel. + pub their_funding_satoshis: u64, + /// The funding transaction locktime suggested by the initiator. If set by us, it is always set + /// to the current block height to align incentives against fee-sniping. + pub funding_tx_locktime: u32, + /// The feerate set by the initiator to be used for the funding transaction. + pub funding_feerate_sat_per_1000_weight: u32, +} + // Holder designates channel data owned for the benefit of the user client. // Counterparty designates channel data owned by the another channel participant entity. pub(super) struct Channel where SP::Target: SignerProvider { From 3fda620cba21ac6c12574f95f2d549fe6da37b25 Mon Sep 17 00:00:00 2001 From: Duncan Dean Date: Fri, 24 Nov 2023 09:32:56 +0200 Subject: [PATCH 4/9] Create ChannelContext constructor for inbound channels --- lightning/src/ln/channel.rs | 639 ++++++++++++++++++++---------------- 1 file changed, 350 insertions(+), 289 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 2ae44fb0eb0..ed235083954 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -1503,6 +1503,338 @@ pub(super) struct ChannelContext where SP::Target: SignerProvider { } impl ChannelContext where SP::Target: SignerProvider { + fn new_for_inbound_channel<'a, ES: Deref, F: Deref, L: Deref>( + fee_estimator: &'a LowerBoundedFeeEstimator, + entropy_source: &'a ES, + signer_provider: &'a SP, + counterparty_node_id: PublicKey, + their_features: &'a InitFeatures, + user_id: u128, + config: &'a UserConfig, + current_chain_height: u32, + logger: &'a L, + is_0conf: bool, + our_funding_satoshis: u64, + counterparty_pubkeys: ChannelPublicKeys, + channel_type: ChannelTypeFeatures, + holder_selected_channel_reserve_satoshis: u64, + msg_channel_reserve_satoshis: u64, + msg_push_msat: u64, + open_channel_fields: msgs::CommonOpenChannelFields, + ) -> Result, ChannelError> + where + ES::Target: EntropySource, + F::Target: FeeEstimator, + L::Target: Logger, + SP::Target: SignerProvider, + { + let logger = WithContext::from(logger, Some(counterparty_node_id), Some(open_channel_fields.temporary_channel_id)); + let announced_channel = if (open_channel_fields.channel_flags & 1) == 1 { true } else { false }; + + let channel_value_satoshis = our_funding_satoshis.saturating_add(open_channel_fields.funding_satoshis); + + let channel_keys_id = signer_provider.generate_channel_keys_id(true, channel_value_satoshis, user_id); + let holder_signer = signer_provider.derive_channel_signer(channel_value_satoshis, channel_keys_id); + let pubkeys = holder_signer.pubkeys().clone(); + + if config.channel_handshake_config.our_to_self_delay < BREAKDOWN_TIMEOUT { + return Err(ChannelError::Close(format!("Configured with an unreasonable our_to_self_delay ({}) putting user funds at risks. It must be greater than {}", config.channel_handshake_config.our_to_self_delay, BREAKDOWN_TIMEOUT))); + } + + // Check sanity of message fields: + if channel_value_satoshis > config.channel_handshake_limits.max_funding_satoshis { + return Err(ChannelError::Close(format!( + "Per our config, funding must be at most {}. It was {}. Peer contribution: {}. Our contribution: {}", + config.channel_handshake_limits.max_funding_satoshis, channel_value_satoshis, + open_channel_fields.funding_satoshis, our_funding_satoshis))); + } + if channel_value_satoshis >= TOTAL_BITCOIN_SUPPLY_SATOSHIS { + return Err(ChannelError::Close(format!("Funding must be smaller than the total bitcoin supply. It was {}", channel_value_satoshis))); + } + if msg_channel_reserve_satoshis > channel_value_satoshis { + return Err(ChannelError::Close(format!("Bogus channel_reserve_satoshis ({}). Must be no greater than channel_value_satoshis: {}", msg_channel_reserve_satoshis, channel_value_satoshis))); + } + let full_channel_value_msat = (channel_value_satoshis - msg_channel_reserve_satoshis) * 1000; + if msg_push_msat > full_channel_value_msat { + return Err(ChannelError::Close(format!("push_msat {} was larger than channel amount minus reserve ({})", msg_push_msat, full_channel_value_msat))); + } + if open_channel_fields.dust_limit_satoshis > channel_value_satoshis { + return Err(ChannelError::Close(format!("dust_limit_satoshis {} was larger than channel_value_satoshis {}. Peer never wants payout outputs?", open_channel_fields.dust_limit_satoshis, channel_value_satoshis))); + } + if open_channel_fields.htlc_minimum_msat >= full_channel_value_msat { + return Err(ChannelError::Close(format!("Minimum htlc value ({}) was larger than full channel value ({})", open_channel_fields.htlc_minimum_msat, full_channel_value_msat))); + } + Channel::::check_remote_fee(&channel_type, fee_estimator, open_channel_fields.commitment_feerate_sat_per_1000_weight, None, &&logger)?; + + let max_counterparty_selected_contest_delay = u16::min(config.channel_handshake_limits.their_to_self_delay, MAX_LOCAL_BREAKDOWN_TIMEOUT); + if open_channel_fields.to_self_delay > max_counterparty_selected_contest_delay { + return Err(ChannelError::Close(format!("They wanted our payments to be delayed by a needlessly long period. Upper limit: {}. Actual: {}", max_counterparty_selected_contest_delay, open_channel_fields.to_self_delay))); + } + if open_channel_fields.max_accepted_htlcs < 1 { + return Err(ChannelError::Close("0 max_accepted_htlcs makes for a useless channel".to_owned())); + } + if open_channel_fields.max_accepted_htlcs > MAX_HTLCS { + return Err(ChannelError::Close(format!("max_accepted_htlcs was {}. It must not be larger than {}", open_channel_fields.max_accepted_htlcs, MAX_HTLCS))); + } + + // Now check against optional parameters as set by config... + if channel_value_satoshis < config.channel_handshake_limits.min_funding_satoshis { + return Err(ChannelError::Close(format!("Funding satoshis ({}) is less than the user specified limit ({})", channel_value_satoshis, config.channel_handshake_limits.min_funding_satoshis))); + } + if open_channel_fields.htlc_minimum_msat > config.channel_handshake_limits.max_htlc_minimum_msat { + return Err(ChannelError::Close(format!("htlc_minimum_msat ({}) is higher than the user specified limit ({})", open_channel_fields.htlc_minimum_msat, config.channel_handshake_limits.max_htlc_minimum_msat))); + } + if open_channel_fields.max_htlc_value_in_flight_msat < config.channel_handshake_limits.min_max_htlc_value_in_flight_msat { + return Err(ChannelError::Close(format!("max_htlc_value_in_flight_msat ({}) is less than the user specified limit ({})", open_channel_fields.max_htlc_value_in_flight_msat, config.channel_handshake_limits.min_max_htlc_value_in_flight_msat))); + } + if msg_channel_reserve_satoshis > config.channel_handshake_limits.max_channel_reserve_satoshis { + return Err(ChannelError::Close(format!("channel_reserve_satoshis ({}) is higher than the user specified limit ({})", msg_channel_reserve_satoshis, config.channel_handshake_limits.max_channel_reserve_satoshis))); + } + if open_channel_fields.max_accepted_htlcs < config.channel_handshake_limits.min_max_accepted_htlcs { + return Err(ChannelError::Close(format!("max_accepted_htlcs ({}) is less than the user specified limit ({})", open_channel_fields.max_accepted_htlcs, config.channel_handshake_limits.min_max_accepted_htlcs))); + } + if open_channel_fields.dust_limit_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS { + return Err(ChannelError::Close(format!("dust_limit_satoshis ({}) is less than the implementation limit ({})", open_channel_fields.dust_limit_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS))); + } + if open_channel_fields.dust_limit_satoshis > MAX_CHAN_DUST_LIMIT_SATOSHIS { + return Err(ChannelError::Close(format!("dust_limit_satoshis ({}) is greater than the implementation limit ({})", open_channel_fields.dust_limit_satoshis, MAX_CHAN_DUST_LIMIT_SATOSHIS))); + } + + // Convert things into internal flags and prep our state: + + if config.channel_handshake_limits.force_announced_channel_preference { + if config.channel_handshake_config.announced_channel != announced_channel { + return Err(ChannelError::Close("Peer tried to open channel but their announcement preference is different from ours".to_owned())); + } + } + + if holder_selected_channel_reserve_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS { + // Protocol level safety check in place, although it should never happen because + // of `MIN_THEIR_CHAN_RESERVE_SATOSHIS` + return Err(ChannelError::Close(format!("Suitable channel reserve not found. remote_channel_reserve was ({}). dust_limit_satoshis is ({}).", holder_selected_channel_reserve_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS))); + } + if holder_selected_channel_reserve_satoshis * 1000 >= full_channel_value_msat { + return Err(ChannelError::Close(format!("Suitable channel reserve not found. remote_channel_reserve was ({})msats. Channel value is ({} - {})msats.", holder_selected_channel_reserve_satoshis * 1000, full_channel_value_msat, msg_push_msat))); + } + if msg_channel_reserve_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS { + log_debug!(logger, "channel_reserve_satoshis ({}) is smaller than our dust limit ({}). We can broadcast stale states without any risk, implying this channel is very insecure for our counterparty.", + msg_channel_reserve_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS); + } + if holder_selected_channel_reserve_satoshis < open_channel_fields.dust_limit_satoshis { + return Err(ChannelError::Close(format!("Dust limit ({}) too high for the channel reserve we require the remote to keep ({})", open_channel_fields.dust_limit_satoshis, holder_selected_channel_reserve_satoshis))); + } + + // check if the funder's amount for the initial commitment tx is sufficient + // for full fee payment plus a few HTLCs to ensure the channel will be useful. + let anchor_outputs_value = if channel_type.supports_anchors_zero_fee_htlc_tx() { + ANCHOR_OUTPUT_VALUE_SATOSHI * 2 + } else { + 0 + }; + let funders_amount_msat = open_channel_fields.funding_satoshis * 1000 - msg_push_msat; + let commitment_tx_fee = commit_tx_fee_msat(open_channel_fields.commitment_feerate_sat_per_1000_weight, MIN_AFFORDABLE_HTLC_COUNT, &channel_type) / 1000; + if (funders_amount_msat / 1000).saturating_sub(anchor_outputs_value) < commitment_tx_fee { + return Err(ChannelError::Close(format!("Funding amount ({} sats) can't even pay fee for initial commitment transaction fee of {} sats.", (funders_amount_msat / 1000).saturating_sub(anchor_outputs_value), commitment_tx_fee))); + } + + let to_remote_satoshis = funders_amount_msat / 1000 - commitment_tx_fee - anchor_outputs_value; + // While it's reasonable for us to not meet the channel reserve initially (if they don't + // want to push much to us), our counterparty should always have more than our reserve. + if to_remote_satoshis < holder_selected_channel_reserve_satoshis { + return Err(ChannelError::Close("Insufficient funding amount for initial reserve".to_owned())); + } + + let counterparty_shutdown_scriptpubkey = if their_features.supports_upfront_shutdown_script() { + match &open_channel_fields.shutdown_scriptpubkey { + &Some(ref script) => { + // Peer is signaling upfront_shutdown and has opt-out with a 0-length script. We don't enforce anything + if script.len() == 0 { + None + } else { + if !script::is_bolt2_compliant(&script, their_features) { + return Err(ChannelError::Close(format!("Peer is signaling upfront_shutdown but has provided an unacceptable scriptpubkey format: {}", script))) + } + Some(script.clone()) + } + }, + // Peer is signaling upfront shutdown but don't opt-out with correct mechanism (a.k.a 0-length script). Peer looks buggy, we fail the channel + &None => { + return Err(ChannelError::Close("Peer is signaling upfront_shutdown but we don't get any script. Use 0-length script to opt-out".to_owned())); + } + } + } else { None }; + + let shutdown_scriptpubkey = if config.channel_handshake_config.commit_upfront_shutdown_pubkey { + match signer_provider.get_shutdown_scriptpubkey() { + Ok(scriptpubkey) => Some(scriptpubkey), + Err(_) => return Err(ChannelError::Close("Failed to get upfront shutdown scriptpubkey".to_owned())), + } + } else { None }; + + if let Some(shutdown_scriptpubkey) = &shutdown_scriptpubkey { + if !shutdown_scriptpubkey.is_compatible(&their_features) { + return Err(ChannelError::Close(format!("Provided a scriptpubkey format not accepted by peer: {}", shutdown_scriptpubkey))); + } + } + + let destination_script = match signer_provider.get_destination_script(channel_keys_id) { + Ok(script) => script, + Err(_) => return Err(ChannelError::Close("Failed to get destination script".to_owned())), + }; + + let mut secp_ctx = Secp256k1::new(); + secp_ctx.seeded_randomize(&entropy_source.get_secure_random_bytes()); + + let minimum_depth = if is_0conf { + Some(0) + } else { + Some(cmp::max(config.channel_handshake_config.minimum_depth, 1)) + }; + + let value_to_self_msat = our_funding_satoshis * 1000 + msg_push_msat; + + // TODO(dual_funding): Checks for `funding_feerate_sat_per_1000_weight`? + + let channel_context = ChannelContext { + user_id, + + config: LegacyChannelConfig { + options: config.channel_config.clone(), + announced_channel, + commit_upfront_shutdown_pubkey: config.channel_handshake_config.commit_upfront_shutdown_pubkey, + }, + + prev_config: None, + + inbound_handshake_limits_override: None, + + temporary_channel_id: Some(open_channel_fields.temporary_channel_id), + channel_id: open_channel_fields.temporary_channel_id, + channel_state: ChannelState::NegotiatingFunding( + NegotiatingFundingFlags::OUR_INIT_SENT | NegotiatingFundingFlags::THEIR_INIT_SENT + ), + announcement_sigs_state: AnnouncementSigsState::NotSent, + secp_ctx, + + latest_monitor_update_id: 0, + + holder_signer: ChannelSignerType::Ecdsa(holder_signer), + shutdown_scriptpubkey, + destination_script, + + cur_holder_commitment_transaction_number: INITIAL_COMMITMENT_NUMBER, + cur_counterparty_commitment_transaction_number: INITIAL_COMMITMENT_NUMBER, + value_to_self_msat, + + pending_inbound_htlcs: Vec::new(), + pending_outbound_htlcs: Vec::new(), + holding_cell_htlc_updates: Vec::new(), + pending_update_fee: None, + holding_cell_update_fee: None, + next_holder_htlc_id: 0, + next_counterparty_htlc_id: 0, + update_time_counter: 1, + + resend_order: RAACommitmentOrder::CommitmentFirst, + + monitor_pending_channel_ready: false, + monitor_pending_revoke_and_ack: false, + monitor_pending_commitment_signed: false, + monitor_pending_forwards: Vec::new(), + monitor_pending_failures: Vec::new(), + monitor_pending_finalized_fulfills: Vec::new(), + + signer_pending_commitment_update: false, + signer_pending_funding: false, + + + #[cfg(debug_assertions)] + holder_max_commitment_tx_output: Mutex::new((value_to_self_msat, (channel_value_satoshis * 1000 - msg_push_msat).saturating_sub(value_to_self_msat))), + #[cfg(debug_assertions)] + counterparty_max_commitment_tx_output: Mutex::new((value_to_self_msat, (channel_value_satoshis * 1000 - msg_push_msat).saturating_sub(value_to_self_msat))), + + last_sent_closing_fee: None, + pending_counterparty_closing_signed: None, + expecting_peer_commitment_signed: false, + closing_fee_limits: None, + target_closing_feerate_sats_per_kw: None, + + funding_tx_confirmed_in: None, + funding_tx_confirmation_height: 0, + short_channel_id: None, + channel_creation_height: current_chain_height, + + feerate_per_kw: open_channel_fields.commitment_feerate_sat_per_1000_weight, + channel_value_satoshis, + counterparty_dust_limit_satoshis: open_channel_fields.dust_limit_satoshis, + holder_dust_limit_satoshis: MIN_CHAN_DUST_LIMIT_SATOSHIS, + counterparty_max_htlc_value_in_flight_msat: cmp::min(open_channel_fields.max_htlc_value_in_flight_msat, channel_value_satoshis * 1000), + holder_max_htlc_value_in_flight_msat: get_holder_max_htlc_value_in_flight_msat(channel_value_satoshis, &config.channel_handshake_config), + counterparty_selected_channel_reserve_satoshis: Some(msg_channel_reserve_satoshis), + holder_selected_channel_reserve_satoshis, + counterparty_htlc_minimum_msat: open_channel_fields.htlc_minimum_msat, + holder_htlc_minimum_msat: if config.channel_handshake_config.our_htlc_minimum_msat == 0 { 1 } else { config.channel_handshake_config.our_htlc_minimum_msat }, + counterparty_max_accepted_htlcs: open_channel_fields.max_accepted_htlcs, + holder_max_accepted_htlcs: cmp::min(config.channel_handshake_config.our_max_accepted_htlcs, MAX_HTLCS), + minimum_depth, + + counterparty_forwarding_info: None, + + channel_transaction_parameters: ChannelTransactionParameters { + holder_pubkeys: pubkeys, + holder_selected_contest_delay: config.channel_handshake_config.our_to_self_delay, + is_outbound_from_holder: false, + counterparty_parameters: Some(CounterpartyChannelTransactionParameters { + selected_contest_delay: open_channel_fields.to_self_delay, + pubkeys: counterparty_pubkeys, + }), + funding_outpoint: None, + channel_type_features: channel_type.clone() + }, + funding_transaction: None, + is_batch_funding: None, + + counterparty_cur_commitment_point: Some(open_channel_fields.first_per_commitment_point), + counterparty_prev_commitment_point: None, + counterparty_node_id, + + counterparty_shutdown_scriptpubkey, + + commitment_secrets: CounterpartyCommitmentSecrets::new(), + + channel_update_status: ChannelUpdateStatus::Enabled, + closing_signed_in_flight: false, + + announcement_sigs: None, + + #[cfg(any(test, fuzzing))] + next_local_commitment_tx_fee_info_cached: Mutex::new(None), + #[cfg(any(test, fuzzing))] + next_remote_commitment_tx_fee_info_cached: Mutex::new(None), + + workaround_lnd_bug_4006: None, + sent_message_awaiting_response: None, + + latest_inbound_scid_alias: None, + outbound_scid_alias: 0, + + channel_pending_event_emitted: false, + channel_ready_event_emitted: false, + + #[cfg(any(test, fuzzing))] + historical_inbound_htlc_fulfills: new_hash_set(), + + channel_type, + channel_keys_id, + + local_initiated_shutdown: None, + + blocked_monitor_updates: Vec::new(), + }; + + Ok(channel_context) + } + /// Allowed in any state (including after shutdown) pub fn get_update_time_counter(&self) -> u32 { self.update_time_counter @@ -7295,15 +7627,12 @@ impl InboundV1Channel where SP::Target: SignerProvider { L::Target: Logger, { let logger = WithContext::from(logger, Some(counterparty_node_id), Some(msg.common_fields.temporary_channel_id)); - let announced_channel = if (msg.common_fields.channel_flags & 1) == 1 { true } else { false }; // First check the channel type is known, failing before we do anything else if we don't // support this channel type. let channel_type = channel_type_from_open_channel(msg, their_features, our_supported_features)?; - let channel_keys_id = signer_provider.generate_channel_keys_id(true, msg.common_fields.funding_satoshis, user_id); - let holder_signer = signer_provider.derive_channel_signer(msg.common_fields.funding_satoshis, channel_keys_id); - let pubkeys = holder_signer.pubkeys().clone(); + let holder_selected_channel_reserve_satoshis = get_holder_selected_channel_reserve_satoshis(msg.common_fields.funding_satoshis, config); let counterparty_pubkeys = ChannelPublicKeys { funding_pubkey: msg.common_fields.funding_pubkey, revocation_basepoint: RevocationBasepoint::from(msg.common_fields.revocation_basepoint), @@ -7312,297 +7641,29 @@ impl InboundV1Channel where SP::Target: SignerProvider { htlc_basepoint: HtlcBasepoint::from(msg.common_fields.htlc_basepoint) }; - if config.channel_handshake_config.our_to_self_delay < BREAKDOWN_TIMEOUT { - return Err(ChannelError::Close(format!("Configured with an unreasonable our_to_self_delay ({}) putting user funds at risks. It must be greater than {}", config.channel_handshake_config.our_to_self_delay, BREAKDOWN_TIMEOUT))); - } - - // Check sanity of message fields: - if msg.common_fields.funding_satoshis > config.channel_handshake_limits.max_funding_satoshis { - return Err(ChannelError::Close(format!("Per our config, funding must be at most {}. It was {}", config.channel_handshake_limits.max_funding_satoshis, msg.common_fields.funding_satoshis))); - } - if msg.common_fields.funding_satoshis >= TOTAL_BITCOIN_SUPPLY_SATOSHIS { - return Err(ChannelError::Close(format!("Funding must be smaller than the total bitcoin supply. It was {}", msg.common_fields.funding_satoshis))); - } - if msg.channel_reserve_satoshis > msg.common_fields.funding_satoshis { - return Err(ChannelError::Close(format!("Bogus channel_reserve_satoshis ({}). Must be not greater than funding_satoshis: {}", msg.channel_reserve_satoshis, msg.common_fields.funding_satoshis))); - } - let full_channel_value_msat = (msg.common_fields.funding_satoshis - msg.channel_reserve_satoshis) * 1000; - if msg.push_msat > full_channel_value_msat { - return Err(ChannelError::Close(format!("push_msat {} was larger than channel amount minus reserve ({})", msg.push_msat, full_channel_value_msat))); - } - if msg.common_fields.dust_limit_satoshis > msg.common_fields.funding_satoshis { - return Err(ChannelError::Close(format!("dust_limit_satoshis {} was larger than funding_satoshis {}. Peer never wants payout outputs?", msg.common_fields.dust_limit_satoshis, msg.common_fields.funding_satoshis))); - } - if msg.common_fields.htlc_minimum_msat >= full_channel_value_msat { - return Err(ChannelError::Close(format!("Minimum htlc value ({}) was larger than full channel value ({})", msg.common_fields.htlc_minimum_msat, full_channel_value_msat))); - } - Channel::::check_remote_fee(&channel_type, fee_estimator, msg.common_fields.commitment_feerate_sat_per_1000_weight, None, &&logger)?; - - let max_counterparty_selected_contest_delay = u16::min(config.channel_handshake_limits.their_to_self_delay, MAX_LOCAL_BREAKDOWN_TIMEOUT); - if msg.common_fields.to_self_delay > max_counterparty_selected_contest_delay { - return Err(ChannelError::Close(format!("They wanted our payments to be delayed by a needlessly long period. Upper limit: {}. Actual: {}", max_counterparty_selected_contest_delay, msg.common_fields.to_self_delay))); - } - if msg.common_fields.max_accepted_htlcs < 1 { - return Err(ChannelError::Close("0 max_accepted_htlcs makes for a useless channel".to_owned())); - } - if msg.common_fields.max_accepted_htlcs > MAX_HTLCS { - return Err(ChannelError::Close(format!("max_accepted_htlcs was {}. It must not be larger than {}", msg.common_fields.max_accepted_htlcs, MAX_HTLCS))); - } - - // Now check against optional parameters as set by config... - if msg.common_fields.funding_satoshis < config.channel_handshake_limits.min_funding_satoshis { - return Err(ChannelError::Close(format!("Funding satoshis ({}) is less than the user specified limit ({})", msg.common_fields.funding_satoshis, config.channel_handshake_limits.min_funding_satoshis))); - } - if msg.common_fields.htlc_minimum_msat > config.channel_handshake_limits.max_htlc_minimum_msat { - return Err(ChannelError::Close(format!("htlc_minimum_msat ({}) is higher than the user specified limit ({})", msg.common_fields.htlc_minimum_msat, config.channel_handshake_limits.max_htlc_minimum_msat))); - } - if msg.common_fields.max_htlc_value_in_flight_msat < config.channel_handshake_limits.min_max_htlc_value_in_flight_msat { - return Err(ChannelError::Close(format!("max_htlc_value_in_flight_msat ({}) is less than the user specified limit ({})", msg.common_fields.max_htlc_value_in_flight_msat, config.channel_handshake_limits.min_max_htlc_value_in_flight_msat))); - } - if msg.channel_reserve_satoshis > config.channel_handshake_limits.max_channel_reserve_satoshis { - return Err(ChannelError::Close(format!("channel_reserve_satoshis ({}) is higher than the user specified limit ({})", msg.channel_reserve_satoshis, config.channel_handshake_limits.max_channel_reserve_satoshis))); - } - if msg.common_fields.max_accepted_htlcs < config.channel_handshake_limits.min_max_accepted_htlcs { - return Err(ChannelError::Close(format!("max_accepted_htlcs ({}) is less than the user specified limit ({})", msg.common_fields.max_accepted_htlcs, config.channel_handshake_limits.min_max_accepted_htlcs))); - } - if msg.common_fields.dust_limit_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS { - return Err(ChannelError::Close(format!("dust_limit_satoshis ({}) is less than the implementation limit ({})", msg.common_fields.dust_limit_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS))); - } - if msg.common_fields.dust_limit_satoshis > MAX_CHAN_DUST_LIMIT_SATOSHIS { - return Err(ChannelError::Close(format!("dust_limit_satoshis ({}) is greater than the implementation limit ({})", msg.common_fields.dust_limit_satoshis, MAX_CHAN_DUST_LIMIT_SATOSHIS))); - } - - // Convert things into internal flags and prep our state: - - if config.channel_handshake_limits.force_announced_channel_preference { - if config.channel_handshake_config.announced_channel != announced_channel { - return Err(ChannelError::Close("Peer tried to open channel but their announcement preference is different from ours".to_owned())); - } - } - - let holder_selected_channel_reserve_satoshis = get_holder_selected_channel_reserve_satoshis(msg.common_fields.funding_satoshis, config); - if holder_selected_channel_reserve_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS { - // Protocol level safety check in place, although it should never happen because - // of `MIN_THEIR_CHAN_RESERVE_SATOSHIS` - return Err(ChannelError::Close(format!("Suitable channel reserve not found. remote_channel_reserve was ({}). dust_limit_satoshis is ({}).", holder_selected_channel_reserve_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS))); - } - if holder_selected_channel_reserve_satoshis * 1000 >= full_channel_value_msat { - return Err(ChannelError::Close(format!("Suitable channel reserve not found. remote_channel_reserve was ({})msats. Channel value is ({} - {})msats.", holder_selected_channel_reserve_satoshis * 1000, full_channel_value_msat, msg.push_msat))); - } - if msg.channel_reserve_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS { - log_debug!(logger, "channel_reserve_satoshis ({}) is smaller than our dust limit ({}). We can broadcast stale states without any risk, implying this channel is very insecure for our counterparty.", - msg.channel_reserve_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS); - } - if holder_selected_channel_reserve_satoshis < msg.common_fields.dust_limit_satoshis { - return Err(ChannelError::Close(format!("Dust limit ({}) too high for the channel reserve we require the remote to keep ({})", msg.common_fields.dust_limit_satoshis, holder_selected_channel_reserve_satoshis))); - } - - // check if the funder's amount for the initial commitment tx is sufficient - // for full fee payment plus a few HTLCs to ensure the channel will be useful. - let anchor_outputs_value = if channel_type.supports_anchors_zero_fee_htlc_tx() { - ANCHOR_OUTPUT_VALUE_SATOSHI * 2 - } else { - 0 - }; - let funders_amount_msat = msg.common_fields.funding_satoshis * 1000 - msg.push_msat; - let commitment_tx_fee = commit_tx_fee_msat(msg.common_fields.commitment_feerate_sat_per_1000_weight, MIN_AFFORDABLE_HTLC_COUNT, &channel_type) / 1000; - if (funders_amount_msat / 1000).saturating_sub(anchor_outputs_value) < commitment_tx_fee { - return Err(ChannelError::Close(format!("Funding amount ({} sats) can't even pay fee for initial commitment transaction fee of {} sats.", (funders_amount_msat / 1000).saturating_sub(anchor_outputs_value), commitment_tx_fee))); - } - - let to_remote_satoshis = funders_amount_msat / 1000 - commitment_tx_fee - anchor_outputs_value; - // While it's reasonable for us to not meet the channel reserve initially (if they don't - // want to push much to us), our counterparty should always have more than our reserve. - if to_remote_satoshis < holder_selected_channel_reserve_satoshis { - return Err(ChannelError::Close("Insufficient funding amount for initial reserve".to_owned())); - } - - let counterparty_shutdown_scriptpubkey = if their_features.supports_upfront_shutdown_script() { - match &msg.common_fields.shutdown_scriptpubkey { - &Some(ref script) => { - // Peer is signaling upfront_shutdown and has opt-out with a 0-length script. We don't enforce anything - if script.len() == 0 { - None - } else { - if !script::is_bolt2_compliant(&script, their_features) { - return Err(ChannelError::Close(format!("Peer is signaling upfront_shutdown but has provided an unacceptable scriptpubkey format: {}", script))) - } - Some(script.clone()) - } - }, - // Peer is signaling upfront shutdown but don't opt-out with correct mechanism (a.k.a 0-length script). Peer looks buggy, we fail the channel - &None => { - return Err(ChannelError::Close("Peer is signaling upfront_shutdown but we don't get any script. Use 0-length script to opt-out".to_owned())); - } - } - } else { None }; - - let shutdown_scriptpubkey = if config.channel_handshake_config.commit_upfront_shutdown_pubkey { - match signer_provider.get_shutdown_scriptpubkey() { - Ok(scriptpubkey) => Some(scriptpubkey), - Err(_) => return Err(ChannelError::Close("Failed to get upfront shutdown scriptpubkey".to_owned())), - } - } else { None }; - - if let Some(shutdown_scriptpubkey) = &shutdown_scriptpubkey { - if !shutdown_scriptpubkey.is_compatible(&their_features) { - return Err(ChannelError::Close(format!("Provided a scriptpubkey format not accepted by peer: {}", shutdown_scriptpubkey))); - } - } - - let destination_script = match signer_provider.get_destination_script(channel_keys_id) { - Ok(script) => script, - Err(_) => return Err(ChannelError::Close("Failed to get destination script".to_owned())), - }; - - let mut secp_ctx = Secp256k1::new(); - secp_ctx.seeded_randomize(&entropy_source.get_secure_random_bytes()); - - let minimum_depth = if is_0conf { - Some(0) - } else { - Some(cmp::max(config.channel_handshake_config.minimum_depth, 1)) - }; - let chan = Self { - context: ChannelContext { - user_id, - - config: LegacyChannelConfig { - options: config.channel_config.clone(), - announced_channel, - commit_upfront_shutdown_pubkey: config.channel_handshake_config.commit_upfront_shutdown_pubkey, - }, - - prev_config: None, - - inbound_handshake_limits_override: None, - - temporary_channel_id: Some(msg.common_fields.temporary_channel_id), - channel_id: msg.common_fields.temporary_channel_id, - channel_state: ChannelState::NegotiatingFunding( - NegotiatingFundingFlags::OUR_INIT_SENT | NegotiatingFundingFlags::THEIR_INIT_SENT - ), - announcement_sigs_state: AnnouncementSigsState::NotSent, - secp_ctx, - - latest_monitor_update_id: 0, - - holder_signer: ChannelSignerType::Ecdsa(holder_signer), - shutdown_scriptpubkey, - destination_script, - - cur_holder_commitment_transaction_number: INITIAL_COMMITMENT_NUMBER, - cur_counterparty_commitment_transaction_number: INITIAL_COMMITMENT_NUMBER, - value_to_self_msat: msg.push_msat, - - pending_inbound_htlcs: Vec::new(), - pending_outbound_htlcs: Vec::new(), - holding_cell_htlc_updates: Vec::new(), - pending_update_fee: None, - holding_cell_update_fee: None, - next_holder_htlc_id: 0, - next_counterparty_htlc_id: 0, - update_time_counter: 1, - - resend_order: RAACommitmentOrder::CommitmentFirst, - - monitor_pending_channel_ready: false, - monitor_pending_revoke_and_ack: false, - monitor_pending_commitment_signed: false, - monitor_pending_forwards: Vec::new(), - monitor_pending_failures: Vec::new(), - monitor_pending_finalized_fulfills: Vec::new(), - - signer_pending_commitment_update: false, - signer_pending_funding: false, - - #[cfg(debug_assertions)] - holder_max_commitment_tx_output: Mutex::new((msg.push_msat, msg.common_fields.funding_satoshis * 1000 - msg.push_msat)), - #[cfg(debug_assertions)] - counterparty_max_commitment_tx_output: Mutex::new((msg.push_msat, msg.common_fields.funding_satoshis * 1000 - msg.push_msat)), - - last_sent_closing_fee: None, - pending_counterparty_closing_signed: None, - expecting_peer_commitment_signed: false, - closing_fee_limits: None, - target_closing_feerate_sats_per_kw: None, - - funding_tx_confirmed_in: None, - funding_tx_confirmation_height: 0, - short_channel_id: None, - channel_creation_height: current_chain_height, - - feerate_per_kw: msg.common_fields.commitment_feerate_sat_per_1000_weight, - channel_value_satoshis: msg.common_fields.funding_satoshis, - counterparty_dust_limit_satoshis: msg.common_fields.dust_limit_satoshis, - holder_dust_limit_satoshis: MIN_CHAN_DUST_LIMIT_SATOSHIS, - counterparty_max_htlc_value_in_flight_msat: cmp::min(msg.common_fields.max_htlc_value_in_flight_msat, msg.common_fields.funding_satoshis * 1000), - holder_max_htlc_value_in_flight_msat: get_holder_max_htlc_value_in_flight_msat(msg.common_fields.funding_satoshis, &config.channel_handshake_config), - counterparty_selected_channel_reserve_satoshis: Some(msg.channel_reserve_satoshis), - holder_selected_channel_reserve_satoshis, - counterparty_htlc_minimum_msat: msg.common_fields.htlc_minimum_msat, - holder_htlc_minimum_msat: if config.channel_handshake_config.our_htlc_minimum_msat == 0 { 1 } else { config.channel_handshake_config.our_htlc_minimum_msat }, - counterparty_max_accepted_htlcs: msg.common_fields.max_accepted_htlcs, - holder_max_accepted_htlcs: cmp::min(config.channel_handshake_config.our_max_accepted_htlcs, MAX_HTLCS), - minimum_depth, - - counterparty_forwarding_info: None, - - channel_transaction_parameters: ChannelTransactionParameters { - holder_pubkeys: pubkeys, - holder_selected_contest_delay: config.channel_handshake_config.our_to_self_delay, - is_outbound_from_holder: false, - counterparty_parameters: Some(CounterpartyChannelTransactionParameters { - selected_contest_delay: msg.common_fields.to_self_delay, - pubkeys: counterparty_pubkeys, - }), - funding_outpoint: None, - channel_type_features: channel_type.clone() - }, - funding_transaction: None, - is_batch_funding: None, - - counterparty_cur_commitment_point: Some(msg.common_fields.first_per_commitment_point), - counterparty_prev_commitment_point: None, + context: ChannelContext::new_for_inbound_channel( + fee_estimator, + entropy_source, + signer_provider, counterparty_node_id, + their_features, + user_id, + config, + current_chain_height, + &&logger, + is_0conf, + 0, - counterparty_shutdown_scriptpubkey, - - commitment_secrets: CounterpartyCommitmentSecrets::new(), - - channel_update_status: ChannelUpdateStatus::Enabled, - closing_signed_in_flight: false, - - announcement_sigs: None, - - #[cfg(any(test, fuzzing))] - next_local_commitment_tx_fee_info_cached: Mutex::new(None), - #[cfg(any(test, fuzzing))] - next_remote_commitment_tx_fee_info_cached: Mutex::new(None), - - workaround_lnd_bug_4006: None, - sent_message_awaiting_response: None, - - latest_inbound_scid_alias: None, - outbound_scid_alias: 0, - - channel_pending_event_emitted: false, - channel_ready_event_emitted: false, - - #[cfg(any(test, fuzzing))] - historical_inbound_htlc_fulfills: new_hash_set(), - + counterparty_pubkeys, channel_type, - channel_keys_id, - - local_initiated_shutdown: None, - - blocked_monitor_updates: Vec::new(), - }, + holder_selected_channel_reserve_satoshis, + msg.channel_reserve_satoshis, + msg.push_msat, + msg.common_fields.clone(), + )?, unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 } }; - Ok(chan) } From fd68df9f95f92a4bc8394c1667d276ef41df7f08 Mon Sep 17 00:00:00 2001 From: Duncan Dean Date: Tue, 12 Sep 2023 21:23:51 +0200 Subject: [PATCH 5/9] Add `InboundV2Channel` struct --- lightning/src/ln/channel.rs | 191 +++++++++++++++++++++++++++-- lightning/src/ln/channelmanager.rs | 2 +- 2 files changed, 185 insertions(+), 8 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index ed235083954..4446801ed48 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -3191,6 +3191,19 @@ pub(crate) fn get_legacy_default_holder_selected_channel_reserve_satoshis(channe cmp::min(channel_value_satoshis, cmp::max(q, 1000)) } +/// Returns a minimum channel reserve value each party needs to maintain, fixed in the spec to a +/// default of 1% of the total channel value. +/// +/// Guaranteed to return a value no larger than channel_value_satoshis +/// +/// This is used both for outbound and inbound channels and has lower bound +/// of `dust_limit_satoshis`. +fn get_v2_channel_reserve_satoshis(channel_value_satoshis: u64, dust_limit_satoshis: u64) -> u64 { + // Fixed at 1% of channel value by spec. + let (q, _) = channel_value_satoshis.overflowing_div(100); + cmp::min(channel_value_satoshis, cmp::max(q, dust_limit_satoshis)) +} + // Get the fee cost in SATS of a commitment tx with a given number of HTLC outputs. // Note that num_htlcs should not include dust HTLCs. #[inline] @@ -3224,6 +3237,8 @@ pub(super) struct DualFundingChannelContext { // Counterparty designates channel data owned by the another channel participant entity. pub(super) struct Channel where SP::Target: SignerProvider { pub context: ChannelContext, + #[cfg(dual_funding)] + pub dual_funding_channel_context: Option, } #[cfg(any(test, fuzzing))] @@ -7554,7 +7569,11 @@ impl OutboundV1Channel where SP::Target: SignerProvider { log_info!(logger, "Received funding_signed from peer for channel {}", &self.context.channel_id()); - let mut channel = Channel { context: self.context }; + let mut channel = Channel { + context: self.context, + #[cfg(dual_funding)] + dual_funding_channel_context: None, + }; let need_channel_ready = channel.check_get_channel_ready(0).is_some(); channel.monitor_updating_paused(false, false, need_channel_ready, Vec::new(), Vec::new(), Vec::new()); @@ -7579,12 +7598,12 @@ pub(super) struct InboundV1Channel where SP::Target: SignerProvider { } /// Fetches the [`ChannelTypeFeatures`] that will be used for a channel built from a given -/// [`msgs::OpenChannel`]. +/// [`msgs::CommonOpenChannelFields`]. pub(super) fn channel_type_from_open_channel( - msg: &msgs::OpenChannel, their_features: &InitFeatures, + common_fields: &msgs::CommonOpenChannelFields, their_features: &InitFeatures, our_supported_features: &ChannelTypeFeatures ) -> Result { - if let Some(channel_type) = &msg.common_fields.channel_type { + if let Some(channel_type) = &common_fields.channel_type { if channel_type.supports_any_optional_bits() { return Err(ChannelError::Close("Channel Type field contained optional bits - this is not allowed".to_owned())); } @@ -7599,7 +7618,7 @@ pub(super) fn channel_type_from_open_channel( if !channel_type.is_subset(our_supported_features) { return Err(ChannelError::Close("Channel Type contains unsupported features".to_owned())); } - let announced_channel = if (msg.common_fields.channel_flags & 1) == 1 { true } else { false }; + let announced_channel = if (common_fields.channel_flags & 1) == 1 { true } else { false }; if channel_type.requires_scid_privacy() && announced_channel { return Err(ChannelError::Close("SCID Alias/Privacy Channel Type cannot be set on a public channel".to_owned())); } @@ -7630,7 +7649,7 @@ impl InboundV1Channel where SP::Target: SignerProvider { // First check the channel type is known, failing before we do anything else if we don't // support this channel type. - let channel_type = channel_type_from_open_channel(msg, their_features, our_supported_features)?; + let channel_type = channel_type_from_open_channel(&msg.common_fields, their_features, our_supported_features)?; let holder_selected_channel_reserve_satoshis = get_holder_selected_channel_reserve_satoshis(msg.common_fields.funding_satoshis, config); let counterparty_pubkeys = ChannelPublicKeys { @@ -7842,6 +7861,8 @@ impl InboundV1Channel where SP::Target: SignerProvider { // `ChannelMonitor`. let mut channel = Channel { context: self.context, + #[cfg(dual_funding)] + dual_funding_channel_context: None, }; let need_channel_ready = channel.check_get_channel_ready(0).is_some(); channel.monitor_updating_paused(false, false, need_channel_ready, Vec::new(), Vec::new(), Vec::new()); @@ -7850,6 +7871,160 @@ impl InboundV1Channel where SP::Target: SignerProvider { } } +// A not-yet-funded inbound (from counterparty) channel using V2 channel establishment. +#[cfg(dual_funding)] +pub(super) struct InboundV2Channel where SP::Target: SignerProvider { + pub context: ChannelContext, + pub unfunded_context: UnfundedChannelContext, + pub dual_funding_context: DualFundingChannelContext, +} + +#[cfg(dual_funding)] +impl InboundV2Channel where SP::Target: SignerProvider { + /// Creates a new dual-funded channel from a remote side's request for one. + /// Assumes chain_hash has already been checked and corresponds with what we expect! + pub fn new( + fee_estimator: &LowerBoundedFeeEstimator, entropy_source: &ES, signer_provider: &SP, + counterparty_node_id: PublicKey, our_supported_features: &ChannelTypeFeatures, + their_features: &InitFeatures, msg: &msgs::OpenChannelV2, funding_satoshis: u64, user_id: u128, + config: &UserConfig, current_chain_height: u32, logger: &L, + ) -> Result, ChannelError> + where ES::Target: EntropySource, + F::Target: FeeEstimator, + L::Target: Logger, + { + let channel_value_satoshis = funding_satoshis.saturating_add(msg.common_fields.funding_satoshis); + let counterparty_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis( + channel_value_satoshis, msg.common_fields.dust_limit_satoshis); + let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis( + channel_value_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS); + + // First check the channel type is known, failing before we do anything else if we don't + // support this channel type. + if msg.common_fields.channel_type.is_none() { + return Err(ChannelError::Close(format!("Rejecting V2 channel {} missing channel_type", + msg.common_fields.temporary_channel_id))) + } + let channel_type = channel_type_from_open_channel(&msg.common_fields, their_features, our_supported_features)?; + + let counterparty_pubkeys = ChannelPublicKeys { + funding_pubkey: msg.common_fields.funding_pubkey, + revocation_basepoint: RevocationBasepoint(msg.common_fields.revocation_basepoint), + payment_point: msg.common_fields.payment_basepoint, + delayed_payment_basepoint: DelayedPaymentBasepoint(msg.common_fields.delayed_payment_basepoint), + htlc_basepoint: HtlcBasepoint(msg.common_fields.htlc_basepoint) + }; + + let mut context = ChannelContext::new_for_inbound_channel( + fee_estimator, + entropy_source, + signer_provider, + counterparty_node_id, + their_features, + user_id, + config, + current_chain_height, + logger, + false, + + funding_satoshis, + + counterparty_pubkeys, + channel_type, + holder_selected_channel_reserve_satoshis, + counterparty_selected_channel_reserve_satoshis, + 0 /* push_msat not used in dual-funding */, + msg.common_fields.clone(), + )?; + let channel_id = ChannelId::v2_from_revocation_basepoints( + &context.get_holder_pubkeys().revocation_basepoint, + &context.get_counterparty_pubkeys().revocation_basepoint); + context.channel_id = channel_id; + + let chan = Self { + context, + unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 }, + dual_funding_context: DualFundingChannelContext { + our_funding_satoshis: funding_satoshis, + their_funding_satoshis: msg.common_fields.funding_satoshis, + funding_tx_locktime: msg.locktime, + funding_feerate_sat_per_1000_weight: msg.funding_feerate_sat_per_1000_weight, + } + }; + + Ok(chan) + } + + /// Marks an inbound channel as accepted and generates a [`msgs::AcceptChannelV2`] message which + /// should be sent back to the counterparty node. + /// + /// [`msgs::AcceptChannelV2`]: crate::ln::msgs::AcceptChannelV2 + pub fn accept_inbound_dual_funded_channel(&mut self) -> msgs::AcceptChannelV2 { + if self.context.is_outbound() { + debug_assert!(false, "Tried to send accept_channel for an outbound channel?"); + } + if !matches!( + self.context.channel_state, ChannelState::NegotiatingFunding(flags) + if flags == (NegotiatingFundingFlags::OUR_INIT_SENT | NegotiatingFundingFlags::THEIR_INIT_SENT) + ) { + debug_assert!(false, "Tried to send accept_channel2 after channel had moved forward"); + } + if self.context.cur_holder_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER { + debug_assert!(false, "Tried to send an accept_channel2 for a channel that has already advanced"); + } + + self.generate_accept_channel_v2_message() + } + + /// This function is used to explicitly generate a [`msgs::AcceptChannel`] message for an + /// inbound channel. If the intention is to accept an inbound channel, use + /// [`InboundV1Channel::accept_inbound_channel`] instead. + /// + /// [`msgs::AcceptChannelV2`]: crate::ln::msgs::AcceptChannelV2 + fn generate_accept_channel_v2_message(&self) -> msgs::AcceptChannelV2 { + let first_per_commitment_point = self.context.holder_signer.as_ref().get_per_commitment_point( + self.context.cur_holder_commitment_transaction_number, &self.context.secp_ctx); + let second_per_commitment_point = self.context.holder_signer.as_ref().get_per_commitment_point( + self.context.cur_holder_commitment_transaction_number - 1, &self.context.secp_ctx); + let keys = self.context.get_holder_pubkeys(); + + msgs::AcceptChannelV2 { + common_fields: msgs::CommonAcceptChannelFields { + temporary_channel_id: self.context.temporary_channel_id.unwrap(), + dust_limit_satoshis: self.context.holder_dust_limit_satoshis, + max_htlc_value_in_flight_msat: self.context.holder_max_htlc_value_in_flight_msat, + htlc_minimum_msat: self.context.holder_htlc_minimum_msat, + minimum_depth: self.context.minimum_depth.unwrap(), + to_self_delay: self.context.get_holder_selected_contest_delay(), + max_accepted_htlcs: self.context.holder_max_accepted_htlcs, + funding_pubkey: keys.funding_pubkey, + revocation_basepoint: keys.revocation_basepoint.to_public_key(), + payment_basepoint: keys.payment_point, + delayed_payment_basepoint: keys.delayed_payment_basepoint.to_public_key(), + htlc_basepoint: keys.htlc_basepoint.to_public_key(), + first_per_commitment_point, + shutdown_scriptpubkey: Some(match &self.context.shutdown_scriptpubkey { + Some(script) => script.clone().into_inner(), + None => Builder::new().into_script(), + }), + channel_type: Some(self.context.channel_type.clone()), + }, + funding_satoshis: self.dual_funding_context.our_funding_satoshis, + second_per_commitment_point, + require_confirmed_inputs: None, + } + } + + /// Enables the possibility for tests to extract a [`msgs::AcceptChannelV2`] message for an + /// inbound channel without accepting it. + /// + /// [`msgs::AcceptChannelV2`]: crate::ln::msgs::AcceptChannelV2 + #[cfg(test)] + pub fn get_accept_channel_v2_message(&self) -> msgs::AcceptChannelV2 { + self.generate_accept_channel_v2_message() + } +} + const SERIALIZATION_VERSION: u8 = 3; const MIN_SERIALIZATION_VERSION: u8 = 3; @@ -8793,7 +8968,9 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch local_initiated_shutdown, blocked_monitor_updates: blocked_monitor_updates.unwrap(), - } + }, + #[cfg(dual_funding)] + dual_funding_channel_context: None, }) } } diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 55b2cbae441..4d8fbdbbf76 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -6247,7 +6247,7 @@ where // If we're doing manual acceptance checks on the channel, then defer creation until we're sure we want to accept. if self.default_configuration.manually_accept_inbound_channels { let channel_type = channel::channel_type_from_open_channel( - &msg, &peer_state.latest_features, &self.channel_type_features() + &msg.common_fields, &peer_state.latest_features, &self.channel_type_features() ).map_err(|e| MsgHandleErrInternal::from_chan_no_close(e, msg.common_fields.temporary_channel_id) )?; From 2f43953089e0216849ae28b1e59107fefecdedf2 Mon Sep 17 00:00:00 2001 From: Duncan Dean Date: Fri, 24 Nov 2023 09:58:21 +0200 Subject: [PATCH 6/9] Create ChannelContext constructor for outbound channels --- lightning/src/ln/channel.rs | 422 ++++++++++++++++++++---------------- 1 file changed, 232 insertions(+), 190 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 4446801ed48..4b18e25e641 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -1835,6 +1835,223 @@ impl ChannelContext where SP::Target: SignerProvider { Ok(channel_context) } + fn new_for_outbound_channel<'a, ES: Deref, F: Deref>( + fee_estimator: &'a LowerBoundedFeeEstimator, + entropy_source: &'a ES, + signer_provider: &'a SP, + counterparty_node_id: PublicKey, + their_features: &'a InitFeatures, + funding_satoshis: u64, + push_msat: u64, + user_id: u128, + config: &'a UserConfig, + current_chain_height: u32, + outbound_scid_alias: u64, + temporary_channel_id: Option, + channel_type: ChannelTypeFeatures, + ) -> Result, APIError> + where + ES::Target: EntropySource, + F::Target: FeeEstimator, + SP::Target: SignerProvider, + { + // This will be updated with the counterparty contribution if this is a dual-funded channel + let channel_value_satoshis = funding_satoshis; + + let holder_selected_contest_delay = config.channel_handshake_config.our_to_self_delay; + let channel_keys_id = signer_provider.generate_channel_keys_id(false, channel_value_satoshis, user_id); + let holder_signer = signer_provider.derive_channel_signer(channel_value_satoshis, channel_keys_id); + let pubkeys = holder_signer.pubkeys().clone(); + + if !their_features.supports_wumbo() && channel_value_satoshis > MAX_FUNDING_SATOSHIS_NO_WUMBO { + return Err(APIError::APIMisuseError{err: format!("funding_value must not exceed {}, it was {}", MAX_FUNDING_SATOSHIS_NO_WUMBO, channel_value_satoshis)}); + } + if channel_value_satoshis >= TOTAL_BITCOIN_SUPPLY_SATOSHIS { + return Err(APIError::APIMisuseError{err: format!("funding_value must be smaller than the total bitcoin supply, it was {}", channel_value_satoshis)}); + } + let channel_value_msat = channel_value_satoshis * 1000; + if push_msat > channel_value_msat { + return Err(APIError::APIMisuseError { err: format!("Push value ({}) was larger than channel_value ({})", push_msat, channel_value_msat) }); + } + if holder_selected_contest_delay < BREAKDOWN_TIMEOUT { + return Err(APIError::APIMisuseError {err: format!("Configured with an unreasonable our_to_self_delay ({}) putting user funds at risks", holder_selected_contest_delay)}); + } + let holder_selected_channel_reserve_satoshis = get_holder_selected_channel_reserve_satoshis(channel_value_satoshis, config); + if holder_selected_channel_reserve_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS { + // Protocol level safety check in place, although it should never happen because + // of `MIN_THEIR_CHAN_RESERVE_SATOSHIS` + return Err(APIError::APIMisuseError { err: format!("Holder selected channel reserve below implemention limit dust_limit_satoshis {}", holder_selected_channel_reserve_satoshis) }); + } + + debug_assert!(channel_type.is_subset(&channelmanager::provided_channel_type_features(&config))); + + let (commitment_conf_target, anchor_outputs_value_msat) = if channel_type.supports_anchors_zero_fee_htlc_tx() { + (ConfirmationTarget::AnchorChannelFee, ANCHOR_OUTPUT_VALUE_SATOSHI * 2 * 1000) + } else { + (ConfirmationTarget::NonAnchorChannelFee, 0) + }; + let commitment_feerate = fee_estimator.bounded_sat_per_1000_weight(commitment_conf_target); + + let value_to_self_msat = channel_value_satoshis * 1000 - push_msat; + let commitment_tx_fee = commit_tx_fee_msat(commitment_feerate, MIN_AFFORDABLE_HTLC_COUNT, &channel_type); + if value_to_self_msat.saturating_sub(anchor_outputs_value_msat) < commitment_tx_fee { + return Err(APIError::APIMisuseError{ err: format!("Funding amount ({}) can't even pay fee for initial commitment transaction fee of {}.", value_to_self_msat / 1000, commitment_tx_fee / 1000) }); + } + + let mut secp_ctx = Secp256k1::new(); + secp_ctx.seeded_randomize(&entropy_source.get_secure_random_bytes()); + + let shutdown_scriptpubkey = if config.channel_handshake_config.commit_upfront_shutdown_pubkey { + match signer_provider.get_shutdown_scriptpubkey() { + Ok(scriptpubkey) => Some(scriptpubkey), + Err(_) => return Err(APIError::ChannelUnavailable { err: "Failed to get shutdown scriptpubkey".to_owned()}), + } + } else { None }; + + if let Some(shutdown_scriptpubkey) = &shutdown_scriptpubkey { + if !shutdown_scriptpubkey.is_compatible(&their_features) { + return Err(APIError::IncompatibleShutdownScript { script: shutdown_scriptpubkey.clone() }); + } + } + + let destination_script = match signer_provider.get_destination_script(channel_keys_id) { + Ok(script) => script, + Err(_) => return Err(APIError::ChannelUnavailable { err: "Failed to get destination script".to_owned()}), + }; + + let temporary_channel_id = temporary_channel_id.unwrap_or_else(|| ChannelId::temporary_from_entropy_source(entropy_source)); + + Ok(Self { + user_id, + + config: LegacyChannelConfig { + options: config.channel_config.clone(), + announced_channel: config.channel_handshake_config.announced_channel, + commit_upfront_shutdown_pubkey: config.channel_handshake_config.commit_upfront_shutdown_pubkey, + }, + + prev_config: None, + + inbound_handshake_limits_override: Some(config.channel_handshake_limits.clone()), + + channel_id: temporary_channel_id, + temporary_channel_id: Some(temporary_channel_id), + channel_state: ChannelState::NegotiatingFunding(NegotiatingFundingFlags::OUR_INIT_SENT), + announcement_sigs_state: AnnouncementSigsState::NotSent, + secp_ctx, + channel_value_satoshis, + + latest_monitor_update_id: 0, + + holder_signer: ChannelSignerType::Ecdsa(holder_signer), + shutdown_scriptpubkey, + destination_script, + + cur_holder_commitment_transaction_number: INITIAL_COMMITMENT_NUMBER, + cur_counterparty_commitment_transaction_number: INITIAL_COMMITMENT_NUMBER, + value_to_self_msat, + + pending_inbound_htlcs: Vec::new(), + pending_outbound_htlcs: Vec::new(), + holding_cell_htlc_updates: Vec::new(), + pending_update_fee: None, + holding_cell_update_fee: None, + next_holder_htlc_id: 0, + next_counterparty_htlc_id: 0, + update_time_counter: 1, + + resend_order: RAACommitmentOrder::CommitmentFirst, + + monitor_pending_channel_ready: false, + monitor_pending_revoke_and_ack: false, + monitor_pending_commitment_signed: false, + monitor_pending_forwards: Vec::new(), + monitor_pending_failures: Vec::new(), + monitor_pending_finalized_fulfills: Vec::new(), + + signer_pending_commitment_update: false, + signer_pending_funding: false, + + #[cfg(debug_assertions)] + holder_max_commitment_tx_output: Mutex::new((channel_value_satoshis * 1000 - push_msat, push_msat)), + #[cfg(debug_assertions)] + counterparty_max_commitment_tx_output: Mutex::new((channel_value_satoshis * 1000 - push_msat, push_msat)), + + last_sent_closing_fee: None, + pending_counterparty_closing_signed: None, + expecting_peer_commitment_signed: false, + closing_fee_limits: None, + target_closing_feerate_sats_per_kw: None, + + funding_tx_confirmed_in: None, + funding_tx_confirmation_height: 0, + short_channel_id: None, + channel_creation_height: current_chain_height, + + feerate_per_kw: commitment_feerate, + counterparty_dust_limit_satoshis: 0, + holder_dust_limit_satoshis: MIN_CHAN_DUST_LIMIT_SATOSHIS, + counterparty_max_htlc_value_in_flight_msat: 0, + holder_max_htlc_value_in_flight_msat: get_holder_max_htlc_value_in_flight_msat(channel_value_satoshis, &config.channel_handshake_config), + counterparty_selected_channel_reserve_satoshis: None, // Filled in in accept_channel + holder_selected_channel_reserve_satoshis, + counterparty_htlc_minimum_msat: 0, + holder_htlc_minimum_msat: if config.channel_handshake_config.our_htlc_minimum_msat == 0 { 1 } else { config.channel_handshake_config.our_htlc_minimum_msat }, + counterparty_max_accepted_htlcs: 0, + holder_max_accepted_htlcs: cmp::min(config.channel_handshake_config.our_max_accepted_htlcs, MAX_HTLCS), + minimum_depth: None, // Filled in in accept_channel + + counterparty_forwarding_info: None, + + channel_transaction_parameters: ChannelTransactionParameters { + holder_pubkeys: pubkeys, + holder_selected_contest_delay: config.channel_handshake_config.our_to_self_delay, + is_outbound_from_holder: true, + counterparty_parameters: None, + funding_outpoint: None, + channel_type_features: channel_type.clone() + }, + funding_transaction: None, + is_batch_funding: None, + + counterparty_cur_commitment_point: None, + counterparty_prev_commitment_point: None, + counterparty_node_id, + + counterparty_shutdown_scriptpubkey: None, + + commitment_secrets: CounterpartyCommitmentSecrets::new(), + + channel_update_status: ChannelUpdateStatus::Enabled, + closing_signed_in_flight: false, + + announcement_sigs: None, + + #[cfg(any(test, fuzzing))] + next_local_commitment_tx_fee_info_cached: Mutex::new(None), + #[cfg(any(test, fuzzing))] + next_remote_commitment_tx_fee_info_cached: Mutex::new(None), + + workaround_lnd_bug_4006: None, + sent_message_awaiting_response: None, + + latest_inbound_scid_alias: None, + outbound_scid_alias, + + channel_pending_event_emitted: false, + channel_ready_event_emitted: false, + + #[cfg(any(test, fuzzing))] + historical_inbound_htlc_fulfills: new_hash_set(), + + channel_type, + channel_keys_id, + + blocked_monitor_updates: Vec::new(), + local_initiated_shutdown: None, + }) + } + /// Allowed in any state (including after shutdown) pub fn get_update_time_counter(&self) -> u32 { self.update_time_counter @@ -6948,202 +7165,27 @@ impl OutboundV1Channel where SP::Target: SignerProvider { where ES::Target: EntropySource, F::Target: FeeEstimator { - let holder_selected_contest_delay = config.channel_handshake_config.our_to_self_delay; - let channel_keys_id = signer_provider.generate_channel_keys_id(false, channel_value_satoshis, user_id); - let holder_signer = signer_provider.derive_channel_signer(channel_value_satoshis, channel_keys_id); - let pubkeys = holder_signer.pubkeys().clone(); - - if !their_features.supports_wumbo() && channel_value_satoshis > MAX_FUNDING_SATOSHIS_NO_WUMBO { - return Err(APIError::APIMisuseError{err: format!("funding_value must not exceed {}, it was {}", MAX_FUNDING_SATOSHIS_NO_WUMBO, channel_value_satoshis)}); - } - if channel_value_satoshis >= TOTAL_BITCOIN_SUPPLY_SATOSHIS { - return Err(APIError::APIMisuseError{err: format!("funding_value must be smaller than the total bitcoin supply, it was {}", channel_value_satoshis)}); - } - let channel_value_msat = channel_value_satoshis * 1000; - if push_msat > channel_value_msat { - return Err(APIError::APIMisuseError { err: format!("Push value ({}) was larger than channel_value ({})", push_msat, channel_value_msat) }); - } - if holder_selected_contest_delay < BREAKDOWN_TIMEOUT { - return Err(APIError::APIMisuseError {err: format!("Configured with an unreasonable our_to_self_delay ({}) putting user funds at risks", holder_selected_contest_delay)}); - } - let holder_selected_channel_reserve_satoshis = get_holder_selected_channel_reserve_satoshis(channel_value_satoshis, config); - if holder_selected_channel_reserve_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS { - // Protocol level safety check in place, although it should never happen because - // of `MIN_THEIR_CHAN_RESERVE_SATOSHIS` - return Err(APIError::APIMisuseError { err: format!("Holder selected channel reserve below implemention limit dust_limit_satoshis {}", holder_selected_channel_reserve_satoshis) }); - } - let channel_type = Self::get_initial_channel_type(&config, their_features); - debug_assert!(channel_type.is_subset(&channelmanager::provided_channel_type_features(&config))); - - let (commitment_conf_target, anchor_outputs_value_msat) = if channel_type.supports_anchors_zero_fee_htlc_tx() { - (ConfirmationTarget::AnchorChannelFee, ANCHOR_OUTPUT_VALUE_SATOSHI * 2 * 1000) - } else { - (ConfirmationTarget::NonAnchorChannelFee, 0) - }; - let commitment_feerate = fee_estimator.bounded_sat_per_1000_weight(commitment_conf_target); - - let value_to_self_msat = channel_value_satoshis * 1000 - push_msat; - let commitment_tx_fee = commit_tx_fee_msat(commitment_feerate, MIN_AFFORDABLE_HTLC_COUNT, &channel_type); - if value_to_self_msat.saturating_sub(anchor_outputs_value_msat) < commitment_tx_fee { - return Err(APIError::APIMisuseError{ err: format!("Funding amount ({}) can't even pay fee for initial commitment transaction fee of {}.", value_to_self_msat / 1000, commitment_tx_fee / 1000) }); - } - - let mut secp_ctx = Secp256k1::new(); - secp_ctx.seeded_randomize(&entropy_source.get_secure_random_bytes()); - let shutdown_scriptpubkey = if config.channel_handshake_config.commit_upfront_shutdown_pubkey { - match signer_provider.get_shutdown_scriptpubkey() { - Ok(scriptpubkey) => Some(scriptpubkey), - Err(_) => return Err(APIError::ChannelUnavailable { err: "Failed to get shutdown scriptpubkey".to_owned()}), - } - } else { None }; - - if let Some(shutdown_scriptpubkey) = &shutdown_scriptpubkey { - if !shutdown_scriptpubkey.is_compatible(&their_features) { - return Err(APIError::IncompatibleShutdownScript { script: shutdown_scriptpubkey.clone() }); - } - } - - let destination_script = match signer_provider.get_destination_script(channel_keys_id) { - Ok(script) => script, - Err(_) => return Err(APIError::ChannelUnavailable { err: "Failed to get destination script".to_owned()}), - }; - - let temporary_channel_id = temporary_channel_id.unwrap_or_else(|| ChannelId::temporary_from_entropy_source(entropy_source)); - - Ok(Self { - context: ChannelContext { - user_id, - - config: LegacyChannelConfig { - options: config.channel_config.clone(), - announced_channel: config.channel_handshake_config.announced_channel, - commit_upfront_shutdown_pubkey: config.channel_handshake_config.commit_upfront_shutdown_pubkey, - }, - - prev_config: None, - - inbound_handshake_limits_override: Some(config.channel_handshake_limits.clone()), - - channel_id: temporary_channel_id, - temporary_channel_id: Some(temporary_channel_id), - channel_state: ChannelState::NegotiatingFunding(NegotiatingFundingFlags::OUR_INIT_SENT), - announcement_sigs_state: AnnouncementSigsState::NotSent, - secp_ctx, - channel_value_satoshis, - - latest_monitor_update_id: 0, - - holder_signer: ChannelSignerType::Ecdsa(holder_signer), - shutdown_scriptpubkey, - destination_script, - - cur_holder_commitment_transaction_number: INITIAL_COMMITMENT_NUMBER, - cur_counterparty_commitment_transaction_number: INITIAL_COMMITMENT_NUMBER, - value_to_self_msat, - - pending_inbound_htlcs: Vec::new(), - pending_outbound_htlcs: Vec::new(), - holding_cell_htlc_updates: Vec::new(), - pending_update_fee: None, - holding_cell_update_fee: None, - next_holder_htlc_id: 0, - next_counterparty_htlc_id: 0, - update_time_counter: 1, - - resend_order: RAACommitmentOrder::CommitmentFirst, - - monitor_pending_channel_ready: false, - monitor_pending_revoke_and_ack: false, - monitor_pending_commitment_signed: false, - monitor_pending_forwards: Vec::new(), - monitor_pending_failures: Vec::new(), - monitor_pending_finalized_fulfills: Vec::new(), - - signer_pending_commitment_update: false, - signer_pending_funding: false, - - #[cfg(debug_assertions)] - holder_max_commitment_tx_output: Mutex::new((channel_value_satoshis * 1000 - push_msat, push_msat)), - #[cfg(debug_assertions)] - counterparty_max_commitment_tx_output: Mutex::new((channel_value_satoshis * 1000 - push_msat, push_msat)), - - last_sent_closing_fee: None, - pending_counterparty_closing_signed: None, - expecting_peer_commitment_signed: false, - closing_fee_limits: None, - target_closing_feerate_sats_per_kw: None, - - funding_tx_confirmed_in: None, - funding_tx_confirmation_height: 0, - short_channel_id: None, - channel_creation_height: current_chain_height, - - feerate_per_kw: commitment_feerate, - counterparty_dust_limit_satoshis: 0, - holder_dust_limit_satoshis: MIN_CHAN_DUST_LIMIT_SATOSHIS, - counterparty_max_htlc_value_in_flight_msat: 0, - holder_max_htlc_value_in_flight_msat: get_holder_max_htlc_value_in_flight_msat(channel_value_satoshis, &config.channel_handshake_config), - counterparty_selected_channel_reserve_satoshis: None, // Filled in in accept_channel - holder_selected_channel_reserve_satoshis, - counterparty_htlc_minimum_msat: 0, - holder_htlc_minimum_msat: if config.channel_handshake_config.our_htlc_minimum_msat == 0 { 1 } else { config.channel_handshake_config.our_htlc_minimum_msat }, - counterparty_max_accepted_htlcs: 0, - holder_max_accepted_htlcs: cmp::min(config.channel_handshake_config.our_max_accepted_htlcs, MAX_HTLCS), - minimum_depth: None, // Filled in in accept_channel - - counterparty_forwarding_info: None, - - channel_transaction_parameters: ChannelTransactionParameters { - holder_pubkeys: pubkeys, - holder_selected_contest_delay: config.channel_handshake_config.our_to_self_delay, - is_outbound_from_holder: true, - counterparty_parameters: None, - funding_outpoint: None, - channel_type_features: channel_type.clone() - }, - funding_transaction: None, - is_batch_funding: None, - - counterparty_cur_commitment_point: None, - counterparty_prev_commitment_point: None, + let chan = Self { + context: ChannelContext::new_for_outbound_channel( + fee_estimator, + entropy_source, + signer_provider, counterparty_node_id, - - counterparty_shutdown_scriptpubkey: None, - - commitment_secrets: CounterpartyCommitmentSecrets::new(), - - channel_update_status: ChannelUpdateStatus::Enabled, - closing_signed_in_flight: false, - - announcement_sigs: None, - - #[cfg(any(test, fuzzing))] - next_local_commitment_tx_fee_info_cached: Mutex::new(None), - #[cfg(any(test, fuzzing))] - next_remote_commitment_tx_fee_info_cached: Mutex::new(None), - - workaround_lnd_bug_4006: None, - sent_message_awaiting_response: None, - - latest_inbound_scid_alias: None, + their_features, + channel_value_satoshis, + push_msat, + user_id, + config, + current_chain_height, outbound_scid_alias, - - channel_pending_event_emitted: false, - channel_ready_event_emitted: false, - - #[cfg(any(test, fuzzing))] - historical_inbound_htlc_fulfills: new_hash_set(), - + temporary_channel_id, channel_type, - channel_keys_id, - - blocked_monitor_updates: Vec::new(), - local_initiated_shutdown: None, - }, + )?, unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 } - }) + }; + Ok(chan) } /// Only allowed after [`ChannelContext::channel_transaction_parameters`] is set. From 909130bfed1df78bd563395990f2a1954347b55a Mon Sep 17 00:00:00 2001 From: Duncan Dean Date: Tue, 12 Sep 2023 21:27:14 +0200 Subject: [PATCH 7/9] Add `OutboundV2Channel` struct --- lightning/src/ln/channel.rs | 197 +++++++++++++++++++++++++++++------- 1 file changed, 162 insertions(+), 35 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 4b18e25e641..7171608c75e 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -1848,7 +1848,10 @@ impl ChannelContext where SP::Target: SignerProvider { current_chain_height: u32, outbound_scid_alias: u64, temporary_channel_id: Option, - channel_type: ChannelTypeFeatures, + holder_selected_channel_reserve_satoshis: u64, + channel_keys_id: [u8; 32], + holder_signer: ::EcdsaSigner, + pubkeys: ChannelPublicKeys, ) -> Result, APIError> where ES::Target: EntropySource, @@ -1859,9 +1862,6 @@ impl ChannelContext where SP::Target: SignerProvider { let channel_value_satoshis = funding_satoshis; let holder_selected_contest_delay = config.channel_handshake_config.our_to_self_delay; - let channel_keys_id = signer_provider.generate_channel_keys_id(false, channel_value_satoshis, user_id); - let holder_signer = signer_provider.derive_channel_signer(channel_value_satoshis, channel_keys_id); - let pubkeys = holder_signer.pubkeys().clone(); if !their_features.supports_wumbo() && channel_value_satoshis > MAX_FUNDING_SATOSHIS_NO_WUMBO { return Err(APIError::APIMisuseError{err: format!("funding_value must not exceed {}, it was {}", MAX_FUNDING_SATOSHIS_NO_WUMBO, channel_value_satoshis)}); @@ -1876,13 +1876,8 @@ impl ChannelContext where SP::Target: SignerProvider { if holder_selected_contest_delay < BREAKDOWN_TIMEOUT { return Err(APIError::APIMisuseError {err: format!("Configured with an unreasonable our_to_self_delay ({}) putting user funds at risks", holder_selected_contest_delay)}); } - let holder_selected_channel_reserve_satoshis = get_holder_selected_channel_reserve_satoshis(channel_value_satoshis, config); - if holder_selected_channel_reserve_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS { - // Protocol level safety check in place, although it should never happen because - // of `MIN_THEIR_CHAN_RESERVE_SATOSHIS` - return Err(APIError::APIMisuseError { err: format!("Holder selected channel reserve below implemention limit dust_limit_satoshis {}", holder_selected_channel_reserve_satoshis) }); - } + let channel_type = get_initial_channel_type(&config, their_features); debug_assert!(channel_type.is_subset(&channelmanager::provided_channel_type_features(&config))); let (commitment_conf_target, anchor_outputs_value_msat) = if channel_type.supports_anchors_zero_fee_htlc_tx() { @@ -1939,6 +1934,7 @@ impl ChannelContext where SP::Target: SignerProvider { channel_state: ChannelState::NegotiatingFunding(NegotiatingFundingFlags::OUR_INIT_SENT), announcement_sigs_state: AnnouncementSigsState::NotSent, secp_ctx, + // We'll add our counterparty's `funding_satoshis` when we receive `accept_channel2`. channel_value_satoshis, latest_monitor_update_id: 0, @@ -1972,6 +1968,8 @@ impl ChannelContext where SP::Target: SignerProvider { signer_pending_commitment_update: false, signer_pending_funding: false, + // We'll add our counterparty's `funding_satoshis` to these max commitment output assertions + // when we receive `accept_channel2`. #[cfg(debug_assertions)] holder_max_commitment_tx_output: Mutex::new((channel_value_satoshis * 1000 - push_msat, push_msat)), #[cfg(debug_assertions)] @@ -1992,6 +1990,8 @@ impl ChannelContext where SP::Target: SignerProvider { counterparty_dust_limit_satoshis: 0, holder_dust_limit_satoshis: MIN_CHAN_DUST_LIMIT_SATOSHIS, counterparty_max_htlc_value_in_flight_msat: 0, + // We'll adjust this to include our counterparty's `funding_satoshis` when we + // receive `accept_channel2`. holder_max_htlc_value_in_flight_msat: get_holder_max_htlc_value_in_flight_msat(channel_value_satoshis, &config.channel_handshake_config), counterparty_selected_channel_reserve_satoshis: None, // Filled in in accept_channel holder_selected_channel_reserve_satoshis, @@ -3415,6 +3415,7 @@ pub(crate) fn get_legacy_default_holder_selected_channel_reserve_satoshis(channe /// /// This is used both for outbound and inbound channels and has lower bound /// of `dust_limit_satoshis`. +#[cfg(dual_funding)] fn get_v2_channel_reserve_satoshis(channel_value_satoshis: u64, dust_limit_satoshis: u64) -> u64 { // Fixed at 1% of channel value by spec. let (q, _) = channel_value_satoshis.overflowing_div(100); @@ -7165,7 +7166,17 @@ impl OutboundV1Channel where SP::Target: SignerProvider { where ES::Target: EntropySource, F::Target: FeeEstimator { - let channel_type = Self::get_initial_channel_type(&config, their_features); + let holder_selected_channel_reserve_satoshis = get_holder_selected_channel_reserve_satoshis(channel_value_satoshis, config); + if holder_selected_channel_reserve_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS { + // Protocol level safety check in place, although it should never happen because + // of `MIN_THEIR_CHAN_RESERVE_SATOSHIS` + return Err(APIError::APIMisuseError { err: format!("Holder selected channel reserve below \ + implemention limit dust_limit_satoshis {}", holder_selected_channel_reserve_satoshis) }); + } + + let channel_keys_id = signer_provider.generate_channel_keys_id(false, channel_value_satoshis, user_id); + let holder_signer = signer_provider.derive_channel_signer(channel_value_satoshis, channel_keys_id); + let pubkeys = holder_signer.pubkeys().clone(); let chan = Self { context: ChannelContext::new_for_outbound_channel( @@ -7181,7 +7192,10 @@ impl OutboundV1Channel where SP::Target: SignerProvider { current_chain_height, outbound_scid_alias, temporary_channel_id, - channel_type, + holder_selected_channel_reserve_satoshis, + channel_keys_id, + holder_signer, + pubkeys, )?, unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 } }; @@ -7279,29 +7293,6 @@ impl OutboundV1Channel where SP::Target: SignerProvider { Ok(funding_created) } - fn get_initial_channel_type(config: &UserConfig, their_features: &InitFeatures) -> ChannelTypeFeatures { - // The default channel type (ie the first one we try) depends on whether the channel is - // public - if it is, we just go with `only_static_remotekey` as it's the only option - // available. If it's private, we first try `scid_privacy` as it provides better privacy - // with no other changes, and fall back to `only_static_remotekey`. - let mut ret = ChannelTypeFeatures::only_static_remote_key(); - if !config.channel_handshake_config.announced_channel && - config.channel_handshake_config.negotiate_scid_privacy && - their_features.supports_scid_privacy() { - ret.set_scid_privacy_required(); - } - - // Optionally, if the user would like to negotiate the `anchors_zero_fee_htlc_tx` option, we - // set it now. If they don't understand it, we'll fall back to our default of - // `only_static_remotekey`. - if config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx && - their_features.supports_anchors_zero_fee_htlc_tx() { - ret.set_anchors_zero_fee_htlc_tx_required(); - } - - ret - } - /// If we receive an error message, it may only be a rejection of the channel type we tried, /// not of our ability to open any channel at all. Thus, on error, we should first call this /// and see if we get a new `OpenChannel` message, otherwise the channel is failed. @@ -7913,6 +7904,117 @@ impl InboundV1Channel where SP::Target: SignerProvider { } } +// A not-yet-funded outbound (from holder) channel using V2 channel establishment. +#[cfg(dual_funding)] +pub(super) struct OutboundV2Channel where SP::Target: SignerProvider { + pub context: ChannelContext, + pub unfunded_context: UnfundedChannelContext, + #[cfg(dual_funding)] + pub dual_funding_context: DualFundingChannelContext, +} + +#[cfg(dual_funding)] +impl OutboundV2Channel where SP::Target: SignerProvider { + pub fn new( + fee_estimator: &LowerBoundedFeeEstimator, entropy_source: &ES, signer_provider: &SP, + counterparty_node_id: PublicKey, their_features: &InitFeatures, funding_satoshis: u64, + user_id: u128, config: &UserConfig, current_chain_height: u32, outbound_scid_alias: u64, + funding_confirmation_target: ConfirmationTarget, + ) -> Result, APIError> + where ES::Target: EntropySource, + F::Target: FeeEstimator, + { + let channel_keys_id = signer_provider.generate_channel_keys_id(false, funding_satoshis, user_id); + let holder_signer = signer_provider.derive_channel_signer(funding_satoshis, channel_keys_id); + let pubkeys = holder_signer.pubkeys().clone(); + + let temporary_channel_id = Some(ChannelId::temporary_v2_from_revocation_basepoint(&pubkeys.revocation_basepoint)); + + let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis( + funding_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS); + + let funding_feerate_sat_per_1000_weight = fee_estimator.bounded_sat_per_1000_weight(funding_confirmation_target); + let funding_tx_locktime = current_chain_height; + + let chan = Self { + context: ChannelContext::new_for_outbound_channel( + fee_estimator, + entropy_source, + signer_provider, + counterparty_node_id, + their_features, + funding_satoshis, + 0, + user_id, + config, + current_chain_height, + outbound_scid_alias, + temporary_channel_id, + holder_selected_channel_reserve_satoshis, + channel_keys_id, + holder_signer, + pubkeys, + )?, + unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 }, + dual_funding_context: DualFundingChannelContext { + our_funding_satoshis: funding_satoshis, + their_funding_satoshis: 0, + funding_tx_locktime, + funding_feerate_sat_per_1000_weight, + } + }; + Ok(chan) + } + + pub fn get_open_channel_v2(&self, chain_hash: ChainHash) -> msgs::OpenChannelV2 { + if self.context.have_received_message() { + debug_assert!(false, "Cannot generate an open_channel2 after we've moved forward"); + } + + if self.context.cur_holder_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER { + debug_assert!(false, "Tried to send an open_channel2 for a channel that has already advanced"); + } + + let first_per_commitment_point = self.context.holder_signer.as_ref() + .get_per_commitment_point(self.context.cur_holder_commitment_transaction_number, + &self.context.secp_ctx); + let second_per_commitment_point = self.context.holder_signer.as_ref() + .get_per_commitment_point(self.context.cur_holder_commitment_transaction_number - 1, + &self.context.secp_ctx); + let keys = self.context.get_holder_pubkeys(); + + msgs::OpenChannelV2 { + common_fields: msgs::CommonOpenChannelFields { + chain_hash, + temporary_channel_id: self.context.temporary_channel_id.unwrap(), + funding_satoshis: self.context.channel_value_satoshis, + dust_limit_satoshis: self.context.holder_dust_limit_satoshis, + max_htlc_value_in_flight_msat: self.context.holder_max_htlc_value_in_flight_msat, + htlc_minimum_msat: self.context.holder_htlc_minimum_msat, + commitment_feerate_sat_per_1000_weight: self.context.feerate_per_kw, + to_self_delay: self.context.get_holder_selected_contest_delay(), + max_accepted_htlcs: self.context.holder_max_accepted_htlcs, + funding_pubkey: keys.funding_pubkey, + revocation_basepoint: keys.revocation_basepoint.to_public_key(), + payment_basepoint: keys.payment_point, + delayed_payment_basepoint: keys.delayed_payment_basepoint.to_public_key(), + htlc_basepoint: keys.htlc_basepoint.to_public_key(), + first_per_commitment_point, + channel_flags: if self.context.config.announced_channel {1} else {0}, + shutdown_scriptpubkey: Some(match &self.context.shutdown_scriptpubkey { + Some(script) => script.clone().into_inner(), + None => Builder::new().into_script(), + }), + channel_type: Some(self.context.channel_type.clone()), + }, + funding_feerate_sat_per_1000_weight: self.context.feerate_per_kw, + second_per_commitment_point, + locktime: self.dual_funding_context.funding_tx_locktime, + require_confirmed_inputs: None, + } + } +} + // A not-yet-funded inbound (from counterparty) channel using V2 channel establishment. #[cfg(dual_funding)] pub(super) struct InboundV2Channel where SP::Target: SignerProvider { @@ -8067,6 +8169,31 @@ impl InboundV2Channel where SP::Target: SignerProvider { } } +// Unfunded channel utilities + +fn get_initial_channel_type(config: &UserConfig, their_features: &InitFeatures) -> ChannelTypeFeatures { + // The default channel type (ie the first one we try) depends on whether the channel is + // public - if it is, we just go with `only_static_remotekey` as it's the only option + // available. If it's private, we first try `scid_privacy` as it provides better privacy + // with no other changes, and fall back to `only_static_remotekey`. + let mut ret = ChannelTypeFeatures::only_static_remote_key(); + if !config.channel_handshake_config.announced_channel && + config.channel_handshake_config.negotiate_scid_privacy && + their_features.supports_scid_privacy() { + ret.set_scid_privacy_required(); + } + + // Optionally, if the user would like to negotiate the `anchors_zero_fee_htlc_tx` option, we + // set it now. If they don't understand it, we'll fall back to our default of + // `only_static_remotekey`. + if config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx && + their_features.supports_anchors_zero_fee_htlc_tx() { + ret.set_anchors_zero_fee_htlc_tx_required(); + } + + ret +} + const SERIALIZATION_VERSION: u8 = 3; const MIN_SERIALIZATION_VERSION: u8 = 3; From e142e4a4e6c856f533ec350ae61c1bbe2a3f20e9 Mon Sep 17 00:00:00 2001 From: Duncan Dean Date: Fri, 16 Feb 2024 08:01:16 +0200 Subject: [PATCH 8/9] Add maybe_handle_error_without_close for OutboundV2Channel --- lightning/src/ln/channel.rs | 88 ++++++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 31 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 7171608c75e..2d720824324 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -3364,6 +3364,49 @@ impl ChannelContext where SP::Target: SignerProvider { _ => todo!() } } + + /// If we receive an error message when attempting to open a channel, it may only be a rejection + /// of the channel type we tried, not of our ability to open any channel at all. We can see if a + /// downgrade of channel features would be possible so that we can still open the channel. + pub(crate) fn maybe_downgrade_channel_features( + &mut self, fee_estimator: &LowerBoundedFeeEstimator + ) -> Result<(), ()> + where + F::Target: FeeEstimator + { + if !self.is_outbound() || + !matches!( + self.channel_state, ChannelState::NegotiatingFunding(flags) + if flags == NegotiatingFundingFlags::OUR_INIT_SENT + ) + { + return Err(()); + } + if self.channel_type == ChannelTypeFeatures::only_static_remote_key() { + // We've exhausted our options + return Err(()); + } + // We support opening a few different types of channels. Try removing our additional + // features one by one until we've either arrived at our default or the counterparty has + // accepted one. + // + // Due to the order below, we may not negotiate `option_anchors_zero_fee_htlc_tx` if the + // counterparty doesn't support `option_scid_privacy`. Since `get_initial_channel_type` + // checks whether the counterparty supports every feature, this would only happen if the + // counterparty is advertising the feature, but rejecting channels proposing the feature for + // whatever reason. + if self.channel_type.supports_anchors_zero_fee_htlc_tx() { + self.channel_type.clear_anchors_zero_fee_htlc_tx(); + self.feerate_per_kw = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee); + assert!(!self.channel_transaction_parameters.channel_type_features.supports_anchors_nonzero_fee_htlc_tx()); + } else if self.channel_type.supports_scid_privacy() { + self.channel_type.clear_scid_privacy(); + } else { + self.channel_type = ChannelTypeFeatures::only_static_remote_key(); + } + self.channel_transaction_parameters.channel_type_features = self.channel_type.clone(); + Ok(()) + } } // Internal utility functions for channels @@ -7302,37 +7345,7 @@ impl OutboundV1Channel where SP::Target: SignerProvider { where F::Target: FeeEstimator { - if !self.context.is_outbound() || - !matches!( - self.context.channel_state, ChannelState::NegotiatingFunding(flags) - if flags == NegotiatingFundingFlags::OUR_INIT_SENT - ) - { - return Err(()); - } - if self.context.channel_type == ChannelTypeFeatures::only_static_remote_key() { - // We've exhausted our options - return Err(()); - } - // We support opening a few different types of channels. Try removing our additional - // features one by one until we've either arrived at our default or the counterparty has - // accepted one. - // - // Due to the order below, we may not negotiate `option_anchors_zero_fee_htlc_tx` if the - // counterparty doesn't support `option_scid_privacy`. Since `get_initial_channel_type` - // checks whether the counterparty supports every feature, this would only happen if the - // counterparty is advertising the feature, but rejecting channels proposing the feature for - // whatever reason. - if self.context.channel_type.supports_anchors_zero_fee_htlc_tx() { - self.context.channel_type.clear_anchors_zero_fee_htlc_tx(); - self.context.feerate_per_kw = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee); - assert!(!self.context.channel_transaction_parameters.channel_type_features.supports_anchors_nonzero_fee_htlc_tx()); - } else if self.context.channel_type.supports_scid_privacy() { - self.context.channel_type.clear_scid_privacy(); - } else { - self.context.channel_type = ChannelTypeFeatures::only_static_remote_key(); - } - self.context.channel_transaction_parameters.channel_type_features = self.context.channel_type.clone(); + self.context.maybe_downgrade_channel_features(fee_estimator)?; Ok(self.get_open_channel(chain_hash)) } @@ -7966,6 +7979,19 @@ impl OutboundV2Channel where SP::Target: SignerProvider { Ok(chan) } + /// If we receive an error message, it may only be a rejection of the channel type we tried, + /// not of our ability to open any channel at all. Thus, on error, we should first call this + /// and see if we get a new `OpenChannelV2` message, otherwise the channel is failed. + pub(crate) fn maybe_handle_error_without_close( + &mut self, chain_hash: ChainHash, fee_estimator: &LowerBoundedFeeEstimator + ) -> Result + where + F::Target: FeeEstimator + { + self.context.maybe_downgrade_channel_features(fee_estimator)?; + Ok(self.get_open_channel_v2(chain_hash)) + } + pub fn get_open_channel_v2(&self, chain_hash: ChainHash) -> msgs::OpenChannelV2 { if self.context.have_received_message() { debug_assert!(false, "Cannot generate an open_channel2 after we've moved forward"); From efec33fc57abb0f1d33585f32eb2b977d086f2f7 Mon Sep 17 00:00:00 2001 From: Duncan Dean Date: Wed, 13 Sep 2023 13:41:20 +0200 Subject: [PATCH 9/9] Add V2 `ChannelPhase` variants --- lightning/src/ln/channel.rs | 12 +++ lightning/src/ln/channelmanager.rs | 118 +++++++++++++++++++++++++-- lightning/src/ln/functional_tests.rs | 2 +- 3 files changed, 122 insertions(+), 10 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 2d720824324..028b100d345 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -1161,6 +1161,10 @@ impl_writeable_tlv_based!(PendingChannelMonitorUpdate, { pub(super) enum ChannelPhase where SP::Target: SignerProvider { UnfundedOutboundV1(OutboundV1Channel), UnfundedInboundV1(InboundV1Channel), + #[cfg(dual_funding)] + UnfundedOutboundV2(OutboundV2Channel), + #[cfg(dual_funding)] + UnfundedInboundV2(InboundV2Channel), Funded(Channel), } @@ -1173,6 +1177,10 @@ impl<'a, SP: Deref> ChannelPhase where ChannelPhase::Funded(chan) => &chan.context, ChannelPhase::UnfundedOutboundV1(chan) => &chan.context, ChannelPhase::UnfundedInboundV1(chan) => &chan.context, + #[cfg(dual_funding)] + ChannelPhase::UnfundedOutboundV2(chan) => &chan.context, + #[cfg(dual_funding)] + ChannelPhase::UnfundedInboundV2(chan) => &chan.context, } } @@ -1181,6 +1189,10 @@ impl<'a, SP: Deref> ChannelPhase where ChannelPhase::Funded(ref mut chan) => &mut chan.context, ChannelPhase::UnfundedOutboundV1(ref mut chan) => &mut chan.context, ChannelPhase::UnfundedInboundV1(ref mut chan) => &mut chan.context, + #[cfg(dual_funding)] + ChannelPhase::UnfundedOutboundV2(ref mut chan) => &mut chan.context, + #[cfg(dual_funding)] + ChannelPhase::UnfundedInboundV2(ref mut chan) => &mut chan.context, } } } diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 4d8fbdbbf76..abfca35f972 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -905,7 +905,14 @@ impl PeerState where SP::Target: SignerProvider { return false } !self.channel_by_id.iter().any(|(_, phase)| - matches!(phase, ChannelPhase::Funded(_) | ChannelPhase::UnfundedOutboundV1(_)) + match phase { + ChannelPhase::Funded(_) | ChannelPhase::UnfundedOutboundV1(_) => true, + ChannelPhase::UnfundedInboundV1(_) => false, + #[cfg(dual_funding)] + ChannelPhase::UnfundedOutboundV2(_) => true, + #[cfg(dual_funding)] + ChannelPhase::UnfundedInboundV2(_) => false, + } ) && self.monitor_update_blocked_actions.is_empty() && self.in_flight_monitor_updates.is_empty() @@ -2092,6 +2099,14 @@ macro_rules! convert_chan_phase_err { ChannelPhase::UnfundedInboundV1(channel) => { convert_chan_phase_err!($self, $err, channel, $channel_id, UNFUNDED_CHANNEL) }, + #[cfg(dual_funding)] + ChannelPhase::UnfundedOutboundV2(channel) => { + convert_chan_phase_err!($self, $err, channel, $channel_id, UNFUNDED_CHANNEL) + }, + #[cfg(dual_funding)] + ChannelPhase::UnfundedInboundV2(channel) => { + convert_chan_phase_err!($self, $err, channel, $channel_id, UNFUNDED_CHANNEL) + }, } }; } @@ -2958,6 +2973,13 @@ where // Unfunded channel has no update (None, chan_phase.context().get_counterparty_node_id()) }, + // TODO(dual_funding): Combine this match arm with above once #[cfg(dual_funding)] is removed. + #[cfg(dual_funding)] + ChannelPhase::UnfundedOutboundV2(_) | ChannelPhase::UnfundedInboundV2(_) => { + self.finish_close_channel(chan_phase.context_mut().force_shutdown(false, closure_reason)); + // Unfunded channel has no update + (None, chan_phase.context().get_counterparty_node_id()) + }, } } else if peer_state.inbound_channel_request_by_id.remove(channel_id).is_some() { log_error!(logger, "Force-closing channel {}", &channel_id); @@ -5031,6 +5053,16 @@ where process_unfunded_channel_tick(chan_id, &mut chan.context, &mut chan.unfunded_context, pending_msg_events, counterparty_node_id) }, + #[cfg(dual_funding)] + ChannelPhase::UnfundedInboundV2(chan) => { + process_unfunded_channel_tick(chan_id, &mut chan.context, &mut chan.unfunded_context, + pending_msg_events, counterparty_node_id) + }, + #[cfg(dual_funding)] + ChannelPhase::UnfundedOutboundV2(chan) => { + process_unfunded_channel_tick(chan_id, &mut chan.context, &mut chan.unfunded_context, + pending_msg_events, counterparty_node_id) + }, } }); @@ -6178,9 +6210,25 @@ where num_unfunded_channels += 1; } }, + // TODO(dual_funding): Combine this match arm with above once #[cfg(dual_funding)] is removed. + #[cfg(dual_funding)] + ChannelPhase::UnfundedInboundV2(chan) => { + // Only inbound V2 channels that are not 0conf and that we do not contribute to will be + // included in the unfunded count. + if chan.context.minimum_depth().unwrap_or(1) != 0 && + chan.dual_funding_context.our_funding_satoshis == 0 { + num_unfunded_channels += 1; + } + }, ChannelPhase::UnfundedOutboundV1(_) => { // Outbound channels don't contribute to the unfunded count in the DoS context. continue; + }, + // TODO(dual_funding): Combine this match arm with above once #[cfg(dual_funding)] is removed. + #[cfg(dual_funding)] + ChannelPhase::UnfundedOutboundV2(_) => { + // Outbound channels don't contribute to the unfunded count in the DoS context. + continue; } } } @@ -6603,6 +6651,14 @@ where let mut chan = remove_channel_phase!(self, chan_phase_entry); finish_shutdown = Some(chan.context_mut().force_shutdown(false, ClosureReason::CounterpartyCoopClosedUnfundedChannel)); }, + // TODO(dual_funding): Combine this match arm with above. + #[cfg(dual_funding)] + ChannelPhase::UnfundedInboundV2(_) | ChannelPhase::UnfundedOutboundV2(_) => { + let context = phase.context_mut(); + log_error!(self.logger, "Immediately closing unfunded channel {} as peer asked to cooperatively shut it down (which is unnecessary)", &msg.channel_id); + let mut chan = remove_channel_phase!(self, chan_phase_entry); + finish_shutdown = Some(chan.context_mut().force_shutdown(false, ClosureReason::CounterpartyCoopClosedUnfundedChannel)); + }, } } else { return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", counterparty_node_id), msg.channel_id)) @@ -8470,6 +8526,9 @@ where match phase { // Retain unfunded channels. ChannelPhase::UnfundedOutboundV1(_) | ChannelPhase::UnfundedInboundV1(_) => true, + // TODO(dual_funding): Combine this match arm with above. + #[cfg(dual_funding)] + ChannelPhase::UnfundedOutboundV2(_) | ChannelPhase::UnfundedInboundV2(_) => true, ChannelPhase::Funded(channel) => { let res = f(channel); if let Ok((channel_ready_opt, mut timed_out_pending_htlcs, announcement_sigs)) = res { @@ -8939,6 +8998,14 @@ where ChannelPhase::UnfundedInboundV1(chan) => { &mut chan.context }, + #[cfg(dual_funding)] + ChannelPhase::UnfundedOutboundV2(chan) => { + &mut chan.context + }, + #[cfg(dual_funding)] + ChannelPhase::UnfundedInboundV2(chan) => { + &mut chan.context + }, }; // Clean up for removal. update_maps_on_chan_removal!(self, &context); @@ -9091,12 +9158,30 @@ where }); } + // TODO(dual_funding): Combine this match arm with above once #[cfg(dual_funding)] is removed. + #[cfg(dual_funding)] + ChannelPhase::UnfundedOutboundV2(chan) => { + pending_msg_events.push(events::MessageSendEvent::SendOpenChannelV2 { + node_id: chan.context.get_counterparty_node_id(), + msg: chan.get_open_channel_v2(self.chain_hash), + }); + }, + ChannelPhase::UnfundedInboundV1(_) => { // Since unfunded inbound channel maps are cleared upon disconnecting a peer, // they are not persisted and won't be recovered after a crash. // Therefore, they shouldn't exist at this point. debug_assert!(false); } + + // TODO(dual_funding): Combine this match arm with above once #[cfg(dual_funding)] is removed. + #[cfg(dual_funding)] + ChannelPhase::UnfundedInboundV2(channel) => { + // Since unfunded inbound channel maps are cleared upon disconnecting a peer, + // they are not persisted and won't be recovered after a crash. + // Therefore, they shouldn't exist at this point. + debug_assert!(false); + }, } } } @@ -9174,14 +9259,29 @@ where if peer_state_mutex_opt.is_none() { return; } let mut peer_state_lock = peer_state_mutex_opt.unwrap().lock().unwrap(); let peer_state = &mut *peer_state_lock; - if let Some(ChannelPhase::UnfundedOutboundV1(chan)) = peer_state.channel_by_id.get_mut(&msg.channel_id) { - if let Ok(msg) = chan.maybe_handle_error_without_close(self.chain_hash, &self.fee_estimator) { - peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannel { - node_id: *counterparty_node_id, - msg, - }); - return; - } + match peer_state.channel_by_id.get_mut(&msg.channel_id) { + Some(ChannelPhase::UnfundedOutboundV1(ref mut chan)) => { + if let Ok(msg) = chan.maybe_handle_error_without_close(self.chain_hash, &self.fee_estimator) { + peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannel { + node_id: *counterparty_node_id, + msg, + }); + return; + } + }, + #[cfg(dual_funding)] + Some(ChannelPhase::UnfundedOutboundV2(ref mut chan)) => { + if let Ok(msg) = chan.maybe_handle_error_without_close(self.chain_hash, &self.fee_estimator) { + peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannelV2 { + node_id: *counterparty_node_id, + msg, + }); + return; + } + }, + None | Some(ChannelPhase::UnfundedInboundV1(_) | ChannelPhase::Funded(_)) => (), + #[cfg(dual_funding)] + Some(ChannelPhase::UnfundedInboundV2(_)) => (), } } diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index aeb60940bb1..2f71ca66a0c 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -190,7 +190,7 @@ fn do_test_counterparty_no_reserve(send_from_initiator: bool) { chan_context.holder_selected_channel_reserve_satoshis = 0; chan_context.holder_max_htlc_value_in_flight_msat = 100_000_000; }, - ChannelPhase::Funded(_) => assert!(false), + _ => assert!(false), } }