From 021c4d4431973cd52e08cca5d73a5de2da5bfeab Mon Sep 17 00:00:00 2001 From: Tibo-lg Date: Thu, 30 Jun 2022 09:33:24 +0900 Subject: [PATCH] Enable spliting lightning channel --- fuzz/src/chanmon_consistency.rs | 5 + fuzz/src/router.rs | 5 + lightning-block-sync/src/poll.rs | 37 +-- lightning-invoice/src/lib.rs | 2 +- lightning-persister/src/lib.rs | 2 +- lightning/rustfmt.toml | 1 + lightning/src/chain/chainmonitor.rs | 38 ++- lightning/src/chain/channelmonitor.rs | 69 +++++- lightning/src/chain/mod.rs | 4 + lightning/src/chain/onchaintx.rs | 2 + lightning/src/ln/chan_utils.rs | 23 +- lightning/src/ln/channel.rs | 110 ++++++++- lightning/src/ln/channelmanager.rs | 248 +++++++++++++++++++- lightning/src/ln/monitor_tests.rs | 2 +- lightning/src/ln/msgs.rs | 5 + lightning/src/ln/peer_handler.rs | 4 +- lightning/src/routing/router.rs | 10 + lightning/src/sign/mod.rs | 7 + lightning/src/util/enforcing_trait_impls.rs | 4 + lightning/src/util/errors.rs | 9 + lightning/src/util/macro_logger.rs | 2 +- lightning/src/util/test_utils.rs | 4 + rustfmt.toml | 2 +- 23 files changed, 542 insertions(+), 53 deletions(-) create mode 100644 lightning/rustfmt.toml diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index e923ef882f2..f3546f21cf2 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -166,6 +166,10 @@ impl chain::Watch for TestChainMonitor { fn release_pending_monitor_events(&self) -> Vec<(OutPoint, Vec, Option)> { return self.chain_monitor.release_pending_monitor_events(); } + + fn update_channel_funding_txo(&self, old_funding_txo: OutPoint, new_funding_txo: OutPoint, channel_value_satoshis: u64) -> ChannelMonitorUpdateStatus { + todo!() + } } struct KeyProvider { @@ -306,6 +310,7 @@ fn check_api_err(api_err: APIError, sendable_bounds_violated: bool) { // We can (obviously) temp-fail a monitor update }, APIError::IncompatibleShutdownScript { .. } => panic!("Cannot send an incompatible shutdown script"), + APIError::ExternalError { .. } => panic!("We don't produce external errors in fuzz!"), } } #[inline] diff --git a/fuzz/src/router.rs b/fuzz/src/router.rs index 31732257c3f..454ed854641 100644 --- a/fuzz/src/router.rs +++ b/fuzz/src/router.rs @@ -271,6 +271,11 @@ pub fn do_test(data: &[u8], out: Out) { config: None, feerate_sat_per_1000_weight: None, channel_shutdown_state: Some(channelmanager::ChannelShutdownState::NotShuttingDown), + funding_redeemscript: None, + holder_funding_pubkey: get_pubkey!(), + counter_funding_pubkey: None, + original_funding_outpoint: None, + channel_keys_id: [0u8; 32], }); } Some(&first_hops_vec[..]) diff --git a/lightning-block-sync/src/poll.rs b/lightning-block-sync/src/poll.rs index e7171cf3656..4418a0d30f0 100644 --- a/lightning-block-sync/src/poll.rs +++ b/lightning-block-sync/src/poll.rs @@ -129,24 +129,25 @@ impl ValidatedBlockHeader { return Err(BlockSourceError::persistent("invalid block height")); } - let work = self.header.work(); - if self.chainwork != previous_header.chainwork + work { - return Err(BlockSourceError::persistent("invalid chainwork")); - } - - if let Network::Bitcoin = network { - if self.height % 2016 == 0 { - let target = self.header.target(); - let previous_target = previous_header.header.target(); - let min_target = previous_target >> 2; - let max_target = previous_target << 2; - if target > max_target || target < min_target { - return Err(BlockSourceError::persistent("invalid difficulty transition")) - } - } else if self.header.bits != previous_header.header.bits { - return Err(BlockSourceError::persistent("invalid difficulty")) - } - } + // let work = self.header.work(); + // if self.chainwork != previous_header.chainwork + work { + // return Err(BlockSourceError::persistent("invalid chainwork")); + // } + + // TODO(Tibo): This causes issues with Esplora, temporary fix. + // if let Network::Bitcoin = network { + // if self.height % 2016 == 0 { + // let target = self.header.target(); + // let previous_target = previous_header.header.target(); + // let min_target = previous_target >> 2; + // let max_target = previous_target << 2; + // if target > max_target || target < min_target { + // return Err(BlockSourceError::persistent("invalid difficulty transition")) + // } + // } else if self.header.bits != previous_header.header.bits { + // return Err(BlockSourceError::persistent("invalid difficulty")) + // } + // } Ok(()) } diff --git a/lightning-invoice/src/lib.rs b/lightning-invoice/src/lib.rs index d1b381130c0..e5784754d26 100644 --- a/lightning-invoice/src/lib.rs +++ b/lightning-invoice/src/lib.rs @@ -503,7 +503,7 @@ pub struct Bolt11InvoiceSignature(pub RecoverableSignature); impl PartialOrd for Bolt11InvoiceSignature { fn partial_cmp(&self, other: &Self) -> Option { - self.0.serialize_compact().1.partial_cmp(&other.0.serialize_compact().1) + Some(self.cmp(other)) } } diff --git a/lightning-persister/src/lib.rs b/lightning-persister/src/lib.rs index 670a7369d8b..47f0c86a64a 100644 --- a/lightning-persister/src/lib.rs +++ b/lightning-persister/src/lib.rs @@ -106,7 +106,7 @@ impl FilesystemPersister { let mut buffer = Cursor::new(&contents); match <(BlockHash, ChannelMonitor<::Signer>)>::read(&mut buffer, (&*entropy_source, &*signer_provider)) { Ok((blockhash, channel_monitor)) => { - if channel_monitor.get_funding_txo().0.txid != txid || channel_monitor.get_funding_txo().0.index != index { + if channel_monitor.get_original_funding_txo().0.txid != txid || channel_monitor.get_original_funding_txo().0.index != index { return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "ChannelMonitor was stored in the wrong file")); } diff --git a/lightning/rustfmt.toml b/lightning/rustfmt.toml new file mode 100644 index 00000000000..c7ad93bafe3 --- /dev/null +++ b/lightning/rustfmt.toml @@ -0,0 +1 @@ +disable_all_formatting = true diff --git a/lightning/src/chain/chainmonitor.rs b/lightning/src/chain/chainmonitor.rs index 2cc71a2ecc7..d03634039ed 100644 --- a/lightning/src/chain/chainmonitor.rs +++ b/lightning/src/chain/chainmonitor.rs @@ -374,7 +374,7 @@ where C::Target: chain::Filter, let monitor_states = self.monitors.read().unwrap(); for (_, monitor_state) in monitor_states.iter().filter(|(funding_outpoint, _)| { for chan in ignored_channels { - if chan.funding_txo.as_ref() == Some(funding_outpoint) { + if chan.funding_txo.as_ref() == Some(funding_outpoint) || chan.original_funding_outpoint.as_ref() == Some(funding_outpoint) { return false; } } @@ -555,6 +555,15 @@ where C::Target: chain::Filter, ) } } + + /// Retrieves the latest holder commitment transaction (and possibly HTLC transactions) for + /// the channel identified with the given `funding_txo`. Errors if no monitor is registered + /// for the given `funding_txo`. + pub fn get_latest_holder_commitment_txn(&self, funding_txo: &OutPoint) -> Result, ()> { + let monitors = self.monitors.read().unwrap(); + let monitor = monitors.get(funding_txo).ok_or(())?; + Ok(monitor.monitor.get_latest_holder_commitment_txn_internal(&self.logger)) + } } impl @@ -685,6 +694,31 @@ where C::Target: chain::Filter, persist_res } + fn update_channel_funding_txo(&self, old_funding_txo: OutPoint, new_funding_txo: OutPoint, channel_value_satoshis: u64) -> ChannelMonitorUpdateStatus { + let mut monitors = self.monitors.write().unwrap(); + let monitor_opt = monitors.get_mut(&old_funding_txo); + match monitor_opt { + None => { + log_error!(self.logger, "Failed to update channel monitor funding txo: no such monitor registered"); + + // We should never ever trigger this from within ChannelManager. Technically a + // user could use this object with some proxying in between which makes this + // possible, but in tests and fuzzing, this should be a panic. + #[cfg(any(test, fuzzing))] + panic!("ChannelManager generated a channel update for a channel that was not yet registered!"); + #[cfg(not(any(test, fuzzing)))] + return ChannelMonitorUpdateStatus::PermanentFailure; + }, + Some(monitor_state) => { + let spk = monitor_state.monitor.update_funding_info(new_funding_txo, channel_value_satoshis); + if let Some(filter) = &self.chain_source { + filter.register_output(WatchedOutput { block_hash: None, outpoint: new_funding_txo, script_pubkey: spk }); + } + return ChannelMonitorUpdateStatus::Completed; + } + } + } + /// Note that we persist the given `ChannelMonitor` update while holding the /// `ChainMonitor` monitors lock. fn update_channel(&self, funding_txo: OutPoint, update: &ChannelMonitorUpdate) -> ChannelMonitorUpdateStatus { @@ -766,7 +800,7 @@ where C::Target: chain::Filter, } let monitor_events = monitor_state.monitor.get_and_clear_pending_monitor_events(); if monitor_events.len() > 0 { - let monitor_outpoint = monitor_state.monitor.get_funding_txo().0; + let monitor_outpoint = monitor_state.monitor.get_original_funding_txo().0; let counterparty_node_id = monitor_state.monitor.get_counterparty_node_id(); pending_monitor_events.push((monitor_outpoint, monitor_events, counterparty_node_id)); } diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index f98c0bcac8c..61dec930dde 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -763,6 +763,7 @@ pub(crate) struct ChannelMonitorImpl { channel_keys_id: [u8; 32], holder_revocation_basepoint: PublicKey, funding_info: (OutPoint, Script), + original_funding_info: Option<(OutPoint, Script)>, current_counterparty_commitment_txid: Option, prev_counterparty_commitment_txid: Option, @@ -945,6 +946,13 @@ impl Writeable for ChannelMonitorImpl ChannelMonitor { channel_keys_id, holder_revocation_basepoint, funding_info, + original_funding_info: None, current_counterparty_commitment_txid: None, prev_counterparty_commitment_txid: None, @@ -1252,6 +1261,23 @@ impl ChannelMonitor { txid, htlc_outputs, commitment_number, their_per_commitment_point, logger) } + pub(crate) fn update_funding_info(&self, fund_outpoint: OutPoint, channel_value_satoshis: u64) -> Script { + let mut inner = self.inner.lock().unwrap(); + let script = inner.funding_info.1.clone(); + if let Some(original) = inner.original_funding_info.as_ref() { + if fund_outpoint == original.0 { + inner.original_funding_info = None; + } + } else { + inner.original_funding_info = Some((inner.funding_info.0.clone(), inner.funding_info.1.clone())); + } + inner.outputs_to_watch.insert(fund_outpoint.txid, vec![(fund_outpoint.index as u32, script.clone())]); + inner.funding_info = (fund_outpoint, script.clone()); + inner.channel_value_satoshis = channel_value_satoshis; + inner.onchain_tx_handler.signer.set_channel_value_satoshis(channel_value_satoshis); + script + } + #[cfg(test)] fn provide_latest_holder_commitment_tx( &self, holder_commitment_tx: HolderCommitmentTransaction, @@ -1308,6 +1334,11 @@ impl ChannelMonitor { self.inner.lock().unwrap().get_funding_txo().clone() } + /// + pub fn get_original_funding_txo(&self) -> (OutPoint, Script) { + self.inner.lock().unwrap().get_original_funding_txo().clone() + } + /// Gets a list of txids, with their output scripts (in the order they appear in the /// transaction), which we must learn about spends of via block_connected(). pub fn get_outputs_to_watch(&self) -> Vec<(Txid, Vec<(u32, Script)>)> { @@ -1416,6 +1447,11 @@ impl ChannelMonitor { self.inner.lock().unwrap().get_latest_holder_commitment_txn(logger) } + pub(crate) fn get_latest_holder_commitment_txn_internal(&self, logger: &L) -> Vec + where L::Target: Logger { + self.inner.lock().unwrap().get_latest_holder_commitment_txn_internal(logger) + } + /// Unsafe test-only version of get_latest_holder_commitment_txn used by our test framework /// to bypass HolderCommitmentTransaction state update lockdown after signature and generate /// revoked commitment transaction. @@ -2573,6 +2609,10 @@ impl ChannelMonitorImpl { &self.funding_info } + pub fn get_original_funding_txo(&self) -> &(OutPoint, Script) { + &self.original_funding_info.as_ref().unwrap_or(&self.funding_info) + } + pub fn get_outputs_to_watch(&self) -> &HashMap> { // If we've detected a counterparty commitment tx on chain, we must include it in the set // of outputs to watch for spends of, otherwise we're likely to lose user funds. Because @@ -3018,8 +3058,12 @@ impl ChannelMonitorImpl { } pub fn get_latest_holder_commitment_txn(&mut self, logger: &L) -> Vec where L::Target: Logger { - log_debug!(logger, "Getting signed latest holder commitment transaction!"); self.holder_tx_signed = true; + self.get_latest_holder_commitment_txn_internal(logger) + } + + pub(crate) fn get_latest_holder_commitment_txn_internal(&mut self, logger: &L) -> Vec where L::Target: Logger { + log_debug!(logger, "Getting signed latest holder commitment transaction!"); let commitment_tx = self.onchain_tx_handler.get_fully_signed_holder_tx(&self.funding_redeemscript); let txid = commitment_tx.txid(); let mut holder_transactions = vec![commitment_tx]; @@ -3186,7 +3230,14 @@ impl ChannelMonitorImpl { // (except for HTLC transactions for channels with anchor outputs), which is an easy // way to filter out any potential non-matching txn for lazy filters. let prevout = &tx.input[0].previous_output; - if prevout.txid == self.funding_info.0.txid && prevout.vout == self.funding_info.0.index as u32 { + let match_prevout = |outpoint: &OutPoint| { + prevout.txid == outpoint.txid && prevout.vout == outpoint.index as u32 + }; + let is_split = tx.output.len() == 2 && tx.output[0].script_pubkey == tx.output[1].script_pubkey; + let is_match = match_prevout(&self.funding_info.0) || + (self.original_funding_info.is_some() && match_prevout(&self.original_funding_info.as_ref().unwrap().0) && !is_split); + + if is_match { let mut balance_spendable_csv = None; log_info!(logger, "Channel {} closed by funding output spend in txid {}.", log_bytes!(self.funding_info.0.to_channel_id()), txid); @@ -3945,6 +3996,16 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP index: Readable::read(reader)?, }; let funding_info = (outpoint, Readable::read(reader)?); + let original_funding_info = match ::read(reader)? { + 0 => { + let outpoint = Readable::read(reader)?; + let script = Readable::read(reader)?; + Some((outpoint, script)) + }, + 1 => { None }, + _ => return Err(DecodeError::InvalidValue), + }; + let current_counterparty_commitment_txid = Readable::read(reader)?; let prev_counterparty_commitment_txid = Readable::read(reader)?; @@ -4141,6 +4202,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP channel_keys_id, holder_revocation_basepoint, funding_info, + original_funding_info, current_counterparty_commitment_txid, prev_counterparty_commitment_txid, @@ -4399,7 +4461,8 @@ mod tests { selected_contest_delay: 67, }), funding_outpoint: Some(funding_outpoint), - channel_type_features: ChannelTypeFeatures::only_static_remote_key() + channel_type_features: ChannelTypeFeatures::only_static_remote_key(), + original_funding_outpoint: None, }; // Prune with one old state and a holder commitment tx holding a few overlaps with the // old state. diff --git a/lightning/src/chain/mod.rs b/lightning/src/chain/mod.rs index 236b10a7b19..ed323877235 100644 --- a/lightning/src/chain/mod.rs +++ b/lightning/src/chain/mod.rs @@ -295,6 +295,10 @@ pub trait Watch { /// [`update_monitor`]: channelmonitor::ChannelMonitor::update_monitor fn update_channel(&self, funding_txo: OutPoint, update: &ChannelMonitorUpdate) -> ChannelMonitorUpdateStatus; + /// Update the outpoint funding the channel. To be used when the channel is split into two to + /// open a DLC channel with the same funding transaction. + fn update_channel_funding_txo(&self, old_funding_txo: OutPoint, new_funding_txo: OutPoint, channel_value_satoshis: u64) -> ChannelMonitorUpdateStatus; + /// Returns any monitor events since the last call. Subsequent calls must only return new /// events. /// diff --git a/lightning/src/chain/onchaintx.rs b/lightning/src/chain/onchaintx.rs index 6ac4973a744..dea949d4821 100644 --- a/lightning/src/chain/onchaintx.rs +++ b/lightning/src/chain/onchaintx.rs @@ -172,6 +172,7 @@ impl Writeable for Option>> { } /// The claim commonly referred to as the pre-signed second-stage HTLC transaction. +#[derive(PartialEq)] pub(crate) struct ExternalHTLCClaim { pub(crate) commitment_txid: Txid, pub(crate) per_commitment_number: u64, @@ -182,6 +183,7 @@ pub(crate) struct ExternalHTLCClaim { // Represents the different types of claims for which events are yielded externally to satisfy said // claims. +#[derive(PartialEq)] pub(crate) enum ClaimEvent { /// Event yielded to signal that the commitment transaction fee must be bumped to claim any /// encumbered funds and proceed to HTLC resolution, if any HTLCs exist. diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index 85490afaec1..6f15f86f65d 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -863,11 +863,22 @@ pub struct ChannelTransactionParameters { /// The late-bound counterparty channel transaction parameters. /// These parameters are populated at the point in the protocol where the counterparty provides them. pub counterparty_parameters: Option, - /// The late-bound funding outpoint + /// The late-bound funding outpoint. + /// + /// If it's a vanilla LN channel, this value corresponds to the actual funding outpoint that + /// goes on-chain when the channel is created. + /// + /// If instead we're dealing with a split channel, this value corresponds to the output of a + /// glue transaction which sits in between the funding transaction and the commitment + /// transaction. pub funding_outpoint: Option, /// This channel's type, as negotiated during channel open. For old objects where this field /// wasn't serialized, it will default to static_remote_key at deserialization. - pub channel_type_features: ChannelTypeFeatures + pub channel_type_features: ChannelTypeFeatures, + /// This value always corresponds to the actual funding outpoint. This is different to + /// [`ChannelTransactionParameters::funding_outpoint`], which varies depending on the type + /// of Lightning channel we have. + pub original_funding_outpoint: Option, } /// Late-bound per-channel counterparty data used to build transactions. @@ -926,6 +937,7 @@ impl Writeable for ChannelTransactionParameters { (8, self.funding_outpoint, option), (10, legacy_deserialization_prevention_marker, option), (11, self.channel_type_features, required), + (12, self.original_funding_outpoint, option), }); Ok(()) } @@ -940,6 +952,7 @@ impl Readable for ChannelTransactionParameters { let mut funding_outpoint = None; let mut _legacy_deserialization_prevention_marker: Option<()> = None; let mut channel_type_features = None; + let mut original_funding_outpoint = None; read_tlv_fields!(reader, { (0, holder_pubkeys, required), @@ -949,6 +962,7 @@ impl Readable for ChannelTransactionParameters { (8, funding_outpoint, option), (10, _legacy_deserialization_prevention_marker, option), (11, channel_type_features, option), + (12, original_funding_outpoint, option), }); let mut additional_features = ChannelTypeFeatures::empty(); @@ -961,7 +975,8 @@ impl Readable for ChannelTransactionParameters { is_outbound_from_holder: is_outbound_from_holder.0.unwrap(), counterparty_parameters, funding_outpoint, - channel_type_features: channel_type_features.unwrap_or(ChannelTypeFeatures::only_static_remote_key()) + channel_type_features: channel_type_features.unwrap_or(ChannelTypeFeatures::only_static_remote_key()), + original_funding_outpoint, }) } } @@ -1087,6 +1102,7 @@ impl HolderCommitmentTransaction { counterparty_parameters: Some(CounterpartyChannelTransactionParameters { pubkeys: channel_pubkeys.clone(), selected_contest_delay: 0 }), funding_outpoint: Some(chain::transaction::OutPoint { txid: Txid::all_zeros(), index: 0 }), channel_type_features: ChannelTypeFeatures::only_static_remote_key(), + original_funding_outpoint: None, }; let mut counterparty_htlc_sigs = Vec::new(); for _ in 0..htlcs.len() { @@ -1796,6 +1812,7 @@ mod tests { counterparty_parameters: Some(CounterpartyChannelTransactionParameters { pubkeys: counterparty_pubkeys.clone(), selected_contest_delay: 0 }), funding_outpoint: Some(chain::transaction::OutPoint { txid: Txid::all_zeros(), index: 0 }), channel_type_features: ChannelTypeFeatures::only_static_remote_key(), + original_funding_outpoint: None, }; let mut htlcs_with_aux: Vec<(_, ())> = Vec::new(); diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index e559ac33550..429dd3aa9d3 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -659,7 +659,7 @@ pub(super) struct ChannelContext { latest_monitor_update_id: u64, - holder_signer: Signer, + pub(crate) holder_signer: Signer, shutdown_scriptpubkey: Option, destination_script: Script, @@ -891,7 +891,7 @@ pub(super) struct ChannelContext { /// The unique identifier used to re-derive the private key material for the channel through /// [`SignerProvider::derive_channel_signer`]. - channel_keys_id: [u8; 32], + pub(crate) channel_keys_id: [u8; 32], /// If we can't release a [`ChannelMonitorUpdate`] until some external action completes, we /// store it here and only release it to the `ChannelManager` once it asks for it. @@ -1027,6 +1027,34 @@ impl ChannelContext { self.channel_transaction_parameters.funding_outpoint } + /// Returns the funding txo which is always the one that was confirmed on chain, even if the + /// channel is split. + pub fn get_original_funding_txo(&self) -> Option { + if self.channel_transaction_parameters.original_funding_outpoint.is_none() { + self.get_funding_txo() + } else { + self.channel_transaction_parameters.original_funding_outpoint + } + } + + /// Set the funding output and value of the channel, returning a `ChannelMonitorUpdate` + /// containing a commitment for the new funding output if requested. + fn set_funding_outpoint(&mut self, funding_outpoint: &OutPoint, channel_value_satoshis: u64, own_balance: u64) + { + self.channel_value_satoshis = channel_value_satoshis; + self.holder_signer.set_channel_value_satoshis(channel_value_satoshis); + self.value_to_self_msat = own_balance + self.pending_outbound_htlcs.iter().map(|x| x.amount_msat).sum::(); + + let original_funding_outpoint = self.channel_transaction_parameters.original_funding_outpoint.unwrap_or_else(|| self.channel_transaction_parameters.funding_outpoint.unwrap()); + self.channel_transaction_parameters.funding_outpoint = Some(funding_outpoint.clone()); + self.channel_transaction_parameters.original_funding_outpoint = if &original_funding_outpoint != funding_outpoint { + Some(original_funding_outpoint.clone()) + } else { + None + }; + } + + /// Returns the block hash in which our funding transaction was confirmed. pub fn get_funding_tx_confirmed_in(&self) -> Option { self.funding_tx_confirmed_in @@ -1971,7 +1999,7 @@ impl ChannelContext { _ => {} } } - let monitor_update = if let Some(funding_txo) = self.get_funding_txo() { + let monitor_update = if let Some(funding_txo) = self.get_original_funding_txo() { // If we haven't yet exchanged funding signatures (ie channel_state < FundingSent), // returning a channel monitor update here would imply a channel monitor update before // we even registered the channel monitor to begin with, which is invalid. @@ -3756,7 +3784,7 @@ impl Channel { Ok(()) } - fn get_last_revoke_and_ack(&self) -> msgs::RevokeAndACK { + pub(super) fn get_last_revoke_and_ack(&self) -> msgs::RevokeAndACK { let next_per_commitment_point = self.context.holder_signer.get_per_commitment_point(self.context.cur_holder_commitment_transaction_number, &self.context.secp_ctx); let per_commitment_secret = self.context.holder_signer.release_commitment_secret(self.context.cur_holder_commitment_transaction_number + 2); msgs::RevokeAndACK { @@ -4730,8 +4758,54 @@ impl Channel { return Ok((Some(channel_ready), announcement_sigs)); } } + + // If we have a vanilla LN channel, this checks if the transaction + // spends from the actual funding output. That could be either a + // commitment transaction or a mutual close transaction. + // + // If we have a split channel, this checks if the transaction spends + // from the glue output. That could only be a commitment + // transaction. + let is_funding_or_glue_txo = |prev_outpoint: &bitcoin::OutPoint| -> bool { + prev_outpoint == &funding_txo.into_bitcoin_outpoint() + }; + + // This check only runs if the check above returns `false`. We know + // that a vanilla LN channel can only be closed by spending from the + // original funding output, so in this check we are only considering + // split channels. + // + // The other ways in which a split channel could be closed are: + // + // - Through a mutual close of the _LN_ channel, which would spend + // directly from the original funding output. + // + // - Through the publication of a revoked commitment transaction + // spending from the original funding output! + // + // And that's exactly what we check here: whether the transaction + // spends from the original funding output and, if it does, whether + // the transaction is NOT the split transaction (the only other + // possible option). + // + // We do not announce the closing of the LN channel with the split + // transaction, because that is reserved to either mutual close or + // commitment transactions. LDK will only react to this announcement + // once, so we should not waste it on the split transaction, as this + // can lead to loss of funds. + let is_final_tx_spending_from_original_funding_txo = |prev_outpoint: &bitcoin::OutPoint, outputs: &[bitcoin::TxOut]| -> bool { + match self.context.get_original_funding_txo().map(|x| x.into_bitcoin_outpoint()) { + Some(original_funding_outpoint) => { + // Transaction spends from actual funding output. + prev_outpoint == &original_funding_outpoint && + // Transaction is _not_ a split transaction. + !(outputs.len() == 2 && outputs[0].script_pubkey == outputs[1].script_pubkey) + } + None => false, + } + }; for inp in tx.input.iter() { - if inp.previous_output == funding_txo.into_bitcoin_outpoint() { + if is_funding_or_glue_txo(&inp.previous_output) || is_final_tx_spending_from_original_funding_txo(&inp.previous_output, &tx.output) { log_info!(logger, "Detected channel-closing tx {} spending {}:{}, closing channel {}", tx.txid(), inp.previous_output.txid, inp.previous_output.vout, log_bytes!(self.context.channel_id())); return Err(ClosureReason::CommitmentTxConfirmed); } @@ -5093,6 +5167,7 @@ impl Channel { // construction but have not received `tx_signatures` we MUST set `next_funding_txid` to the // txid of that interactive transaction, else we MUST NOT set it. next_funding_txid: None, + sub_channel_state: None, } } @@ -5329,9 +5404,9 @@ impl Channel { signature = res.0; htlc_signatures = res.1; - log_trace!(logger, "Signed remote commitment tx {} (txid {}) with redeemscript {} -> {} in channel {}", + log_trace!(logger, "Signed remote commitment tx {} (txid {}) with redeemscript {} with value {} -> {} in channel {}", encode::serialize_hex(&commitment_stats.tx.trust().built_transaction().transaction), - &counterparty_commitment_txid, encode::serialize_hex(&self.context.get_funding_redeemscript()), + &counterparty_commitment_txid, encode::serialize_hex(&self.context.get_funding_redeemscript()), self.context.channel_value_satoshis, log_bytes!(signature.serialize_compact()[..]), log_bytes!(self.context.channel_id())); for (ref htlc_sig, ref htlc) in htlc_signatures.iter().zip(htlcs) { @@ -5506,6 +5581,21 @@ impl Channel { }) .chain(self.context.pending_outbound_htlcs.iter().map(|htlc| (&htlc.source, &htlc.payment_hash))) } + + /// Set the funding output and value of the channel, returning a `ChannelMonitorUpdate` + /// containing a commitment for the new funding output if requested. + pub fn set_funding_outpoint(&mut self, funding_outpoint: &OutPoint, channel_value_satoshis: u64, own_balance: u64, need_commitment: bool, logger: &L) -> Option + where + L::Target: Logger + { + self.context.set_funding_outpoint(funding_outpoint, channel_value_satoshis, own_balance); + + if need_commitment { + let monitor_update = self.build_commitment_no_status_check(logger); + self.monitor_updating_paused(false, true, false, Vec::new(), Vec::new(), Vec::new()); + self.push_ret_blockable_mon_update(monitor_update) + } else { None } + } } /// A not-yet-funded outbound (from holder) channel using V1 channel establishment. @@ -5675,7 +5765,8 @@ impl OutboundV1Channel { is_outbound_from_holder: true, counterparty_parameters: None, funding_outpoint: None, - channel_type_features: channel_type.clone() + channel_type_features: channel_type.clone(), + original_funding_outpoint: None, }, funding_transaction: None, @@ -6309,7 +6400,8 @@ impl InboundV1Channel { pubkeys: counterparty_pubkeys, }), funding_outpoint: None, - channel_type_features: channel_type.clone() + channel_type_features: channel_type.clone(), + original_funding_outpoint: None, }, funding_transaction: None, diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index b22c3716ccb..66d4b198fb0 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -21,6 +21,7 @@ use bitcoin::blockdata::block::BlockHeader; use bitcoin::blockdata::transaction::Transaction; use bitcoin::blockdata::constants::{genesis_block, ChainHash}; use bitcoin::network::constants::Network; +use bitcoin::Script; use bitcoin::hashes::Hash; use bitcoin::hashes::sha256::Hash as Sha256; @@ -79,6 +80,7 @@ use core::ops::Deref; // Re-export this for use in the public API. pub use crate::ln::outbound_payment::{PaymentSendFailure, Retry, RetryableSendFailure, RecipientOnionFields}; use crate::ln::script::ShutdownScript; +use super::msgs::{CommitmentSigned, RevokeAndACK}; // We hold various information about HTLC relay in the HTLC objects in Channel itself: // @@ -721,6 +723,17 @@ struct PendingInboundPayment { min_value_msat: Option, } +/// A structure holding a reference to a channel while under lock. +pub struct ChannelLock<'a, Signer: ChannelSigner> { + channel: &'a mut Channel, +} + +impl<'a, Signer: ChannelSigner> ChannelLock<'a, Signer> { + fn get_channel(&mut self) -> &mut Channel { + self.channel + } +} + /// [`SimpleArcChannelManager`] is useful when you need a [`ChannelManager`] with a static lifetime, e.g. /// when you're using `lightning-net-tokio` (since `tokio::spawn` requires parameters with static /// lifetimes). Other times you can afford a reference, which is more efficient, in which case @@ -1505,6 +1518,17 @@ pub struct ChannelDetails { /// /// This field is only `None` for `ChannelDetails` objects serialized prior to LDK 0.0.109. pub config: Option, + /// The late bound redeemscript used for the funding output. + pub funding_redeemscript: Option