Skip to content

Commit

Permalink
Add OutboundV2Channel struct
Browse files Browse the repository at this point in the history
  • Loading branch information
dunxen committed Sep 12, 2023
1 parent fbb9f80 commit bb0e630
Showing 1 changed file with 289 additions and 24 deletions.
313 changes: 289 additions & 24 deletions lightning/src/ln/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5687,7 +5687,7 @@ impl<SP: Deref> OutboundV1Channel<SP> where SP::Target: SignerProvider {
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);
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 = if channel_type.supports_anchors_zero_fee_htlc_tx() {
Expand Down Expand Up @@ -5933,29 +5933,6 @@ impl<SP: Deref> OutboundV1Channel<SP> where SP::Target: SignerProvider {
}))
}

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.
Expand Down Expand Up @@ -6705,6 +6682,269 @@ impl<SP: Deref> InboundV1Channel<SP> where SP::Target: SignerProvider {
}
}

// A not-yet-funded outbound (from holder) channel using V2 channel establishment.
pub(super) struct OutboundV2Channel<SP: Deref> where SP::Target: SignerProvider {
pub context: DualFundingChannelContext<SP>,
pub unfunded_context: UnfundedChannelContext,
}

impl<SP: Deref> OutboundV2Channel<SP> where SP::Target: SignerProvider {
pub fn new<ES: Deref, F: Deref>(
fee_estimator: &LowerBoundedFeeEstimator<F>, entropy_source: &ES, signer_provider: &SP,
counterparty_node_id: PublicKey, their_features: &InitFeatures, funding_satoshis: u64,
funding_inputs: Vec<DualFundingUtxo>, change_script_pubkey: Script, user_id: u128,
config: &UserConfig, current_chain_height: u32, outbound_scid_alias: u64,
require_confirmed_inputs: bool, funding_conf_target: Option<ConfirmationTarget>,
) -> Result<OutboundV2Channel<SP>, APIError>
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, funding_satoshis, user_id);
let holder_signer = signer_provider.derive_channel_signer(funding_satoshis, channel_keys_id);
let pubkeys = holder_signer.pubkeys().clone();

if !their_features.supports_wumbo() && funding_satoshis > MAX_FUNDING_SATOSHIS_NO_WUMBO {
return Err(APIError::APIMisuseError{err: format!("funding_value must not exceed {}, it was {}", MAX_FUNDING_SATOSHIS_NO_WUMBO, funding_satoshis)});
}
if funding_satoshis >= TOTAL_BITCOIN_SUPPLY_SATOSHIS {
return Err(APIError::APIMisuseError{err: format!("funding_value must be smaller than the total bitcoin supply, it was {}", funding_satoshis)});
}
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 channel_type = get_initial_channel_type(&config, their_features);
debug_assert!(channel_type.is_subset(&channelmanager::provided_channel_type_features(&config)));

let commitment_tx_feerate = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::Background);
let funding_tx_feerate = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::Normal);

let value_to_self_msat = funding_satoshis * 1000;
let commitment_tx_fee = commit_tx_fee_msat(commitment_tx_feerate,
MIN_AFFORDABLE_HTLC_COUNT, &channel_type);
if value_to_self_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() {
Ok(script) => script,
Err(_) => return Err(APIError::ChannelUnavailable { err: "Failed to get destination script".to_owned()}),
};

let temporary_channel_id = ChannelId::temporary_v2_from_revocation_basepoint(&pubkeys.revocation_basepoint);

let funding_tx_locktime = current_chain_height;

