Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Reset the channel state before restarting the channel establishment process. #2983

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
145 changes: 134 additions & 11 deletions lightning/src/ln/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1247,6 +1247,43 @@ impl UnfundedChannelContext {
}
}

/// Stores values related to retrying the handshake process for outbound channels
/// after disconnection and reconnection.
pub(super) struct OutboundContext {
received_accept_channel_msg: Option<msgs::AcceptChannel>,
created_funding_transaction: Option<FundingTransaction>
}

impl OutboundContext {
/// Create a new [`OutboundContext`] with blank values.
pub fn new() -> Self {
OutboundContext {
received_accept_channel_msg: None,
created_funding_transaction: None,
}
}

/// Determines whether an `AcceptChannel` message has been received during a previous
/// channel handshake.
pub fn received_accept_channel(&self) -> bool {
self.received_accept_channel_msg.is_some()
}

/// Determines whether a Funding Transaction had been created during a previous
/// channel handshake.
pub fn created_funding_transaction(&self) -> bool {
self.created_funding_transaction.is_some()
}
}

/// Represents a funding transaction used in the establishment [`Channel`]
#[derive(Clone)]
pub struct FundingTransaction {
funding_transaction: Transaction,
outpoint: OutPoint,
is_batch_funding: bool,
}

/// Contains everything about the channel including state, and various flags.
pub(super) struct ChannelContext<SP: Deref> where SP::Target: SignerProvider {
config: LegacyChannelConfig,
Expand Down Expand Up @@ -7281,10 +7318,32 @@ impl<SP: Deref> Channel<SP> where
}
}

/// Represents whether the associated [`FundingTransaction`] instance is newly created
/// or was generated during a previous channel handshake.
#[allow(unused)]
pub enum TransactionEnum {
New(FundingTransaction),
Old(FundingTransaction),
}

impl TransactionEnum {
/// Creates a new `TransactionEnum::New` variant representing a newly created funding transaction.
pub fn new_funding_transaction(transaction: Transaction, txo: OutPoint, is_batch_funding: bool) -> Self {
TransactionEnum::New(
FundingTransaction {
funding_transaction: transaction,
outpoint: txo,
is_batch_funding
}
)
}
}

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