Ok(Self {
context: DualFundingChannelContext {
our_funding_satoshis: funding_satoshis,
our_funding_inputs: funding_inputs,
we_require_confirmed_inputs: require_confirmed_inputs,
counterparty_requires_confirmed_inputs: false,
funding_tx_locktime,
our_change_script_pubkey: change_script_pubkey,

common: 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::OurInitSent as u32,
announcement_sigs_state: AnnouncementSigsState::NotSent,
secp_ctx,
// We'll add our counterparty's `funding_satoshis` when we receive `accept_channel2`.
channel_value_satoshis: funding_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(),

// 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((funding_satoshis * 1000, 0)),
#[cfg(debug_assertions)]
counterparty_max_commitment_tx_output: Mutex::new((funding_satoshis * 1000, 0)),

last_sent_closing_fee: None,
pending_counterparty_closing_signed: None,
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_tx_feerate,
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(
funding_satoshis, &config.channel_handshake_config),
// We'll adjust the reserves to include our counterparty's `funding_satoshis` when we
// receive `accept_channel2`.
counterparty_selected_channel_reserve_satoshis: None, // Fill
holder_selected_channel_reserve_satoshis: get_v2_channel_reserve_satoshis(
funding_satoshis, MIN_CHAN_DUST_LIMIT_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,

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: HashSet::new(),

channel_type,
channel_keys_id,

blocked_monitor_updates: Vec::new(),
},
},
unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 }
})
}

pub fn get_open_channel_v2(&self, chain_hash: BlockHash) -> msgs::OpenChannelV2 {
if self.context.common.channel_state != ChannelState::OurInitSent as u32 {
panic!("Cannot generate an open_channel2 after we've moved forward");
}

if self.context.common.cur_holder_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER {
panic!("Tried to send an open_channel2 for a channel that has already advanced");
}

let first_per_commitment_point = self.context.common.holder_signer.as_ref()
.get_per_commitment_point(self.context.common.cur_holder_commitment_transaction_number,
&self.context.common.secp_ctx);
let second_per_commitment_point = self.context.common.holder_signer.as_ref()
.get_per_commitment_point(self.context.common.cur_holder_commitment_transaction_number - 1,
&self.context.common.secp_ctx);
let keys = self.context.common.get_holder_pubkeys();

msgs::OpenChannelV2 {
chain_hash,
temporary_channel_id: self.context.common.channel_id,
funding_satoshis: self.context.common.channel_value_satoshis,
dust_limit_satoshis: self.context.common.holder_dust_limit_satoshis,
max_htlc_value_in_flight_msat: self.context.common.holder_max_htlc_value_in_flight_msat,
htlc_minimum_msat: self.context.common.holder_htlc_minimum_msat,
funding_feerate_sat_per_1000_weight: self.context.common.feerate_per_kw,
commitment_feerate_sat_per_1000_weight: self.context.common.feerate_per_kw,
to_self_delay: self.context.common.get_holder_selected_contest_delay(),
max_accepted_htlcs: self.context.common.holder_max_accepted_htlcs,
funding_pubkey: keys.funding_pubkey,
revocation_basepoint: keys.revocation_basepoint,
payment_basepoint: keys.payment_point,
delayed_payment_basepoint: keys.delayed_payment_basepoint,
htlc_basepoint: keys.htlc_basepoint,
first_per_commitment_point,
second_per_commitment_point,
channel_flags: if self.context.common.config.announced_channel {1} else {0},
shutdown_scriptpubkey: Some(match &self.context.common.shutdown_scriptpubkey {
Some(script) => script.clone().into_inner(),
None => Builder::new().into_script(),
}),
channel_type: Some(self.context.common.channel_type.clone()),
locktime: self.context.funding_tx_locktime,
require_confirmed_inputs: if self.context.we_require_confirmed_inputs { Some(()) } else { None },
}
}
}

// A not-yet-funded inbound (from counterparty) channel using V2 channel establishment.
pub(super) struct InboundV2Channel<SP: Deref> where SP::Target: SignerProvider {
pub context: DualFundingChannelContext<SP>,
Expand Down Expand Up @@ -7135,6 +7375,31 @@ impl<SP: Deref> InboundV2Channel<SP> 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 = 2;

Expand Down

0 comments on commit bb0e630

Please sign in to comment.