impl<SP: Deref> OutboundV1Channel<SP> where SP::Target: SignerProvider {
Expand Down Expand Up @@ -7327,7 +7386,8 @@ impl<SP: Deref> OutboundV1Channel<SP> where SP::Target: SignerProvider {
holder_signer,
pubkeys,
)?,
unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 }
unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 },
outbound_context: OutboundContext::new(),
};
Ok(chan)
}
Expand Down Expand Up @@ -7371,7 +7431,7 @@ impl<SP: Deref> OutboundV1Channel<SP> where SP::Target: SignerProvider {
/// Note that channel_id changes during this call!
/// Do NOT broadcast the funding transaction until after a successful funding_signed call!
/// If an Err is returned, it is a ChannelError::Close.
pub fn get_funding_created<L: Deref>(&mut self, funding_transaction: Transaction, funding_txo: OutPoint, is_batch_funding: bool, logger: &L)
pub fn get_funding_created<L: Deref>(&mut self, transaction: TransactionEnum, logger: &L)
-> Result<Option<msgs::FundingCreated>, (Self, ChannelError)> where L::Target: Logger {
if !self.context.is_outbound() {
panic!("Tried to create outbound funding_created message on an inbound channel!");
Expand All @@ -7388,6 +7448,39 @@ impl<SP: Deref> OutboundV1Channel<SP> where SP::Target: SignerProvider {
panic!("Should not have advanced channel commitment tx numbers prior to funding_created");
}

let (funding_transaction, funding_txo, is_batch_funding) = match transaction {
TransactionEnum::New(tx) => {
self.outbound_context.created_funding_transaction = Some(tx.clone());
(tx.funding_transaction, tx.outpoint, tx.is_batch_funding)
},
TransactionEnum::Old(tx) => {
// Sanity checks
if self.context.channel_id != ChannelId::v1_from_funding_outpoint(tx.outpoint) {
panic!("Previously saved funding_transaction in channel parameter does not match funding_txo saved in outbound channel parameters.")
}
if self.context.funding_transaction != Some(tx.funding_transaction) {
panic!("Previously saved funding_transaction in channel parameter does not match funding_transaction saved in outbound channel parameters.")
}
if self.context.is_batch_funding != Some(()).filter(|_| tx.is_batch_funding) {
panic!("Previously saved funding_transaction in channel parameter does not match is_batch_funding saved in outbound channel parameters.")
}

let funding_created = self.get_funding_created_msg(logger);
if funding_created.is_none() {
#[cfg(not(async_signing))] {
panic!("Failed to get signature for new funding creation");
}
#[cfg(async_signing)] {
if !self.context.signer_pending_funding {
log_trace!(logger, "funding_created awaiting signer; setting signer_pending_funding");
self.context.signer_pending_funding = true;
}
}
}
return Ok(funding_created);
}
};

self.context.channel_transaction_parameters.funding_outpoint = Some(funding_txo);
self.context.holder_signer.as_mut().provide_channel_parameters(&self.context.channel_transaction_parameters);

Expand Down Expand Up @@ -7440,8 +7533,8 @@ impl<SP: Deref> OutboundV1Channel<SP> where SP::Target: SignerProvider {
if !self.context.is_outbound() {
panic!("Tried to open a channel for an inbound channel?");
}
if self.context.have_received_message() {
panic!("Cannot generate an open_channel after we've moved forward");
if self.context.have_received_message() && !self.deja_vu() {
panic!("Cannot generate an open_channel after we've moved forward earlier, but doesn't know the previous accept_channel_msg");
}

if self.context.cur_holder_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER {
Expand Down Expand Up @@ -7488,9 +7581,16 @@ impl<SP: Deref> OutboundV1Channel<SP> where SP::Target: SignerProvider {
if !self.context.is_outbound() {
return Err(ChannelError::Close("Got an accept_channel message from an inbound peer".to_owned()));
}
if !matches!(self.context.channel_state, ChannelState::NegotiatingFunding(flags) if flags == NegotiatingFundingFlags::OUR_INIT_SENT) {
if !matches!(self.context.channel_state, ChannelState::NegotiatingFunding(flags) if flags == NegotiatingFundingFlags::OUR_INIT_SENT) && !self.deja_vu() {
return Err(ChannelError::Close("Got an accept_channel message at a strange time".to_owned()));
}
if self.outbound_context.received_accept_channel() {
if self.outbound_context.received_accept_channel_msg.as_ref().unwrap() == msg {
return Ok(());
} else {
return Err(ChannelError::Close("The new accep_channel_msg doesn't match the old one. Should renegotiating channel parameters".to_owned()));
}
}
if msg.common_fields.dust_limit_satoshis > 21000000 * 100000000 {
return Err(ChannelError::Close(format!("Peer never wants payout outputs? dust_limit_satoshis was {}", msg.common_fields.dust_limit_satoshis)));
}
Expand Down Expand Up @@ -7577,6 +7677,10 @@ impl<SP: Deref> OutboundV1Channel<SP> where SP::Target: SignerProvider {
}
} else { None };


// Save the accept channel msg after all the sanity checks to the outbound_context parameters.
self.outbound_context.received_accept_channel_msg = Some(msg.clone());

self.context.counterparty_dust_limit_satoshis = msg.common_fields.dust_limit_satoshis;
self.context.counterparty_max_htlc_value_in_flight_msat = cmp::min(msg.common_fields.max_htlc_value_in_flight_msat, self.context.channel_value_satoshis * 1000);
self.context.counterparty_selected_channel_reserve_satoshis = Some(msg.channel_reserve_satoshis);
Expand Down Expand Up @@ -7713,6 +7817,19 @@ impl<SP: Deref> OutboundV1Channel<SP> where SP::Target: SignerProvider {
Ok((channel, channel_monitor))
}

/// Determines if the outbound channel handshake process is being retried.
fn deja_vu(&self) -> bool {
let negotiating = !matches!(self.context.channel_state, ChannelState::NegotiatingFunding(flags) if flags == NegotiatingFundingFlags::OUR_INIT_SENT);
let negotiated = self.context.channel_state == ChannelState::FundingNegotiated;

if (negotiated && self.outbound_context.created_funding_transaction()) ||
(negotiating && self.outbound_context.received_accept_channel()) {
true
} else {
false
}
}

/// Indicates that the signer may have some signatures for us, so we should retry if we're
/// blocked.
#[cfg(async_signing)]
Expand Down Expand Up @@ -9321,7 +9438,7 @@ mod tests {
use crate::ln::{PaymentHash, PaymentPreimage};
use crate::ln::channel_keys::{RevocationKey, RevocationBasepoint};
use crate::ln::channelmanager::{self, HTLCSource, PaymentId};
use crate::ln::channel::InitFeatures;
use crate::ln::channel::{InitFeatures, TransactionEnum};
use crate::ln::channel::{AwaitingChannelReadyFlags, Channel, ChannelState, InboundHTLCOutput, OutboundV1Channel, InboundV1Channel, OutboundHTLCOutput, InboundHTLCState, OutboundHTLCState, HTLCCandidate, HTLCInitiator, HTLCUpdateAwaitingACK, commit_tx_fee_msat};
use crate::ln::channel::{MAX_FUNDING_SATOSHIS_NO_WUMBO, TOTAL_BITCOIN_SUPPLY_SATOSHIS, MIN_THEIR_CHAN_RESERVE_SATOSHIS};
use crate::ln::features::{ChannelFeatures, ChannelTypeFeatures, NodeFeatures};
Expand Down Expand Up @@ -9507,7 +9624,9 @@ mod tests {
value: 10000000, script_pubkey: output_script.clone(),
}]};
let funding_outpoint = OutPoint{ txid: tx.txid(), index: 0 };
let funding_created_msg = node_a_chan.get_funding_created(tx.clone(), funding_outpoint, false, &&logger).map_err(|_| ()).unwrap();

let funding_transaction = TransactionEnum::new_funding_transaction(tx, funding_outpoint, false);
let funding_created_msg = node_a_chan.get_funding_created(funding_transaction, &&logger).map_err(|_| ()).unwrap();
let (_, funding_signed_msg, _) = node_b_chan.funding_created(&funding_created_msg.unwrap(), best_block, &&keys_provider, &&logger).map_err(|_| ()).unwrap();

// Node B --> Node A: funding signed
Expand Down Expand Up @@ -9636,7 +9755,8 @@ mod tests {
value: 10000000, script_pubkey: output_script.clone(),
}]};
let funding_outpoint = OutPoint{ txid: tx.txid(), index: 0 };
let funding_created_msg = node_a_chan.get_funding_created(tx.clone(), funding_outpoint, false, &&logger).map_err(|_| ()).unwrap();
let transaction = TransactionEnum::new_funding_transaction(tx, funding_outpoint, false);
let funding_created_msg = node_a_chan.get_funding_created(transaction, &&logger).map_err(|_| ()).unwrap();
let (mut node_b_chan, funding_signed_msg, _) = node_b_chan.funding_created(&funding_created_msg.unwrap(), best_block, &&keys_provider, &&logger).map_err(|_| ()).unwrap();

// Node B --> Node A: funding signed
Expand Down Expand Up @@ -9825,7 +9945,8 @@ mod tests {
value: 10000000, script_pubkey: output_script.clone(),
}]};
let funding_outpoint = OutPoint{ txid: tx.txid(), index: 0 };
let funding_created_msg = node_a_chan.get_funding_created(tx.clone(), funding_outpoint, false, &&logger).map_err(|_| ()).unwrap();
let transaction = TransactionEnum::new_funding_transaction(tx.clone(), funding_outpoint, false);
let funding_created_msg = node_a_chan.get_funding_created(transaction, &&logger).map_err(|_| ()).unwrap();
let (_, funding_signed_msg, _) = node_b_chan.funding_created(&funding_created_msg.unwrap(), best_block, &&keys_provider, &&logger).map_err(|_| ()).unwrap();

// Node B --> Node A: funding signed
Expand Down Expand Up @@ -9892,7 +10013,8 @@ mod tests {
value: 10000000, script_pubkey: outbound_chan.context.get_funding_redeemscript(),
}]};
let funding_outpoint = OutPoint{ txid: tx.txid(), index: 0 };
let funding_created = outbound_chan.get_funding_created(tx.clone(), funding_outpoint, false, &&logger).map_err(|_| ()).unwrap().unwrap();
let transaction = TransactionEnum::new_funding_transaction(tx.clone(), funding_outpoint, false);
let funding_created = outbound_chan.get_funding_created(transaction, &&logger).map_err(|_| ()).unwrap().unwrap();
let mut chan = match inbound_chan.funding_created(&funding_created, best_block, &&keys_provider, &&logger) {
Ok((chan, _, _)) => chan,
Err((_, e)) => panic!("{}", e),
Expand Down Expand Up @@ -11024,8 +11146,9 @@ mod tests {
},
]};
let funding_outpoint = OutPoint{ txid: tx.txid(), index: 0 };
let transaction = TransactionEnum::new_funding_transaction(tx.clone(), funding_outpoint, true);
let funding_created_msg = node_a_chan.get_funding_created(
tx.clone(), funding_outpoint, true, &&logger,
transaction, &&logger,
).map_err(|_| ()).unwrap();
let (mut node_b_chan, funding_signed_msg, _) = node_b_chan.funding_created(
&funding_created_msg.unwrap(),
Expand Down
7 changes: 5 additions & 2 deletions lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ use crate::util::string::UntrustedString;
use crate::util::ser::{BigSize, FixedLengthReader, Readable, ReadableArgs, MaybeReadable, Writeable, Writer, VecWriter};
use crate::util::logger::{Level, Logger, WithContext};
use crate::util::errors::APIError;
use super::channel::TransactionEnum;

#[cfg(not(c_bindings))]
use {
crate::offers::offer::DerivedMetadata,
Expand Down Expand Up @@ -4500,8 +4502,9 @@ where
Some(ChannelPhase::UnfundedOutboundV1(mut chan)) => {
funding_txo = find_funding_output(&chan, &funding_transaction)?;

let logger = WithChannelContext::from(&self.logger, &chan.context);
let funding_res = chan.get_funding_created(funding_transaction, funding_txo, is_batch_funding, &&logger)
let logger: WithChannelContext<'_, L> = WithChannelContext::from(&self.logger, &chan.context);
let transaction = TransactionEnum::new_funding_transaction(funding_transaction, funding_txo, is_batch_funding);
let funding_res = chan.get_funding_created(transaction, &&logger)
.map_err(|(mut chan, e)| if let ChannelError::Close(msg) = e {
let channel_id = chan.context.channel_id();
let reason = ClosureReason::ProcessingError { err: msg.clone() };
Expand Down
5 changes: 3 additions & 2 deletions lightning/src/ln/functional_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::chain::transaction::OutPoint;
use crate::sign::{ecdsa::EcdsaChannelSigner, EntropySource, OutputSpender, SignerProvider};
use crate::events::{Event, MessageSendEvent, MessageSendEventsProvider, PathFailure, PaymentPurpose, ClosureReason, HTLCDestination, PaymentFailureReason};
use crate::ln::{ChannelId, PaymentPreimage, PaymentSecret, PaymentHash};
use crate::ln::channel::{commitment_tx_base_weight, COMMITMENT_TX_WEIGHT_PER_HTLC, CONCURRENT_INBOUND_HTLC_FEE_BUFFER, FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE, MIN_AFFORDABLE_HTLC_COUNT, get_holder_selected_channel_reserve_satoshis, OutboundV1Channel, InboundV1Channel, COINBASE_MATURITY, ChannelPhase};
use crate::ln::channel::{commitment_tx_base_weight, get_holder_selected_channel_reserve_satoshis, ChannelPhase, InboundV1Channel, OutboundV1Channel, TransactionEnum, COINBASE_MATURITY, COMMITMENT_TX_WEIGHT_PER_HTLC, CONCURRENT_INBOUND_HTLC_FEE_BUFFER, FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE, MIN_AFFORDABLE_HTLC_COUNT};
use crate::ln::channelmanager::{self, PaymentId, RAACommitmentOrder, PaymentSendFailure, RecipientOnionFields, BREAKDOWN_TIMEOUT, ENABLE_GOSSIP_TICKS, DISABLE_GOSSIP_TICKS, MIN_CLTV_EXPIRY_DELTA};
use crate::ln::channel::{DISCONNECT_PEER_AWAITING_RESPONSE_TICKS, ChannelError};
use crate::ln::{chan_utils, onion_utils};
Expand Down Expand Up @@ -9235,7 +9235,8 @@ fn test_duplicate_chan_id() {
match a_peer_state.channel_by_id.remove(&open_chan_2_msg.common_fields.temporary_channel_id).unwrap() {
ChannelPhase::UnfundedOutboundV1(mut chan) => {
let logger = test_utils::TestLogger::new();
chan.get_funding_created(tx.clone(), funding_outpoint, false, &&logger).map_err(|_| ()).unwrap()
let transaction = TransactionEnum::new_funding_transaction(tx.clone(), funding_outpoint, false);
chan.get_funding_created(transaction, &&logger).map_err(|_| ()).unwrap()
},
_ => panic!("Unexpected ChannelPhase variant"),
}.unwrap()
Expand Down