From de786272e92cbf75c5395c8c5ceb2d939d24123d Mon Sep 17 00:00:00 2001 From: optout <13562139+optout21@users.noreply.github.com> Date: Wed, 11 Oct 2023 08:34:26 +0200 Subject: [PATCH 001/101] Splicing happy-path proto 3 --- README.md | 24 + lightning-net-tokio/src/lib.rs | 9 + lightning/src/chain/channelmonitor.rs | 9 +- lightning/src/events/mod.rs | 98 +++ lightning/src/ln/channel.rs | 932 ++++++++++++++++++++-- lightning/src/ln/channelmanager.rs | 647 ++++++++++++++- lightning/src/ln/functional_test_utils.rs | 62 +- lightning/src/ln/functional_tests.rs | 450 ++++++++++- lightning/src/ln/msgs.rs | 270 ++++++- lightning/src/ln/peer_handler.rs | 106 ++- lightning/src/ln/wire.rs | 97 +++ lightning/src/sign/mod.rs | 31 + lightning/src/util/test_channel_signer.rs | 11 + lightning/src/util/test_utils.rs | 25 + 14 files changed, 2703 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index a4ab59b5383..6782fc836d4 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,30 @@ and networking can be provided by LDK's [sample modules](#crates), or you may pr own custom implementations. More information is available in the [`About`](#about) section. +Splicing Prototype +------------------ + +'Happy Path' PoC for Splicing + +Objective, Restrictions: +- Splice-in supported (increase channel capacity) +- between two LDK instances +- No quiscence is used/checked +- Happy path only, no complex combinations, no error scenarios +- Prototype with minimal changes +- Semantics of some splicing messages is not fully according to specs +- TX negotiation messages are skipped, two simpler messages are used instead (splice_created, splice_signed) +- It is assumed that all extra inputs belong to the initiator (the full capacity increase is credited to the channel initiator) +- Only a single pending splicing is supported +- The channel ID is not changed during splicing (which is incorrect) + +Up-to-date with main branch as of v0.0.117 (Oct 4, commit 9de51f0; originally branched off v0.0.115). + +See also `ldk-sample` https://github.com/catenocrypt/ldk-sample/tree/splicing-hapa2 + +To test: `cargo test splic` + + Status ------ The project implements all of the [BOLT specifications](https://github.com/lightning/bolts), diff --git a/lightning-net-tokio/src/lib.rs b/lightning-net-tokio/src/lib.rs index 5527d85adf6..126f0a60bc9 100644 --- a/lightning-net-tokio/src/lib.rs +++ b/lightning-net-tokio/src/lib.rs @@ -605,6 +605,15 @@ mod tests { fn handle_channel_update(&self, _their_node_id: &PublicKey, _msg: &ChannelUpdate) {} fn handle_open_channel_v2(&self, _their_node_id: &PublicKey, _msg: &OpenChannelV2) {} fn handle_accept_channel_v2(&self, _their_node_id: &PublicKey, _msg: &AcceptChannelV2) {} + // #SPLICING + fn handle_splice(&self, _their_node_id: &PublicKey, _msg: &Splice) {} + fn handle_splice_ack(&self, _their_node_id: &PublicKey, _msg: &SpliceAck) {} + fn handle_splice_locked(&self, _their_node_id: &PublicKey, _msg: &SpliceLocked) {} + fn handle_splice_created(&self, _their_node_id: &PublicKey, _msg: &SpliceCreated) {} + fn handle_splice_comm_signed(&self, their_node_id: &PublicKey, msg: &SpliceCommSigned) {} + fn handle_splice_comm_ack(&self, their_node_id: &PublicKey, msg: &SpliceCommAck) {} + fn handle_splice_signed(&self, their_node_id: &PublicKey, msg: &SpliceSigned) {} + fn handle_splice_signed_ack(&self, _their_node_id: &PublicKey, _msg: &SpliceSignedAck) {} fn handle_tx_add_input(&self, _their_node_id: &PublicKey, _msg: &TxAddInput) {} fn handle_tx_add_output(&self, _their_node_id: &PublicKey, _msg: &TxAddOutput) {} fn handle_tx_remove_input(&self, _their_node_id: &PublicKey, _msg: &TxRemoveInput) {} diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index bd0c1548428..3ee9c49f624 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -1264,8 +1264,10 @@ impl ChannelMonitor { /// The monitor watches for it to be broadcasted and then uses the HTLC information (and /// possibly future revocation/preimage information) to claim outputs where possible. /// We cache also the mapping hash:commitment number to lighten pruning of old preimages by watchtowers. - #[cfg(test)] - fn provide_latest_counterparty_commitment_tx( + /// #SPLICING + /// TODO changed to prod & pub(crate), from test & private + // #[cfg(test)] + pub(crate) fn provide_latest_counterparty_commitment_tx( &self, txid: Txid, htlc_outputs: Vec<(HTLCOutputInCommitment, Option>)>, @@ -3777,6 +3779,8 @@ impl ChannelMonitorImpl { if *idx == input.previous_output.vout { #[cfg(test)] { + // TODO put it back, add witness to splice tx + /* // If the expected script is a known type, check that the witness // appears to be spending the correct type (ie that the match would // actually succeed in BIP 158/159-style filters). @@ -3792,6 +3796,7 @@ impl ChannelMonitorImpl { } else if _script_pubkey.is_v0_p2wpkh() { assert_eq!(&bitcoin::Address::p2wpkh(&bitcoin::PublicKey::from_slice(&input.witness.last().unwrap()).unwrap(), bitcoin::Network::Bitcoin).unwrap().script_pubkey(), _script_pubkey); } else { panic!(); } + */ } return true; } diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index c8c736c1f71..7f4ded21986 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -938,6 +938,23 @@ pub enum Event { /// /// [`ChannelHandshakeConfig::negotiate_anchors_zero_fee_htlc_tx`]: crate::util::config::ChannelHandshakeConfig::negotiate_anchors_zero_fee_htlc_tx BumpTransaction(BumpTransactionEvent), + /// #SPLICING + /// Indicates that the splice negotiation is done, `splice_ack` msg was received + /// TODO Change name, this should come after tx negotiation, maybe not needed in this form + SpliceAcked { + /// The channel_id of the channel where the splice was initiated + channel_id: ChannelId, + /// The counterparty's node_id + counterparty_node_id: PublicKey, + /// The current funding TX outpoint, which must be an input to the new splice TX + current_funding_outpoint: OutPoint, + /// The pre-splice channel value, in satoshis. + pre_channel_value_satoshis: u64, + /// The post-splice channel value, in satoshis. + post_channel_value_satoshis: u64, + /// The script which should be used in the transaction output (channel funding output). + output_script: Script, + } } impl Writeable for Event { @@ -1171,6 +1188,18 @@ impl Writeable for Event { (0, payment_id, required), }) }, + // #SPLICING + &Event::SpliceAcked { ref channel_id, ref counterparty_node_id, ref current_funding_outpoint, ref pre_channel_value_satoshis, ref post_channel_value_satoshis, ref output_script } => { + 33u8.write(writer)?; // TODO value + write_tlv_fields!(writer, { + (0, channel_id, required), + (2, counterparty_node_id, required), + (4, current_funding_outpoint, required), + (6, pre_channel_value_satoshis, required), + (8, post_channel_value_satoshis, required), + (10, output_script, required), + }); + }, // Note that, going forward, all new events must only write data inside of // `write_tlv_fields`. Versions 0.0.101+ will ignore odd-numbered events that write // data via `write_tlv_fields`. @@ -1641,6 +1670,75 @@ pub enum MessageSendEvent { /// The message which should be sent. msg: msgs::FundingSigned, }, + // /// Used to indicate that a commitment_signed message should be sent to the peer with the given node_id. + // SendCommitmentSigned { + // /// The node_id of the node which should receive this message + // node_id: PublicKey, + // /// The message which should be sent. + // msg: msgs::CommitmentSigned, + // }, + /// #SPLICING + /// Used to indicate that a splice message should be sent to the peer with the given node id. + SendSplice { + /// The node_id of the node which should receive this message + node_id: PublicKey, + /// The message which should be sent. + msg: msgs::Splice, + }, + /// #SPLICING + /// Used to indicate that a splice_ack message should be sent to the peer with the given node id. + SendSpliceAck { + /// The node_id of the node which should receive this message + node_id: PublicKey, + /// The message which should be sent. + msg: msgs::SpliceAck, + }, + /// Used to indicate that a splice_locked message should be sent to the peer with the given node id. + SendSpliceLocked { + /// The node_id of the node which should receive this message + node_id: PublicKey, + /// The message which should be sent. + msg: msgs::SpliceLocked, + }, + /// #SPLICING + /// Used to indicate that a splice_created message should be sent to the peer with the given node_id. + SendSpliceCreated { + /// The node_id of the node which should receive this message + node_id: PublicKey, + /// The message which should be sent. + msg: msgs::SpliceCreated, + }, + /// #SPLICING + /// Used to indicate that a splice_comm_signed message should be sent to the peer with the given node_id. + SendSpliceCommSigned { + /// The node_id of the node which should receive this message + node_id: PublicKey, + /// The message which should be sent. + msg: msgs::SpliceCommSigned, + }, + /// Used to indicate that a splice_comm_ack message should be sent to the peer with the given node_id. + SendSpliceCommAck { + /// The node_id of the node which should receive this message + node_id: PublicKey, + /// The message which should be sent. + msg: msgs::SpliceCommAck, + }, + /// #SPLICING + /// Used to indicate that a splice_signed_signed message should be sent to the peer with the given node_id. + SendSpliceSigned { + /// The node_id of the node which should receive this message + node_id: PublicKey, + /// The message which should be sent. + msg: msgs::SpliceSigned, + }, + /// #SPLICING + /// Used to indicate that a splice_signed_ack message should be sent to the peer with the given node_id. + SendSpliceSignedAck { + /// The node_id of the node which should receive this message + node_id: PublicKey, + /// The message which should be sent. + msg: msgs::SpliceSignedAck, + }, /// Used to indicate that a tx_add_input message should be sent to the peer with the given node_id. SendTxAddInput { /// The node_id of the node which should receive this message diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index a61a8de82de..bc8b2c590cf 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -676,6 +676,38 @@ impl UnfundedChannelContext { } } +/// Info about a pending splice +#[derive(Default, Clone)] +pub(super) struct PendingSpliceInfo { + /// The relative splice value (change in capacity value relative to current value) + pub relative_satoshis: i64, + /// The post splice value (current + relative) + pub post_channel_value: u64, + /// The pre splice value (a bit redundant) + pub pre_channel_value: u64, + /// Whether we are the initiator or not + pub is_outgoing: bool, + + prev_funding_input_index: Option, + initial_commitment_tx: Option, + cp_commitment_sig: Option, +} + +impl PendingSpliceInfo { + pub fn new(relative_satoshis: i64, pre_channel_value: u64, is_outgoing: bool) -> Self { + // TODO check for underflow + let post_channel_value = (pre_channel_value as i64 + relative_satoshis) as u64; + Self { + relative_satoshis, + post_channel_value, + pre_channel_value, + is_outgoing, + prev_funding_input_index: None, + initial_commitment_tx: None, + cp_commitment_sig: None, + } + } +} /// Contains everything about the channel including state, and various flags. pub(super) struct ChannelContext where SP::Target: SignerProvider { config: LegacyChannelConfig, @@ -711,6 +743,11 @@ pub(super) struct ChannelContext where SP::Target: SignerProvider { secp_ctx: Secp256k1, channel_value_satoshis: u64, + /// #SPLICING + /// Info about an in-progress, pending splice (if any) + /// TODO: later support >1 outstanding splice (a map?) + pub(crate) pending_splice: Option, + latest_monitor_update_id: u64, holder_signer: ChannelSignerType<::Signer>, @@ -2065,6 +2102,126 @@ impl ChannelContext where SP::Target: SignerProvider { self.update_time_counter += 1; (monitor_update, dropped_outbound_htlcs, unbroadcasted_batch_funding_txid) } + + /// #SPLICING Moved from InboundV1Channel to ChannelContext + fn funding_created_signature(&mut self, sig: &Signature, logger: &L) -> Result<(CommitmentTransaction, CommitmentTransaction, Signature), ChannelError> where L::Target: Logger { + let keys = self.build_holder_transaction_keys(self.cur_holder_commitment_transaction_number); + let initial_commitment_tx = self.build_commitment_transaction(self.cur_holder_commitment_transaction_number, &keys, true, false, logger).tx; + + let funding_script = self.get_funding_redeemscript(); + { + let trusted_tx = initial_commitment_tx.trust(); + let initial_commitment_bitcoin_tx = trusted_tx.built_transaction(); + let sighash = initial_commitment_bitcoin_tx.get_sighash_all(&funding_script, self.channel_value_satoshis); + // They sign the holder commitment transaction... + log_trace!(logger, "Checking funding_created tx signature {} by key {} against tx {} (sighash {}) with redeemscript {} for channel {}.", + log_bytes!(sig.serialize_compact()[..]), log_bytes!(self.counterparty_funding_pubkey().serialize()), + encode::serialize_hex(&initial_commitment_bitcoin_tx.transaction), log_bytes!(sighash[..]), + encode::serialize_hex(&funding_script), &self.channel_id()); + secp_check!(self.secp_ctx.verify_ecdsa(&sighash, &sig, self.counterparty_funding_pubkey()), "Invalid funding_created signature from peer".to_owned()); + } + + let counterparty_keys = self.build_remote_transaction_keys(); + let counterparty_initial_commitment_tx = self.build_commitment_transaction(self.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, false, logger).tx; + + match &self.holder_signer { + // TODO (arik): move match into calling method for Taproot + ChannelSignerType::Ecdsa(ecdsa) => { + let counterparty_signature = ecdsa.sign_counterparty_commitment(&counterparty_initial_commitment_tx, Vec::new(), &self.secp_ctx) + .map_err(|_| ChannelError::Close("Failed to get signatures for new commitment_signed".to_owned()))?.0; + + // We sign "counterparty" commitment transaction, allowing them to broadcast the tx if they wish. + Ok((counterparty_initial_commitment_tx, initial_commitment_tx, counterparty_signature)) + } + } + } + + /// #SPLICING + /// Update channel capacity to a new value + /// It is assumed that the increase is coming to A's side + fn update_channel_value(&mut self, new_value_sats: u64, belongs_to_local: bool, logger: &L) -> Result<(), ChannelError> where L::Target: Logger { + let old_value = self.channel_value_satoshis; + let old_to_self = self.value_to_self_msat; + if belongs_to_local { + let delta_msats = (new_value_sats as i64 - old_value as i64) * 1000; + // Check if not reducing by too much + if delta_msats < 0 && -delta_msats > self.value_to_self_msat as i64 { + return Err(ChannelError::Close("Cannot decrease channel value to requested amount, too low".to_string())); + } + self.value_to_self_msat = (self.value_to_self_msat as i64 + delta_msats) as u64; + } + + self.channel_value_satoshis = new_value_sats; + log_trace!(logger, "Changed channel value, channel_id {} value old {} new {} to_self old {} new {}", self.channel_id, old_value, self.channel_value_satoshis, old_to_self, self.value_to_self_msat); + Ok(()) + } + + /* + /// #SPLICING + fn get_pending_splice_or_default_mut(&mut self) -> &mut PendingSpliceInfo { + if self.pending_splice.is_none() { + self.pending_splice = Some(PendingSpliceInfo::default()); + } + self.pending_splice.as_mut().unwrap() + } + */ + + /// #SPLICING + /// Commit channel to the pending splice: once it completes, channel will be the new spliced, + /// from this point there is no going back to the old one, in case of error channel will error. + /// Update channel capacity, funding txid, etc. + pub fn commit_pending_splice(&mut self, splice_txo: OutPoint, logger: &L) -> Result<(), ChannelError> + where L::Target: Logger + { + if let Some(pending_splice) = &self.pending_splice { + self.channel_state = match pending_splice.is_outgoing { + true => ChannelState::OurInitSent, + false => ChannelState::TheirInitSent, + } as u32; + self.funding_transaction = None; // will be set later + self.channel_transaction_parameters.funding_outpoint = Some(splice_txo); + // Also mark that it is not confirmed + self.funding_tx_confirmation_height = 0; + self.funding_tx_confirmed_in = None; + + let old_value_debug = self.channel_value_satoshis; + let _ = self.update_channel_value(pending_splice.post_channel_value, pending_splice.is_outgoing, logger)?; + + // Note: pending is not cleared here yet, some parts of it are needed, cleared later + + log_trace!(logger, "Committed channel to the splice, channel_id {} capacity old {} new {} new funding txid {}", + self.channel_id, old_value_debug, self.channel_value_satoshis, splice_txo.txid); + Ok(()) + } else { + Err(ChannelError::Warn("Internal error: No pending splice found".to_owned())) + } + } + + pub fn clear_pending_splice(&mut self, logger: &L) + where L::Target: Logger + { + self.pending_splice = None; + log_trace!(logger, "Cleared pending splice, channel_id {}", self.channel_id); + } + + /// If an Err is returned, it is a ChannelError::Close (for get_funding_created) + /// #SPLICING: Moved to ChannelContext from OutboundV1Channel + fn get_funding_created_signature(&mut self, logger: &L) + -> Result<(CommitmentTransaction, CommitmentTransaction, Signature), ChannelError> where L::Target: Logger { + let keys = self.build_holder_transaction_keys(self.cur_holder_commitment_transaction_number); + let initial_commitment_tx = self.build_commitment_transaction(self.cur_holder_commitment_transaction_number, &keys, true, false, logger).tx; + + let counterparty_keys = self.build_remote_transaction_keys(); + let counterparty_initial_commitment_tx = self.build_commitment_transaction(self.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, false, logger).tx; + match &self.holder_signer { + // TODO (taproot|arik): move match into calling method for Taproot + ChannelSignerType::Ecdsa(ecdsa) => { + let signature = ecdsa.sign_counterparty_commitment(&counterparty_initial_commitment_tx, Vec::new(), &self.secp_ctx) + .map_err(|_| ChannelError::Close("Failed to get signatures for new commitment_signed".to_owned()))?.0; + Ok((initial_commitment_tx, counterparty_initial_commitment_tx, signature)) + } + } + } } // Internal utility functions for channels @@ -2964,7 +3121,7 @@ impl Channel where where L::Target: Logger { if (self.context.channel_state & (ChannelState::ChannelReady as u32)) != (ChannelState::ChannelReady as u32) { - return Err(ChannelError::Close("Got commitment signed message when channel was not in an operational state".to_owned())); + return Err(ChannelError::Close("Got commitment signed message when channel was not in an operational state".to_owned())); } if self.context.channel_state & (ChannelState::PeerDisconnected as u32) == ChannelState::PeerDisconnected as u32 { return Err(ChannelError::Close("Peer sent commitment_signed when we needed a channel_reestablish".to_owned())); @@ -4849,7 +5006,7 @@ impl Channel where // We generated a malleable funding transaction, implying we've // just exposed ourselves to funds loss to our counterparty. #[cfg(not(fuzzing))] - panic!("Client called ChannelManager::funding_transaction_generated with bogus transaction!"); + panic!("Client called ChannelManager::funding_transaction_generated with bogus transaction! witness len {} txid {} inputs {} txlen {}", input.witness.len(), tx.txid(), tx.input.len(), tx.encode().len()); } } } @@ -5016,6 +5173,139 @@ impl Channel where // Methods to get unprompted messages to send to the remote end (or where we already returned // something in the handler for the message that prompted this message): + /// #SPLICING STEP19 I + /// Handles a splice_signed_ack message from the remote end. + /// If this call is successful, broadcast the funding transaction (and not before!) + pub fn splice_signed_ack( + &mut self, msg: &msgs::SpliceSignedAck, best_block: BestBlock, signer_provider: &SP, logger: &L + ) + -> Result::Signer>, ChannelError> + where L::Target: Logger + { + if !self.context.is_outbound() { + return Err(ChannelError::Close("Received splice_signed_ack for an inbound channel?".to_owned())); + } + if self.context.channel_state & !(ChannelState::MonitorUpdateInProgress as u32) != ChannelState::FundingCreated as u32 { + return Err(ChannelError::Close("Received splice_signed_ack in strange state!".to_owned())); + } + // TODO check + /* + if self.commitment_secrets.get_min_seen_secret() != (1 << 48) || + self.cur_counterparty_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER || + self.cur_holder_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER { + panic!("Should not have advanced channel commitment tx numbers prior to splice_created"); + } + */ + + // Retrieve transaction properties from PendingSpliceInfo + let (prev_funding_input_index, pre_channel_value, initial_commitment_tx, cp_comm_sig) = + if let Some(pending_splice) = &self.context.pending_splice { + ( + pending_splice.prev_funding_input_index.unwrap_or_default(), + pending_splice.pre_channel_value, + if let Some(ict) = &pending_splice.initial_commitment_tx { ict } else { panic!("No splice in progress!"); }, // TODO proper error handling + if let Some(cpsig) = &pending_splice.cp_commitment_sig { cpsig } else { panic!("No splice in progress!"); }, // TODO proper error handling + ) + } else { + panic!("No splice in progress!"); // TODO proper error handling + }; + + // TODO: validate the signature from peer + + // Update funding TX with signatures (ours and the one from acceptor) + // #SPLICE-SIG + // the redeem script + let sig_order_ours_first = self.context.get_holder_pubkeys().funding_pubkey.serialize() < self.context.counterparty_funding_pubkey().serialize(); + log_info!(logger, "Pubkeys used for redeem script: {} {} {}", &self.context.get_holder_pubkeys().funding_pubkey, &self.context.counterparty_funding_pubkey(), sig_order_ours_first); + let redeem_script = self.context.get_funding_redeemscript(); + let mut funding_transaction_with_sigs = self.context.funding_transaction.as_ref().unwrap().clone(); + // our sig + let holder_signature = match &self.context.holder_signer { + ChannelSignerType::Ecdsa(ecdsa) => { + ecdsa.sign_splicing_funding_input(&funding_transaction_with_sigs, prev_funding_input_index, pre_channel_value, &redeem_script, &self.context.secp_ctx) + .map_err(|_| ChannelError::Close("Failed to sign the previous funding input in the new splicing funding tx".to_owned()))? + } + }; + let mut holder_sig = holder_signature.serialize_der().to_vec(); + holder_sig.push(EcdsaSighashType::All as u8); + // sig from counterparty + let mut cp_sig = msg.funding_signature.serialize_der().to_vec(); + cp_sig.push(EcdsaSighashType::All as u8); + // prepare witness stack + funding_transaction_with_sigs.input[prev_funding_input_index as usize].witness.clear(); + funding_transaction_with_sigs.input[prev_funding_input_index as usize].witness.push(Vec::new()); // First is the multisig dummy + if sig_order_ours_first { + funding_transaction_with_sigs.input[prev_funding_input_index as usize].witness.push(holder_sig); + funding_transaction_with_sigs.input[prev_funding_input_index as usize].witness.push(cp_sig); + } else { + funding_transaction_with_sigs.input[prev_funding_input_index as usize].witness.push(cp_sig); + funding_transaction_with_sigs.input[prev_funding_input_index as usize].witness.push(holder_sig); + } + funding_transaction_with_sigs.input[prev_funding_input_index as usize].witness.push(redeem_script.clone().into_bytes()); + + log_info!(logger, "Updated splice funding transaction with signatures, our and from acceptor; txid {} txlen {} sigs {} {} {} tx {}", + funding_transaction_with_sigs.txid(), funding_transaction_with_sigs.encode().len(), + holder_signature, msg.funding_signature, sig_order_ours_first, + encode::serialize_hex(&funding_transaction_with_sigs) + ); + self.context.funding_transaction = Some(funding_transaction_with_sigs.clone()); + + let counterparty_keys = self.context.build_remote_transaction_keys(); + let counterparty_initial_commitment_tx = self.context.build_commitment_transaction(self.context.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, false, logger).tx; + let counterparty_trusted_tx = counterparty_initial_commitment_tx.trust(); + let counterparty_initial_bitcoin_tx = counterparty_trusted_tx.built_transaction(); + + log_trace!(logger, "Initial counterparty tx for channel {} is: txid {} tx {}", + self.context.channel_id(), counterparty_initial_bitcoin_tx.txid, encode::serialize_hex(&counterparty_initial_bitcoin_tx.transaction)); + + let holder_commitment_tx = HolderCommitmentTransaction::new( + initial_commitment_tx.clone(), + cp_comm_sig.clone(), //msg.signature, + Vec::new(), + &self.context.get_holder_pubkeys().funding_pubkey, + self.context.counterparty_funding_pubkey() + ); + + match &self.context.holder_signer { + ChannelSignerType::Ecdsa(ecdsa) => { + match ecdsa.validate_holder_commitment(&holder_commitment_tx, Vec::new()) { + Err(_) => return Err(ChannelError::Close("Failed to validate our commitment".to_owned())), + Ok(_) => {} + } + } + } + + let funding_redeemscript = self.context.get_funding_redeemscript(); + let funding_txo = self.context.get_funding_txo().unwrap(); + let funding_txo_script = funding_redeemscript.to_v0_p2wsh(); + let obscure_factor = get_commitment_transaction_number_obscure_factor(&self.context.get_holder_pubkeys().payment_point, &self.context.get_counterparty_pubkeys().payment_point, self.context.is_outbound()); + let shutdown_script = self.context.shutdown_scriptpubkey.clone().map(|script| script.into_inner()); + let mut monitor_signer = signer_provider.derive_channel_signer(self.context.channel_value_satoshis, self.context.channel_keys_id); + monitor_signer.provide_channel_parameters(&self.context.channel_transaction_parameters); + let channel_monitor = ChannelMonitor::new(self.context.secp_ctx.clone(), monitor_signer, + shutdown_script, self.context.get_holder_selected_contest_delay(), + &self.context.destination_script, (funding_txo, funding_txo_script), + &self.context.channel_transaction_parameters, + funding_redeemscript.clone(), self.context.channel_value_satoshis, + obscure_factor, + holder_commitment_tx, best_block, self.context.counterparty_node_id); + + channel_monitor.provide_latest_counterparty_commitment_tx(counterparty_initial_bitcoin_tx.txid, Vec::new(), self.context.cur_counterparty_commitment_transaction_number, self.context.counterparty_cur_commitment_point.unwrap(), logger); + + assert_eq!(self.context.channel_state & (ChannelState::MonitorUpdateInProgress as u32), 0); // We have no had any monitor(s) yet to fail update! + + self.context.clear_pending_splice(logger); + self.context.channel_state = ChannelState::FundingSent as u32; + // self.context.cur_holder_commitment_transaction_number -= 1; + // self.context.cur_counterparty_commitment_transaction_number -= 1; + + log_info!(logger, "Received splice_signed_ack from peer for channel {}", self.context.channel_id()); + + let need_channel_ready = self.check_get_channel_ready(0).is_some(); + self.monitor_updating_paused(false, false, need_channel_ready, Vec::new(), Vec::new(), Vec::new()); + Ok(channel_monitor) + } + /// Gets an UnsignedChannelAnnouncement for this channel. The channel must be publicly /// announceable and available for use (have exchanged [`ChannelReady`] messages in both /// directions). Should be used for both broadcasted announcements and in response to an @@ -5254,6 +5544,66 @@ impl Channel where } } + /// #SPLICING STEP2 + /// Inspired by get_open_channel() + /// Get the splice message that can be sent during splice initiation + pub fn get_splice(&self, chain_hash: BlockHash, + // TODO; should this be a param, or stored in the channel? + relative_satoshis: i64, funding_feerate_perkw: u32, locktime: u32 + ) -> msgs::Splice { + if !self.context.is_outbound() { + panic!("Tried to initiate a splice on an inbound channel?"); + } + + // TODO impl, checks + /* + if self.channel_state != ChannelState::OurInitSent as u32 { + panic!("Cannot generate an open_channel after we've moved forward"); + } + + if self.cur_holder_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER { + panic!("Tried to send an open_channel for a channel that has already advanced"); + } + + let first_per_commitment_point = self.holder_signer.get_per_commitment_point(self.cur_holder_commitment_transaction_number, &self.secp_ctx); + */ + + let keys = self.context.get_holder_pubkeys(); + + // TODO how to handle channel capacity, orig is stored in Channel, has to be updated, in the interim there are two + msgs::Splice { + chain_hash, + channel_id: self.context.channel_id, + relative_satoshis, + funding_feerate_perkw, + locktime, + funding_pubkey: keys.funding_pubkey, + } + } + + /// #SPLICING STEP4 + /// Get the splice_ack message that can be sent in response to splice initiation + /// TODO move to ChannelContext + pub fn get_splice_ack(&self, chain_hash: BlockHash, + // TODO; should this be a param, or stored in the channel? + relative_satoshis: i64 + ) -> msgs::SpliceAck { + if self.context.is_outbound() { + panic!("Tried to accept a splice on an outound channel?"); + } + + // TODO checks + + let keys = self.context.get_holder_pubkeys(); + + // TODO how to handle channel capacity, orig is stored in Channel, has to be updated, in the interim there are two + msgs::SpliceAck { + chain_hash, + channel_id: self.context.channel_id, + relative_satoshis, + funding_pubkey: keys.funding_pubkey, + } + } // Send stuff to our remote peers: @@ -5681,6 +6031,522 @@ impl Channel where }) .chain(self.context.pending_outbound_htlcs.iter().map(|htlc| (&htlc.source, &htlc.payment_hash))) } + + /// #SPLICING STEP7 I + /// Updates channel state with knowledge of the splicing transaction's txid/index, and generates + /// a splice_created message for the remote peer. + /// Panics if called at some time other than immediately after splice acknowledgement, if called twice, + /// or if called on an inbound channel. + /// Note that channel_id is not changed. + /// Do NOT broadcast the splicing transaction until after a successful splicing_signed call! + /// Based on get_funding_created() + pub fn splice_generated(&mut self, splice_transaction: Transaction, splice_txo: OutPoint, splice_prev_funding_input_index: u16, splice_prev_funding_input_value: u64, logger: &L) + -> Result + where L::Target: Logger + { + if !self.context.is_outbound() { + panic!("Tried to create outbound splice_created message on an inbound channel!"); + } + // TODO: Check for quiscence + if self.context.channel_state != (ChannelState::ChannelReady as u32) { + panic!("Tried to get a splice_created messsage at a time when channel is not ready {}", self.context.channel_state); + } + /* TODO change check; how is this affected? + if self.commitment_secrets.get_min_seen_secret() != (1 << 48) || + self.cur_counterparty_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER || + self.cur_holder_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER { + panic!("Should not have advanced channel commitment tx numbers prior to funding_created"); + } + */ + + // Save transaction parameters for later + if self.context.pending_splice.is_none() { panic!("No splice in progress"); } // TODO proper error handling + self.context.pending_splice.as_mut().unwrap().prev_funding_input_index = Some(splice_prev_funding_input_index); + + // Commit to the new channel value (capacity). The increase belongs to them. + // TODO Should this be done only later? What if splice TX never confirms? + let _ = self.context.commit_pending_splice(splice_txo, logger)?; + + // Save funding transaction + self.context.funding_transaction = Some(splice_transaction.clone()); + log_info!(logger, "Stored splice funding tx, txid {} len {}", splice_transaction.txid(), splice_transaction.encode().len()); + + // Reset commitment counters + // TODO do we need to reset them? + self.context.cur_counterparty_commitment_transaction_number = INITIAL_COMMITMENT_NUMBER; + self.context.cur_holder_commitment_transaction_number = INITIAL_COMMITMENT_NUMBER; + + // Set tx parameters, for commitment signing + match &mut self.context.holder_signer { + ChannelSignerType::Ecdsa(ecdsa) => ecdsa.reprovide_channel_parameters(&self.context.channel_transaction_parameters, self.context.channel_value_satoshis), + } + + // TODO: Should this be set only later? + self.context.channel_state = ChannelState::FundingCreated as u32; + + Ok(msgs::SpliceCreated { + channel_id: self.context.channel_id, + splice_txid: splice_txo.txid, + funding_output_index: splice_txo.index, + splice_transaction, + splice_prev_funding_input_index, + splice_prev_funding_input_value, + /* + #[cfg(taproot)] + partial_signature_with_nonce: None, + #[cfg(taproot)] + next_local_nonce: None, + */ + }) + } + + /// #SPLICING STEP11 I + /// Generates a commitment_signed message for the remote peer. + /// Panics if called at some time other than immediately after splice acknowledgement, if called twice, + /// or if called on an inbound channel. + /// Note that channel_id is not changed. + /// Do NOT broadcast the splicing transaction until after a successful commitment_signed call! + /// Based on get_funding_created() + pub fn splice_tx_complete(&mut self, logger: &L) + -> Result + where L::Target: Logger + { + if !self.context.is_outbound() { + panic!("Tried to create outbound splice_comm_signed message on an inbound channel!"); + } + if self.context.channel_state != (ChannelState::FundingCreated as u32) { + panic!("Tried to get a splice_comm_signed messsage at a time when channel is not ready {}", self.context.channel_state); + } + /* TODO change check; how is this affected? + if self.commitment_secrets.get_min_seen_secret() != (1 << 48) || + self.cur_counterparty_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER || + self.cur_holder_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER { + panic!("Should not have advanced channel commitment tx numbers prior to funding_created"); + } + */ + + let (initial_commitment_tx, commitment_signature) = match self.context.get_funding_created_signature(logger) { + Err(e) => { + log_error!(logger, "Got bad signatures: {:?}!", e); + // TODO: restore it. Maybe it should not be overwritten + // self.channel_transaction_parameters.funding_outpoint = None; + return Err(e); + } + Ok((i, _, s)) => (i, s), + }; + + // Store initial commitment tx + if self.context.pending_splice.is_none() { panic!("No splice in progress"); } // TODO proper error handling + self.context.pending_splice.as_mut().unwrap().initial_commitment_tx = Some(initial_commitment_tx); + + Ok(msgs::SpliceCommSigned { + channel_id: self.context.channel_id, + signature: commitment_signature, + // #[cfg(taproot)] + // partial_signature_with_nonce: None, + }) + } + + /// #SPLICING STEP13 A + pub fn splice_comm_signed(&mut self, msg: &msgs::SpliceCommSigned, logger: &L) + -> Result + where L::Target: Logger + { + if self.context.is_outbound() { + panic!("Tried to create outbound splice_comm_ack message on an outbound channel!"); + } + if self.context.channel_state != (ChannelState::FundingCreated as u32) { + panic!("Tried to get a splice_comm_ack messsage at a time when channel is not ready {}", self.context.channel_state); + } + /* TODO change check; how is this affected? + if self.commitment_secrets.get_min_seen_secret() != (1 << 48) || + self.cur_counterparty_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER || + self.cur_holder_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER { + panic!("Should not have advanced channel commitment tx numbers prior to funding_created"); + } + */ + + let (initial_commitment_tx, commitment_signature) = match self.context.get_funding_created_signature(logger) { + Err(e) => { + log_error!(logger, "Got bad signatures: {:?}!", e); + // TODO: restore it. Maybe it should not be overwritten + // self.channel_transaction_parameters.funding_outpoint = None; + return Err(e); + } + Ok((i, _, s)) => (i, s), + }; + + // Store cp commitment sig into PendingSpliceInfo + if self.context.pending_splice.is_none() { panic!("No splice in progress!"); } // TODO proper error handling + self.context.pending_splice.as_mut().unwrap().cp_commitment_sig = Some(msg.signature.clone()); + // Store initial commitment tx + self.context.pending_splice.as_mut().unwrap().initial_commitment_tx = Some(initial_commitment_tx); + + Ok(msgs::SpliceCommAck{ + channel_id: self.context.channel_id, + signature: commitment_signature, + // #[cfg(taproot)] + // partial_signature_with_nonce: None, + }) + } + + /// #SPLICING STEP15 I + pub fn splice_comm_ack(&mut self, msg: &msgs::SpliceCommAck, logger: &L) + -> Result + where L::Target: Logger + { + if !self.context.is_outbound() { + panic!("Tried to create outbound splice_signed message on an inbound channel!"); + } + // TODO: Check for quiscence + if self.context.channel_state != (ChannelState::FundingCreated as u32) { + panic!("Tried to get a splice_created messsage at a time when channel is not ready {}", self.context.channel_state); + } + /* TODO change check; how is this affected? + if self.commitment_secrets.get_min_seen_secret() != (1 << 48) || + self.cur_counterparty_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER || + self.cur_holder_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER { + panic!("Should not have advanced channel commitment tx numbers prior to funding_created"); + } + */ + + // Store cp commitment sig into PendingSpliceInfo + if self.context.pending_splice.is_none() { panic!("No splice in progress!"); } // TODO proper error handling + self.context.pending_splice.as_mut().unwrap().cp_commitment_sig = Some(msg.signature.clone()); + + // Retrieve transaction properties from PendingSpliceInfo + let (prev_funding_input_index, pre_channel_value) = + if let Some(pending_splice) = &self.context.pending_splice { + ( + pending_splice.prev_funding_input_index.unwrap_or_default(), + pending_splice.pre_channel_value, + ) + } else { + panic!("No splice in progress!"); // TODO proper error handling + }; + + // Sign splice funding tx: add signature to the input that is the previous funding tx + // #SPLICE-SIG + log_info!(logger, "Pubkeys used for redeem script: {} {}", &self.context.get_holder_pubkeys().funding_pubkey, &self.context.counterparty_funding_pubkey()); + let redeem_script = make_funding_redeemscript(&self.context.get_holder_pubkeys().funding_pubkey, self.context.counterparty_funding_pubkey()); + + // Sign splice funding tx: create our signature on the funding tx + let funding_tx = &self.context.funding_transaction.as_ref().unwrap(); + // #SPLICE-SIG + let funding_signature = match &mut self.context.holder_signer { + ChannelSignerType::Ecdsa(ecdsa) => { + ecdsa.sign_splicing_funding_input(funding_tx, prev_funding_input_index, pre_channel_value, &redeem_script, &self.context.secp_ctx) + .map_err(|_| ChannelError::Close("Failed to sign the previous funding input in the new splicing funding tx".to_owned()))? + } + }; + log_info!(logger, "Created signature for funding tx input / acceptor, input idx {} txid {} value {} sig {:?}", prev_funding_input_index, funding_tx.txid(), self.context.channel_value_satoshis, funding_signature.serialize_der().to_hex()); + + Ok(msgs::SpliceSigned { + channel_id: self.context.channel_id, + funding_signature, + /* + #[cfg(taproot)] + partial_signature_with_nonce: None, + #[cfg(taproot)] + next_local_nonce: None, + */ + }) + } + + /// #SPLICING STEP17 A + pub fn splice_signed(&mut self, _msg: &msgs::SpliceSigned, best_block: BestBlock, signer_provider: &SP, logger: &L) + -> Result<(msgs::SpliceSignedAck, ChannelMonitor<::Signer>), ChannelError> + where L::Target: Logger + { + if self.context.is_outbound() { + panic!("Tried to create outbound splice_signed_ack message on an outbound channel!"); + } + // TODO: Check for quiscence + if self.context.channel_state != (ChannelState::FundingCreated as u32) { + panic!("Tried to get a splice_created messsage at a time when channel is not ready {}", self.context.channel_state); + } + /* TODO change check; how is this affected? + if self.commitment_secrets.get_min_seen_secret() != (1 << 48) || + self.cur_counterparty_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER || + self.cur_holder_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER { + panic!("Should not have advanced channel commitment tx numbers prior to funding_created"); + } + */ + + // Retrieve transaction properties from PendingSpliceInfo + let (prev_funding_input_index, pre_channel_value, initial_commitment_tx, cp_comm_sig) = + if let Some(pending_splice) = &self.context.pending_splice { + ( + pending_splice.prev_funding_input_index.unwrap_or_default(), + pending_splice.pre_channel_value, + if let Some(ict) = &pending_splice.initial_commitment_tx { ict } else { panic!("No splice in progress!"); }, // TODO proper error handling + if let Some(cps) = &pending_splice.cp_commitment_sig { cps } else { panic!("No splice in progress!"); }, // TODO proper error handling + ) + } else { + panic!("No splice in progress!"); // TODO proper error handling + }; + + // TODO: validate the signature from peer + + // Sign splice funding tx: add signature to the input that is the previous funding tx + // #SPLICE-SIG + log_info!(logger, "Pubkeys used for redeem script: {} {}", &self.context.get_holder_pubkeys().funding_pubkey, &self.context.counterparty_funding_pubkey()); + let redeem_script = make_funding_redeemscript(&self.context.get_holder_pubkeys().funding_pubkey, self.context.counterparty_funding_pubkey()); + + // Sign splice funding tx: create our signature on the funding tx + let funding_tx = &self.context.funding_transaction.as_ref().unwrap(); + // #SPLICE-SIG + let funding_signature = match &self.context.holder_signer { + ChannelSignerType::Ecdsa(ecdsa) => { + ecdsa.sign_splicing_funding_input(&funding_tx, prev_funding_input_index, pre_channel_value, &redeem_script, &self.context.secp_ctx) + .map_err(|_| ChannelError::Close("Failed to sign the previous funding input in the new splicing funding tx".to_owned()))? + } + }; + log_info!(logger, "Created signature for funding tx input / acceptor, input idx {} txid {} value {} sig {:?}", prev_funding_input_index, funding_tx.txid(), self.context.channel_value_satoshis, funding_signature.serialize_der().to_hex()); + + let counterparty_keys = self.context.build_remote_transaction_keys(); + let counterparty_initial_commitment_tx = self.context.build_commitment_transaction(self.context.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, false, logger).tx; + let counterparty_trusted_tx = counterparty_initial_commitment_tx.trust(); + let counterparty_initial_bitcoin_tx = counterparty_trusted_tx.built_transaction(); + + log_trace!(logger, "Initial counterparty tx for channel {} is: txid {} tx {}", + self.context.channel_id(), counterparty_initial_bitcoin_tx.txid, encode::serialize_hex(&counterparty_initial_bitcoin_tx.transaction)); + + let holder_commitment_tx = HolderCommitmentTransaction::new( + initial_commitment_tx.clone(), + cp_comm_sig.clone(), + Vec::new(), + &self.context.get_holder_pubkeys().funding_pubkey, + self.context.counterparty_funding_pubkey() + ); + + match &self.context.holder_signer { + ChannelSignerType::Ecdsa(ecdsa) => { + match ecdsa.validate_holder_commitment(&holder_commitment_tx, Vec::new()) { + Err(_) => return Err(ChannelError::Close("Failed to validate our commitment".to_owned())), + Ok(_) => {} + } + } + } + + let funding_redeemscript = self.context.get_funding_redeemscript(); + let funding_txo = self.context.get_funding_txo().unwrap(); + let funding_txo_script = funding_redeemscript.to_v0_p2wsh(); + let obscure_factor = get_commitment_transaction_number_obscure_factor(&self.context.get_holder_pubkeys().payment_point, &self.context.get_counterparty_pubkeys().payment_point, self.context.is_outbound()); + let shutdown_script = self.context.shutdown_scriptpubkey.clone().map(|script| script.into_inner()); + let mut monitor_signer = signer_provider.derive_channel_signer(self.context.channel_value_satoshis, self.context.channel_keys_id); + monitor_signer.provide_channel_parameters(&self.context.channel_transaction_parameters); + let channel_monitor = ChannelMonitor::new(self.context.secp_ctx.clone(), monitor_signer, + shutdown_script, self.context.get_holder_selected_contest_delay(), + &self.context.destination_script, (funding_txo, funding_txo_script), + &self.context.channel_transaction_parameters, + funding_redeemscript.clone(), self.context.channel_value_satoshis, + obscure_factor, + holder_commitment_tx, best_block, self.context.counterparty_node_id); + + channel_monitor.provide_latest_counterparty_commitment_tx(counterparty_initial_bitcoin_tx.txid, Vec::new(), self.context.cur_counterparty_commitment_transaction_number, self.context.counterparty_cur_commitment_point.unwrap(), logger); + + assert_eq!(self.context.channel_state & (ChannelState::MonitorUpdateInProgress as u32), 0); // We have no had any monitor(s) yet to fail update! + + self.context.clear_pending_splice(logger); + self.context.channel_state = ChannelState::FundingSent as u32; + // self.context.cur_holder_commitment_transaction_number -= 1; + // self.context.cur_counterparty_commitment_transaction_number -= 1; + + let need_channel_ready = self.check_get_channel_ready(0).is_some(); + self.monitor_updating_paused(false, false, need_channel_ready, Vec::new(), Vec::new(), Vec::new()); + + Ok((msgs::SpliceSignedAck { + channel_id: self.context.channel_id, + funding_signature, + /* + #[cfg(taproot)] + partial_signature_with_nonce: None, + #[cfg(taproot)] + next_local_nonce: None, + */ + }, channel_monitor)) + } + + /// #SPLICING STEP9 A + pub fn splice_created(&mut self, msg: &msgs::SpliceCreated, logger: &L) + -> Result + where L::Target: Logger + { + if self.context.is_outbound() { + return Err(ChannelError::Close("Received splice_created for an outbound channel?".to_owned())); + } + // TODO checks taken out + /* + if self.channel_state != (ChannelState::OurInitSent as u32 | ChannelState::TheirInitSent as u32) { + // BOLT 2 says that if we disconnect before we send funding_signed we SHOULD NOT + // remember the channel, so it's safe to just send an error_message here and drop the + // channel. + return Err(ChannelError::Close("Received splice_created after we got the channel!".to_owned())); + } + if self.inbound_awaiting_accept { + return Err(ChannelError::Close("SpliceCreated message received before the channel was accepted".to_owned())); + } + if self.commitment_secrets.get_min_seen_secret() != (1 << 48) || + self.cur_counterparty_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER || + self.cur_holder_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER { + panic!("Should not have advanced channel commitment tx numbers prior to funding_created"); + } + */ + + let splice_txo = OutPoint { txid: msg.splice_txid, index: msg.funding_output_index }; + // Save transaction parameters for later + if self.context.pending_splice.is_none() { panic!("No pending splice"); } // TODO proper error handling + self.context.pending_splice.as_mut().unwrap().prev_funding_input_index = Some(msg.splice_prev_funding_input_index); + + // Commit to the new channel value (capacity). The increase belongs to them. + // TODO Should this be done only later? What if splice TX never confirms? + let _ = self.context.commit_pending_splice(splice_txo, logger)?; + + self.context.funding_transaction = Some(msg.splice_transaction.clone()); + log_info!(logger, "Stored splice funding tx, txid {} len {}", msg.splice_transaction.txid(), msg.splice_transaction.encode().len()); + + // Reset commitment counters + // TODO do we need to reset them? + self.context.cur_counterparty_commitment_transaction_number = INITIAL_COMMITMENT_NUMBER; + self.context.cur_holder_commitment_transaction_number = INITIAL_COMMITMENT_NUMBER; + + // Set tx parameters, for commitment signing + match &mut self.context.holder_signer { + ChannelSignerType::Ecdsa(ecdsa) => ecdsa.reprovide_channel_parameters(&self.context.channel_transaction_parameters, self.context.channel_value_satoshis), + } + + // TODO: Should this be set only later? + self.context.channel_state = ChannelState::FundingCreated as u32; + + Ok(msgs::TxComplete { + channel_id: self.context.channel_id(), + }) + } + + /* + /// #SPLICING + /// Based on funding_created() + /// Note: there is no need to return Channel (as it is not re-created, channel ID not changed) + pub fn splice_created( + &mut self, msg: &msgs::SpliceCreated, best_block: BestBlock, signer_provider: &SP, logger: &L + ) -> Result<(msgs::SpliceSignedAck, ChannelMonitor<::Signer>), ChannelError> + where + L::Target: Logger + { + if self.context.is_outbound() { + return Err(ChannelError::Close("Received splice_created for an outbound channel?".to_owned())); + } + // TODO checks taken out + /* + if self.channel_state != (ChannelState::OurInitSent as u32 | ChannelState::TheirInitSent as u32) { + // BOLT 2 says that if we disconnect before we send funding_signed we SHOULD NOT + // remember the channel, so it's safe to just send an error_message here and drop the + // channel. + return Err(ChannelError::Close("Received splice_created after we got the channel!".to_owned())); + } + if self.inbound_awaiting_accept { + return Err(ChannelError::Close("SpliceCreated message received before the channel was accepted".to_owned())); + } + if self.commitment_secrets.get_min_seen_secret() != (1 << 48) || + self.cur_counterparty_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER || + self.cur_holder_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER { + panic!("Should not have advanced channel commitment tx numbers prior to funding_created"); + } + */ + + // Sign splice funding tx: create our signature on the funding tx + // #SPLICE-SIG + let funding_signature = match &mut self.context.holder_signer { + ChannelSignerType::Ecdsa(ecdsa) => { + ecdsa.sign_splicing_funding_input(&msg.splice_transaction, msg.splice_prev_funding_input_index, msg.splice_prev_funding_input_value, &msg.splice_tx_redeem_script, &self.context.secp_ctx) + .map_err(|_| ChannelError::Close("Failed to sign the previous funding input in the new splicing funding tx".to_owned()))? + } + }; + log_info!(logger, "Created signature for funding tx input / acceptor, input idx {} txid {} value {} sig {:?}", msg.splice_prev_funding_input_index, msg.splice_transaction.txid(), self.context.channel_value_satoshis, funding_signature.serialize_der().to_hex()); + + // Commit to the new channel value (capacity). The increase belongs to them. + let splice_txo = OutPoint { txid: msg.splice_txid, index: msg.funding_output_index }; + let _ = self.context.commit_pending_splice(splice_txo, logger)?; + + // This is an externally observable change before we finish all our checks. In particular + // funding_created_signature may fail. + self.context.holder_signer.as_mut().reprovide_channel_parameters(&self.context.channel_transaction_parameters, self.context.channel_value_satoshis); + let txoutp = &self.context.channel_transaction_parameters.funding_outpoint.unwrap(); + log_trace!(logger, "fund_tx_outpoint {} {}", log_bytes!(txoutp.txid), txoutp.index); + + let (counterparty_initial_commitment_tx, initial_commitment_tx, signature) = match self.context.funding_created_signature(&msg.signature, logger) { + Ok(res) => res, + Err(ChannelError::Close(e)) => { + self.context.channel_transaction_parameters.funding_outpoint = None; + return Err(ChannelError::Close(e)); + }, + Err(e) => { + // The only error we know how to handle is ChannelError::Close, so we fall over here + // to make sure we don't continue with an inconsistent state. + panic!("unexpected error type from splice_created_signature {:?}", e); + } + }; + + let holder_commitment_tx = HolderCommitmentTransaction::new( + initial_commitment_tx, + msg.signature, + Vec::new(), + &self.context.get_holder_pubkeys().funding_pubkey, + self.context.counterparty_funding_pubkey() + ); + + if let Err(_) = self.context.holder_signer.as_ref().validate_holder_commitment(&holder_commitment_tx, Vec::new()) { + return Err(ChannelError::Close("Failed to validate our commitment".to_owned())); + } + + // Now that we're past error-generating stuff, update our local state: + + let funding_redeemscript = self.context.get_funding_redeemscript(); + let funding_txo_script = funding_redeemscript.to_v0_p2wsh(); + let obscure_factor = get_commitment_transaction_number_obscure_factor(&self.context.get_holder_pubkeys().payment_point, &self.context.get_counterparty_pubkeys().payment_point, self.context.is_outbound()); + let shutdown_script = self.context.shutdown_scriptpubkey.clone().map(|script| script.into_inner()); + let mut monitor_signer = signer_provider.derive_channel_signer(self.context.channel_value_satoshis, self.context.channel_keys_id); + monitor_signer.provide_channel_parameters(&self.context.channel_transaction_parameters); + let channel_monitor = ChannelMonitor::new(self.context.secp_ctx.clone(), monitor_signer, + shutdown_script, self.context.get_holder_selected_contest_delay(), + &self.context.destination_script, (splice_txo, funding_txo_script.clone()), + &self.context.channel_transaction_parameters, + funding_redeemscript.clone(), self.context.channel_value_satoshis, + obscure_factor, + holder_commitment_tx, best_block, self.context.counterparty_node_id); + + // TODO: Is this initial or latest commitment? + channel_monitor.provide_latest_counterparty_commitment_tx( + counterparty_initial_commitment_tx.trust().txid(), Vec::new(), + self.context.cur_counterparty_commitment_transaction_number, + self.context.counterparty_cur_commitment_point.unwrap(), logger); + + self.context.channel_state = ChannelState::FundingSent as u32; + // Note: channel_id is not changed + // self.channel_id = splice_txo.to_channel_id(); + // TODO: Check if we reset transation number counters or not + // self.cur_counterparty_commitment_transaction_number -= 1; + // self.cur_holder_commitment_transaction_number -= 1; + + log_info!(logger, "Generated funding_signed for peer for channel {}", self.context.channel_id()); + + let need_channel_ready = self.check_get_channel_ready(0).is_some(); + self.monitor_updating_paused(false, false, need_channel_ready, Vec::new(), Vec::new(), Vec::new()); + + Ok((msgs::SpliceSignedAck { + channel_id: self.context.channel_id(), + funding_signature, + splice_prev_funding_input_index: msg.splice_prev_funding_input_index, + splice_prev_funding_input_value: msg.splice_prev_funding_input_value, + signature, + /* + #[cfg(taproot)] + partial_signature_with_nonce: None, + */ + }, channel_monitor)) + } + */ } /// A not-yet-funded outbound (from holder) channel using V1 channel establishment. @@ -5782,6 +6648,7 @@ impl OutboundV1Channel where SP::Target: SignerProvider { announcement_sigs_state: AnnouncementSigsState::NotSent, secp_ctx, channel_value_satoshis, + pending_splice: None, latest_monitor_update_id: 0, @@ -5892,16 +6759,10 @@ impl OutboundV1Channel where SP::Target: SignerProvider { } /// If an Err is returned, it is a ChannelError::Close (for get_funding_created) - fn get_funding_created_signature(&mut self, logger: &L) -> Result where L::Target: Logger { - let counterparty_keys = self.context.build_remote_transaction_keys(); - let counterparty_initial_commitment_tx = self.context.build_commitment_transaction(self.context.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, false, logger).tx; - match &self.context.holder_signer { - // TODO (taproot|arik): move match into calling method for Taproot - ChannelSignerType::Ecdsa(ecdsa) => { - Ok(ecdsa.sign_counterparty_commitment(&counterparty_initial_commitment_tx, Vec::new(), &self.context.secp_ctx) - .map_err(|_| ChannelError::Close("Failed to get signatures for new commitment_signed".to_owned()))?.0) - } - } + /// #SPLICING: Impl. moved to ChannelContext + fn get_funding_created_signature(&mut self, logger: &L) + -> Result<(CommitmentTransaction, CommitmentTransaction, Signature), ChannelError> where L::Target: Logger { + self.context.get_funding_created_signature(logger) } /// Updates channel state with knowledge of the funding transaction's txid/index, and generates @@ -5929,7 +6790,7 @@ impl OutboundV1Channel where SP::Target: SignerProvider { self.context.holder_signer.as_mut().provide_channel_parameters(&self.context.channel_transaction_parameters); let signature = match self.get_funding_created_signature(logger) { - Ok(res) => res, + Ok((_, _, s)) => s, Err(e) => { log_error!(logger, "Got bad signatures: {:?}!", e); self.context.channel_transaction_parameters.funding_outpoint = None; @@ -6539,6 +7400,8 @@ impl InboundV1Channel where SP::Target: SignerProvider { channel_keys_id, blocked_monitor_updates: Vec::new(), + + pending_splice: None, }, unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 } }; @@ -6607,42 +7470,8 @@ impl InboundV1Channel where SP::Target: SignerProvider { self.generate_accept_channel_message() } - fn funding_created_signature(&mut self, sig: &Signature, logger: &L) -> Result<(CommitmentTransaction, CommitmentTransaction, Signature), ChannelError> where L::Target: Logger { - let funding_script = self.context.get_funding_redeemscript(); - - let keys = self.context.build_holder_transaction_keys(self.context.cur_holder_commitment_transaction_number); - let initial_commitment_tx = self.context.build_commitment_transaction(self.context.cur_holder_commitment_transaction_number, &keys, true, false, logger).tx; - { - let trusted_tx = initial_commitment_tx.trust(); - let initial_commitment_bitcoin_tx = trusted_tx.built_transaction(); - let sighash = initial_commitment_bitcoin_tx.get_sighash_all(&funding_script, self.context.channel_value_satoshis); - // They sign the holder commitment transaction... - log_trace!(logger, "Checking funding_created tx signature {} by key {} against tx {} (sighash {}) with redeemscript {} for channel {}.", - log_bytes!(sig.serialize_compact()[..]), log_bytes!(self.context.counterparty_funding_pubkey().serialize()), - encode::serialize_hex(&initial_commitment_bitcoin_tx.transaction), log_bytes!(sighash[..]), - encode::serialize_hex(&funding_script), &self.context.channel_id()); - secp_check!(self.context.secp_ctx.verify_ecdsa(&sighash, &sig, self.context.counterparty_funding_pubkey()), "Invalid funding_created signature from peer".to_owned()); - } - - let counterparty_keys = self.context.build_remote_transaction_keys(); - let counterparty_initial_commitment_tx = self.context.build_commitment_transaction(self.context.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, false, logger).tx; - - let counterparty_trusted_tx = counterparty_initial_commitment_tx.trust(); - let counterparty_initial_bitcoin_tx = counterparty_trusted_tx.built_transaction(); - log_trace!(logger, "Initial counterparty tx for channel {} is: txid {} tx {}", - &self.context.channel_id(), counterparty_initial_bitcoin_tx.txid, encode::serialize_hex(&counterparty_initial_bitcoin_tx.transaction)); - - match &self.context.holder_signer { - // TODO (arik): move match into calling method for Taproot - ChannelSignerType::Ecdsa(ecdsa) => { - let counterparty_signature = ecdsa.sign_counterparty_commitment(&counterparty_initial_commitment_tx, Vec::new(), &self.context.secp_ctx) - .map_err(|_| ChannelError::Close("Failed to get signatures for new commitment_signed".to_owned()))?.0; - - // We sign "counterparty" commitment transaction, allowing them to broadcast the tx if they wish. - Ok((counterparty_initial_commitment_tx, initial_commitment_tx, counterparty_signature)) - } - } - } + /// #SPLICE moved to ChannelContext from InboundV1Channel + //fn funding_created_signature(&mut self, sig: &Signature, logger: &L) -> Result<(CommitmentTransaction, CommitmentTransaction, Signature), ChannelError> where L::Target: Logger { pub fn funding_created( mut self, msg: &msgs::FundingCreated, best_block: BestBlock, signer_provider: &SP, logger: &L @@ -6671,7 +7500,7 @@ impl InboundV1Channel where SP::Target: SignerProvider { // funding_created_signature may fail. self.context.holder_signer.as_mut().provide_channel_parameters(&self.context.channel_transaction_parameters); - let (counterparty_initial_commitment_tx, initial_commitment_tx, signature) = match self.funding_created_signature(&msg.signature, logger) { + let (counterparty_initial_commitment_tx, initial_commitment_tx, signature) = match self.context.funding_created_signature(&msg.signature, logger) { Ok(res) => res, Err(ChannelError::Close(e)) => { self.context.channel_transaction_parameters.funding_outpoint = None; @@ -7625,6 +8454,9 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch channel_keys_id, blocked_monitor_updates: blocked_monitor_updates.unwrap(), + + // pending_monitor_updates: Vec::new(), + pending_splice: None, } }) } diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 62c6741fbdf..c093f3ef871 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -40,7 +40,7 @@ use crate::events::{Event, EventHandler, EventsProvider, MessageSendEvent, Messa // Since this struct is returned in `list_channels` methods, expose it here in case users want to // construct one themselves. use crate::ln::{inbound_payment, ChannelId, PaymentHash, PaymentPreimage, PaymentSecret}; -use crate::ln::channel::{Channel, ChannelPhase, ChannelContext, ChannelError, ChannelUpdateStatus, ShutdownResult, UnfundedChannelContext, UpdateFulfillCommitFetch, OutboundV1Channel, InboundV1Channel}; +use crate::ln::channel::{Channel, ChannelPhase, ChannelContext, ChannelError, ChannelUpdateStatus, ShutdownResult, UnfundedChannelContext, UpdateFulfillCommitFetch, OutboundV1Channel, InboundV1Channel, PendingSpliceInfo}; use crate::ln::features::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures}; #[cfg(any(feature = "_test_utils", test))] use crate::ln::features::Bolt11InvoiceFeatures; @@ -2408,6 +2408,73 @@ where Ok(temporary_channel_id) } + /// #SPLICING STEP1 + /// Inspired by create_channel() and close_channel() + /// Initiate a splice, to change the channel capacity + /// TODO funding_feerate_perkw + /// TODO locktime + /// + /// Doc on message flow: + /// splice_channel(): + /// --- splice ------------------> signals intent for splice, with change amount + /// <-------------- splice_ack --- accepts proposed splice + /// splice_funding_generated(): + /// --- splice_created ----------> sends new funding transaction parameters. + /// In future, this should be a set of tx_add_input, tx_add_output, etc. + /// <------------- tx_complete --- + /// --- splice_comm_sign --------> send commitment signature + /// <-------- splice_comm_ack --- send commitment signature + /// --- splice_signed -----------> send signature on funding tx. In future this should be tx_signatures + /// <------- splice_signed_ack --- send signature on funding tx. In future this should be tx_signatures + /// [new funding tx can be broadcast] + pub fn splice_channel(&self, channel_id: &ChannelId, their_network_key: &PublicKey, relative_satoshis: i64, funding_feerate_perkw: u32, locktime: u32) -> Result<(), APIError> { + // TODO handle code duplication with create_channel + + let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); + // We want to make sure the lock is actually acquired by PersistenceNotifierGuard. + debug_assert!(&self.total_consistency_lock.try_write().is_err()); + + let per_peer_state = self.per_peer_state.read().unwrap(); + + let peer_state_mutex = per_peer_state.get(their_network_key) + .ok_or_else(|| APIError::APIMisuseError{ err: format!("Not connected to node: {}", their_network_key) })?; + + // Look for channel; taken from close_channel() + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + match peer_state.channel_by_id.entry(channel_id.clone()) { + hash_map::Entry::Vacant(_) => return Err(APIError::ChannelUnavailable{err: format!("Channel with id {} not found for the passed counterparty node_id {}", channel_id, their_network_key) }), + hash_map::Entry::Occupied(mut chan_phase_entry) => { + if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() { + let current_value_sats = chan.context.get_value_satoshis(); + if relative_satoshis < 0 && -relative_satoshis > (current_value_sats as i64) { + return Err(APIError::APIMisuseError { err: format!("Post-splicing channel value cannot be negative. It was {} - {}", current_value_sats, -relative_satoshis) }); + } + let post_splice_funding_satoshis: u64 = (current_value_sats as i64 + relative_satoshis) as u64; + + // TODO handle code duplication with create_channel + if post_splice_funding_satoshis < 1000 { + return Err(APIError::APIMisuseError { err: format!("Post-splicing channel value must be at least 1000 satoshis. It was {}", post_splice_funding_satoshis) }); + } + // Store post-splicing channel value (pending) + // TODO check if pending_splice is already set + chan.context.pending_splice = Some(PendingSpliceInfo::new(relative_satoshis, current_value_sats, true)); + + let msg = chan.get_splice(self.genesis_hash.clone(), relative_satoshis, funding_feerate_perkw, locktime); + + peer_state.pending_msg_events.push(events::MessageSendEvent::SendSplice { + node_id: *their_network_key, + msg, + }); + Ok(()) + } else { + return Err(APIError::ChannelUnavailable{err: format!("Channel with id {} is not funded", channel_id) }); + } + + }, + } + } + fn list_funded_channels_with_filter)) -> bool + Copy>(&self, f: Fn) -> Vec { // Allocate our best estimate of the number of channels we have in the `res` // Vec. Sadly the `short_to_chan_info` map doesn't cover channels without @@ -3803,6 +3870,70 @@ where }) } + /// #SPLICING STEP6 I + /// Handles the generation of a splicing transaction, optionally (for tests) with a function + /// which checks the correctness of the splicing transaction given the associated channel. + /// Based on funding_transaction_generated_intern() + fn splice_transaction_generated_intern< + FundingInput: Fn(&Channel, &Transaction) -> Result, + FundingOutput: Fn(&Channel, &Transaction) -> Result + >( + &self, channel_id: &ChannelId, counterparty_node_id: &PublicKey, splice_transaction: Transaction, find_prev_funding_input: FundingInput, find_funding_output: FundingOutput + ) -> Result<(), APIError> { + let per_peer_state = self.per_peer_state.read().unwrap(); + let peer_state_mutex = per_peer_state.get(counterparty_node_id) + .ok_or_else(|| APIError::ChannelUnavailable { err: format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id) })?; + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + // Note on channel/channel_id handling: + // In funding_transaction_generated_intern(), channel is removed (with temp ID), then re-added (with new ID) + // Here we don't change the channel_id (which is a gross oversimplification and incorrect), hence no need to remove and re-add. + // Here we don't change the channel. + match peer_state.channel_by_id.get_mut(channel_id) { + Some(ChannelPhase::Funded(chan)) => { + let prev_funding_txinp = find_prev_funding_input(&chan, &splice_transaction)?; + let funding_txo = find_funding_output(&chan, &splice_transaction)?; + log_trace!(self.logger, "... prev_funding_txinp {} fund_tx_outpoint {} {}", prev_funding_txinp, log_bytes!(funding_txo.txid), funding_txo.index); + + let splice_created_res = chan.splice_generated(splice_transaction, funding_txo, prev_funding_txinp, chan.context.get_value_satoshis(), &self.logger) + .map_err(|e| if let ChannelError::Close(msg) = e { + MsgHandleErrInternal::from_finish_shutdown(msg, chan.context.channel_id(), chan.context.get_user_id(), chan.context.force_shutdown(true), None, chan.context.get_value_satoshis()) + } else { unreachable!(); }); + match splice_created_res { + Ok(splice_created_msg) => { + peer_state.pending_msg_events.push(events::MessageSendEvent::SendSpliceCreated { + node_id: chan.context.get_counterparty_node_id(), + msg: splice_created_msg, + }); + Ok(()) + } + Err(_) => { + let counterparty_node_id = chan.context.get_counterparty_node_id().clone(); + mem::drop(peer_state_lock); + mem::drop(per_peer_state); + + let _ = handle_error!(self, splice_created_res, counterparty_node_id); + return Err(APIError::ChannelUnavailable { + err: "Signer refused to sign the post-splice commitment transaction".to_owned() + }); + }, + } + }, + None => { + return Err(APIError::ChannelUnavailable { + err: format!( + "Channel with id {} not found for the passed counterparty node_id {}", *channel_id, counterparty_node_id), + }) + }, + _ => { + return Err(APIError::ChannelUnavailable { + err: format!( + "Channel with id {} is with wrong state, node_id {}", *channel_id, counterparty_node_id), + }) + }, + } + } + /// Call this upon creation of a funding transaction for the given channel. /// /// Returns an [`APIError::APIMisuseError`] if the funding_transaction spent non-SegWit outputs @@ -4036,6 +4167,93 @@ where Ok(()) } + /// #SPLICING + /// Call this upon creation of a splicing transaction for the given channel. + /// + /// TODO doc + /// + /// splice_transaction: The new splice funding transaction being prepared, without all signatures. It must contain an input the previous funding transaction, and an output that is the new funding transaction. + pub fn splice_transaction_generated(&self, channel_id: &ChannelId, counterparty_node_id: &PublicKey, splice_transaction: Transaction) -> Result<(), APIError> { + let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); + + // TODO: Omit this check, caused issues in func test + /* + for inp in splice_transaction.input.iter() { + if inp.witness.is_empty() { + return Err(APIError::APIMisuseError { + err: "Splicing transaction must be fully signed and spend Segwit outputs".to_owned() + }); + } + } + */ + { + let height = self.best_block.read().unwrap().height(); + // Transactions are evaluated as final by network mempools if their locktime is strictly + // lower than the next block height. However, the modules constituting our Lightning + // node might not have perfect sync about their blockchain views. Thus, if the wallet + // module is ahead of LDK, only allow one more block of headroom. + if !splice_transaction.input.iter().all(|input| input.sequence == Sequence::MAX) && LockTime::from(splice_transaction.lock_time).is_block_height() && splice_transaction.lock_time.0 > height + 1 { + return Err(APIError::APIMisuseError { + err: "Funding transaction absolute timelock is non-final".to_owned() + }); + } + } + self.splice_transaction_generated_intern(channel_id, counterparty_node_id, splice_transaction, + |chan, tx| { + let mut input_index = None; + let expected_inp = chan.context.get_funding_txo().unwrap(); + for (idx, inp) in tx.input.iter().enumerate() { + if inp.previous_output.txid == expected_inp.txid && (inp.previous_output.vout == (expected_inp.index as u32)) /* && inp.value == chan.channel_value */ { + if input_index.is_some() { + return Err(APIError::APIMisuseError { + err: "Multiple inputs matched the expected input address and value".to_owned() + }); + } + if idx > u16::max_value() as usize { + return Err(APIError::APIMisuseError { + err: "Transaction had more than 2^16 inputs, which is not supported".to_owned() + }); + } + input_index = Some(idx as u16); + } + } + if input_index.is_none() { + return Err(APIError::APIMisuseError { + err: format!("No input matched the address and value in the SpliceAcked event {}", if chan.context.pending_splice.is_some() { chan.context.pending_splice.as_ref().unwrap().relative_satoshis } else { 0 }) + }); + } + Ok(input_index.unwrap()) + }, + |chan, tx| { + let mut output_index = None; + let expected_spk = chan.context.get_funding_redeemscript().to_v0_p2wsh(); + for (idx, outp) in tx.output.iter().enumerate() { + if let Some(pending_info) = &chan.context.pending_splice { + if outp.script_pubkey == expected_spk && outp.value == pending_info.post_channel_value { + if output_index.is_some() { + return Err(APIError::APIMisuseError { + err: "Multiple outputs matched the expected script and value".to_owned() + }); + } + if idx > u16::max_value() as usize { + return Err(APIError::APIMisuseError { + err: "Transaction had more than 2^16 outputs, which is not supported".to_owned() + }); + } + output_index = Some(idx as u16); + } + } + } + if output_index.is_none() { + return Err(APIError::APIMisuseError { + err: format!("No output matched the script_pubkey and value in the SpliceAcked event {}", if chan.context.pending_splice.is_some() { chan.context.pending_splice.as_ref().unwrap().relative_satoshis } else { 0 }) + }); + } + Ok(OutPoint { txid: tx.txid(), index: output_index.unwrap() }) + } + ) + } + /// Atomically updates the [`ChannelConfig`] for the given channels. /// /// Once the updates are applied, each eligible channel (advertised with a known short channel @@ -5647,7 +5865,7 @@ where } if let Some(tx) = funding_broadcastable { - log_info!(self.logger, "Broadcasting funding transaction with txid {}", tx.txid()); + log_info!(self.logger, "Broadcasting funding transaction with txid {} len {}", tx.txid(), tx.encode().len()); self.tx_broadcaster.broadcast_transactions(&[&tx]); } @@ -6843,6 +7061,369 @@ where Ok(persist) } + // #SPLICING STEP3 + // Inspired by handle_open_channel() + // Logic for incoming splicing request + fn internal_splice(&self, counterparty_node_id: &PublicKey, msg: &msgs::Splice) -> Result<(), MsgHandleErrInternal> { + if msg.chain_hash != self.genesis_hash { + return Err(MsgHandleErrInternal::send_err_msg_no_close("Unknown genesis block hash".to_owned(), msg.channel_id.clone())); + } + + // TODO checks + // TODO check if we accept splicing, quiscence + + /* + // Get the number of peers with channels, but without funded ones. We don't care too much + // about peers that never open a channel, so we filter by peers that have at least one + // channel, and then limit the number of those with unfunded channels. + let _channeled_peers_without_funding = self.peers_without_funded_channels(|node| !node.channel_by_id.is_empty()); + */ + + let per_peer_state = self.per_peer_state.read().unwrap(); + let peer_state_mutex = per_peer_state.get(counterparty_node_id) + .ok_or_else(|| { + debug_assert!(false); + MsgHandleErrInternal::send_err_msg_no_close(format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), msg.channel_id.clone()) + })?; + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + + match peer_state.channel_by_id.entry(msg.channel_id) { + hash_map::Entry::Vacant(_) => 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)), + hash_map::Entry::Occupied(mut chan_entry) => { + if let ChannelPhase::Funded(chan) = chan_entry.get_mut() { + // Store post-splicing channel value (pending) + // TODO check if there is a pending already + chan.context.pending_splice = Some(PendingSpliceInfo::new(msg.relative_satoshis, chan.context.get_value_satoshis(), false)); + + let msg = chan.get_splice_ack(self.genesis_hash.clone(), msg.relative_satoshis); + peer_state.pending_msg_events.push(events::MessageSendEvent::SendSpliceAck { + node_id: *counterparty_node_id, + msg, + }); + Ok(()) + } else { + return Err(MsgHandleErrInternal::send_err_msg_no_close("Channel in wrong state".to_owned(), msg.channel_id.clone())); + } + }, + } + } + + // #SPLICING STEP5 + // Logic for incoming splicing_ack message + fn internal_splice_ack(&self, counterparty_node_id: &PublicKey, msg: &msgs::SpliceAck) -> Result<(), MsgHandleErrInternal> { + if msg.chain_hash != self.genesis_hash { + return Err(MsgHandleErrInternal::send_err_msg_no_close("Unknown genesis block hash".to_owned(), msg.channel_id.clone())); + } + + // TODO checks + // TODO check if we have initiated splicing + + let (pre_value, funding_outpoint, output_script) = { // note: user_id skipped + let per_peer_state = self.per_peer_state.read().unwrap(); + let peer_state_mutex = per_peer_state.get(counterparty_node_id) + .ok_or_else(|| { + debug_assert!(false); + MsgHandleErrInternal::send_err_msg_no_close(format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), msg.channel_id.clone()) + })?; + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + + match peer_state.channel_by_id.entry(msg.channel_id) { + hash_map::Entry::Vacant(_) => 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)), + hash_map::Entry::Occupied(mut chan) => { + if let ChannelPhase::Funded(chan) = chan.get_mut() { + ( + chan.context.get_value_satoshis(), + chan.context.channel_transaction_parameters.funding_outpoint.unwrap().into_bitcoin_outpoint(), + chan.context.get_funding_redeemscript().to_v0_p2wsh(), // TODO check wether this is correct, correct keys used (from splice negot, and not from pre) + ) + } else { + return Err(MsgHandleErrInternal::send_err_msg_no_close("Channel in wrong state".to_owned(), msg.channel_id.clone())); + } + }, + } + }; + + // Prepare SpliceAcked event + let mut pending_events = self.pending_events.lock().unwrap(); + pending_events.push_back((events::Event::SpliceAcked { + channel_id: msg.channel_id, + counterparty_node_id: *counterparty_node_id, + current_funding_outpoint: funding_outpoint, + pre_channel_value_satoshis: pre_value, + post_channel_value_satoshis: (pre_value as i64 + msg.relative_satoshis) as u64, // TODO handle underflow + output_script, + }, None)); + Ok(()) + } + + /// #SPLICING STEP8 A + /// Based on internal_funding_created() + fn internal_splice_created(&self, counterparty_node_id: &PublicKey, msg: &msgs::SpliceCreated) -> Result<(), MsgHandleErrInternal> { + let per_peer_state = self.per_peer_state.read().unwrap(); + let peer_state_mutex = per_peer_state.get(counterparty_node_id) + .ok_or_else(|| { + debug_assert!(false); + MsgHandleErrInternal::send_err_msg_no_close(format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), msg.channel_id) + })?; + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + // Note: do not remove the channel (no change in channel_id) + match peer_state.channel_by_id.entry(msg.channel_id) { + hash_map::Entry::Occupied(mut chan_phase_entry) => { + match chan_phase_entry.get_mut() { + ChannelPhase::Funded(ref mut chan) => { + match chan.splice_created(msg, &self.logger) { + Ok(tx_complete_msg) => { + let mut id_to_peer_lock = self.id_to_peer.lock().unwrap(); + match id_to_peer_lock.entry(chan.context.channel_id()) { + hash_map::Entry::Vacant(_) => { + return Err(MsgHandleErrInternal::send_err_msg_no_close( + "Channel not found".to_owned(), + tx_complete_msg.channel_id)) + }, + hash_map::Entry::Occupied(_) => { + peer_state.pending_msg_events.push(events::MessageSendEvent::SendTxComplete { + node_id: counterparty_node_id.clone(), + msg: tx_complete_msg, + }); + Ok(()) + } + } + } + Err(e) => { + let (_drop, res) = convert_chan_phase_err!(self, e, chan, &chan.context.channel_id(), UNFUNDED_CHANNEL); + return Err(res); + } + } + } + _ => { + return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Channel with id {} is not funded! counterparty_node_id {}", msg.channel_id, counterparty_node_id), msg.channel_id)); + } + } + } + hash_map::Entry::Vacant(_) => 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)) + } + } + + /// #SPLICING STEP10 I + fn internal_tx_complete(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxComplete) -> Result<(), MsgHandleErrInternal> { + let per_peer_state = self.per_peer_state.read().unwrap(); + let peer_state_mutex = per_peer_state.get(counterparty_node_id) + .ok_or_else(|| { + debug_assert!(false); + MsgHandleErrInternal::send_err_msg_no_close(format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), msg.channel_id) + })?; + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + match peer_state.channel_by_id.get_mut(&msg.channel_id) { + Some(ChannelPhase::Funded(chan)) => { + let commitment_res = chan.splice_tx_complete(&self.logger) + .map_err(|e| if let ChannelError::Close(msg) = e { + MsgHandleErrInternal::from_finish_shutdown(msg, chan.context.channel_id(), chan.context.get_user_id(), chan.context.force_shutdown(true), None, chan.context.get_value_satoshis()) + } else { unreachable!(); }); + match commitment_res { + Ok(comm_signed_msg) => { + peer_state.pending_msg_events.push(events::MessageSendEvent::SendSpliceCommSigned { + node_id: chan.context.get_counterparty_node_id(), + msg: comm_signed_msg, + }); + Ok(()) + } + Err(_) => { + let counterparty_node_id = chan.context.get_counterparty_node_id().clone(); + mem::drop(peer_state_lock); + mem::drop(per_peer_state); + + let _ = handle_error!(self, commitment_res, counterparty_node_id); + return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Error signing post-splice commitment transaction"), msg.channel_id)) + }, + } + }, + None => { + return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Channel with id {} not found for the passed counterparty node_id {}", msg.channel_id, counterparty_node_id), msg.channel_id)); + }, + _ => { + return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Channel with id {} is with wrong state, node_id {}", msg.channel_id, counterparty_node_id), msg.channel_id)); + }, + } + } + + /// #SPLICING STEP12 A + fn internal_splice_comm_signed(&self, counterparty_node_id: &PublicKey, msg: &msgs::SpliceCommSigned) -> Result<(), MsgHandleErrInternal> { + let per_peer_state = self.per_peer_state.read().unwrap(); + let peer_state_mutex = per_peer_state.get(counterparty_node_id) + .ok_or_else(|| { + debug_assert!(false); + MsgHandleErrInternal::send_err_msg_no_close(format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), msg.channel_id) + })?; + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + match peer_state.channel_by_id.get_mut(&msg.channel_id) { + Some(ChannelPhase::Funded(chan)) => { + let commitment_res = chan.splice_comm_signed(msg, &self.logger) + .map_err(|e| if let ChannelError::Close(msg) = e { + MsgHandleErrInternal::from_finish_shutdown(msg, chan.context.channel_id(), chan.context.get_user_id(), chan.context.force_shutdown(true), None, chan.context.get_value_satoshis()) + } else { unreachable!(); }); + match commitment_res { + Ok(comm_ack_msg) => { + peer_state.pending_msg_events.push(events::MessageSendEvent::SendSpliceCommAck { + node_id: chan.context.get_counterparty_node_id(), + msg: comm_ack_msg, + }); + Ok(()) + } + Err(_) => { + let counterparty_node_id = chan.context.get_counterparty_node_id().clone(); + mem::drop(peer_state_lock); + mem::drop(per_peer_state); + + let _ = handle_error!(self, commitment_res, counterparty_node_id); + return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Error signing post-splice commitment transaction"), msg.channel_id)) + }, + } + }, + None => { + return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Channel with id {} not found for the passed counterparty node_id {}", msg.channel_id, counterparty_node_id), msg.channel_id)); + }, + _ => { + return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Channel with id {} is with wrong state, node_id {}", msg.channel_id, counterparty_node_id), msg.channel_id)); + }, + } + } + + /// #SPLICING STEP14 I + fn internal_splice_comm_ack(&self, counterparty_node_id: &PublicKey, msg: &msgs::SpliceCommAck) -> Result<(), MsgHandleErrInternal> { + let per_peer_state = self.per_peer_state.read().unwrap(); + let peer_state_mutex = per_peer_state.get(counterparty_node_id) + .ok_or_else(|| { + debug_assert!(false); + MsgHandleErrInternal::send_err_msg_no_close(format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), msg.channel_id) + })?; + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + match peer_state.channel_by_id.get_mut(&msg.channel_id) { + Some(ChannelPhase::Funded(chan)) => { + let commitment_res = chan.splice_comm_ack(msg, &self.logger) + .map_err(|e| if let ChannelError::Close(msg) = e { + MsgHandleErrInternal::from_finish_shutdown(msg, chan.context.channel_id(), chan.context.get_user_id(), chan.context.force_shutdown(true), None, chan.context.get_value_satoshis()) + } else { unreachable!(); }); + match commitment_res { + Ok(splice_signed_msg) => { + peer_state.pending_msg_events.push(events::MessageSendEvent::SendSpliceSigned { + node_id: chan.context.get_counterparty_node_id(), + msg: splice_signed_msg, + }); + Ok(()) + } + Err(_) => { + let counterparty_node_id = chan.context.get_counterparty_node_id().clone(); + mem::drop(peer_state_lock); + mem::drop(per_peer_state); + + let _ = handle_error!(self, commitment_res, counterparty_node_id); + return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Error signing post-splice commitment transaction"), msg.channel_id)) + }, + } + }, + None => { + return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Channel with id {} not found for the passed counterparty node_id {}", msg.channel_id, counterparty_node_id), msg.channel_id)); + }, + _ => { + return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Channel with id {} is with wrong state, node_id {}", msg.channel_id, counterparty_node_id), msg.channel_id)); + }, + } + } + + /// #SPLICING STEP16 A + fn internal_splice_signed(&self, counterparty_node_id: &PublicKey, msg: &msgs::SpliceSigned) -> Result<(), MsgHandleErrInternal> { + let best_block = *self.best_block.read().unwrap(); + + let per_peer_state = self.per_peer_state.read().unwrap(); + let peer_state_mutex = per_peer_state.get(counterparty_node_id) + .ok_or_else(|| { + debug_assert!(false); + MsgHandleErrInternal::send_err_msg_no_close(format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), msg.channel_id) + })?; + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + match peer_state.channel_by_id.get_mut(&msg.channel_id) { + Some(ChannelPhase::Funded(chan)) => { + let commitment_res = chan.splice_signed(&msg, best_block, &self.signer_provider, &self.logger) + .map_err(|e| if let ChannelError::Close(msg) = e { + MsgHandleErrInternal::from_finish_shutdown(msg, chan.context.channel_id(), chan.context.get_user_id(), chan.context.force_shutdown(true), None, chan.context.get_value_satoshis()) + } else { unreachable!(); }); + match commitment_res { + Ok((splice_signed_ack_msg, monitor)) => { + peer_state.pending_msg_events.push(events::MessageSendEvent::SendSpliceSignedAck { + node_id: chan.context.get_counterparty_node_id(), + msg: splice_signed_ack_msg, + }); + if let Ok(persist_status) = self.chain_monitor.watch_channel(chan.context.get_funding_txo().unwrap(), monitor) { + handle_new_monitor_update!(self, persist_status, peer_state_lock, peer_state, per_peer_state, chan, INITIAL_MONITOR); + Ok(()) + } else { + log_error!(self.logger, "Persisting initial ChannelMonitor failed, implying the funding outpoint was duplicated"); + return Err(MsgHandleErrInternal::send_err_msg_no_close( + "The funding_created message had the same funding_txid as an existing channel - funding is not possible".to_owned(), + chan.context.channel_id())); + } + } + Err(_) => { + let counterparty_node_id = chan.context.get_counterparty_node_id().clone(); + mem::drop(peer_state_lock); + mem::drop(per_peer_state); + + let _ = handle_error!(self, commitment_res, counterparty_node_id); + return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Error signing post-splice commitment transaction"), msg.channel_id)) + }, + } + }, + None => { + return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Channel with id {} not found for the passed counterparty node_id {}", msg.channel_id, counterparty_node_id), msg.channel_id)); + }, + _ => { + return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Channel with id {} is with wrong state, node_id {}", msg.channel_id, counterparty_node_id), msg.channel_id)); + }, + } + } + + /// #SPLICING STEP18 I + /// Based on internal_funding_signed() + fn internal_splice_signed_ack(&self, counterparty_node_id: &PublicKey, msg: &msgs::SpliceSignedAck) -> Result<(), MsgHandleErrInternal> { + let best_block = *self.best_block.read().unwrap(); + + let per_peer_state = self.per_peer_state.read().unwrap(); + let peer_state_mutex = per_peer_state.get(counterparty_node_id) + .ok_or_else(|| { + debug_assert!(false); + MsgHandleErrInternal::send_err_msg_no_close(format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), msg.channel_id) + })?; + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + match peer_state.channel_by_id.entry(msg.channel_id) { + hash_map::Entry::Occupied(mut chan_phase_entry) => { + match chan_phase_entry.get_mut() { + ChannelPhase::Funded(ref mut chan) => { + let monitor = try_chan_phase_entry!(self, + chan.splice_signed_ack(&msg, best_block, &self.signer_provider, &self.logger), chan_phase_entry); + if let Ok(persist_status) = self.chain_monitor.watch_channel(chan.context.get_funding_txo().unwrap(), monitor) { + handle_new_monitor_update!(self, persist_status, peer_state_lock, peer_state, per_peer_state, chan, INITIAL_MONITOR); + Ok(()) + } else { + try_chan_phase_entry!(self, Err(ChannelError::Close("Channel funding outpoint was a duplicate".to_owned())), chan_phase_entry) + } + }, + _ => { + return Err(MsgHandleErrInternal::send_err_msg_no_close("Failed to find corresponding channel".to_owned(), msg.channel_id)); + }, + } + }, + hash_map::Entry::Vacant(_) => return Err(MsgHandleErrInternal::send_err_msg_no_close("Failed to find corresponding channel".to_owned(), msg.channel_id)) + } + } + /// Process pending events from the [`chain::Watch`], returning whether any events were processed. fn process_pending_monitor_events(&self) -> bool { debug_assert!(self.total_consistency_lock.try_write().is_err()); // Caller holds read lock @@ -7883,6 +8464,54 @@ where }); } + // #SPLICING + fn handle_splice(&self, counterparty_node_id: &PublicKey, msg: &msgs::Splice) { + let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); + let _ = handle_error!(self, self.internal_splice(counterparty_node_id, msg), *counterparty_node_id); + } + + // #SPLICING + fn handle_splice_ack(&self, counterparty_node_id: &PublicKey, msg: &msgs::SpliceAck) { + let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); + let _ = handle_error!(self, self.internal_splice_ack(counterparty_node_id, msg), *counterparty_node_id); + } + + fn handle_splice_locked(&self, counterparty_node_id: &PublicKey, msg: &msgs::SpliceLocked) { + let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( + "Splicing not supported (splice_locked)".to_owned(), + msg.channel_id.clone())), *counterparty_node_id); + } + + // #SPLICING + fn handle_splice_created(&self, counterparty_node_id: &PublicKey, msg: &msgs::SpliceCreated) { + let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); + let _ = handle_error!(self, self.internal_splice_created(counterparty_node_id, msg), *counterparty_node_id); + } + + // #SPLICING + fn handle_splice_comm_signed(&self, counterparty_node_id: &PublicKey, msg: &msgs::SpliceCommSigned) { + let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); + let _ = handle_error!(self, self.internal_splice_comm_signed(counterparty_node_id, msg), *counterparty_node_id); + } + + // #SPLICING + fn handle_splice_comm_ack(&self, counterparty_node_id: &PublicKey, msg: &msgs::SpliceCommAck) { + let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); + let _ = handle_error!(self, self.internal_splice_comm_ack(counterparty_node_id, msg), *counterparty_node_id); + } + + // #SPLICING + fn handle_splice_signed(&self, counterparty_node_id: &PublicKey, msg: &msgs::SpliceSigned) { + let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); + let _ = handle_error!(self, self.internal_splice_signed(counterparty_node_id, msg), *counterparty_node_id); + } + + // #SPLICING + fn handle_splice_signed_ack(&self, counterparty_node_id: &PublicKey, msg: &msgs::SpliceSignedAck) { + let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); + let _ = handle_error!(self, self.internal_splice_signed_ack(counterparty_node_id, msg), *counterparty_node_id); + } + fn handle_shutdown(&self, counterparty_node_id: &PublicKey, msg: &msgs::Shutdown) { let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); let _ = handle_error!(self, self.internal_shutdown(counterparty_node_id, msg), *counterparty_node_id); @@ -8051,6 +8680,15 @@ where // Common Channel Establishment &events::MessageSendEvent::SendChannelReady { .. } => false, &events::MessageSendEvent::SendAnnouncementSignatures { .. } => false, + // Splicing + &events::MessageSendEvent::SendSplice { .. } => false, + &events::MessageSendEvent::SendSpliceAck { .. } => false, + &events::MessageSendEvent::SendSpliceLocked { .. } => false, + &events::MessageSendEvent::SendSpliceCreated { .. } => false, + &events::MessageSendEvent::SendSpliceCommSigned { .. } => false, + &events::MessageSendEvent::SendSpliceCommAck { .. } => false, + &events::MessageSendEvent::SendSpliceSigned { .. } => false, + &events::MessageSendEvent::SendSpliceSignedAck { .. } => false, // Interactive Transaction Construction &events::MessageSendEvent::SendTxAddInput { .. } => false, &events::MessageSendEvent::SendTxAddOutput { .. } => false, @@ -8299,9 +8937,8 @@ where } fn handle_tx_complete(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxComplete) { - let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( - "Dual-funded channels not supported".to_owned(), - msg.channel_id.clone())), *counterparty_node_id); + let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); + let _ = handle_error!(self, self.internal_tx_complete(counterparty_node_id, msg), *counterparty_node_id); } fn handle_tx_signatures(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxSignatures) { diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index acf1c7dfb70..aacdbff2bbe 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -31,12 +31,14 @@ use crate::util::errors::APIError; use crate::util::config::{UserConfig, MaxDustHTLCExposure}; use crate::util::ser::{ReadableArgs, Writeable}; +use bitcoin::Script; use bitcoin::blockdata::block::{Block, BlockHeader}; -use bitcoin::blockdata::transaction::{Transaction, TxOut}; +use bitcoin::blockdata::transaction::{Sequence, Transaction, TxOut}; +use bitcoin::blockdata::witness::Witness; +use bitcoin::network::constants::Network; use bitcoin::hash_types::BlockHash; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::Hash as _; -use bitcoin::network::constants::Network; use bitcoin::secp256k1::{PublicKey, SecretKey}; use crate::io; @@ -648,7 +650,7 @@ macro_rules! get_event_msg { assert_eq!(*node_id, $node_id); (*msg).clone() }, - _ => panic!("Unexpected event"), + _ => panic!("Unexpected event {:?}", events[0]), } } } @@ -681,7 +683,7 @@ macro_rules! get_event { $event_type { .. } => { ev }, - _ => panic!("Unexpected event"), + _ => panic!("Unexpected event {:?}", ev), } } } @@ -787,6 +789,31 @@ pub fn remove_first_msg_event_to_node(msg_node_id: &PublicKey, msg_events: &mut MessageSendEvent::SendOpenChannelV2 { node_id, .. } => { node_id == msg_node_id }, + // #SPLICING + MessageSendEvent::SendSplice { node_id, .. } => { + node_id == msg_node_id + }, + MessageSendEvent::SendSpliceAck { node_id, .. } => { + node_id == msg_node_id + }, + MessageSendEvent::SendSpliceLocked { node_id, .. } => { + node_id == msg_node_id + }, + MessageSendEvent::SendSpliceCreated { node_id, .. } => { + node_id == msg_node_id + }, + MessageSendEvent::SendSpliceCommSigned { node_id, .. } => { + node_id == msg_node_id + }, + MessageSendEvent::SendSpliceCommAck { node_id, .. } => { + node_id == msg_node_id + }, + MessageSendEvent::SendSpliceSigned { node_id, .. } => { + node_id == msg_node_id + }, + MessageSendEvent::SendSpliceSignedAck { node_id, .. } => { + node_id == msg_node_id + }, MessageSendEvent::SendTxAddInput { node_id, .. } => { node_id == msg_node_id }, @@ -1093,6 +1120,33 @@ pub fn sign_funding_transaction<'a, 'b, 'c>(node_a: &Node<'a, 'b, 'c>, node_b: & tx } +/// #SPLICING +pub fn create_splice_in_transaction<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, expected_channel_id: &ChannelId, expected_post_splice_chan_value: u64) -> (Transaction, OutPoint) { + let chan_id = *node.network_chan_count.borrow(); + + let events = node.node.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + match events[0] { + Event::SpliceAcked { ref channel_id, counterparty_node_id: _, ref current_funding_outpoint, pre_channel_value_satoshis: _, ref post_channel_value_satoshis, ref output_script } => { + assert_eq!(*channel_id, *expected_channel_id); + assert_eq!(*post_channel_value_satoshis, expected_post_splice_chan_value); + + let tx = Transaction { + version: chan_id as i32, + lock_time: PackedLockTime::ZERO, + // TODO: witness! must not be empty + input: vec![ + TxIn {previous_output: *current_funding_outpoint, script_sig: Script::new(), sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, witness: Witness::new()} + ], + output: vec![TxOut {value: *post_channel_value_satoshis, script_pubkey: output_script.clone()}] + }; + let funding_outpoint = OutPoint { txid: tx.txid(), index: 0 }; + (tx, funding_outpoint) + }, + _ => panic!("Unexpected event"), + } +} + // Receiver must have been initialized with manually_accept_inbound_channels set to true. pub fn open_zero_conf_channel<'a, 'b, 'c, 'd>(initiator: &'a Node<'b, 'c, 'd>, receiver: &'a Node<'b, 'c, 'd>, initiator_config: Option) -> (bitcoin::Transaction, ChannelId) { let initiator_channels = initiator.node.list_usable_channels().len(); diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index 5e90ee7e92e..72cfe30486d 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -35,18 +35,21 @@ use crate::util::test_utils::{self, WatchtowerPersister}; use crate::util::errors::APIError; use crate::util::ser::{Writeable, ReadableArgs}; use crate::util::string::UntrustedString; -use crate::util::config::{UserConfig, MaxDustHTLCExposure}; +use crate::util::config::{ChannelHandshakeConfig, UserConfig, MaxDustHTLCExposure}; -use bitcoin::hash_types::BlockHash; +// WIP payment use lightning_invoice::{Currency, utils}; +use bitcoin::hash_types::{BlockHash, Txid}; use bitcoin::blockdata::script::{Builder, Script}; use bitcoin::blockdata::opcodes; use bitcoin::blockdata::constants::genesis_block; use bitcoin::network::constants::Network; use bitcoin::{PackedLockTime, Sequence, Transaction, TxIn, TxOut, Witness}; use bitcoin::OutPoint as BitcoinOutPoint; +use bitcoin::hashes::hex::ToHex; -use bitcoin::secp256k1::Secp256k1; -use bitcoin::secp256k1::{PublicKey,SecretKey}; +use bitcoin::secp256k1::{Message, PublicKey, Secp256k1, SecretKey}; +use bitcoin::secp256k1::ecdsa::Signature; +use bitcoin::util::sighash::{EcdsaSighashType, SighashCache}; use regex; @@ -63,6 +66,445 @@ use crate::ln::chan_utils::CommitmentTransaction; use super::channel::UNFUNDED_CHANNEL_AGE_LIMIT_TICKS; +// Create a 2-of-2 multisig redeem script. Return the script, and the two keys in the order they appear in the script. +fn create_multisig_redeem_script(key1: &PublicKey, key2: &PublicKey) -> (Script, PublicKey, PublicKey) { + let (smaller_key, larger_key) = if key1.serialize() < key2.serialize() { + (key1, key2) + } else { + (key2, key1) + }; + let script = Builder::new() + .push_opcode(opcodes::all::OP_PUSHNUM_2) + .push_slice(&smaller_key.serialize()[..]) + .push_slice(&larger_key.serialize()[..]) + .push_opcode(opcodes::all::OP_PUSHNUM_2) + .push_opcode(opcodes::all::OP_CHECKMULTISIG) + .into_script(); + (script, smaller_key.clone(), larger_key.clone()) +} + +// Create an output script for a 2-of-2 multisig. +fn create_multisig_output_script(key1: &PublicKey, key2: &PublicKey) -> Script { + let (redeem_script, _k1, _k2) = create_multisig_redeem_script(key1, key2); + Builder::new() + .push_opcode(opcodes::all::OP_PUSHBYTES_0) + .push_slice(&redeem_script.wscript_hash().as_ref()) + .into_script() +} + +// Verify a 2-of-2 multisig redeem script. Return the same keys, but in the order as they appear in the script +fn verify_multisig_redeem_script(script: &Vec, exp_key_1: &PublicKey, exp_key_2: &PublicKey) -> (PublicKey, PublicKey) { + let (exp_script,exp_smaller_key, exp_larger_key) = create_multisig_redeem_script(exp_key_1, exp_key_2); + assert_eq!(hex::encode(script), hex::encode(exp_script.as_bytes())); + (exp_smaller_key, exp_larger_key) +} + +// Verify a 2-of-2 multisig output script. +fn verify_multisig_output_script(script: &Script, exp_key_1: &PublicKey, exp_key_2: &PublicKey) { + let exp_script = create_multisig_output_script(exp_key_1, exp_key_2); + assert_eq!(hex::encode(script), hex::encode(exp_script.as_bytes())); +} + +// Get the funding key of a node towards another node +fn get_funding_key(node: &Node, counterparty_node: &Node, channel_id: &ChannelId) -> PublicKey { + let per_peer_state = node.node.per_peer_state.read().unwrap(); + let chan_lock = per_peer_state.get(&counterparty_node.node.get_our_node_id()).unwrap().lock().unwrap(); + let local_chan = chan_lock.channel_by_id.get(&channel_id).map( + |phase| if let ChannelPhase::Funded(chan) = phase { Some(chan) } else { None } + ).flatten().unwrap(); + local_chan.get_signer().as_ref().pubkeys().funding_pubkey +} + +/// Verify the funding output of a funding tx +fn verify_funding_output(funding_tx: &Transaction, value: u64, funding_key_1: &PublicKey, funding_key_2: &PublicKey) { + // find the output with the given value + let mut funding_output_opt: Option<&TxOut> = None; + for o in &funding_tx.output { + if o.value == value { + funding_output_opt = Some(o); + } + } + if funding_output_opt.is_none() { + panic!("Funding output not found, no output with value {}", value); + } + let act_script = &funding_output_opt.unwrap().script_pubkey; + verify_multisig_output_script(&act_script, funding_key_1, funding_key_2); +} + +/// Do checks on a funding tx +fn verify_funding_tx(funding_tx: &Transaction, value: u64, funding_key_1: &PublicKey, funding_key_2: &PublicKey) { + verify_funding_output(funding_tx, value, funding_key_1, funding_key_2); +} + +/// Simple open channel flow +#[test] +fn test_channel_open_simple() { + // Stand up a network of 2 nodes + let announced_channel = true; + let cfg = UserConfig { + channel_handshake_config: ChannelHandshakeConfig { + announced_channel, + ..Default::default() + }, + ..Default::default() + }; + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, Some(cfg)]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + // Initiator and Acceptor node indices. Order matters, we want the case when initiator pubkey is larger. + let i = 0; + let a = 1; + + // Instantiate channel parameters where we push the maximum msats given our + // funding satoshis + let channel_value_sat = 100000; // same as funding satoshis + let push_msat = 0; + + // Have node0 initiate a channel to node1 with aforementioned parameters + let _res = nodes[i].node.create_channel(nodes[a].node.get_our_node_id(), channel_value_sat, push_msat, 42, None).unwrap(); + + // Extract the channel open message from node0 to node1 + let open_channel_message = get_event_msg!(nodes[i], MessageSendEvent::SendOpenChannel, nodes[a].node.get_our_node_id()); + + let _res = nodes[a].node.handle_open_channel(&nodes[i].node.get_our_node_id(), &open_channel_message.clone()); + // Extract the accept channel message from node1 to node0 + let accept_channel_message = get_event_msg!(nodes[a], MessageSendEvent::SendAcceptChannel, nodes[i].node.get_our_node_id()); + let _res = nodes[i].node.handle_accept_channel(&nodes[a].node.get_our_node_id(), &accept_channel_message.clone()); + // Note: FundingGenerationReady emitted, checked and used below + let (temporary_channel_id, funding_tx, _funding_output) = create_funding_transaction(&nodes[i], &nodes[a].node.get_our_node_id(), channel_value_sat, 42); + assert_eq!(funding_tx.encode().len(), 55); + + // Funding transation created, provide it + let _res = nodes[i].node.funding_transaction_generated(&temporary_channel_id, &nodes[a].node.get_our_node_id(), funding_tx.clone()).unwrap(); + + let funding_created_message = get_event_msg!(nodes[i], MessageSendEvent::SendFundingCreated, nodes[a].node.get_our_node_id()); + let _res = nodes[a].node.handle_funding_created(&nodes[i].node.get_our_node_id(), &funding_created_message); + + let funding_signed_message = get_event_msg!(nodes[a], MessageSendEvent::SendFundingSigned, nodes[i].node.get_our_node_id()); + let _res = nodes[i].node.handle_funding_signed(&nodes[a].node.get_our_node_id(), &funding_signed_message); + // Take new channel ID + let channel_id = funding_signed_message.channel_id; + + // Check that funding transaction has been broadcasted + assert_eq!(chanmon_cfgs[i].tx_broadcaster.txn_broadcasted.lock().unwrap().len(), 1); + let broadcasted_funding_tx = chanmon_cfgs[i].tx_broadcaster.txn_broadcasted.lock().unwrap()[0].clone(); + assert_eq!(broadcasted_funding_tx.encode().len(), 55); + assert_eq!(broadcasted_funding_tx.txid(), funding_tx.txid()); + assert_eq!(broadcasted_funding_tx.encode(), funding_tx.encode()); + + check_added_monitors!(nodes[i], 1); + let _ev = get_event!(nodes[i], Event::ChannelPending); + check_added_monitors!(nodes[a], 1); + let _ev = get_event!(nodes[a], Event::ChannelPending); + + confirm_transaction(&nodes[i], &broadcasted_funding_tx); + let channel_ready_message = get_event_msg!(nodes[i], MessageSendEvent::SendChannelReady, nodes[a].node.get_our_node_id()); + + confirm_transaction(&nodes[a], &broadcasted_funding_tx); + let channel_ready_message2 = get_event_msg!(nodes[a], MessageSendEvent::SendChannelReady, nodes[i].node.get_our_node_id()); + + let _res = nodes[a].node.handle_channel_ready(&nodes[i].node.get_our_node_id(), &channel_ready_message); + let _ev = get_event!(nodes[a], Event::ChannelReady); + let _announcement_signatures = get_event_msg!(nodes[a], MessageSendEvent::SendAnnouncementSignatures, nodes[i].node.get_our_node_id()); + + let _res = nodes[i].node.handle_channel_ready(&nodes[a].node.get_our_node_id(), &channel_ready_message2); + let _ev = get_event!(nodes[i], Event::ChannelReady); + let _announcement_signatures = get_event_msg!(nodes[i], MessageSendEvent::SendAnnouncementSignatures, nodes[a].node.get_our_node_id()); + + // check channel capacity and other parameters + assert_eq!(nodes[i].node.list_channels().len(), 1); + let channel = &nodes[i].node.list_channels()[0]; + { + assert!(channel.is_usable); + assert!(channel.is_channel_ready); + assert_eq!(channel.channel_value_satoshis, channel_value_sat); + assert_eq!(channel.outbound_capacity_msat, 100000000 - 1000000); + assert_eq!(channel.funding_txo.unwrap().txid, funding_tx.txid()); + assert_eq!(channel.confirmations.unwrap(), 10); + } + + let initiator_funding_key = get_funding_key(&nodes[i], &nodes[a], &channel.channel_id); + let acceptor_funding_key = get_funding_key(&nodes[a], &nodes[i], &channel.channel_id); + + verify_funding_tx(&broadcasted_funding_tx, channel_value_sat, &initiator_funding_key, &acceptor_funding_key); + assert_eq!(broadcasted_funding_tx.encode().to_hex(), + "0000000000010001a08601000000000022002034c0cc0ad0dd5fe61dcf7ef58f995e3d34f8dbd24aa2a6fae68fefe102bf025c00000000"); + + // Normal operation + // TODO payment WIP + // WIP let _pay_amount = 3000; + // WIP utils::create_invoice_from_channelmanager(nodes[a].node, nodes[a].keys_manager, nodes[a].logger.clone(), Currency::Bitcoin, Some(pay_amount), "Test_payment_1".to_string(), 999999, None); + + // close channel + nodes[i].node.close_channel(&channel_id, &nodes[a].node.get_our_node_id()).unwrap(); + let node0_shutdown_message = get_event_msg!(nodes[i], MessageSendEvent::SendShutdown, nodes[a].node.get_our_node_id()); + nodes[a].node.handle_shutdown(&nodes[i].node.get_our_node_id(), &node0_shutdown_message); + let nodes_1_shutdown = get_event_msg!(nodes[a], MessageSendEvent::SendShutdown, nodes[i].node.get_our_node_id()); + nodes[i].node.handle_shutdown(&nodes[a].node.get_our_node_id(), &nodes_1_shutdown); + let _ = get_event_msg!(nodes[i], MessageSendEvent::SendClosingSigned, nodes[a].node.get_our_node_id()); +} + +fn verify_signature(msg: &Vec, sig: &Vec, pubkey: &PublicKey) -> Result<(), String> { + let m = Message::from_slice(&msg).unwrap(); + let s = Signature::from_der(&sig).unwrap(); + let ctx = Secp256k1::new(); + match ctx.verify_ecdsa(&m, &s, &pubkey) { + Ok(_) => Ok(()), + Err(e) => Err(format!("Signature verification failed! err {} msg {} sig {} pk {}", e, hex::encode(&msg), hex::encode(&sig), &pubkey.serialize().to_hex())), + } +} + +/// #SPLICING +/// Verify the previous funding input on a splicing funding transaction +fn verify_splice_funding_input(splice_tx: &Transaction, prev_funding_txid: &Txid, prev_funding_value: u64, funding_key_1: &PublicKey, funding_key_2: &PublicKey) { + // check that the previous funding tx is an input + let mut prev_fund_input_idx: Option = None; + for idx in 0..splice_tx.input.len() { + if splice_tx.input[idx].previous_output.txid == *prev_funding_txid { + prev_fund_input_idx = Some(idx); + } + } + if prev_fund_input_idx.is_none() { + panic!("Splice tx should contain the pervious funding tx as input! {} {}", prev_funding_txid, splice_tx.encode().to_hex()); + } + let prev_fund_input = &splice_tx.input[prev_fund_input_idx.unwrap()]; + let witness = &prev_fund_input.witness.to_vec(); + let witness_count = witness.len(); + let expected_witness_count = 4; + if witness_count != expected_witness_count { + println!("Prev fund tx inp {:?}", prev_fund_input); + panic!("Prev funding tx input should have {} witness elements! {}", expected_witness_count, witness_count); + } + if witness[0].len() != 0 { + panic!("First multisig witness should be empty! {}", witness[0].len()); + } + // check witness 1&2, signatures + let wit1_sig = &witness[1]; + let wit2_sig = &witness[2]; + if wit1_sig.len() < 70 || wit1_sig.len() > 72 || wit2_sig.len() < 70 || wit2_sig.len() > 72 { + panic!("Witness entries 2&3 should signatures! {} {}", hex::encode(wit1_sig), hex::encode(wit2_sig)); + } + if wit1_sig[wit1_sig.len()-1] != 1 || wit2_sig[wit2_sig.len()-1] != 1 { + panic!("Witness entries 2&3 should be signatures with SIGHASHALL! {} {}", hex::encode(wit1_sig), hex::encode(wit2_sig)); + } + let (script_key1, script_key2) = verify_multisig_redeem_script(&witness[3], funding_key_1, funding_key_2); + let redeemscript = Script::from(witness[3].to_vec()); + // check signatures, sigs are in same order as keys + let sighash = &SighashCache::new(splice_tx).segwit_signature_hash(prev_fund_input_idx.unwrap(), &redeemscript, prev_funding_value, EcdsaSighashType::All).unwrap()[..].to_vec(); + let sig1 = wit1_sig[0..(wit1_sig.len()-1)].to_vec(); + let sig2 = wit2_sig[0..(wit2_sig.len()-1)].to_vec(); + if let Err(e1) = verify_signature(sighash, &sig1, &script_key1) { + panic!("Sig 1 check fails {}", e1); + } + if let Err(e2) = verify_signature(sighash, &sig2, &script_key2) { + panic!("Sig 2 check fails {}", e2); + } +} + +/// #SPLICING +/// Do checks on a splice funding tx +fn verify_splice_funding_tx(splice_tx: &Transaction, prev_funding_txid: &Txid, funding_value: u64, prev_funding_value: u64, funding_key_1: &PublicKey, funding_key_2: &PublicKey) { + verify_splice_funding_input(splice_tx, prev_funding_txid, prev_funding_value, funding_key_1, funding_key_2); + verify_funding_output(splice_tx, funding_value, funding_key_1, funding_key_2); +} + +/// #SPLICING Builds on test_channel_open_simple() +/// Splicing test, simple splice-in flow. Starts with opening a channel first. +#[test] +fn test_splice_in_simple() { + // Stand up a network of 2 nodes + let announced_channel = true; + let cfg = UserConfig { + channel_handshake_config: ChannelHandshakeConfig { + announced_channel, + ..Default::default() + }, + ..Default::default() + }; + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, Some(cfg)]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + // Initiator and Acceptor node indices. Order matters, we want the case when initiator pubkey is larger. + let i = 0; + let a = 1; + + // Instantiate channel parameters where we push the maximum msats given our funding satoshis + let channel_value_sat = 100000; // same as funding satoshis + let push_msat = 0; + + // Have node0 initiate a channel to node1 with aforementioned parameters + let _res = nodes[i].node.create_channel(nodes[a].node.get_our_node_id(), channel_value_sat, push_msat, 42, None).unwrap(); + + // Extract the channel open message from node0 to node1 + let open_channel_message = get_event_msg!(nodes[i], MessageSendEvent::SendOpenChannel, nodes[a].node.get_our_node_id()); + + let _res = nodes[a].node.handle_open_channel(&nodes[i].node.get_our_node_id(), &open_channel_message.clone()); + // Extract the accept channel message from node1 to node0 + let accept_channel_message = get_event_msg!(nodes[a], MessageSendEvent::SendAcceptChannel, nodes[i].node.get_our_node_id()); + let _res = nodes[i].node.handle_accept_channel(&nodes[a].node.get_our_node_id(), &accept_channel_message.clone()); + // Note: FundingGenerationReady emitted, checked and used below + // Create funding tx + let (temporary_channel_id, funding_tx, _funding_output) = create_funding_transaction(&nodes[i], &nodes[a].node.get_our_node_id(), channel_value_sat, 42); + assert_eq!(funding_tx.encode().len(), 55); + + // Funding transaction has been created, provide it + let _res = nodes[i].node.funding_transaction_generated(&temporary_channel_id, &nodes[a].node.get_our_node_id(), funding_tx.clone()).unwrap(); + + let funding_created_message = get_event_msg!(nodes[i], MessageSendEvent::SendFundingCreated, nodes[a].node.get_our_node_id()); + let _res = nodes[a].node.handle_funding_created(&nodes[i].node.get_our_node_id(), &funding_created_message); + + let funding_signed_message = get_event_msg!(nodes[a], MessageSendEvent::SendFundingSigned, nodes[i].node.get_our_node_id()); + let _res = nodes[i].node.handle_funding_signed(&nodes[a].node.get_our_node_id(), &funding_signed_message); + // Take new channel ID + let channel_id = funding_signed_message.channel_id; + + // Check that funding transaction has been broadcasted + assert_eq!(chanmon_cfgs[i].tx_broadcaster.txn_broadcasted.lock().unwrap().len(), 1); + let broadcasted_funding_tx = chanmon_cfgs[i].tx_broadcaster.txn_broadcasted.lock().unwrap()[0].clone(); + assert_eq!(broadcasted_funding_tx.encode().len(), 55); + assert_eq!(broadcasted_funding_tx.txid(), funding_tx.txid()); + assert_eq!(broadcasted_funding_tx.encode(), funding_tx.encode()); + + check_added_monitors!(nodes[i], 1); + let _ev = get_event!(nodes[i], Event::ChannelPending); + check_added_monitors!(nodes[a], 1); + let _ev = get_event!(nodes[a], Event::ChannelPending); + + confirm_transaction(&nodes[i], &broadcasted_funding_tx); + let channel_ready_message = get_event_msg!(nodes[i], MessageSendEvent::SendChannelReady, nodes[a].node.get_our_node_id()); + + confirm_transaction(&nodes[a], &broadcasted_funding_tx); + let channel_ready_message2 = get_event_msg!(nodes[a], MessageSendEvent::SendChannelReady, nodes[i].node.get_our_node_id()); + + let _res = nodes[a].node.handle_channel_ready(&nodes[i].node.get_our_node_id(), &channel_ready_message); + let _ev = get_event!(nodes[a], Event::ChannelReady); + let _announcement_signatures = get_event_msg!(nodes[a], MessageSendEvent::SendAnnouncementSignatures, nodes[i].node.get_our_node_id()); + + let _res = nodes[i].node.handle_channel_ready(&nodes[a].node.get_our_node_id(), &channel_ready_message2); + let _ev = get_event!(nodes[i], Event::ChannelReady); + let _announcement_signatures = get_event_msg!(nodes[i], MessageSendEvent::SendAnnouncementSignatures, nodes[a].node.get_our_node_id()); + + // check channel capacity and other parameters + assert_eq!(nodes[i].node.list_channels().len(), 1); + let channel = &nodes[i].node.list_channels()[0]; + { + assert!(channel.is_usable); + assert!(channel.is_channel_ready); + assert_eq!(channel.channel_value_satoshis, channel_value_sat); + assert_eq!(channel.outbound_capacity_msat, 100000000 - 1000000); + assert_eq!(channel.funding_txo.unwrap().txid, funding_tx.txid()); + assert_eq!(channel.confirmations.unwrap(), 10); + } + + let initiator_funding_key = get_funding_key(&nodes[i], &nodes[a], &channel.channel_id); + let acceptor_funding_key = get_funding_key(&nodes[a], &nodes[i], &channel.channel_id); + + verify_funding_tx(&broadcasted_funding_tx, channel_value_sat, &initiator_funding_key, &acceptor_funding_key); + assert_eq!(broadcasted_funding_tx.encode().to_hex(), + "0000000000010001a08601000000000022002034c0cc0ad0dd5fe61dcf7ef58f995e3d34f8dbd24aa2a6fae68fefe102bf025c00000000"); + + // Start of Splicing ... + + // Although this condition only depends on the test nodes used (and their order), + // we verify it here to make sure we test the case when the initiator funding pubkey is the 'larger', + // as this is the more error prone case (e.g. signature order) + assert!(initiator_funding_key > acceptor_funding_key); + + // Amount being added to the channel through the splice-in + let splice_in_sats: u64 = 20000; + let funding_feerate_perkw = 1024; // TODO + let locktime = 0; // TODO + + // Initiate splice-in (on node0) + let _res = nodes[i].node.splice_channel(&channel_id, &nodes[a].node.get_our_node_id(), splice_in_sats as i64, funding_feerate_perkw, locktime).unwrap(); + // Extract the splice message from node0 to node1 + let splice_message = get_event_msg!(nodes[i], MessageSendEvent::SendSplice, nodes[a].node.get_our_node_id()); + + let _res = nodes[a].node.handle_splice(&nodes[i].node.get_our_node_id(), &splice_message); + // Extract the splice_ack message from node1 to node0 + let splice_ack_message = get_event_msg!(nodes[a], MessageSendEvent::SendSpliceAck, nodes[i].node.get_our_node_id()); + + let _res = nodes[i].node.handle_splice_ack(&nodes[a].node.get_our_node_id(), &splice_ack_message); + // Note: SpliceAcked emitted, checked and used below + + // Create splice tx + let post_splice_channel_value = channel_value_sat + splice_in_sats; + let (splice_tx, _funding_output) = create_splice_in_transaction(&nodes[i], &channel_id, post_splice_channel_value); + assert_eq!(splice_tx.encode().len(), 94); + + // Splice transaction has been created, provide it + let _res = nodes[i].node.splice_transaction_generated(&channel_id, &nodes[a].node.get_our_node_id(), splice_tx.clone()).unwrap(); + // Extract the splice_created message from node0 to node1 + let splice_created_message = get_event_msg!(nodes[i], MessageSendEvent::SendSpliceCreated, nodes[a].node.get_our_node_id()); + let _res = nodes[a].node.handle_splice_created(&nodes[i].node.get_our_node_id(), &splice_created_message); + + let tx_complete_message = get_event_msg!(nodes[a], MessageSendEvent::SendTxComplete, nodes[i].node.get_our_node_id()); + let _res = nodes[i].node.handle_tx_complete(&nodes[a].node.get_our_node_id(), &tx_complete_message); + + let splice_comm_signed_message = get_event_msg!(nodes[i], MessageSendEvent::SendSpliceCommSigned, nodes[a].node.get_our_node_id()); + let _res = nodes[a].node.handle_splice_comm_signed(&nodes[i].node.get_our_node_id(), &splice_comm_signed_message); + + let splice_comm_ack_message = get_event_msg!(nodes[a], MessageSendEvent::SendSpliceCommAck, nodes[i].node.get_our_node_id()); + let _res = nodes[i].node.handle_splice_comm_ack(&nodes[a].node.get_our_node_id(), &splice_comm_ack_message); + + let splice_signed_message = get_event_msg!(nodes[i], MessageSendEvent::SendSpliceSigned, nodes[a].node.get_our_node_id()); + let _res = nodes[a].node.handle_splice_signed(&nodes[i].node.get_our_node_id(), &splice_signed_message); + + let splice_signed_ack_message = get_event_msg!(nodes[a], MessageSendEvent::SendSpliceSignedAck, nodes[i].node.get_our_node_id()); + let _res = nodes[i].node.handle_splice_signed_ack(&nodes[a].node.get_our_node_id(), &splice_signed_ack_message); + + // Check that signed splice funding transaction has been broadcasted + assert_eq!(chanmon_cfgs[i].tx_broadcaster.txn_broadcasted.lock().unwrap().len(), 2); + let broadcasted_splice_tx = chanmon_cfgs[i].tx_broadcaster.txn_broadcasted.lock().unwrap()[1].clone(); + assert!(broadcasted_splice_tx.encode().len() >= 314 && broadcasted_splice_tx.encode().len() <= 315); + assert_eq!(broadcasted_splice_tx.txid(), splice_tx.txid()); + assert_ne!(broadcasted_splice_tx.encode(), splice_tx.encode()); + verify_splice_funding_tx(&broadcasted_splice_tx, &broadcasted_funding_tx.txid(), post_splice_channel_value, channel_value_sat, &initiator_funding_key, &acceptor_funding_key); + assert_eq!(broadcasted_splice_tx.encode().to_hex(), + "0000000000010174c52ab4f11296d62b66a6dba9513b04a3e7fb5a09a30cee22fce7294ab55b7e0000000000fdffffff01c0d401000000000022002034c0cc0ad0dd5fe61dcf7ef58f995e3d34f8dbd24aa2a6fae68fefe102bf025c0400473044022021caaa9ce61a6f7213b1c5a35f5d34cf57ce2c38b9e38edde90d7a159ac77f42022037f1dce5947b36f5ae127bf174b9cff95fa9043ea581a9d47c25efab3b6c549301473044022019f6ee98b4a1fdbcbca241151483185c2a67d4d2ececfd59acc570df885ee3a80220508e4aa3a45ec859da0e3c112b4754142babe5435900267fe305d381f48a6fbb014752210307a78def56cba9fc4db22a25928181de538ee59ba1a475ae113af7790acd0db32103c21e841cbc0b48197d060c71e116c185fa0ac281b7d0aa5924f535154437ca3b52ae00000000"); + + check_added_monitors!(nodes[i], 1); + check_added_monitors!(nodes[a], 1); + + confirm_transaction(&nodes[i], &broadcasted_splice_tx); + let channel_ready_message = get_event_msg!(nodes[i], MessageSendEvent::SendChannelReady, nodes[a].node.get_our_node_id()); + + confirm_transaction(&nodes[a], &broadcasted_splice_tx); + let channel_ready_message2 = get_event_msg!(nodes[a], MessageSendEvent::SendChannelReady, nodes[i].node.get_our_node_id()); + + let _res = nodes[a].node.handle_channel_ready(&nodes[i].node.get_our_node_id(), &channel_ready_message); + let _channel_update = get_event_msg!(nodes[a], MessageSendEvent::SendChannelUpdate, nodes[i].node.get_our_node_id()); + + let _res = nodes[i].node.handle_channel_ready(&nodes[a].node.get_our_node_id(), &channel_ready_message2); + let _channel_update = get_event_msg!(nodes[i], MessageSendEvent::SendChannelUpdate, nodes[a].node.get_our_node_id()); + + // check new channel capacity and other parameters + assert_eq!(nodes[i].node.list_channels().len(), 1); + { + let channel = &nodes[i].node.list_channels()[0]; + assert!(channel.is_usable); + assert!(channel.is_channel_ready); + assert_eq!(channel.channel_value_satoshis, post_splice_channel_value); + assert_eq!(channel.outbound_capacity_msat, 120000000 - 1000000); + assert_eq!(channel.funding_txo.unwrap().txid, splice_tx.txid()); + assert_eq!(channel.confirmations.unwrap(), 10); + } + + // ... End of Splicing + + // close channel + nodes[i].node.close_channel(&channel_id, &nodes[a].node.get_our_node_id()).unwrap(); + let node0_shutdown_message = get_event_msg!(nodes[i], MessageSendEvent::SendShutdown, nodes[a].node.get_our_node_id()); + nodes[a].node.handle_shutdown(&nodes[i].node.get_our_node_id(), &node0_shutdown_message); + let nodes_1_shutdown = get_event_msg!(nodes[a], MessageSendEvent::SendShutdown, nodes[i].node.get_our_node_id()); + nodes[i].node.handle_shutdown(&nodes[a].node.get_our_node_id(), &nodes_1_shutdown); + let _ = get_event_msg!(nodes[i], MessageSendEvent::SendClosingSigned, nodes[a].node.get_our_node_id()); +} + #[test] fn test_insane_channel_opens() { // Stand up a network of 2 nodes diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index a68ac352185..96676f4f9c7 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -29,6 +29,7 @@ use bitcoin::secp256k1::PublicKey; use bitcoin::secp256k1::ecdsa::Signature; use bitcoin::{secp256k1, Witness}; use bitcoin::blockdata::script::Script; +use bitcoin::blockdata::transaction::Transaction; use bitcoin::hash_types::{Txid, BlockHash}; use crate::blinded_path::payment::ReceiveTlvs; @@ -438,6 +439,142 @@ pub struct ChannelReady { pub short_channel_id_alias: Option, } +/// #SPLICING Inspired by OpenChannel, Shutdown +/// A splice message to be sent by or received from the splice initiator. +/// TODO(splicing): Is using 'splice initiator' role OK? +/// TODO(splicing): Can the channel acceptor later be the splice initiator? +/// +// TODO(splicing): Add spec link for `splice`; still in draft, using from https://github.com/lightning/bolts/pull/863 +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Splice { + /// The channel ID where splicing is intended + pub channel_id: ChannelId, + /// The genesis hash of the blockchain where the channel is intended to be spliced + pub chain_hash: BlockHash, + /// The intended change in channel capacity: the amount to be added (positive value) + /// or removed (negative value) by the sender (splice initiator) by splicing into/from the channel. + pub relative_satoshis: i64, + /// The feerate for the new funding transaction, set by the splice initiator + pub funding_feerate_perkw: u32, + /// The locktime for the new funding transaction + pub locktime: u32, + /// The key of the sender (splice initiator) controlling the new funding transaction + pub funding_pubkey: PublicKey, +} + +/// #SPLICING +/// A splice_ack message to be received by or sent to the splice initiator. +/// +// TODO(splicing): Add spec link for `splice_ack`; still in draft, using from https://github.com/lightning/bolts/pull/863 +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SpliceAck { + /// The channel ID where splicing is intended + pub channel_id: ChannelId, + /// The genesis hash of the blockchain where the channel is intended to be spliced + pub chain_hash: BlockHash, + /// The intended change in channel capacity: the amount to be added (positive value) + /// or removed (negative value) by the sender (splice acceptor) by splicing into/from the channel. + pub relative_satoshis: i64, + /// The key of the sender (splice acceptor) controlling the new funding transaction + pub funding_pubkey: PublicKey, +} + +/// A splice_locked message to be sent to or received from a peer. +/// +// TODO(splicing): Add spec link for `splice_locked`; still in draft, using from https://github.com/lightning/bolts/pull/863 +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SpliceLocked { + /// The channel ID + pub channel_id: ChannelId, +} + +/// #SPLICING +/// A [`splice_created`] message to be sent to or received from a peer. +/// Contains details of the splicing transaction +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SpliceCreated { + /// The channel ID + pub channel_id: ChannelId, + /// The splicing transaction ID (not yet signed nor broadcast) + pub splice_txid: Txid, + /// The specific output index funding this channel + pub funding_output_index: u16, + /// The complete splice funding transaction, used for signing by the other party. Not needed in final version with tx negotiation, TODO remove + pub splice_transaction: Transaction, + /// The input index in the splice transaction that is the previous funding transaction, used by the other party for signing. + /// It could be also omitted and found by looking for the previous funding tx among the inputs. + /// Not needed in final version with tx negotiation, TODO remove + pub splice_prev_funding_input_index: u16, + /// The value of the previous funding transaction, the previous channel capacity + pub splice_prev_funding_input_value: u64, + /* + #[cfg(taproot)] + /// The partial signature of the channel initiator (funder) + pub partial_signature_with_nonce: Option, + #[cfg(taproot)] + /// Next nonce the channel acceptor should use to finalize the funding output signature + pub next_local_nonce: Option + */ +} + +/// #SPLICING +/// A [`splice_comm_signed`] message to be sent to or received from a peer. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SpliceCommSigned { + /// The channel ID + pub channel_id: ChannelId, + /// A signature on the commitment transaction + pub signature: Signature, + // pub htlc_signatures: Vec, + // pub partial_signature_with_nonce: Option, +} + +/// #SPLICING +/// A [`splice_comm_ack`] message to be sent to or received from a peer. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SpliceCommAck { + /// The channel ID + pub channel_id: ChannelId, + /// A signature on the commitment transaction + pub signature: Signature, + // pub htlc_signatures: Vec, + // pub partial_signature_with_nonce: Option, +} + +/// #SPLICING +/// A [`splice_signed`] message to be sent to or received from a peer. There are two things signed here: the previous funding tx input and the the commitment transaction. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SpliceSigned { + /// The channel ID + pub channel_id: ChannelId, + /// The signature of the splice acceptor (fundee) on the splicing transaction. + /// This should be the result of transaction negotiation, and not needed here, it is needed only in the prototype, TODO remove it later. + /// Not to be confused with the `signature` field. + pub funding_signature: Signature, + /* + #[cfg(taproot)] + /// The partial signature of the channel acceptor (fundee) + pub partial_signature_with_nonce: Option, + */ +} + +/// #SPLICING +/// A [`splice_signed_ack`] message to be sent to or received from a peer. There are two things signed here: the previous funding tx input and the the commitment transaction. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SpliceSignedAck { + /// The channel ID + pub channel_id: ChannelId, + /// The signature of the splice acceptor (fundee) on the splicing transaction. + /// This should be the result of transaction negotiation, and not needed here, it is needed only in the prototype, TODO remove it later. + /// Not to be confused with the `signature` field. + pub funding_signature: Signature, + /* + #[cfg(taproot)] + /// The partial signature of the channel acceptor (fundee) + pub partial_signature_with_nonce: Option, + */ +} + /// A tx_add_input message for adding an input during interactive transaction construction /// // TODO(dual_funding): Add spec link for `tx_add_input`. @@ -517,6 +654,8 @@ pub struct TxSignatures { pub tx_hash: Txid, /// The list of witnesses pub witnesses: Vec, + /// Optional signature for shared funding outpoint input (signed by both parties) + pub tlvs: Option, } /// A tx_init_rbf message which initiates a replacement of the transaction after it's been @@ -1346,6 +1485,25 @@ pub trait ChannelMessageHandler : MessageSendEventsProvider { /// Handle an incoming `closing_signed` message from the given peer. fn handle_closing_signed(&self, their_node_id: &PublicKey, msg: &ClosingSigned); + // Splicing + // #SPLICING + /// Handle an incoming `splice` message from the given peer. + fn handle_splice(&self, their_node_id: &PublicKey, msg: &Splice); + /// Handle an incoming `splice_ack` message from the given peer. + fn handle_splice_ack(&self, their_node_id: &PublicKey, msg: &SpliceAck); + /// Handle an incoming `splice_locked` message from the given peer. + fn handle_splice_locked(&self, their_node_id: &PublicKey, msg: &SpliceLocked); + /// Handle an incoming `splice_created` message from the given peer. + fn handle_splice_created(&self, their_node_id: &PublicKey, msg: &SpliceCreated); + /// Handle an incoming `splice_commm_signed` message from the given peer. + fn handle_splice_comm_signed(&self, their_node_id: &PublicKey, msg: &SpliceCommSigned); + /// Handle an incoming `splice_commm_ack` message from the given peer. + fn handle_splice_comm_ack(&self, their_node_id: &PublicKey, msg: &SpliceCommAck); + /// Handle an incoming `splice_signed` message from the given peer. + fn handle_splice_signed(&self, their_node_id: &PublicKey, msg: &SpliceSigned); + /// Handle an incoming `splice_signed_ack` message from the given peer. + fn handle_splice_signed_ack(&self, their_node_id: &PublicKey, msg: &SpliceSignedAck); + // Interactive channel construction /// Handle an incoming `tx_add_input message` from the given peer. fn handle_tx_add_input(&self, their_node_id: &PublicKey, msg: &TxAddInput); @@ -1745,6 +1903,65 @@ impl_writeable_msg!(AcceptChannelV2, { (2, require_confirmed_inputs, option), }); +// #SPLICING Inspired by OpenChannel +impl_writeable_msg!(Splice, { + channel_id, + chain_hash, + relative_satoshis, + funding_feerate_perkw, + locktime, + funding_pubkey, +}, {}); + +// #SPLICING +impl_writeable_msg!(SpliceAck, { + channel_id, + chain_hash, + relative_satoshis, + funding_pubkey, +}, {}); + +impl_writeable_msg!(SpliceLocked, { + channel_id, +}, {}); + +// #SPLICING +// #[cfg(not(taproot))] +impl_writeable_msg!(SpliceCreated, { + channel_id, + splice_txid, + funding_output_index, + splice_transaction, + splice_prev_funding_input_index, + splice_prev_funding_input_value, +}, {}); + +// #SPLICING +impl_writeable_msg!(SpliceCommSigned, { + channel_id, + signature, +}, {}); + +// #SPLICING +impl_writeable_msg!(SpliceCommAck, { + channel_id, + signature, +}, {}); + +// #SPLICING +// #[cfg(not(taproot))] +impl_writeable_msg!(SpliceSigned, { + channel_id, + funding_signature, +}, {}); + +// #SPLICING +// #[cfg(not(taproot))] +impl_writeable_msg!(SpliceSignedAck, { + channel_id, + funding_signature, +}, {}); + impl_writeable_msg!(TxAddInput, { channel_id, serial_id, @@ -1778,7 +1995,9 @@ impl_writeable_msg!(TxSignatures, { channel_id, tx_hash, witnesses, -}, {}); +}, { + (0, tlvs, option), +}); impl_writeable_msg!(TxInitRbf, { channel_id, @@ -3296,6 +3515,46 @@ mod tests { assert_eq!(encoded_value, target_value); } + /// #SPLICING + #[test] + fn encoding_splice() { + let secp_ctx = Secp256k1::new(); + let (_, pubkey_1,) = get_keys_from!("0101010101010101010101010101010101010101010101010101010101010101", secp_ctx); + let splice = msgs::Splice { + chain_hash: BlockHash::from_hex("6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000").unwrap(), + channel_id: ChannelId::from_bytes([2; 32]), + relative_satoshis: -123456, + funding_feerate_perkw: 2000, + locktime: 0, + funding_pubkey: pubkey_1, + }; + let encoded_value = splice.encode(); + assert_eq!(hex::encode(encoded_value), "0202020202020202020202020202020202020202020202020202020202020202000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26ffffffffffffe1dc0000007d000000000031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f"); + } + + #[test] + fn encoding_splice_ack() { + let secp_ctx = Secp256k1::new(); + let (_, pubkey_1,) = get_keys_from!("0101010101010101010101010101010101010101010101010101010101010101", secp_ctx); + let splice = msgs::SpliceAck { + chain_hash: BlockHash::from_hex("6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000").unwrap(), + channel_id: ChannelId::from_bytes([2; 32]), + relative_satoshis: -123456, + funding_pubkey: pubkey_1, + }; + let encoded_value = splice.encode(); + assert_eq!(hex::encode(encoded_value), "0202020202020202020202020202020202020202020202020202020202020202000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26ffffffffffffe1dc0031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f"); + } + + #[test] + fn encoding_splice_locked() { + let splice = msgs::SpliceLocked { + channel_id: ChannelId::from_bytes([2; 32]), + }; + let encoded_value = splice.encode(); + assert_eq!(hex::encode(encoded_value), "0202020202020202020202020202020202020202020202020202020202020202"); + } + #[test] fn encoding_tx_add_input() { let tx_add_input = msgs::TxAddInput { @@ -3378,6 +3637,10 @@ mod tests { #[test] fn encoding_tx_signatures() { + let secp_ctx = Secp256k1::new(); + let (privkey_1, _) = get_keys_from!("0101010101010101010101010101010101010101010101010101010101010101", secp_ctx); + let sig_1 = get_sig_on!(privkey_1, secp_ctx, String::from("01010101010101010101010101010101")); + let tx_signatures = msgs::TxSignatures { channel_id: ChannelId::from_bytes([2; 32]), tx_hash: Txid::from_hex("c2d4449afa8d26140898dd54d3390b057ba2a5afcf03ba29d7dc0d8b9ffe966e").unwrap(), @@ -3389,6 +3652,7 @@ mod tests { hex::decode("3045022100ee00dbf4a862463e837d7c08509de814d620e4d9830fa84818713e0fa358f145022021c3c7060c4d53fe84fd165d60208451108a778c13b92ca4c6bad439236126cc01").unwrap(), hex::decode("028fbbf0b16f5ba5bcb5dd37cd4047ce6f726a21c06682f9ec2f52b057de1dbdb5").unwrap()]), ], + tlvs: Some(sig_1), }; let encoded_value = tx_signatures.encode(); let mut target_value = hex::decode("0202020202020202020202020202020202020202020202020202020202020202").unwrap(); // channel_id @@ -3408,7 +3672,9 @@ mod tests { target_value.append(&mut hex::decode("3045022100ee00dbf4a862463e837d7c08509de814d620e4d9830fa84818713e0fa358f145022021c3c7060c4d53fe84fd165d60208451108a778c13b92ca4c6bad439236126cc01").unwrap()); target_value.append(&mut hex::decode("21").unwrap()); // len of witness element data (VarInt) target_value.append(&mut hex::decode("028fbbf0b16f5ba5bcb5dd37cd4047ce6f726a21c06682f9ec2f52b057de1dbdb5").unwrap()); - assert_eq!(encoded_value, target_value); + target_value.append(&mut hex::decode("0040").unwrap()); // type and len (64) + target_value.append(&mut hex::decode("d977cb9b53d93a6ff64bb5f1e158b4094b66e798fb12911168a3ccdf80a83096340a6a95da0ae8d9f776528eecdbb747eb6b545495a4319ed5378e35b21e073a").unwrap()); + assert_eq!(hex::encode(encoded_value), hex::encode(target_value)); } fn do_encoding_tx_init_rbf(funding_value_with_hex_target: Option<(i64, &str)>) { diff --git a/lightning/src/ln/peer_handler.rs b/lightning/src/ln/peer_handler.rs index 92826244e09..4e81affef04 100644 --- a/lightning/src/ln/peer_handler.rs +++ b/lightning/src/ln/peer_handler.rs @@ -227,6 +227,31 @@ impl ChannelMessageHandler for ErroringMessageHandler { fn handle_closing_signed(&self, their_node_id: &PublicKey, msg: &msgs::ClosingSigned) { ErroringMessageHandler::push_error(self, their_node_id, msg.channel_id); } + // #SPLICING + fn handle_splice(&self, their_node_id: &PublicKey, msg: &msgs::Splice) { + ErroringMessageHandler::push_error(&self, their_node_id, msg.channel_id); + } + fn handle_splice_ack(&self, their_node_id: &PublicKey, msg: &msgs::SpliceAck) { + ErroringMessageHandler::push_error(&self, their_node_id, msg.channel_id); + } + fn handle_splice_locked(&self, their_node_id: &PublicKey, msg: &msgs::SpliceLocked) { + ErroringMessageHandler::push_error(&self, their_node_id, msg.channel_id); + } + fn handle_splice_created(&self, their_node_id: &PublicKey, msg: &msgs::SpliceCreated) { + ErroringMessageHandler::push_error(self, their_node_id, msg.channel_id); + } + fn handle_splice_comm_signed(&self, their_node_id: &PublicKey, msg: &msgs::SpliceCommSigned) { + ErroringMessageHandler::push_error(self, their_node_id, msg.channel_id); + } + fn handle_splice_comm_ack(&self, their_node_id: &PublicKey, msg: &msgs::SpliceCommAck) { + ErroringMessageHandler::push_error(self, their_node_id, msg.channel_id); + } + fn handle_splice_signed(&self, their_node_id: &PublicKey, msg: &msgs::SpliceSigned) { + ErroringMessageHandler::push_error(self, their_node_id, msg.channel_id); + } + fn handle_splice_signed_ack(&self, their_node_id: &PublicKey, msg: &msgs::SpliceSignedAck) { + ErroringMessageHandler::push_error(self, their_node_id, msg.channel_id); + } fn handle_update_add_htlc(&self, their_node_id: &PublicKey, msg: &msgs::UpdateAddHTLC) { ErroringMessageHandler::push_error(self, their_node_id, msg.channel_id); } @@ -254,7 +279,7 @@ impl ChannelMessageHandler for ErroringMessageHandler { fn handle_channel_reestablish(&self, their_node_id: &PublicKey, msg: &msgs::ChannelReestablish) { ErroringMessageHandler::push_error(self, their_node_id, msg.channel_id); } - // msgs::ChannelUpdate does not contain the channel_id field, so we just drop them. + // msgs::ChannelUpdate does not contain the channel_id field, so we just drop them. fn handle_channel_update(&self, _their_node_id: &PublicKey, _msg: &msgs::ChannelUpdate) {} fn peer_disconnected(&self, _their_node_id: &PublicKey) {} fn peer_connected(&self, _their_node_id: &PublicKey, _init: &msgs::Init, _inbound: bool) -> Result<(), ()> { Ok(()) } @@ -1637,6 +1662,33 @@ impl { + self.message_handler.chan_handler.handle_splice(&their_node_id, &msg); + } + wire::Message::SpliceAck(msg) => { + self.message_handler.chan_handler.handle_splice_ack(&their_node_id, &msg); + } + wire::Message::SpliceLocked(msg) => { + self.message_handler.chan_handler.handle_splice_locked(&their_node_id, &msg); + } + wire::Message::SpliceCreated(msg) => { + self.message_handler.chan_handler.handle_splice_created(&their_node_id, &msg); + }, + wire::Message::SpliceCommSigned(msg) => { + self.message_handler.chan_handler.handle_splice_comm_signed(&their_node_id, &msg); + }, + wire::Message::SpliceCommAck(msg) => { + self.message_handler.chan_handler.handle_splice_comm_ack(&their_node_id, &msg); + }, + wire::Message::SpliceSigned(msg) => { + self.message_handler.chan_handler.handle_splice_signed(&their_node_id, &msg); + }, + wire::Message::SpliceSignedAck(msg) => { + self.message_handler.chan_handler.handle_splice_signed_ack(&their_node_id, &msg); + }, + // Interactive transaction construction messages: wire::Message::TxAddInput(msg) => { self.message_handler.chan_handler.handle_tx_add_input(&their_node_id, &msg); @@ -1956,6 +2008,58 @@ impl { + log_debug!(self.logger, "Handling SendSplice event in peer_handler for node {} for channel {}", + log_pubkey!(node_id), + &msg.channel_id); + self.enqueue_message(&mut *get_peer_for_forwarding!(node_id), msg); + } + MessageSendEvent::SendSpliceAck { ref node_id, ref msg} => { + log_debug!(self.logger, "Handling SendSpliceAck event in peer_handler for node {} for channel {}", + log_pubkey!(node_id), + &msg.channel_id); + self.enqueue_message(&mut *get_peer_for_forwarding!(node_id), msg); + } + MessageSendEvent::SendSpliceLocked { ref node_id, ref msg} => { + log_debug!(self.logger, "Handling SendSpliceLocked event in peer_handler for node {} for channel {}", + log_pubkey!(node_id), + &msg.channel_id); + self.enqueue_message(&mut *get_peer_for_forwarding!(node_id), msg); + } + MessageSendEvent::SendSpliceCreated { ref node_id, ref msg } => { + log_debug!(self.logger, "Handling SendSpliceCreated event in peer_handler for node {} for channel {} (which becomes {})", + log_pubkey!(node_id), + msg.channel_id, + log_funding_channel_id!(msg.splice_txid, msg.funding_output_index)); + // TODO: If the peer is gone we should generate a DiscardFunding event + // indicating to the wallet that they should just throw away this funding transaction + self.enqueue_message(&mut *get_peer_for_forwarding!(node_id), msg); + }, + MessageSendEvent::SendSpliceCommSigned { ref node_id, ref msg } => { + log_debug!(self.logger, "Handling SendCommSigned event in peer_handler for node {} for channel {}", + log_pubkey!(node_id), + &msg.channel_id); + self.enqueue_message(&mut *get_peer_for_forwarding!(node_id), msg); + }, + MessageSendEvent::SendSpliceCommAck { ref node_id, ref msg } => { + log_debug!(self.logger, "Handling SendCommAck event in peer_handler for node {} for channel {}", + log_pubkey!(node_id), + &msg.channel_id); + self.enqueue_message(&mut *get_peer_for_forwarding!(node_id), msg); + }, + MessageSendEvent::SendSpliceSigned { ref node_id, ref msg } => { + log_debug!(self.logger, "Handling SendSpliceSigned event in peer_handler for node {} for channel {}", + log_pubkey!(node_id), + msg.channel_id); + self.enqueue_message(&mut *get_peer_for_forwarding!(node_id), msg); + }, + MessageSendEvent::SendSpliceSignedAck { ref node_id, ref msg } => { + log_debug!(self.logger, "Handling SendSpliceSignedAck event in peer_handler for node {} for channel {}", + log_pubkey!(node_id), + msg.channel_id); + self.enqueue_message(&mut *get_peer_for_forwarding!(node_id), msg); + }, MessageSendEvent::SendTxAddInput { ref node_id, ref msg } => { log_debug!(self.logger, "Handling SendTxAddInput event in peer_handler for node {} for channel {}", log_pubkey!(node_id), diff --git a/lightning/src/ln/wire.rs b/lightning/src/ln/wire.rs index 88e2ad7c1da..032024f6ce6 100644 --- a/lightning/src/ln/wire.rs +++ b/lightning/src/ln/wire.rs @@ -59,6 +59,15 @@ pub(crate) enum Message where T: core::fmt::Debug + Type + TestEq { AcceptChannelV2(msgs::AcceptChannelV2), FundingCreated(msgs::FundingCreated), FundingSigned(msgs::FundingSigned), + // #SPLICING + Splice(msgs::Splice), + SpliceAck(msgs::SpliceAck), + SpliceLocked(msgs::SpliceLocked), + SpliceCreated(msgs::SpliceCreated), + SpliceCommSigned(msgs::SpliceCommSigned), + SpliceCommAck(msgs::SpliceCommAck), + SpliceSigned(msgs::SpliceSigned), + SpliceSignedAck(msgs::SpliceSignedAck), TxAddInput(msgs::TxAddInput), TxAddOutput(msgs::TxAddOutput), TxRemoveInput(msgs::TxRemoveInput), @@ -110,6 +119,14 @@ impl Writeable for Message where T: core::fmt::Debug + Type + TestEq { &Message::AcceptChannelV2(ref msg) => msg.write(writer), &Message::FundingCreated(ref msg) => msg.write(writer), &Message::FundingSigned(ref msg) => msg.write(writer), + &Message::Splice(ref msg) => msg.write(writer), + &Message::SpliceAck(ref msg) => msg.write(writer), + &Message::SpliceLocked(ref msg) => msg.write(writer), + &Message::SpliceCreated(ref msg) => msg.write(writer), + &Message::SpliceCommSigned(ref msg) => msg.write(writer), + &Message::SpliceCommAck(ref msg) => msg.write(writer), + &Message::SpliceSigned(ref msg) => msg.write(writer), + &Message::SpliceSignedAck(ref msg) => msg.write(writer), &Message::TxAddInput(ref msg) => msg.write(writer), &Message::TxAddOutput(ref msg) => msg.write(writer), &Message::TxRemoveInput(ref msg) => msg.write(writer), @@ -161,6 +178,15 @@ impl Type for Message where T: core::fmt::Debug + Type + TestEq { &Message::AcceptChannelV2(ref msg) => msg.type_id(), &Message::FundingCreated(ref msg) => msg.type_id(), &Message::FundingSigned(ref msg) => msg.type_id(), + // #SPLICING + &Message::Splice(ref msg) => msg.type_id(), + &Message::SpliceAck(ref msg) => msg.type_id(), + &Message::SpliceLocked(ref msg) => msg.type_id(), + &Message::SpliceCreated(ref msg) => msg.type_id(), + &Message::SpliceCommSigned(ref msg) => msg.type_id(), + &Message::SpliceCommAck(ref msg) => msg.type_id(), + &Message::SpliceSigned(ref msg) => msg.type_id(), + &Message::SpliceSignedAck(ref msg) => msg.type_id(), &Message::TxAddInput(ref msg) => msg.type_id(), &Message::TxAddOutput(ref msg) => msg.type_id(), &Message::TxRemoveInput(ref msg) => msg.type_id(), @@ -258,6 +284,31 @@ fn do_read(buffer: &mut R, message_type: u1 msgs::FundingSigned::TYPE => { Ok(Message::FundingSigned(Readable::read(buffer)?)) }, + // #SPLICING + msgs::Splice::TYPE => { + Ok(Message::Splice(Readable::read(buffer)?)) + }, + msgs::SpliceAck::TYPE => { + Ok(Message::SpliceAck(Readable::read(buffer)?)) + }, + msgs::SpliceLocked::TYPE => { + Ok(Message::SpliceLocked(Readable::read(buffer)?)) + }, + msgs::SpliceCreated::TYPE => { + Ok(Message::SpliceCreated(Readable::read(buffer)?)) + }, + msgs::SpliceCommSigned::TYPE => { + Ok(Message::SpliceCommSigned(Readable::read(buffer)?)) + }, + msgs::SpliceCommAck::TYPE => { + Ok(Message::SpliceCommAck(Readable::read(buffer)?)) + }, + msgs::SpliceSigned::TYPE => { + Ok(Message::SpliceSigned(Readable::read(buffer)?)) + }, + msgs::SpliceSignedAck::TYPE => { + Ok(Message::SpliceSignedAck(Readable::read(buffer)?)) + }, msgs::TxAddInput::TYPE => { Ok(Message::TxAddInput(Readable::read(buffer)?)) }, @@ -464,6 +515,52 @@ impl Encode for msgs::AcceptChannelV2 { const TYPE: u16 = 65; } +// #SPLICING +impl Encode for msgs::Splice { + // TODO(splicing) Double check with spec; spec contains 74, which is probably wrong as it is used by tx_Abort; CLN uses 75 + const TYPE: u16 = 75; +} + +// #SPLICING +impl Encode for msgs::SpliceAck { + const TYPE: u16 = 76; +} + +// #SPLICING +impl Encode for msgs::SpliceLocked { + const TYPE: u16 = 77; +} + +// #SPLICING +impl Encode for msgs::SpliceCreated { + // TODO: Made-up value! + const TYPE: u16 = 78; +} + +// #SPLICING +impl Encode for msgs::SpliceCommSigned { + // TODO: Made-up value! + const TYPE: u16 = 80; +} + +// #SPLICING +impl Encode for msgs::SpliceCommAck { + // TODO: Made-up value! + const TYPE: u16 = 81; +} + +// #SPLICING +impl Encode for msgs::SpliceSigned { + // TODO: Made-up value! + const TYPE: u16 = 82; +} + +// #SPLICING +impl Encode for msgs::SpliceSignedAck { + // TODO: Made-up value! + const TYPE: u16 = 83; +} + impl Encode for msgs::TxAddInput { const TYPE: u16 = 66; } diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index 04c4446e2c0..0a7d993df72 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -454,6 +454,10 @@ pub trait ChannelSigner { /// /// channel_parameters.is_populated() MUST be true. fn provide_channel_parameters(&mut self, channel_parameters: &ChannelTransactionParameters); + + /// #SPLICING + /// Similar to provide_channel_parameters(), but can be called a second time (or even more). Also update channel value (capacity) + fn reprovide_channel_parameters(&mut self, channel_parameters: &ChannelTransactionParameters, channel_value_satoshis: u64); } /// A trait to sign Lightning channel transactions as described in @@ -603,6 +607,10 @@ pub trait EcdsaChannelSigner: ChannelSigner { fn sign_channel_announcement_with_funding_key( &self, msg: &UnsignedChannelAnnouncement, secp_ctx: &Secp256k1 ) -> Result; + + /// #SPLICING + /// Create a signature for a splicing funding transaction, for the input which is the previous funding tx. + fn sign_splicing_funding_input(&self, splicing_tx: &Transaction, splice_prev_funding_input_index: u16, splice_prev_funding_input_value: u64, redeem_script: &Script, secp_ctx: &Secp256k1) -> Result; } /// A writeable signer. @@ -1082,6 +1090,16 @@ impl ChannelSigner for InMemorySigner { assert!(channel_parameters.is_populated(), "Channel parameters must be fully populated"); self.channel_parameters = Some(channel_parameters.clone()); } + + /// #SPLICING + fn reprovide_channel_parameters(&mut self, channel_parameters: &ChannelTransactionParameters, channel_value_satoshis: u64) { + assert!(self.channel_parameters.is_some()); + assert!(channel_parameters.is_populated(), "Channel parameters must be fully populated"); + self.channel_parameters = Some(channel_parameters.clone()); + + self.channel_value_satoshis = channel_value_satoshis; + // println!("reprovide_channel_parameters channel_value {}", self.channel_value_satoshis); + } } const MISSING_PARAMS_ERR: &'static str = "ChannelSigner::provide_channel_parameters must be called before signing operations"; @@ -1096,6 +1114,7 @@ impl EcdsaChannelSigner for InMemorySigner { let channel_funding_redeemscript = make_funding_redeemscript(&funding_pubkey, &counterparty_keys.funding_pubkey); let built_tx = trusted_tx.built_transaction(); + // println!("sign_counterparty_commitment tx {:?} {:?} channel_value_satoshis {}", built_tx.txid, built_tx.transaction.encode(), self.channel_value_satoshis); let commitment_sig = built_tx.sign_counterparty_commitment(&self.funding_key, &channel_funding_redeemscript, self.channel_value_satoshis, secp_ctx); let commitment_txid = built_tx.txid; @@ -1225,6 +1244,18 @@ impl EcdsaChannelSigner for InMemorySigner { let msghash = hash_to_message!(&Sha256dHash::hash(&msg.encode()[..])[..]); Ok(secp_ctx.sign_ecdsa(&msghash, &self.funding_key)) } + + /// #SPLICING + /// #SPLICE-SIG + fn sign_splicing_funding_input(&self, splicing_tx: &Transaction, splice_prev_funding_input_index: u16, splice_prev_funding_input_value: u64, redeem_script: &Script, secp_ctx: &Secp256k1) -> Result { + // println!("sign_splicing_funding_input txlen {} idx {} val {} tx {}", splicing_tx.encode().len(), splice_prev_funding_input_index, prev_funding_value, splicing_tx.encode().to_hex()); + let sighash = &sighash::SighashCache::new(splicing_tx).segwit_signature_hash(splice_prev_funding_input_index as usize, &redeem_script, splice_prev_funding_input_value, EcdsaSighashType::All).unwrap()[..]; + let msg = hash_to_message!(sighash); + let sig = sign(secp_ctx, &msg, &self.funding_key); + // println!("sign_splicing_funding_input hash {} msg{} pubkey {} sig {} val {}", sighash.to_hex(), msg.to_hex(), &self.funding_key.public_key(secp_ctx), sig.serialize_der().to_hex(), splice_prev_funding_input_value); + Ok(sig) + } + } const SERIALIZATION_VERSION: u8 = 1; diff --git a/lightning/src/util/test_channel_signer.rs b/lightning/src/util/test_channel_signer.rs index c57c5a9d6fd..fb68aaa5a8c 100644 --- a/lightning/src/util/test_channel_signer.rs +++ b/lightning/src/util/test_channel_signer.rs @@ -18,6 +18,7 @@ use crate::sync::{Mutex, Arc}; #[cfg(test)] use crate::sync::MutexGuard; use bitcoin::blockdata::transaction::{Transaction, EcdsaSighashType}; +use bitcoin::blockdata::script::Script; use bitcoin::util::sighash; use bitcoin::secp256k1; @@ -126,6 +127,12 @@ impl ChannelSigner for TestChannelSigner { fn provide_channel_parameters(&mut self, channel_parameters: &ChannelTransactionParameters) { self.inner.provide_channel_parameters(channel_parameters) } + + /// #SPLICING + fn reprovide_channel_parameters(&mut self, channel_parameters: &ChannelTransactionParameters, channel_value_satoshis: u64) { + self.state = Arc::new(Mutex::new(EnforcementState::new())); + self.inner.reprovide_channel_parameters(channel_parameters, channel_value_satoshis) + } } impl EcdsaChannelSigner for TestChannelSigner { @@ -239,6 +246,10 @@ impl EcdsaChannelSigner for TestChannelSigner { ) -> Result { self.inner.sign_channel_announcement_with_funding_key(msg, secp_ctx) } + + fn sign_splicing_funding_input(&self, splicing_tx: &Transaction, splice_prev_funding_input_index: u16, splice_prev_funding_input_value: u64, redeem_script: &Script, secp_ctx: &Secp256k1) -> Result { + self.inner.sign_splicing_funding_input(splicing_tx, splice_prev_funding_input_index, splice_prev_funding_input_value, redeem_script, secp_ctx) + } } impl WriteableEcdsaChannelSigner for TestChannelSigner {} diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index bd66e4cae3e..996a5bfd8be 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -646,6 +646,31 @@ impl msgs::ChannelMessageHandler for TestChannelMessageHandler { fn handle_closing_signed(&self, _their_node_id: &PublicKey, msg: &msgs::ClosingSigned) { self.received_msg(wire::Message::ClosingSigned(msg.clone())); } + // #SPLICING + fn handle_splice(&self, _their_node_id: &PublicKey, msg: &msgs::Splice) { + self.received_msg(wire::Message::Splice(msg.clone())); + } + fn handle_splice_ack(&self, _their_node_id: &PublicKey, msg: &msgs::SpliceAck) { + self.received_msg(wire::Message::SpliceAck(msg.clone())); + } + fn handle_splice_locked(&self, _their_node_id: &PublicKey, msg: &msgs::SpliceLocked) { + self.received_msg(wire::Message::SpliceLocked(msg.clone())); + } + fn handle_splice_created(&self, _their_node_id: &PublicKey, msg: &msgs::SpliceCreated) { + self.received_msg(wire::Message::SpliceCreated(msg.clone())); + } + fn handle_splice_comm_signed(&self, _their_node_id: &PublicKey, msg: &msgs::SpliceCommSigned) { + self.received_msg(wire::Message::SpliceCommSigned(msg.clone())); + } + fn handle_splice_comm_ack(&self, _their_node_id: &PublicKey, msg: &msgs::SpliceCommAck) { + self.received_msg(wire::Message::SpliceCommAck(msg.clone())); + } + fn handle_splice_signed(&self, _their_node_id: &PublicKey, msg: &msgs::SpliceSigned) { + self.received_msg(wire::Message::SpliceSigned(msg.clone())); + } + fn handle_splice_signed_ack(&self, _their_node_id: &PublicKey, msg: &msgs::SpliceSignedAck) { + self.received_msg(wire::Message::SpliceSignedAck(msg.clone())); + } fn handle_update_add_htlc(&self, _their_node_id: &PublicKey, msg: &msgs::UpdateAddHTLC) { self.received_msg(wire::Message::UpdateAddHTLC(msg.clone())); } From 04df7b7a2f84ae555a820fdde68561bdd95731e2 Mon Sep 17 00:00:00 2001 From: optout <13562139+optout21@users.noreply.github.com> Date: Thu, 23 Nov 2023 11:44:13 +0100 Subject: [PATCH 002/101] Move committing to new splice transaction only upon confirmation --- lightning/src/ln/channel.rs | 131 ++++++++++++++++++----------- lightning/src/ln/channelmanager.rs | 8 +- 2 files changed, 88 insertions(+), 51 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index d30f2c3e835..29d6f267dd1 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -692,6 +692,11 @@ pub(super) struct PendingSpliceInfo { prev_funding_input_index: Option, initial_commitment_tx: Option, cp_commitment_sig: Option, + + // the new funding transaction (candidate). TODO later should be TransactionConfirmation? + funding_transaction: Option, + // the new funding transaction txo (candidate) + pub(crate) funding_txo: Option, } impl PendingSpliceInfo { @@ -706,6 +711,8 @@ impl PendingSpliceInfo { prev_funding_input_index: None, initial_commitment_tx: None, cp_commitment_sig: None, + funding_transaction: None, + funding_txo: None, } } } @@ -2181,7 +2188,7 @@ impl ChannelContext where SP::Target: SignerProvider { /// Commit channel to the pending splice: once it completes, channel will be the new spliced, /// from this point there is no going back to the old one, in case of error channel will error. /// Update channel capacity, funding txid, etc. - pub fn commit_pending_splice(&mut self, splice_txo: OutPoint, logger: &L) -> Result<(), ChannelError> + pub fn commit_pending_splice(&mut self, logger: &L) -> Result<(), ChannelError> where L::Target: Logger { if let Some(pending_splice) = &self.pending_splice { @@ -2189,11 +2196,8 @@ impl ChannelContext where SP::Target: SignerProvider { true => ChannelState::OurInitSent, false => ChannelState::TheirInitSent, } as u32; - self.funding_transaction = None; // will be set later - self.channel_transaction_parameters.funding_outpoint = Some(splice_txo); - // Also mark that it is not confirmed - self.funding_tx_confirmation_height = 0; - self.funding_tx_confirmed_in = None; + self.funding_transaction = pending_splice.funding_transaction.clone(); + self.channel_transaction_parameters.funding_outpoint = pending_splice.funding_txo; let old_value_debug = self.channel_value_satoshis; let _ = self.update_channel_value(pending_splice.post_channel_value, pending_splice.is_outgoing, logger)?; @@ -2201,7 +2205,9 @@ impl ChannelContext where SP::Target: SignerProvider { // Note: pending is not cleared here yet, some parts of it are needed, cleared later log_trace!(logger, "Committed channel to the splice, channel_id {} capacity old {} new {} new funding txid {}", - self.channel_id, old_value_debug, self.channel_value_satoshis, splice_txo.txid); + self.channel_id, old_value_debug, self.channel_value_satoshis, + if let Some(txo) = &self.pending_splice.as_ref().unwrap().funding_txo { txo.txid.to_string() } else { "missing txo".to_owned() } + ); Ok(()) } else { Err(ChannelError::Warn("Internal error: No pending splice found".to_owned())) @@ -3952,7 +3958,11 @@ impl Channel where // first received the funding_signed. let mut funding_broadcastable = if self.context.is_outbound() && self.context.channel_state & !STATE_FLAGS >= ChannelState::FundingSent as u32 && self.context.channel_state & ChannelState::WaitingForBatch as u32 == 0 { - self.context.funding_transaction.take() + if let Some(pending_splice) = &mut self.context.pending_splice { + pending_splice.funding_transaction.take() + } else { + self.context.funding_transaction.take() + } } else { None }; // That said, if the funding transaction is already confirmed (ie we're active with a // minimum_depth over 0) don't bother re-broadcasting the confirmed funding tx. @@ -4993,7 +5003,13 @@ impl Channel where L::Target: Logger { let mut msgs = (None, None); - if let Some(funding_txo) = self.context.get_funding_txo() { + let (funding_txo_to_watch, funding_value, is_splicing) = + if let Some(pending_splice) = &self.context.pending_splice { + (pending_splice.funding_txo.clone(), pending_splice.post_channel_value, true) + } else { + (self.context.get_funding_txo(), self.context.channel_value_satoshis, false) + }; + if let Some(funding_txo) = funding_txo_to_watch { for &(index_in_block, tx) in txdata.iter() { // Check if the transaction is the expected funding transaction, and if it is, // check that it pays the right amount to the right script. @@ -5001,7 +5017,7 @@ impl Channel where if tx.txid() == funding_txo.txid { let txo_idx = funding_txo.index as usize; if txo_idx >= tx.output.len() || tx.output[txo_idx].script_pubkey != self.context.get_funding_redeemscript().to_v0_p2wsh() || - tx.output[txo_idx].value != self.context.channel_value_satoshis { + tx.output[txo_idx].value != funding_value { if self.context.is_outbound() { // If we generated the funding transaction and it doesn't match what it // should, the client is really broken and we should just panic and @@ -5027,6 +5043,16 @@ impl Channel where } } } + + // #SPLICING + if is_splicing { + let _ = self.context.commit_pending_splice(logger); + // TODO: Should this be set only later? or in commit? + log_debug!(logger, "transactions_confirmed: Updating state, from {} to FundingCreated", self.context.channel_state); + self.context.channel_state = ChannelState::FundingSent as u32; + self.context.clear_pending_splice(logger); + } + self.context.funding_tx_confirmation_height = height; self.context.funding_tx_confirmed_in = Some(*block_hash); self.context.short_channel_id = match scid_from_parts(height as u64, index_in_block as u64, txo_idx as u64) { @@ -5219,8 +5245,8 @@ impl Channel where ( pending_splice.prev_funding_input_index.unwrap_or_default(), pending_splice.pre_channel_value, - if let Some(ict) = &pending_splice.initial_commitment_tx { ict } else { panic!("No splice in progress!"); }, // TODO proper error handling - if let Some(cpsig) = &pending_splice.cp_commitment_sig { cpsig } else { panic!("No splice in progress!"); }, // TODO proper error handling + if let Some(ict) = &pending_splice.initial_commitment_tx { ict.clone() } else { panic!("No splice in progress!"); }, // TODO proper error handling + if let Some(cpsig) = &pending_splice.cp_commitment_sig { cpsig.clone() } else { panic!("No splice in progress!"); }, // TODO proper error handling ) } else { panic!("No splice in progress!"); // TODO proper error handling @@ -5234,7 +5260,7 @@ impl Channel where let sig_order_ours_first = self.context.get_holder_pubkeys().funding_pubkey.serialize() < self.context.counterparty_funding_pubkey().serialize(); log_info!(logger, "Pubkeys used for redeem script: {} {} {}", &self.context.get_holder_pubkeys().funding_pubkey, &self.context.counterparty_funding_pubkey(), sig_order_ours_first); let redeem_script = self.context.get_funding_redeemscript(); - let mut funding_transaction_with_sigs = self.context.funding_transaction.as_ref().unwrap().clone(); + let mut funding_transaction_with_sigs = self.context.pending_splice.as_ref().unwrap().funding_transaction.as_ref().unwrap().clone(); // our sig let holder_signature = match &self.context.holder_signer { ChannelSignerType::Ecdsa(ecdsa) => { @@ -5264,7 +5290,7 @@ impl Channel where holder_signature, msg.funding_signature, sig_order_ours_first, encode::serialize_hex(&funding_transaction_with_sigs) ); - self.context.funding_transaction = Some(funding_transaction_with_sigs.clone()); + self.context.pending_splice.as_mut().unwrap().funding_transaction = Some(funding_transaction_with_sigs.clone()); let counterparty_keys = self.context.build_remote_transaction_keys(); let counterparty_initial_commitment_tx = self.context.build_commitment_transaction(self.context.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, false, logger).tx; @@ -5296,21 +5322,24 @@ impl Channel where let funding_txo_script = funding_redeemscript.to_v0_p2wsh(); let obscure_factor = get_commitment_transaction_number_obscure_factor(&self.context.get_holder_pubkeys().payment_point, &self.context.get_counterparty_pubkeys().payment_point, self.context.is_outbound()); let shutdown_script = self.context.shutdown_scriptpubkey.clone().map(|script| script.into_inner()); - let mut monitor_signer = signer_provider.derive_channel_signer(self.context.channel_value_satoshis, self.context.channel_keys_id); + let mut monitor_signer = signer_provider.derive_channel_signer( + self.context.pending_splice.as_ref().unwrap().post_channel_value, + self.context.channel_keys_id); monitor_signer.provide_channel_parameters(&self.context.channel_transaction_parameters); let channel_monitor = ChannelMonitor::new(self.context.secp_ctx.clone(), monitor_signer, - shutdown_script, self.context.get_holder_selected_contest_delay(), - &self.context.destination_script, (funding_txo, funding_txo_script), - &self.context.channel_transaction_parameters, - funding_redeemscript.clone(), self.context.channel_value_satoshis, - obscure_factor, - holder_commitment_tx, best_block, self.context.counterparty_node_id); + shutdown_script, self.context.get_holder_selected_contest_delay(), + &self.context.destination_script, (funding_txo, funding_txo_script), + &self.context.channel_transaction_parameters, + funding_redeemscript.clone(), self.context.channel_value_satoshis, + obscure_factor, + holder_commitment_tx, best_block, self.context.counterparty_node_id); channel_monitor.provide_latest_counterparty_commitment_tx(counterparty_initial_bitcoin_tx.txid, Vec::new(), self.context.cur_counterparty_commitment_transaction_number, self.context.counterparty_cur_commitment_point.unwrap(), logger); assert_eq!(self.context.channel_state & (ChannelState::MonitorUpdateInProgress as u32), 0); // We have no had any monitor(s) yet to fail update! - self.context.clear_pending_splice(logger); + + // TODO is this needed? self.context.channel_state = ChannelState::FundingSent as u32; // self.context.cur_holder_commitment_transaction_number -= 1; // self.context.cur_counterparty_commitment_transaction_number -= 1; @@ -5319,6 +5348,7 @@ impl Channel where let need_channel_ready = self.check_get_channel_ready(0).is_some(); self.monitor_updating_paused(false, false, need_channel_ready, Vec::new(), Vec::new(), Vec::new()); + Ok(channel_monitor) } @@ -6079,13 +6109,11 @@ impl Channel where if self.context.pending_splice.is_none() { panic!("No splice in progress"); } // TODO proper error handling self.context.pending_splice.as_mut().unwrap().prev_funding_input_index = Some(splice_prev_funding_input_index); - // Commit to the new channel value (capacity). The increase belongs to them. - // TODO Should this be done only later? What if splice TX never confirms? - let _ = self.context.commit_pending_splice(splice_txo, logger)?; - // Save funding transaction - self.context.funding_transaction = Some(splice_transaction.clone()); - log_info!(logger, "Stored splice funding tx, txid {} len {}", splice_transaction.txid(), splice_transaction.encode().len()); + self.context.pending_splice.as_mut().unwrap().funding_transaction = Some(splice_transaction.clone()); + self.context.pending_splice.as_mut().unwrap().funding_txo = Some(splice_txo.clone()); + self.context.channel_transaction_parameters.funding_outpoint = Some(splice_txo); + log_info!(logger, "Stored pending splice funding tx, txid {} len {}", splice_transaction.txid(), splice_transaction.encode().len()); // Reset commitment counters // TODO do we need to reset them? @@ -6094,7 +6122,8 @@ impl Channel where // Set tx parameters, for commitment signing match &mut self.context.holder_signer { - ChannelSignerType::Ecdsa(ecdsa) => ecdsa.reprovide_channel_parameters(&self.context.channel_transaction_parameters, self.context.channel_value_satoshis), + // ChannelSignerType::Ecdsa(ecdsa) => ecdsa.reprovide_channel_parameters(&self.context.channel_transaction_parameters, self.context.channel_value_satoshis), + ChannelSignerType::Ecdsa(ecdsa) => ecdsa.reprovide_channel_parameters(&self.context.channel_transaction_parameters, self.context.pending_splice.as_ref().unwrap().post_channel_value), } // TODO: Should this be set only later? @@ -6231,15 +6260,17 @@ impl Channel where self.context.pending_splice.as_mut().unwrap().cp_commitment_sig = Some(msg.signature.clone()); // Retrieve transaction properties from PendingSpliceInfo - let (prev_funding_input_index, pre_channel_value) = - if let Some(pending_splice) = &self.context.pending_splice { - ( - pending_splice.prev_funding_input_index.unwrap_or_default(), - pending_splice.pre_channel_value, - ) - } else { - panic!("No splice in progress!"); // TODO proper error handling - }; + let (funding_tx, prev_funding_input_index, pre_channel_value) = + if let Some(pending_splice) = &self.context.pending_splice { + if pending_splice.funding_transaction.is_none() { panic!("No transaction in pending splice!"); } // TODO proper error handling + ( + pending_splice.funding_transaction.as_ref().unwrap().clone(), + pending_splice.prev_funding_input_index.unwrap_or_default(), + pending_splice.pre_channel_value, + ) + } else { + panic!("No splice in progress!"); // TODO proper error handling + }; // Sign splice funding tx: add signature to the input that is the previous funding tx // #SPLICE-SIG @@ -6247,11 +6278,10 @@ impl Channel where let redeem_script = make_funding_redeemscript(&self.context.get_holder_pubkeys().funding_pubkey, self.context.counterparty_funding_pubkey()); // Sign splice funding tx: create our signature on the funding tx - let funding_tx = &self.context.funding_transaction.as_ref().unwrap(); // #SPLICE-SIG let funding_signature = match &mut self.context.holder_signer { ChannelSignerType::Ecdsa(ecdsa) => { - ecdsa.sign_splicing_funding_input(funding_tx, prev_funding_input_index, pre_channel_value, &redeem_script, &self.context.secp_ctx) + ecdsa.sign_splicing_funding_input(&funding_tx, prev_funding_input_index, pre_channel_value, &redeem_script, &self.context.secp_ctx) .map_err(|_| ChannelError::Close("Failed to sign the previous funding input in the new splicing funding tx".to_owned()))? } }; @@ -6290,9 +6320,11 @@ impl Channel where */ // Retrieve transaction properties from PendingSpliceInfo - let (prev_funding_input_index, pre_channel_value, initial_commitment_tx, cp_comm_sig) = + let (funding_tx, prev_funding_input_index, pre_channel_value, initial_commitment_tx, cp_comm_sig) = if let Some(pending_splice) = &self.context.pending_splice { + if pending_splice.funding_transaction.is_none() { panic!("No transaction in pending splice!"); } // TODO proper error handling ( + pending_splice.funding_transaction.as_ref().unwrap().clone(), pending_splice.prev_funding_input_index.unwrap_or_default(), pending_splice.pre_channel_value, if let Some(ict) = &pending_splice.initial_commitment_tx { ict } else { panic!("No splice in progress!"); }, // TODO proper error handling @@ -6310,7 +6342,6 @@ impl Channel where let redeem_script = make_funding_redeemscript(&self.context.get_holder_pubkeys().funding_pubkey, self.context.counterparty_funding_pubkey()); // Sign splice funding tx: create our signature on the funding tx - let funding_tx = &self.context.funding_transaction.as_ref().unwrap(); // #SPLICE-SIG let funding_signature = match &self.context.holder_signer { ChannelSignerType::Ecdsa(ecdsa) => { @@ -6364,7 +6395,7 @@ impl Channel where assert_eq!(self.context.channel_state & (ChannelState::MonitorUpdateInProgress as u32), 0); // We have no had any monitor(s) yet to fail update! - self.context.clear_pending_splice(logger); + // TODO check if this is needed self.context.channel_state = ChannelState::FundingSent as u32; // self.context.cur_holder_commitment_transaction_number -= 1; // self.context.cur_counterparty_commitment_transaction_number -= 1; @@ -6415,12 +6446,11 @@ impl Channel where if self.context.pending_splice.is_none() { panic!("No pending splice"); } // TODO proper error handling self.context.pending_splice.as_mut().unwrap().prev_funding_input_index = Some(msg.splice_prev_funding_input_index); - // Commit to the new channel value (capacity). The increase belongs to them. - // TODO Should this be done only later? What if splice TX never confirms? - let _ = self.context.commit_pending_splice(splice_txo, logger)?; - - self.context.funding_transaction = Some(msg.splice_transaction.clone()); - log_info!(logger, "Stored splice funding tx, txid {} len {}", msg.splice_transaction.txid(), msg.splice_transaction.encode().len()); + // Save funding transaction + self.context.pending_splice.as_mut().unwrap().funding_transaction = Some(msg.splice_transaction.clone()); + self.context.pending_splice.as_mut().unwrap().funding_txo = Some(splice_txo.clone()); + self.context.channel_transaction_parameters.funding_outpoint = Some(splice_txo); + log_info!(logger, "Stored pending splice funding tx, txid {} len {}", msg.splice_transaction.txid(), msg.splice_transaction.encode().len()); // Reset commitment counters // TODO do we need to reset them? @@ -6429,7 +6459,8 @@ impl Channel where // Set tx parameters, for commitment signing match &mut self.context.holder_signer { - ChannelSignerType::Ecdsa(ecdsa) => ecdsa.reprovide_channel_parameters(&self.context.channel_transaction_parameters, self.context.channel_value_satoshis), + // ChannelSignerType::Ecdsa(ecdsa) => ecdsa.reprovide_channel_parameters(&self.context.channel_transaction_parameters, self.context.channel_value_satoshis), + ChannelSignerType::Ecdsa(ecdsa) => ecdsa.reprovide_channel_parameters(&self.context.channel_transaction_parameters, self.context.pending_splice.as_ref().unwrap().post_channel_value), } // TODO: Should this be set only later? diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index e13a203a0db..5436fa06328 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -7608,7 +7608,13 @@ where node_id: chan.context.get_counterparty_node_id(), msg: splice_signed_ack_msg, }); - if let Ok(persist_status) = self.chain_monitor.watch_channel(chan.context.get_funding_txo().unwrap(), monitor) { + let funding_txo_to_watch = if let Some(pending) = &chan.context.pending_splice { + pending.funding_txo.unwrap() + } else { + panic!("There is no pending splice!"); // TODO error handling + // chan.context.get_funding_txo().unwrap() + }; + if let Ok(persist_status) = self.chain_monitor.watch_channel(funding_txo_to_watch, monitor) { handle_new_monitor_update!(self, persist_status, peer_state_lock, peer_state, per_peer_state, chan, INITIAL_MONITOR); Ok(()) } else { From 03978ed0c8635729b6becd67e49a3a492706d9b4 Mon Sep 17 00:00:00 2001 From: optout <13562139+optout21@users.noreply.github.com> Date: Thu, 23 Nov 2023 12:01:51 +0100 Subject: [PATCH 003/101] Extra check to splice test case (no change before locking) --- lightning/src/ln/functional_tests.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index 1cf8ba0505c..85d6d438a41 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -470,6 +470,18 @@ fn test_splice_in_simple() { check_added_monitors!(nodes[i], 1); check_added_monitors!(nodes[a], 1); + // check that capacity has _not_ been changed yet + assert_eq!(nodes[i].node.list_channels().len(), 1); + { + let channel = &nodes[i].node.list_channels()[0]; + assert!(!channel.is_usable); // TODO check + assert!(!channel.is_channel_ready); // TODO check + assert_eq!(channel.channel_value_satoshis, channel_value_sat); + assert_eq!(channel.outbound_capacity_msat, 100000000 - 1000000); + assert_eq!(channel.funding_txo.unwrap().txid, splice_tx.txid()); // TODO check + assert_eq!(channel.confirmations.unwrap(), 0); // TODO check + } + confirm_transaction(&nodes[i], &broadcasted_splice_tx); let channel_ready_message = get_event_msg!(nodes[i], MessageSendEvent::SendChannelReady, nodes[a].node.get_our_node_id()); From a23d7ddb2f0f3fc263380f0437b6f8fdd64812e8 Mon Sep 17 00:00:00 2001 From: optout <13562139+optout21@users.noreply.github.com> Date: Sat, 25 Nov 2023 22:36:24 +0100 Subject: [PATCH 004/101] Reduce redundancy in pre+post+diff storage, check for overflows --- lightning/src/ln/channel.rs | 106 +++++++++++++++++++++++++++-- lightning/src/ln/channelmanager.rs | 9 +-- 2 files changed, 104 insertions(+), 11 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 29d6f267dd1..a744145bc1b 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -48,6 +48,7 @@ use crate::util::scid_utils::scid_from_parts; use crate::io; use crate::prelude::*; use core::{cmp,mem,fmt}; +use core::convert::TryFrom; use core::ops::Deref; #[cfg(any(test, fuzzing, debug_assertions))] use crate::sync::Mutex; @@ -680,8 +681,6 @@ impl UnfundedChannelContext { /// Info about a pending splice #[derive(Default, Clone)] pub(super) struct PendingSpliceInfo { - /// The relative splice value (change in capacity value relative to current value) - pub relative_satoshis: i64, /// The post splice value (current + relative) pub post_channel_value: u64, /// The pre splice value (a bit redundant) @@ -700,11 +699,9 @@ pub(super) struct PendingSpliceInfo { } impl PendingSpliceInfo { - pub fn new(relative_satoshis: i64, pre_channel_value: u64, is_outgoing: bool) -> Self { - // TODO check for underflow - let post_channel_value = (pre_channel_value as i64 + relative_satoshis) as u64; + pub(crate) fn new(relative_satoshis: i64, pre_channel_value: u64, is_outgoing: bool) -> Self { + let post_channel_value = Self::add_checked(pre_channel_value, relative_satoshis); Self { - relative_satoshis, post_channel_value, pre_channel_value, is_outgoing, @@ -715,6 +712,24 @@ impl PendingSpliceInfo { funding_txo: None, } } + + /// Add a u64 and an i64, handling i64 overflow cases (doing without cast to i64) + pub(crate) fn add_checked(pre_channel_value: u64, relative_satoshis: i64) -> u64 { + if relative_satoshis >= 0 { + pre_channel_value + (relative_satoshis as u64) + } else { + pre_channel_value.checked_sub((-relative_satoshis) as u64).unwrap_or_default() + } + } + + /// The relative splice value (change in capacity value relative to current value) + pub(crate) fn relative_satoshis(&self) -> i64 { + if self.post_channel_value > self.pre_channel_value { + i64::try_from(self.post_channel_value.checked_sub(self.pre_channel_value).unwrap_or_default()).unwrap_or_default() + } else { + -i64::try_from(self.pre_channel_value.checked_sub(self.post_channel_value).unwrap_or_default()).unwrap_or_default() + } + } } /// Contains everything about the channel including state, and various flags. pub(super) struct ChannelContext where SP::Target: SignerProvider { @@ -2161,6 +2176,7 @@ impl ChannelContext where SP::Target: SignerProvider { let old_value = self.channel_value_satoshis; let old_to_self = self.value_to_self_msat; if belongs_to_local { + // TODO check for i64 overflow let delta_msats = (new_value_sats as i64 - old_value as i64) * 1000; // Check if not reducing by too much if delta_msats < 0 && -delta_msats > self.value_to_self_msat as i64 { @@ -8526,7 +8542,7 @@ mod tests { use crate::ln::PaymentHash; use crate::ln::channelmanager::{self, HTLCSource, PaymentId}; use crate::ln::channel::InitFeatures; - use crate::ln::channel::{Channel, ChannelState, InboundHTLCOutput, OutboundV1Channel, InboundV1Channel, OutboundHTLCOutput, InboundHTLCState, OutboundHTLCState, HTLCCandidate, HTLCInitiator, commit_tx_fee_msat}; + use crate::ln::channel::{Channel, ChannelState, InboundHTLCOutput, OutboundV1Channel, InboundV1Channel, OutboundHTLCOutput, InboundHTLCState, OutboundHTLCState, HTLCCandidate, HTLCInitiator, PendingSpliceInfo, 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::ChannelTypeFeatures; use crate::ln::msgs::{ChannelUpdate, DecodeError, UnsignedChannelUpdate, MAX_VALUE_MSAT}; @@ -8579,6 +8595,82 @@ mod tests { u32::max_value(), None, &&test_utils::TestLogger::new()).is_err()); } + fn create_pending_splice_info(pre_channel_value: u64, post_channel_value: u64) -> PendingSpliceInfo { + PendingSpliceInfo { + post_channel_value, + pre_channel_value, + is_outgoing: true, + prev_funding_input_index: None, + initial_commitment_tx: None, + cp_commitment_sig: None, + funding_transaction: None, + funding_txo: None, + } + } + + #[test] + fn test_pending_splice_info_new() { + { + // increase, small amounts + let ps = create_pending_splice_info(9_000, 15_000); + assert_eq!(ps.pre_channel_value, 9_000); + assert_eq!(ps.post_channel_value, 15_000); + assert_eq!(ps.relative_satoshis(), 6_000); + } + { + // decrease, small amounts + let ps = create_pending_splice_info(15_000, 9_000); + assert_eq!(ps.pre_channel_value, 15_000); + assert_eq!(ps.post_channel_value, 9_000); + assert_eq!(ps.relative_satoshis(), -6_000); + } + let base2: u64 = 2; + let huge63 = base2.pow(63); + assert_eq!(huge63, 9223372036854775808); + { + // increase, one huge amount + let ps = create_pending_splice_info(9_000, huge63 + 9_000 - 1); + assert_eq!(ps.pre_channel_value, 9_000); + assert_eq!(ps.post_channel_value, 9223372036854784807); // 2^63 + 9000 - 1 + assert_eq!(ps.relative_satoshis(), 9223372036854775807); // 2^63 - 1 + } + { + // decrease, one huge amount + let ps = create_pending_splice_info(huge63 + 9_000 - 1, 9_000); + assert_eq!(ps.pre_channel_value, 9223372036854784807); // 2^63 + 9000 - 1 + assert_eq!(ps.post_channel_value, 9_000); + assert_eq!(ps.relative_satoshis(), -9223372036854775807); // 2^63 - 1 + } + { + // increase, two huge amounts + let ps = create_pending_splice_info(huge63 + 9_000, huge63 + 15_000); + assert_eq!(ps.pre_channel_value, 9223372036854784808); // 2^63 + 9000 + assert_eq!(ps.post_channel_value, 9223372036854790808); // 2^63 + 15000 + assert_eq!(ps.relative_satoshis(), 6_000); + } + { + // decrease, two huge amounts + let ps = create_pending_splice_info(huge63 + 15_000, huge63 + 9_000); + assert_eq!(ps.pre_channel_value, 9223372036854790808); // 2^63 + 15000 + assert_eq!(ps.post_channel_value, 9223372036854784808); // 2^63 + 9000 + assert_eq!(ps.relative_satoshis(), -6_000); + } + { + // underflow + let ps = create_pending_splice_info(9_000, huge63 + 9_000 + 20); + assert_eq!(ps.pre_channel_value, 9_000); + assert_eq!(ps.post_channel_value, 9223372036854784828); // 2^63 + 9000 + 20 + assert_eq!(ps.relative_satoshis(), -0); + } + { + // underflow + let ps = create_pending_splice_info(huge63 + 9_000 + 20, 9_000); + assert_eq!(ps.pre_channel_value, 9223372036854784828); // 2^63 + 9000 + 20 + assert_eq!(ps.post_channel_value, 9_000); + assert_eq!(ps.relative_satoshis(), -0); + } + } + struct Keys { signer: InMemorySigner, } diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 5436fa06328..1930a6b0853 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -2490,10 +2490,11 @@ where hash_map::Entry::Occupied(mut chan_phase_entry) => { if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() { let current_value_sats = chan.context.get_value_satoshis(); + // TODO check for i64 overflow if relative_satoshis < 0 && -relative_satoshis > (current_value_sats as i64) { return Err(APIError::APIMisuseError { err: format!("Post-splicing channel value cannot be negative. It was {} - {}", current_value_sats, -relative_satoshis) }); } - let post_splice_funding_satoshis: u64 = (current_value_sats as i64 + relative_satoshis) as u64; + let post_splice_funding_satoshis = PendingSpliceInfo::add_checked(current_value_sats, relative_satoshis); // TODO handle code duplication with create_channel if post_splice_funding_satoshis < 1000 { @@ -4284,7 +4285,7 @@ where } if input_index.is_none() { return Err(APIError::APIMisuseError { - err: format!("No input matched the address and value in the SpliceAcked event {}", if chan.context.pending_splice.is_some() { chan.context.pending_splice.as_ref().unwrap().relative_satoshis } else { 0 }) + err: format!("No input matched the address and value in the SpliceAcked event {}", if chan.context.pending_splice.is_some() { chan.context.pending_splice.as_ref().unwrap().relative_satoshis() } else { 0 }) }); } Ok(input_index.unwrap()) @@ -4311,7 +4312,7 @@ where } if output_index.is_none() { return Err(APIError::APIMisuseError { - err: format!("No output matched the script_pubkey and value in the SpliceAcked event {}", if chan.context.pending_splice.is_some() { chan.context.pending_splice.as_ref().unwrap().relative_satoshis } else { 0 }) + err: format!("No output matched the script_pubkey and value in the SpliceAcked event {}", if chan.context.pending_splice.is_some() { chan.context.pending_splice.as_ref().unwrap().relative_satoshis() } else { 0 }) }); } Ok(OutPoint { txid: tx.txid(), index: output_index.unwrap() }) @@ -7400,7 +7401,7 @@ where counterparty_node_id: *counterparty_node_id, current_funding_outpoint: funding_outpoint, pre_channel_value_satoshis: pre_value, - post_channel_value_satoshis: (pre_value as i64 + msg.relative_satoshis) as u64, // TODO handle underflow + post_channel_value_satoshis: PendingSpliceInfo::add_checked(pre_value, msg.relative_satoshis), output_script, }, None)); Ok(()) From 15176d0e771946bf4971b9996ca98b1e08a1dc66 Mon Sep 17 00:00:00 2001 From: Duncan Dean Date: Tue, 12 Sep 2023 10:52:50 +0200 Subject: [PATCH 005/101] Add V2 constructors to `ChannelId` --- lightning/src/ln/channel_id.rs | 27 +++++++++++++++++++++++++++ lightning/src/ln/channel_keys.rs | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/lightning/src/ln/channel_id.rs b/lightning/src/ln/channel_id.rs index 8df6d75ef5e..288935d2aa4 100644 --- a/lightning/src/ln/channel_id.rs +++ b/lightning/src/ln/channel_id.rs @@ -13,10 +13,15 @@ use crate::ln::msgs::DecodeError; use crate::sign::EntropySource; use crate::util::ser::{Readable, Writeable, Writer}; +use bitcoin::hashes::Hash; +use bitcoin::hashes::sha256::Hash as Sha256; + use crate::io; use core::fmt; use core::ops::Deref; +use super::channel_keys::RevocationBasepoint; + /// A unique 32-byte identifier for a channel. /// Depending on how the ID is generated, several varieties are distinguished /// (but all are stored as 32 bytes): @@ -61,6 +66,28 @@ 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 (lesser_point, greater_point) = if *ours < *theirs { + (ours, theirs) + } else { + (theirs, ours) + }; + + Self(Sha256::hash(&[lesser_point.0.serialize(), greater_point.0.serialize()].concat()).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 { diff --git a/lightning/src/ln/channel_keys.rs b/lightning/src/ln/channel_keys.rs index f737dd23407..ed173b0ac52 100644 --- a/lightning/src/ln/channel_keys.rs +++ b/lightning/src/ln/channel_keys.rs @@ -166,7 +166,7 @@ fn derive_public_key(secp_ctx: &Secp256k1, per_commitm /// Master key used in conjunction with per_commitment_point to generate [htlcpubkey](https://github.com/lightning/bolts/blob/master/03-transactions.md#key-derivation) for the latest state of a channel. /// A watcher can be given a [RevocationBasepoint] to generate per commitment [RevocationKey] to create justice transactions. -#[derive(PartialEq, Eq, Clone, Copy, Debug, Hash)] +#[derive(PartialEq, PartialOrd, Eq, Clone, Copy, Debug, Hash)] pub struct RevocationBasepoint(pub PublicKey); basepoint_impl!(RevocationBasepoint); key_read_write!(RevocationBasepoint); From 1b8f177d68050cd737658e0715183cc0e86f0e54 Mon Sep 17 00:00:00 2001 From: Duncan Dean Date: Thu, 7 Dec 2023 10:40:08 +0200 Subject: [PATCH 006/101] f use-only Hash trait --- lightning/src/ln/channel_id.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lightning/src/ln/channel_id.rs b/lightning/src/ln/channel_id.rs index 288935d2aa4..35e90ed3c44 100644 --- a/lightning/src/ln/channel_id.rs +++ b/lightning/src/ln/channel_id.rs @@ -13,7 +13,7 @@ use crate::ln::msgs::DecodeError; use crate::sign::EntropySource; use crate::util::ser::{Readable, Writeable, Writer}; -use bitcoin::hashes::Hash; +use bitcoin::hashes::Hash as _; use bitcoin::hashes::sha256::Hash as Sha256; use crate::io; From fcd399dc96e4921262e29fa01f723a95230b4488 Mon Sep 17 00:00:00 2001 From: Duncan Dean Date: Mon, 4 Dec 2023 09:22:14 +0200 Subject: [PATCH 007/101] Test builds with #[cfg(dual_funding)] enabled --- ci/ci-tests.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ci/ci-tests.sh b/ci/ci-tests.sh index 2db32a1081c..afc3f541ebf 100755 --- a/ci/ci-tests.sh +++ b/ci/ci-tests.sh @@ -156,3 +156,8 @@ echo -e "\n\nTest Taproot builds" pushd lightning RUSTFLAGS="$RUSTFLAGS --cfg=taproot" cargo test --verbose --color always -p lightning popd + +echo -e "\n\nTest dual-funding builds" +pushd lightning +RUSTFLAGS="$RUSTFLAGS --cfg=dual_funding" cargo test --verbose --color always -p lightning +popd From 476f6cb41548542969438cc3b1f1f54833154338 Mon Sep 17 00:00:00 2001 From: Duncan Dean Date: Tue, 12 Sep 2023 21:18:14 +0200 Subject: [PATCH 008/101] 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 582f67878e6..2e6b566104b 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -2503,6 +2503,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 2e66dc5abf5143f4d6d234182068071cb06f75de Mon Sep 17 00:00:00 2001 From: Duncan Dean Date: Fri, 24 Nov 2023 09:32:56 +0200 Subject: [PATCH 009/101] Create ChannelContext constructor for inbound channels --- lightning/src/ln/channel.rs | 724 ++++++++++++++++++++---------------- 1 file changed, 404 insertions(+), 320 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 2e6b566104b..f5d3f4d7212 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -1231,6 +1231,378 @@ 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, + our_supported_features: &'a ChannelTypeFeatures, + 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, + msg_channel_flags: u8, + msg_channel_type: Option, + msg_funding_satoshis: u64, + msg_to_self_delay: u16, + holder_selected_channel_reserve_satoshis: u64, + msg_channel_reserve_satoshis: u64, + msg_push_msat: u64, + msg_dust_limit_satoshis: u64, + msg_htlc_minimum_msat: u64, + msg_commitment_feerate_sat_per_1000_weight: u32, + msg_max_accepted_htlcs: u16, + msg_shutdown_scriptpubkey: Option, + msg_max_htlc_value_in_flight_msat: u64, + msg_temporary_channel_id: ChannelId, + msg_first_per_commitment_point: PublicKey, + ) -> 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(msg_temporary_channel_id)); + let announced_channel = if (msg_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 = if let Some(channel_type) = &msg_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())); + } + + // We only support the channel types defined by the `ChannelManager` in + // `provided_channel_type_features`. The channel type must always support + // `static_remote_key`. + if !channel_type.requires_static_remote_key() { + return Err(ChannelError::Close("Channel Type was not understood - we require static remote key".to_owned())); + } + // Make sure we support all of the features behind the channel type. + if !channel_type.is_subset(our_supported_features) { + return Err(ChannelError::Close("Channel Type contains unsupported features".to_owned())); + } + 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())); + } + channel_type.clone() + } else { + let channel_type = ChannelTypeFeatures::from_init(&their_features); + if channel_type != ChannelTypeFeatures::only_static_remote_key() { + return Err(ChannelError::Close("Only static_remote_key is supported for non-negotiated channel types".to_owned())); + } + channel_type + }; + + let channel_keys_id = signer_provider.generate_channel_keys_id(true, msg_funding_satoshis, user_id); + let holder_signer = signer_provider.derive_channel_signer(msg_funding_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))); + } + + let channel_value_satoshis = our_funding_satoshis.saturating_add(msg_funding_satoshis); + + // 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, + msg_funding_satoshis, our_funding_satoshis))); + } + if msg_funding_satoshis >= TOTAL_BITCOIN_SUPPLY_SATOSHIS { + return Err(ChannelError::Close(format!("Funding must be smaller than the total bitcoin supply. It was {}", msg_funding_satoshis))); + } + if msg_channel_reserve_satoshis > msg_funding_satoshis { + return Err(ChannelError::Close(format!("Bogus channel_reserve_satoshis ({}). Must be not greater than funding_satoshis: {}", msg_channel_reserve_satoshis, msg_funding_satoshis))); + } + let full_channel_value_msat = (msg_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_dust_limit_satoshis > msg_funding_satoshis { + return Err(ChannelError::Close(format!("dust_limit_satoshis {} was larger than funding_satoshis {}. Peer never wants payout outputs?", msg_dust_limit_satoshis, msg_funding_satoshis))); + } + if msg_htlc_minimum_msat >= full_channel_value_msat { + return Err(ChannelError::Close(format!("Minimum htlc value ({}) was larger than full channel value ({})", msg_htlc_minimum_msat, full_channel_value_msat))); + } + Channel::::check_remote_fee(&channel_type, fee_estimator, msg_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_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_to_self_delay))); + } + if msg_max_accepted_htlcs < 1 { + return Err(ChannelError::Close("0 max_accepted_htlcs makes for a useless channel".to_owned())); + } + if msg_max_accepted_htlcs > MAX_HTLCS { + return Err(ChannelError::Close(format!("max_accepted_htlcs was {}. It must not be larger than {}", msg_max_accepted_htlcs, MAX_HTLCS))); + } + + // Now check against optional parameters as set by config... + if msg_funding_satoshis < config.channel_handshake_limits.min_funding_satoshis { + return Err(ChannelError::Close(format!("Funding satoshis ({}) is less than the user specified limit ({})", msg_funding_satoshis, config.channel_handshake_limits.min_funding_satoshis))); + } + if msg_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_htlc_minimum_msat, config.channel_handshake_limits.max_htlc_minimum_msat))); + } + if msg_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_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_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_max_accepted_htlcs, config.channel_handshake_limits.min_max_accepted_htlcs))); + } + if msg_dust_limit_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS { + return Err(ChannelError::Close(format!("dust_limit_satoshis ({}) is less than the implementation limit ({})", msg_dust_limit_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS))); + } + if msg_dust_limit_satoshis > MAX_CHAN_DUST_LIMIT_SATOSHIS { + return Err(ChannelError::Close(format!("dust_limit_satoshis ({}) is greater than the implementation limit ({})", msg_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 < msg_dust_limit_satoshis { + return Err(ChannelError::Close(format!("Dust limit ({}) too high for the channel reserve we require the remote to keep ({})", msg_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_funding_satoshis * 1000 - msg_push_msat; + let commitment_tx_fee = commit_tx_fee_msat(msg_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_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(msg_temporary_channel_id), + channel_id: msg_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, (msg_funding_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, (msg_funding_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: msg_commitment_feerate_sat_per_1000_weight, + channel_value_satoshis, + counterparty_dust_limit_satoshis: msg_dust_limit_satoshis, + holder_dust_limit_satoshis: MIN_CHAN_DUST_LIMIT_SATOSHIS, + counterparty_max_htlc_value_in_flight_msat: cmp::min(msg_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: msg_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_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_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_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: HashSet::new(), + + channel_type, + channel_keys_id, + + 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 @@ -6750,41 +7122,8 @@ impl InboundV1Channel where SP::Target: SignerProvider { F::Target: FeeEstimator, L::Target: Logger, { - let logger = WithContext::from(logger, Some(counterparty_node_id), Some(msg.temporary_channel_id)); - let announced_channel = if (msg.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 = if let Some(channel_type) = &msg.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())); - } - - // We only support the channel types defined by the `ChannelManager` in - // `provided_channel_type_features`. The channel type must always support - // `static_remote_key`. - if !channel_type.requires_static_remote_key() { - return Err(ChannelError::Close("Channel Type was not understood - we require static remote key".to_owned())); - } - // Make sure we support all of the features behind the channel type. - if !channel_type.is_subset(our_supported_features) { - return Err(ChannelError::Close("Channel Type contains unsupported features".to_owned())); - } - 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())); - } - channel_type.clone() - } else { - let channel_type = ChannelTypeFeatures::from_init(&their_features); - if channel_type != ChannelTypeFeatures::only_static_remote_key() { - return Err(ChannelError::Close("Only static_remote_key is supported for non-negotiated channel types".to_owned())); - } - channel_type - }; - - let channel_keys_id = signer_provider.generate_channel_keys_id(true, msg.funding_satoshis, user_id); - let holder_signer = signer_provider.derive_channel_signer(msg.funding_satoshis, channel_keys_id); - let pubkeys = holder_signer.pubkeys().clone(); + let holder_selected_channel_reserve_satoshis = get_holder_selected_channel_reserve_satoshis(msg.funding_satoshis, config); + let counterparty_pubkeys = ChannelPublicKeys { funding_pubkey: msg.funding_pubkey, revocation_basepoint: RevocationBasepoint::from(msg.revocation_basepoint), @@ -6792,296 +7131,41 @@ impl InboundV1Channel where SP::Target: SignerProvider { delayed_payment_basepoint: DelayedPaymentBasepoint::from(msg.delayed_payment_basepoint), htlc_basepoint: HtlcBasepoint::from(msg.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.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.funding_satoshis))); - } - if msg.funding_satoshis >= TOTAL_BITCOIN_SUPPLY_SATOSHIS { - return Err(ChannelError::Close(format!("Funding must be smaller than the total bitcoin supply. It was {}", msg.funding_satoshis))); - } - if msg.channel_reserve_satoshis > msg.funding_satoshis { - return Err(ChannelError::Close(format!("Bogus channel_reserve_satoshis ({}). Must be not greater than funding_satoshis: {}", msg.channel_reserve_satoshis, msg.funding_satoshis))); - } - let full_channel_value_msat = (msg.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.dust_limit_satoshis > msg.funding_satoshis { - return Err(ChannelError::Close(format!("dust_limit_satoshis {} was larger than funding_satoshis {}. Peer never wants payout outputs?", msg.dust_limit_satoshis, msg.funding_satoshis))); - } - if msg.htlc_minimum_msat >= full_channel_value_msat { - return Err(ChannelError::Close(format!("Minimum htlc value ({}) was larger than full channel value ({})", msg.htlc_minimum_msat, full_channel_value_msat))); - } - Channel::::check_remote_fee(&channel_type, fee_estimator, msg.feerate_per_kw, None, &&logger)?; - - let max_counterparty_selected_contest_delay = u16::min(config.channel_handshake_limits.their_to_self_delay, MAX_LOCAL_BREAKDOWN_TIMEOUT); - if msg.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.to_self_delay))); - } - if msg.max_accepted_htlcs < 1 { - return Err(ChannelError::Close("0 max_accepted_htlcs makes for a useless channel".to_owned())); - } - if msg.max_accepted_htlcs > MAX_HTLCS { - return Err(ChannelError::Close(format!("max_accepted_htlcs was {}. It must not be larger than {}", msg.max_accepted_htlcs, MAX_HTLCS))); - } - - // Now check against optional parameters as set by config... - if msg.funding_satoshis < config.channel_handshake_limits.min_funding_satoshis { - return Err(ChannelError::Close(format!("Funding satoshis ({}) is less than the user specified limit ({})", msg.funding_satoshis, config.channel_handshake_limits.min_funding_satoshis))); - } - if msg.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.htlc_minimum_msat, config.channel_handshake_limits.max_htlc_minimum_msat))); - } - if msg.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.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.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.max_accepted_htlcs, config.channel_handshake_limits.min_max_accepted_htlcs))); - } - if msg.dust_limit_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS { - return Err(ChannelError::Close(format!("dust_limit_satoshis ({}) is less than the implementation limit ({})", msg.dust_limit_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS))); - } - if msg.dust_limit_satoshis > MAX_CHAN_DUST_LIMIT_SATOSHIS { - return Err(ChannelError::Close(format!("dust_limit_satoshis ({}) is greater than the implementation limit ({})", msg.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.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.dust_limit_satoshis { - return Err(ChannelError::Close(format!("Dust limit ({}) too high for the channel reserve we require the remote to keep ({})", msg.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.funding_satoshis * 1000 - msg.push_msat; - let commitment_tx_fee = commit_tx_fee_msat(msg.feerate_per_kw, 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.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 { + context: ChannelContext::new_for_inbound_channel( + fee_estimator, + entropy_source, + signer_provider, + counterparty_node_id, + our_supported_features, + their_features, 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.temporary_channel_id), - channel_id: msg.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.funding_satoshis * 1000 - msg.push_msat)), - #[cfg(debug_assertions)] - counterparty_max_commitment_tx_output: Mutex::new((msg.push_msat, msg.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.feerate_per_kw, - channel_value_satoshis: msg.funding_satoshis, - counterparty_dust_limit_satoshis: msg.dust_limit_satoshis, - holder_dust_limit_satoshis: MIN_CHAN_DUST_LIMIT_SATOSHIS, - counterparty_max_htlc_value_in_flight_msat: cmp::min(msg.max_htlc_value_in_flight_msat, msg.funding_satoshis * 1000), - holder_max_htlc_value_in_flight_msat: get_holder_max_htlc_value_in_flight_msat(msg.funding_satoshis, &config.channel_handshake_config), - counterparty_selected_channel_reserve_satoshis: Some(msg.channel_reserve_satoshis), + config, + current_chain_height, + logger, + is_0conf, + + 0, + + counterparty_pubkeys, + msg.channel_flags, + msg.channel_type.clone(), + msg.funding_satoshis, + msg.to_self_delay, holder_selected_channel_reserve_satoshis, - counterparty_htlc_minimum_msat: msg.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.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.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.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: HashSet::new(), - - channel_type, - channel_keys_id, - - blocked_monitor_updates: Vec::new(), - }, + msg.channel_reserve_satoshis, + msg.push_msat, + msg.dust_limit_satoshis, + msg.htlc_minimum_msat, + msg.feerate_per_kw, + msg.max_accepted_htlcs, + msg.shutdown_scriptpubkey.clone(), + msg.max_htlc_value_in_flight_msat, + msg.temporary_channel_id, + msg.first_per_commitment_point, + )?, unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 } }; - Ok(chan) } From 2ee04b0d0d0cad6f51cc9ba619bbfebe1034cf48 Mon Sep 17 00:00:00 2001 From: Duncan Dean Date: Tue, 12 Sep 2023 21:23:51 +0200 Subject: [PATCH 010/101] Add `InboundV2Channel` struct --- lightning/src/ln/channel.rs | 187 +++++++++++++++++++++++++++++++++++- 1 file changed, 184 insertions(+), 3 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index f5d3f4d7212..1a217bd862a 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -2860,6 +2860,20 @@ 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 { + let channel_reserve_proportional_millionths = 10_000; // Fixed at 1% in spec. + let calculated_reserve = + channel_value_satoshis.saturating_mul(channel_reserve_proportional_millionths) / 1_000_000; + cmp::min(channel_value_satoshis, cmp::max(calculated_reserve, 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] @@ -2893,6 +2907,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))] @@ -7085,7 +7101,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()); @@ -7342,6 +7362,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()); @@ -7350,6 +7372,159 @@ 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, + { + // TODO(dual_funding): Fix this + let channel_value_satoshis = funding_satoshis * 1000 + msg.funding_satoshis; + let counterparty_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis( + channel_value_satoshis, msg.dust_limit_satoshis); + let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis( + channel_value_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS); + + let counterparty_pubkeys = ChannelPublicKeys { + funding_pubkey: msg.funding_pubkey, + revocation_basepoint: RevocationBasepoint(msg.revocation_basepoint), + payment_point: msg.payment_basepoint, + delayed_payment_basepoint: DelayedPaymentBasepoint(msg.delayed_payment_basepoint), + htlc_basepoint: HtlcBasepoint(msg.htlc_basepoint) + }; + + let mut context = ChannelContext::new_for_inbound_channel( + fee_estimator, + entropy_source, + signer_provider, + counterparty_node_id, + our_supported_features, + their_features, + user_id, + config, + current_chain_height, + logger, + false, + + funding_satoshis, + + counterparty_pubkeys, + msg.channel_flags, + msg.channel_type.clone(), + msg.funding_satoshis, + msg.to_self_delay, + holder_selected_channel_reserve_satoshis, + counterparty_selected_channel_reserve_satoshis, + 0 /* push_msat not used in dual-funding */, + msg.dust_limit_satoshis, + msg.htlc_minimum_msat, + msg.commitment_feerate_sat_per_1000_weight, + msg.max_accepted_htlcs, + msg.shutdown_scriptpubkey.clone(), + msg.max_htlc_value_in_flight_msat, + msg.temporary_channel_id, + msg.first_per_commitment_point, + )?; + 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.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() { + panic!("Tried to send accept_channel2 for an outbound channel?"); + } + if self.context.channel_state != (ChannelState::OurInitSent as u32) | (ChannelState::TheirInitSent as u32) { + panic!("Tried to send accept_channel2 after channel had moved forward"); + } + if self.context.cur_holder_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER { + panic!("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 { + temporary_channel_id: self.context.temporary_channel_id.unwrap(), + funding_satoshis: self.dual_funding_context.our_funding_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, + 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, + second_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()), + 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; @@ -8251,7 +8426,9 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch channel_keys_id, blocked_monitor_updates: blocked_monitor_updates.unwrap(), - } + }, + #[cfg(dual_funding)] + dual_funding_channel_context: None, }) } } @@ -8812,7 +8989,11 @@ mod tests { let config = UserConfig::default(); let features = channelmanager::provided_init_features(&config); let outbound_chan = OutboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, node_b_node_id, &features, 10000000, 100000, 42, &config, 0, 42, None).unwrap(); - let mut chan = Channel { context: outbound_chan.context }; + let mut chan = Channel { + context: outbound_chan.context, + #[cfg(dual_funding)] + dual_funding_channel_context: None, + }; let dummy_htlc_source = HTLCSource::OutboundRoute { path: Path { From 2f1fcb70f4820c8ee9ff23455ccd0e3e2b391296 Mon Sep 17 00:00:00 2001 From: Duncan Dean Date: Fri, 24 Nov 2023 09:58:21 +0200 Subject: [PATCH 011/101] Create ChannelContext constructor for outbound channels --- lightning/src/ln/channel.rs | 420 ++++++++++++++++++++---------------- 1 file changed, 231 insertions(+), 189 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 1a217bd862a..655a123ca41 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -1603,6 +1603,221 @@ 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: ChannelId, + 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()}), + }; + + + 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: HashSet::new(), + + channel_type, + channel_keys_id, + + blocked_monitor_updates: Vec::new(), + }) + } + /// Allowed in any state (including after shutdown) pub fn get_update_time_counter(&self) -> u32 { self.update_time_counter @@ -6488,201 +6703,28 @@ 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)); + let channel_type = Self::get_initial_channel_type(&config, their_features); - 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: HashSet::new(), - + temporary_channel_id, channel_type, - channel_keys_id, - - blocked_monitor_updates: Vec::new(), - }, + )?, unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 } - }) + }; + Ok(chan) } /// Only allowed after [`ChannelContext::channel_transaction_parameters`] is set. From 5c68d1033998bf8836f0f6065cf305df4820de9c Mon Sep 17 00:00:00 2001 From: Duncan Dean Date: Tue, 12 Sep 2023 21:27:14 +0200 Subject: [PATCH 012/101] Add `OutboundV2Channel` struct --- lightning/src/ln/channel.rs | 193 +++++++++++++++++++++++++++++------- 1 file changed, 158 insertions(+), 35 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 655a123ca41..a2b1c1ba625 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -1616,7 +1616,10 @@ impl ChannelContext where SP::Target: SignerProvider { current_chain_height: u32, outbound_scid_alias: u64, temporary_channel_id: ChannelId, - 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, @@ -1627,9 +1630,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)}); @@ -1644,13 +1644,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() { @@ -1706,6 +1701,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, @@ -1739,6 +1735,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)] @@ -1759,6 +1757,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, @@ -6704,7 +6704,17 @@ impl OutboundV1Channel where SP::Target: SignerProvider { F::Target: FeeEstimator { let temporary_channel_id = temporary_channel_id.unwrap_or_else(|| ChannelId::temporary_from_entropy_source(entropy_source)); - 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( @@ -6720,7 +6730,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 } }; @@ -6813,29 +6826,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. @@ -7414,6 +7404,114 @@ impl InboundV1Channel where SP::Target: SignerProvider { } } +// A not-yet-funded outbound (from holder) channel using V2 channel establishment. +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 = 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.channel_state != ChannelState::OurInitSent as u32 { + panic!("Cannot generate an open_channel2 after we've moved forward"); + } + + if self.context.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.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 { + 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, + funding_feerate_sat_per_1000_weight: self.context.feerate_per_kw, + 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, + second_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()), + 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 { @@ -7567,6 +7665,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 23febee5384052c8287861d3bb4c715ddffdba90 Mon Sep 17 00:00:00 2001 From: Duncan Dean Date: Wed, 13 Sep 2023 13:41:20 +0200 Subject: [PATCH 013/101] Add V2 `ChannelPhase` variants --- lightning/src/ln/channel.rs | 12 ++++++ lightning/src/ln/channelmanager.rs | 64 ++++++++++++++++++++++++++-- lightning/src/ln/functional_tests.rs | 2 +- 3 files changed, 74 insertions(+), 4 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index a2b1c1ba625..c22da6b1ef3 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -895,6 +895,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), } @@ -907,6 +911,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, } } @@ -915,6 +923,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 7c6ee1e429c..39ce092489c 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -2055,6 +2055,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) + }, } }; } @@ -2923,6 +2931,13 @@ where // Unfunded channel has no update (None, chan_phase.context().get_counterparty_node_id()) }, + // TODO(dual_funding): Combine this match arm with above. + #[cfg(dual_funding)] + ChannelPhase::UnfundedOutboundV2(_) | ChannelPhase::UnfundedInboundV2(_) => { + self.finish_close_channel(chan_phase.context_mut().force_shutdown(false)); + // 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); @@ -4946,6 +4961,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) + }, } }); @@ -6042,14 +6067,27 @@ where num_unfunded_channels += 1; } }, - ChannelPhase::UnfundedInboundV1(chan) => { - if chan.context.minimum_depth().unwrap_or(1) != 0 { + ChannelPhase::UnfundedInboundV1(_) => { + if phase.context().minimum_depth().unwrap_or(1) != 0 { + num_unfunded_channels += 1; + } + }, + // TODO(dual_funding): Combine this match arm with above. + #[cfg(dual_funding)] + ChannelPhase::UnfundedInboundV2(_) => { + if phase.context().minimum_depth().unwrap_or(1) != 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. + #[cfg(dual_funding)] + ChannelPhase::UnfundedOutboundV2(_) => { + // Outbound channels don't contribute to the unfunded count in the DoS context. + continue; } } } @@ -6225,7 +6263,7 @@ where }, } }, - Some(ChannelPhase::Funded(_)) | Some(ChannelPhase::UnfundedOutboundV1(_)) => { + Some(_) => { return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got an unexpected funding_created message from peer with counterparty_node_id {}", counterparty_node_id), msg.temporary_channel_id)); }, None => 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.temporary_channel_id)) @@ -6448,6 +6486,15 @@ where let mut chan = remove_channel_phase!(self, chan_phase_entry); finish_shutdown = Some(chan.context_mut().force_shutdown(false)); }, + // 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); + self.issue_channel_close_events(&context, ClosureReason::CounterpartyCoopClosedUnfundedChannel); + let mut chan = remove_channel_phase!(self, chan_phase_entry); + finish_shutdown = Some(chan.context_mut().force_shutdown(false)); + }, } } 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)) @@ -8263,6 +8310,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 { @@ -8730,6 +8780,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); diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index fee3a3b3994..998a1a17233 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), } } From 3739affe00431df65325e282e19e3d50a20615e1 Mon Sep 17 00:00:00 2001 From: Duncan Dean Date: Tue, 21 Mar 2023 21:12:53 +0200 Subject: [PATCH 014/101] Initial `InteractiveTxConstructor` design Co-authored-by: Wilmer Paulino <9447167+wpaulino@users.noreply.github.com> Co-authored-by: Duncan Dean Co-authored-by: Jurvis Tan <5944973+jurvis@users.noreply.github.com> --- lightning/src/events/bump_transaction.rs | 4 +- lightning/src/ln/interactivetxs.rs | 920 +++++++++++++++++++++++ lightning/src/ln/mod.rs | 2 + lightning/src/ln/msgs.rs | 12 +- lightning/src/util/ser.rs | 2 +- 5 files changed, 933 insertions(+), 7 deletions(-) create mode 100644 lightning/src/ln/interactivetxs.rs diff --git a/lightning/src/events/bump_transaction.rs b/lightning/src/events/bump_transaction.rs index 8c6390302f1..e799800adc1 100644 --- a/lightning/src/events/bump_transaction.rs +++ b/lightning/src/events/bump_transaction.rs @@ -39,11 +39,11 @@ use bitcoin::secp256k1; use bitcoin::secp256k1::Secp256k1; use bitcoin::secp256k1::ecdsa::Signature; -const EMPTY_SCRIPT_SIG_WEIGHT: u64 = 1 /* empty script_sig */ * WITNESS_SCALE_FACTOR as u64; +pub(crate) const EMPTY_SCRIPT_SIG_WEIGHT: u64 = 1 /* empty script_sig */ * WITNESS_SCALE_FACTOR as u64; const BASE_INPUT_SIZE: u64 = 32 /* txid */ + 4 /* vout */ + 4 /* sequence */; -const BASE_INPUT_WEIGHT: u64 = BASE_INPUT_SIZE * WITNESS_SCALE_FACTOR as u64; +pub(crate) const BASE_INPUT_WEIGHT: u64 = BASE_INPUT_SIZE * WITNESS_SCALE_FACTOR as u64; /// A descriptor used to sign for a commitment transaction's anchor output. #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/lightning/src/ln/interactivetxs.rs b/lightning/src/ln/interactivetxs.rs new file mode 100644 index 00000000000..9d8eb4f1918 --- /dev/null +++ b/lightning/src/ln/interactivetxs.rs @@ -0,0 +1,920 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +use crate::prelude::*; +use crate::io_extras::sink; +use core::ops::Deref; + +use bitcoin::blockdata::constants::WITNESS_SCALE_FACTOR; +use bitcoin::consensus::Encodable; +use bitcoin::locktime::absolute::LockTime; +use bitcoin::policy::MAX_STANDARD_TX_WEIGHT; +use bitcoin::{OutPoint, Sequence, Transaction, TxIn, TxOut}; + +use crate::chain::chaininterface::fee_for_weight; +use crate::events::bump_transaction::{BASE_INPUT_WEIGHT, EMPTY_SCRIPT_SIG_WEIGHT}; +use crate::ln::channel::TOTAL_BITCOIN_SUPPLY_SATOSHIS; +use crate::ln::{ChannelId, msgs}; +use crate::ln::msgs::SerialId; +use crate::sign::EntropySource; +use crate::util::ser::TransactionU16LenLimited; + +/// The number of received `tx_add_input` messages during a negotiation at which point the +/// negotiation MUST be failed. +const MAX_RECEIVED_TX_ADD_INPUT_COUNT: u16 = 4096; + +/// The number of received `tx_add_output` messages during a negotiation at which point the +/// negotiation MUST be failed. +const MAX_RECEIVED_TX_ADD_OUTPUT_COUNT: u16 = 4096; + +/// The number of inputs or outputs that the state machine can have, before it MUST fail the +/// negotiation. +const MAX_INPUTS_OUTPUTS_COUNT: usize = 252; + +trait SerialIdExt { + fn is_valid_for_initiator(&self) -> bool; +} + +impl SerialIdExt for SerialId { + fn is_valid_for_initiator(&self) -> bool { + self % 2 == 0 + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum AbortReason { + InvalidStateTransition, + UnexpectedCounterpartyMessage, + ReceivedTooManyTxAddInputs, + ReceivedTooManyTxAddOutputs, + IncorrectInputSequenceValue, + IncorrectSerialIdParity, + SerialIdUnknown, + DuplicateSerialId, + PrevTxOutInvalid, + ExceededMaximumSatsAllowed, + ExceededNumberOfInputsOrOutputs, + TransactionTooLarge, + BelowDustLimit, + InvalidOutputScript, + InsufficientFees, +} + +#[derive(Debug)] +pub struct TxInputWithPrevOutput { + input: TxIn, + prev_output: TxOut, +} + +#[derive(Debug)] +struct NegotiationContext { + holder_is_initiator: bool, + received_tx_add_input_count: u16, + received_tx_add_output_count: u16, + inputs: HashMap, + prevtx_outpoints: HashSet, + outputs: HashMap, + tx_locktime: LockTime, + feerate_sat_per_kw: u32, +} + +impl NegotiationContext { + fn is_serial_id_valid_for_counterparty(&self, serial_id: &SerialId) -> bool { + // A received `SerialId`'s parity must match the role of the counterparty. + self.holder_is_initiator == !serial_id.is_valid_for_initiator() + } + + fn counterparty_inputs_contributed(&self) -> impl Iterator + Clone { + self.inputs.iter() + .filter(move |(serial_id, _)| self.is_serial_id_valid_for_counterparty(serial_id)) + .map(|(_, input_with_prevout)| input_with_prevout) + } + + fn counterparty_outputs_contributed(&self) -> impl Iterator + Clone{ + self.outputs.iter() + .filter(move |(serial_id, _)| self.is_serial_id_valid_for_counterparty(serial_id)) + .map(|(_, input_with_prevout)| input_with_prevout) + } + + fn remote_tx_add_input(&mut self, msg: &msgs::TxAddInput) -> Result<(), AbortReason> { + // The interactive-txs spec calls for us to fail negotiation if the `prevtx` we receive is + // invalid. However, we would not need to account for this explicit negotiation failure + // mode here since `PeerManager` would already disconnect the peer if the `prevtx` is + // invalid; implicitly ending the negotiation. + + if !self.is_serial_id_valid_for_counterparty(&msg.serial_id) { + // The receiving node: + // - MUST fail the negotiation if: + // - the `serial_id` has the wrong parity + return Err(AbortReason::IncorrectSerialIdParity); + } + + if msg.sequence >= 0xFFFFFFFE { + // The receiving node: + // - MUST fail the negotiation if: + // - `sequence` is set to `0xFFFFFFFE` or `0xFFFFFFFF` + return Err(AbortReason::IncorrectInputSequenceValue); + } + + let transaction = msg.prevtx.clone().into_transaction(); + + if let Some(tx_out) = transaction.output.get(msg.prevtx_out as usize) { + if !tx_out.script_pubkey.is_witness_program() { + // The receiving node: + // - MUST fail the negotiation if: + // - the `scriptPubKey` is not a witness program + return Err(AbortReason::PrevTxOutInvalid); + } else if !self.prevtx_outpoints.insert(OutPoint { + txid: transaction.txid(), + vout: msg.prevtx_out, + }) { + // The receiving node: + // - MUST fail the negotiation if: + // - the `prevtx` and `prevtx_vout` are identical to a previously added + // (and not removed) input's + return Err(AbortReason::PrevTxOutInvalid); + } + } else { + // The receiving node: + // - MUST fail the negotiation if: + // - `prevtx_vout` is greater or equal to the number of outputs on `prevtx` + return Err(AbortReason::PrevTxOutInvalid); + } + + self.received_tx_add_input_count += 1; + if self.received_tx_add_input_count > MAX_RECEIVED_TX_ADD_INPUT_COUNT { + // The receiving node: + // - MUST fail the negotiation if: + // - if has received 4096 `tx_add_input` messages during this negotiation + return Err(AbortReason::ReceivedTooManyTxAddInputs); + } + + let prev_out = if let Some(prev_out) = msg.prevtx.0.output.get(msg.prevtx_out as usize) { + prev_out.clone() + } else { + return Err(AbortReason::PrevTxOutInvalid); + }; + if self.inputs.iter().any(|(serial_id, _)| *serial_id == msg.serial_id) { + // The receiving node: + // - MUST fail the negotiation if: + // - the `serial_id` is already included in the transaction + return Err(AbortReason::DuplicateSerialId); + } + let prev_outpoint = OutPoint { + txid: transaction.txid(), + vout: msg.prevtx_out, + }; + self.inputs.insert(msg.serial_id, TxInputWithPrevOutput { + input: TxIn { + previous_output: prev_outpoint.clone(), + sequence: Sequence(msg.sequence), + ..Default::default() + }, + prev_output: prev_out, + }); + self.prevtx_outpoints.insert(prev_outpoint); + Ok(()) + } + + fn remote_tx_remove_input(&mut self, msg: &msgs::TxRemoveInput) -> Result<(), AbortReason> { + if !self.is_serial_id_valid_for_counterparty(&msg.serial_id) { + return Err(AbortReason::IncorrectSerialIdParity); + } + + if let Some(_) = self.inputs.remove(&msg.serial_id) { + Ok(()) + } else { + // The receiving node: + // - MUST fail the negotiation if: + // - the input or output identified by the `serial_id` was not added by the sender + // - the `serial_id` does not correspond to a currently added input + Err(AbortReason::SerialIdUnknown) + } + } + + fn remote_tx_add_output(&mut self, msg: &msgs::TxAddOutput) -> Result<(), AbortReason> { + // The receiving node: + // - MUST fail the negotiation if: + // - the serial_id has the wrong parity + if !self.is_serial_id_valid_for_counterparty(&msg.serial_id) { + return Err(AbortReason::IncorrectSerialIdParity); + } + + self.received_tx_add_output_count += 1; + if self.received_tx_add_output_count > MAX_RECEIVED_TX_ADD_OUTPUT_COUNT { + // The receiving node: + // - MUST fail the negotiation if: + // - if has received 4096 `tx_add_output` messages during this negotiation + return Err(AbortReason::ReceivedTooManyTxAddOutputs); + } + + if msg.sats < msg.script.dust_value().to_sat() { + // The receiving node: + // - MUST fail the negotiation if: + // - the sats amount is less than the dust_limit + return Err(AbortReason::BelowDustLimit); + } + if msg.sats > TOTAL_BITCOIN_SUPPLY_SATOSHIS { + // The receiving node: + // - MUST fail the negotiation if: + // - the sats amount is greater than 2,100,000,000,000,000 (TOTAL_BITCOIN_SUPPLY_SATOSHIS) + return Err(AbortReason::ExceededMaximumSatsAllowed); + } + + // The receiving node: + // - MUST accept P2WSH, P2WPKH, P2TR scripts + // - MAY fail the negotiation if script is non-standard + if !msg.script.is_v0_p2wpkh() && !msg.script.is_v0_p2wsh() && !msg.script.is_v1_p2tr() { + return Err(AbortReason::InvalidOutputScript); + } + + if self.outputs.iter().any(|(serial_id, _)| *serial_id == msg.serial_id) { + // The receiving node: + // - MUST fail the negotiation if: + // - the `serial_id` is already included in the transaction + return Err(AbortReason::DuplicateSerialId); + } + + let output = TxOut { + value: msg.sats, + script_pubkey: msg.script.clone(), + }; + self.outputs.insert(msg.serial_id, output); + Ok(()) + } + + fn remote_tx_remove_output(&mut self, msg: &msgs::TxRemoveOutput) -> Result<(), AbortReason> { + if !self.is_serial_id_valid_for_counterparty(&msg.serial_id) { + return Err(AbortReason::IncorrectSerialIdParity); + } + if let Some(_) = self.outputs.remove(&msg.serial_id) { + Ok(()) + } else { + // The receiving node: + // - MUST fail the negotiation if: + // - the input or output identified by the `serial_id` was not added by the sender + // - the `serial_id` does not correspond to a currently added input + Err(AbortReason::SerialIdUnknown) + } + } + + fn local_tx_add_input(&mut self, msg: &msgs::TxAddInput) { + let tx = msg.prevtx.clone().into_transaction(); + let input = TxIn { + previous_output: OutPoint { + txid: tx.txid(), + vout: msg.prevtx_out, + }, + sequence: Sequence(msg.sequence), + ..Default::default() + }; + debug_assert!((msg.prevtx_out as usize) < tx.output.len()); + let prev_output = &tx.output[msg.prevtx_out as usize]; + self.prevtx_outpoints.insert(input.previous_output.clone()); + self.inputs.insert(msg.serial_id, TxInputWithPrevOutput { + input, + prev_output: prev_output.clone(), + }); + } + + fn local_tx_add_output(&mut self, msg: &msgs::TxAddOutput) { + self.outputs.insert(msg.serial_id, TxOut { + value: msg.sats, + script_pubkey: msg.script.clone(), + }); + } + + fn local_tx_remove_input(&mut self, msg: &msgs::TxRemoveInput) { + self.inputs.remove(&msg.serial_id); + } + + fn local_tx_remove_output(&mut self, msg: &msgs::TxRemoveOutput) { + self.outputs.remove(&msg.serial_id); + } + + fn build_transaction(mut self) -> Result { + // The receiving node: + // MUST fail the negotiation if: + + // - the peer's total input satoshis is less than their outputs + let counterparty_inputs_contributed = self.counterparty_inputs_contributed(); + let counterparty_inputs_value: u64 = counterparty_inputs_contributed.clone() + .map(|input| input.prev_output.value).sum(); + let counterparty_outputs_contributed = self.counterparty_outputs_contributed(); + let counterparty_outputs_value: u64 = counterparty_outputs_contributed.clone() + .map(|output| output.value).sum(); + if counterparty_inputs_value < counterparty_outputs_value { + return Err(AbortReason::InsufficientFees); + } + + // - there are more than 252 inputs + // - there are more than 252 outputs + if self.inputs.len() > MAX_INPUTS_OUTPUTS_COUNT || self.outputs.len() > MAX_INPUTS_OUTPUTS_COUNT { + return Err(AbortReason::ExceededNumberOfInputsOrOutputs); + } + + // Inputs and outputs must be sorted by serial_id + let mut inputs = self.inputs.iter().collect::>(); + let mut outputs = self.outputs.iter().collect::>(); + inputs.sort_unstable_by_key(|(serial_id, _)| *serial_id); + outputs.sort_unstable_by_key(|(serial_id, _)| *serial_id); + + let tx_to_validate = Transaction { + version: 2, + lock_time: self.tx_locktime, + input: inputs.into_iter().map(|(_, input)| input.input.clone()).collect(), + output: outputs.into_iter().map(|(_, output)| output.clone()).collect(), + }; + if tx_to_validate.weight().to_wu() > MAX_STANDARD_TX_WEIGHT as u64 { + return Err(AbortReason::TransactionTooLarge); + } + + // TODO: How do we enforce their fees cover the witness without knowing its expected length? + const INPUT_WEIGHT: u64 = BASE_INPUT_WEIGHT + EMPTY_SCRIPT_SIG_WEIGHT; + + // - the peer's paid feerate does not meet or exceed the agreed feerate (based on the minimum fee). + let counterparty_output_weight_contributed: u64 = counterparty_outputs_contributed.clone().map(|output| + (8 /* value */ + output.script_pubkey.consensus_encode(&mut sink()).unwrap() as u64) * + WITNESS_SCALE_FACTOR as u64 + ).sum(); + let counterparty_weight_contributed = counterparty_output_weight_contributed + + counterparty_inputs_contributed.clone().count() as u64 * INPUT_WEIGHT; + let counterparty_fees_contributed = + counterparty_inputs_value.saturating_sub(counterparty_outputs_value); + let mut required_counterparty_contribution_fee = fee_for_weight(self.feerate_sat_per_kw, counterparty_weight_contributed); + if !self.holder_is_initiator { + // if is the non-initiator: + // - the initiator's fees do not cover the common fields (version, segwit marker + flag, + // input count, output count, locktime) + let tx_common_fields_weight = + (4 /* version */ + 4 /* locktime */ + 1 /* input count */ + 1 /* output count */) * + WITNESS_SCALE_FACTOR as u64 + 2 /* segwit marker + flag */; + let tx_common_fields_fee = fee_for_weight(self.feerate_sat_per_kw, tx_common_fields_weight); + required_counterparty_contribution_fee += tx_common_fields_fee; + } + if counterparty_fees_contributed < required_counterparty_contribution_fee { + return Err(AbortReason::InsufficientFees); + } + + Ok(tx_to_validate) + } +} + +// Channel states that can receive `(send|receive)_tx_(add|remove)_(input|output)` +trait State {} + +trait LocalState: State { + fn into_negotiation_context(self) -> NegotiationContext; +} + +trait RemoteState: State { + fn into_negotiation_context(self) -> NegotiationContext; +} + +macro_rules! define_state { + (LOCAL_STATE, $state: ident, $doc: expr) => { + define_state!($state, NegotiationContext, $doc); + impl LocalState for $state { + fn into_negotiation_context(self) -> NegotiationContext { + self.0 + } + } + }; + (REMOTE_STATE, $state: ident, $doc: expr) => { + define_state!($state, NegotiationContext, $doc); + impl RemoteState for $state { + fn into_negotiation_context(self) -> NegotiationContext { + self.0 + } + } + }; + ($state: ident, $inner: ident, $doc: expr) => { + #[doc = $doc] + #[derive(Debug)] + struct $state($inner); + impl State for $state {} + }; +} + +define_state!(LOCAL_STATE, LocalChange, "We have sent a message to the counterparty that has affected our negotiation state."); +define_state!(LOCAL_STATE, LocalTxComplete, "We have sent a `tx_complete` message and are awaiting the counterparty's."); +define_state!(REMOTE_STATE, RemoteChange, "We have received a message from the counterparty that has affected our negotiation state."); +define_state!(REMOTE_STATE, RemoteTxComplete, "We have received a `tx_complete` message and the counterparty is awaiting ours."); +define_state!(NegotiationComplete, Transaction, "We have exchanged consecutive `tx_complete` messages with the counterparty and the transaction negotiation is complete."); +define_state!(NegotiationAborted, AbortReason, "The negotiation has failed and cannot be continued."); + +type StateTransitionResult = Result; + +trait StateTransition { + fn transition(self, data: TransitionData) -> StateTransitionResult; +} + +macro_rules! define_state_transitions { + (LOCAL_STATE, [$(DATA $data: ty, TRANSITION $transition: ident),+]) => { + $( + impl StateTransition for S { + fn transition(self, data: $data) -> StateTransitionResult { + let mut context = self.into_negotiation_context(); + let _ = context.$transition(data)?; + Ok(RemoteChange(context)) + } + } + )* + }; + (REMOTE_STATE, [$(DATA $data: ty, TRANSITION $transition: ident),+]) => { + $( + impl StateTransition for S { + fn transition(self, data: $data) -> StateTransitionResult { + let mut context = self.into_negotiation_context(); + let _ = context.$transition(data); + Ok(LocalChange(context)) + } + } + )* + }; + (TX_COMPLETE_AS_ACK, $from_state: ident, $to_state: ident) => { + impl StateTransition<$to_state, &msgs::TxComplete> for $from_state { + fn transition(self, _data: &msgs::TxComplete) -> StateTransitionResult<$to_state> { + Ok($to_state(self.into_negotiation_context())) + } + } + }; + (TX_COMPLETE, $from_state: ident) => { + impl StateTransition for $from_state { + fn transition(self, _data: &msgs::TxComplete) -> StateTransitionResult { + let context = self.into_negotiation_context(); + let tx = context.build_transaction()?; + Ok(NegotiationComplete(tx)) + } + } + }; +} + +define_state_transitions!(LOCAL_STATE, [ + DATA &msgs::TxAddInput, TRANSITION remote_tx_add_input, + DATA &msgs::TxRemoveInput, TRANSITION remote_tx_remove_input, + DATA &msgs::TxAddOutput, TRANSITION remote_tx_add_output, + DATA &msgs::TxRemoveOutput, TRANSITION remote_tx_remove_output +]); +define_state_transitions!(REMOTE_STATE, [ + DATA &msgs::TxAddInput, TRANSITION local_tx_add_input, + DATA &msgs::TxRemoveInput, TRANSITION local_tx_remove_input, + DATA &msgs::TxAddOutput, TRANSITION local_tx_add_output, + DATA &msgs::TxRemoveOutput, TRANSITION local_tx_remove_output +]); +define_state_transitions!(TX_COMPLETE_AS_ACK, LocalChange, RemoteTxComplete); +define_state_transitions!(TX_COMPLETE_AS_ACK, RemoteChange, LocalTxComplete); +define_state_transitions!(TX_COMPLETE, LocalTxComplete); +define_state_transitions!(TX_COMPLETE, RemoteTxComplete); + +#[derive(Debug)] +enum StateMachine { + Indeterminate, + LocalChange(LocalChange), + RemoteChange(RemoteChange), + LocalTxComplete(LocalTxComplete), + RemoteTxComplete(RemoteTxComplete), + NegotiationComplete(NegotiationComplete), + NegotiationAborted(NegotiationAborted), +} + +impl Default for StateMachine { + fn default() -> Self { + Self::Indeterminate + } +} + +macro_rules! define_state_machine_transitions { + ($transition: ident, $msg: ty, [$(FROM $from_state: ident, TO $to_state: ident),+]) => { + fn $transition(self, msg: $msg) -> StateMachine { + match self { + $( + Self::$from_state(s) => match s.transition(msg) { + Ok(new_state) => StateMachine::$to_state(new_state), + Err(abort_reason) => StateMachine::NegotiationAborted(NegotiationAborted(abort_reason)), + } + )* + _ => StateMachine::NegotiationAborted(NegotiationAborted(AbortReason::UnexpectedCounterpartyMessage)), + } + } + }; + (LOCAL_OR_REMOTE_CHANGE, $to_local_transition: ident, $to_remote_transition: ident, $msg: ty) => { + define_state_machine_transitions!($to_local_transition, $msg, [ + FROM RemoteChange, TO LocalChange, + FROM RemoteTxComplete, TO LocalChange + ]); + define_state_machine_transitions!($to_remote_transition, $msg, [ + FROM LocalChange, TO RemoteChange, + FROM LocalTxComplete, TO RemoteChange + ]); + }; +} + +impl StateMachine { + fn new(feerate_sat_per_kw: u32, is_initiator: bool, tx_locktime: LockTime) -> Self { + let context = NegotiationContext { + tx_locktime, + holder_is_initiator: is_initiator, + received_tx_add_input_count: 0, + received_tx_add_output_count: 0, + inputs: HashMap::new(), + prevtx_outpoints: HashSet::new(), + outputs: HashMap::new(), + feerate_sat_per_kw, + }; + if is_initiator { + Self::RemoteChange(RemoteChange(context)) + } else { + Self::LocalChange(LocalChange(context)) + } + } + + define_state_machine_transitions!( + LOCAL_OR_REMOTE_CHANGE, local_tx_add_input, remote_tx_add_input, &msgs::TxAddInput + ); + define_state_machine_transitions!( + LOCAL_OR_REMOTE_CHANGE, local_tx_add_output, remote_tx_add_output, &msgs::TxAddOutput + ); + define_state_machine_transitions!( + LOCAL_OR_REMOTE_CHANGE, local_tx_remove_input, remote_tx_remove_input, &msgs::TxRemoveInput + ); + define_state_machine_transitions!( + LOCAL_OR_REMOTE_CHANGE, local_tx_remove_output, remote_tx_remove_output, &msgs::TxRemoveOutput + ); + define_state_machine_transitions!(local_tx_complete, &msgs::TxComplete, [ + FROM RemoteChange, TO LocalTxComplete, + FROM RemoteTxComplete, TO NegotiationComplete + ]); + define_state_machine_transitions!(remote_tx_complete, &msgs::TxComplete, [ + FROM LocalChange, TO RemoteTxComplete, + FROM LocalTxComplete, TO NegotiationComplete + ]); +} + +pub struct InteractiveTxConstructor { + state_machine: StateMachine, + channel_id: ChannelId, + inputs_to_contribute: Vec<(SerialId, TxIn, Transaction)>, + outputs_to_contribute: Vec<(SerialId, TxOut)>, +} + +pub enum InteractiveTxMessageSend { + TxAddInput(msgs::TxAddInput), + TxAddOutput(msgs::TxAddOutput), + TxComplete(msgs::TxComplete), +} + +macro_rules! do_state_transition { + ($self: ident, $transition: ident, $msg: expr) => {{ + let state_machine = core::mem::take(&mut $self.state_machine); + $self.state_machine = state_machine.$transition($msg); + match &$self.state_machine { + StateMachine::NegotiationAborted(state) => Err(state.0.clone()), + _ => Ok(()), + } + }}; +} + +fn generate_local_serial_id(entropy_source: &ES, is_initiator: bool) -> SerialId where ES::Target: EntropySource { + let rand_bytes = entropy_source.get_secure_random_bytes(); + let mut serial_id_bytes = [0u8; 8]; + serial_id_bytes.copy_from_slice(&rand_bytes[..8]); + let mut serial_id = u64::from_be_bytes(serial_id_bytes); + if serial_id.is_valid_for_initiator() != is_initiator { + serial_id ^= 1; + } + serial_id +} + +impl InteractiveTxConstructor { + pub fn new( + entropy_source: &ES, channel_id: ChannelId, feerate_sat_per_kw: u32, is_initiator: bool, + tx_locktime: LockTime, inputs_to_contribute: Vec<(TxIn, Transaction)>, + outputs_to_contribute: Vec, + ) -> (Self, Option) + where + ES::Target: EntropySource, + { + let state_machine = StateMachine::new(feerate_sat_per_kw, is_initiator, tx_locktime); + let inputs_to_contribute = inputs_to_contribute.into_iter().map(|(input, tx)| { + let serial_id = generate_local_serial_id(entropy_source, is_initiator); + (serial_id, input, tx) + }).collect(); + let outputs_to_contribute = outputs_to_contribute.into_iter().map(|output| { + let serial_id = generate_local_serial_id(entropy_source, is_initiator); + (serial_id, output) + }).collect(); + let mut constructor = Self { + state_machine, + channel_id, + inputs_to_contribute, + outputs_to_contribute, + }; + let message_send = if is_initiator { + match constructor.do_local_state_transition() { + Ok(msg_send) => Some(msg_send), + Err(_) => { + debug_assert!(false, "We should always be able to start our state machine successfully"); + None + } + } + } else { + None + }; + (constructor, message_send) + } + + fn do_local_state_transition(&mut self) -> Result { + if let Some((serial_id, input, prev_tx)) = self.inputs_to_contribute.pop() { + let msg = msgs::TxAddInput { + channel_id: self.channel_id, + serial_id, + prevtx: TransactionU16LenLimited(prev_tx), + prevtx_out: input.previous_output.vout, + sequence: input.sequence.to_consensus_u32(), + }; + let _ = do_state_transition!(self, local_tx_add_input, &msg)?; + Ok(InteractiveTxMessageSend::TxAddInput(msg)) + } else if let Some((serial_id, output)) = self.outputs_to_contribute.pop() { + let msg = msgs::TxAddOutput { + channel_id: self.channel_id, + serial_id, + sats: output.value, + script: output.script_pubkey, + }; + let _ = do_state_transition!(self, local_tx_add_output, &msg)?; + Ok(InteractiveTxMessageSend::TxAddOutput(msg)) + } else { + let msg = msgs::TxComplete { channel_id: self.channel_id }; + let _ = do_state_transition!(self, local_tx_complete, &msg)?; + Ok(InteractiveTxMessageSend::TxComplete(msg)) + } + } + + pub fn handle_tx_add_input(&mut self, msg: &msgs::TxAddInput) -> Result { + let _ = do_state_transition!(self, remote_tx_add_input, msg)?; + self.do_local_state_transition() + } + + pub fn handle_tx_remove_input(&mut self, msg: &msgs::TxRemoveInput) -> Result { + let _ = do_state_transition!(self, remote_tx_remove_input, msg)?; + self.do_local_state_transition() + } + + pub fn handle_tx_add_output(&mut self, msg: &msgs::TxAddOutput) -> Result { + let _ = do_state_transition!(self, remote_tx_add_output, msg)?; + self.do_local_state_transition() + } + + pub fn handle_tx_remove_output(&mut self, msg: &msgs::TxRemoveOutput) -> Result { + let _ = do_state_transition!(self, remote_tx_remove_output, msg)?; + self.do_local_state_transition() + } + + pub fn handle_tx_complete(&mut self, msg: &msgs::TxComplete) -> Result<(Option, Option), AbortReason> { + let _ = do_state_transition!(self, remote_tx_complete, msg)?; + match &self.state_machine { + StateMachine::RemoteTxComplete(_) => { + let msg_send = self.do_local_state_transition()?; + let negotiated_tx = match &self.state_machine { + StateMachine::NegotiationComplete(s) => Some(s.0.clone()), + StateMachine::LocalChange(_) => None, // We either had an input or output to contribute. + _ => { + debug_assert!(false, "We cannot transition to any other states after receiving `tx_complete` and responding"); + return Err(AbortReason::InvalidStateTransition); + } + }; + Ok((Some(msg_send), negotiated_tx)) + } + StateMachine::NegotiationComplete(s) => Ok((None, Some(s.0.clone()))), + _ => { + debug_assert!(false, "We cannot transition to any other states after receiving `tx_complete`"); + Err(AbortReason::InvalidStateTransition) + } + } + } +} + +#[cfg(test)] +mod tests { + use core::default::Default; + use crate::chain::chaininterface::FEERATE_FLOOR_SATS_PER_KW; + use crate::ln::interactivetxs::{AbortReason, InteractiveTxConstructor, InteractiveTxMessageSend}; + use bitcoin::{OutPoint, Sequence, Transaction, TxIn, TxOut}; + use bitcoin::blockdata::opcodes; + use bitcoin::blockdata::script::Builder; + use bitcoin::locktime::absolute::LockTime; + use crate::ln::ChannelId; + use crate::sign::EntropySource; + use crate::util::atomic_counter::AtomicCounter; + + struct TestEntropySource(AtomicCounter); + impl EntropySource for TestEntropySource { + fn get_secure_random_bytes(&self) -> [u8; 32] { + let bytes = self.0.get_increment().to_be_bytes(); + let mut res = [0u8; 32]; + res[0..8].copy_from_slice(&bytes); + res + } + } + struct TestSession { + inputs_a: Vec<(TxIn, Transaction)>, + outputs_a: Vec, + inputs_b: Vec<(TxIn, Transaction)>, + outputs_b: Vec, + expect_error: Option, + } + + fn do_test_interactive_tx_constructor(session: TestSession) { + let entropy_source = TestEntropySource(AtomicCounter::new()); + let channel_id = ChannelId(entropy_source.get_secure_random_bytes()); + let tx_locktime = LockTime::from_height(1337).unwrap(); + + let (mut constructor_a, first_message_a) = InteractiveTxConstructor::new( + &&entropy_source, channel_id, FEERATE_FLOOR_SATS_PER_KW * 10, true, tx_locktime, session.inputs_a.clone(), session.outputs_a.clone() + ); + let (mut constructor_b, first_message_b) = InteractiveTxConstructor::new( + &&entropy_source, channel_id, FEERATE_FLOOR_SATS_PER_KW * 10, false, tx_locktime, session.inputs_b.clone(), session.outputs_b.clone() + ); + + let handle_message_send = |msg: InteractiveTxMessageSend, for_constructor: &mut InteractiveTxConstructor| { + match msg { + InteractiveTxMessageSend::TxAddInput(msg) => { + for_constructor.handle_tx_add_input(&msg).map(|msg_send| (Some(msg_send), None)) + }, + InteractiveTxMessageSend::TxAddOutput(msg) => { + for_constructor.handle_tx_add_output(&msg).map(|msg_send| (Some(msg_send), None)) + }, + InteractiveTxMessageSend::TxComplete(msg) => { + for_constructor.handle_tx_complete(&msg) + }, + } + }; + + assert!(first_message_b.is_none()); + let mut message_send_a = first_message_a; + let mut message_send_b = None; + let mut final_tx_a = None; + let mut final_tx_b = None; + while final_tx_a.is_none() || final_tx_b.is_none() { + if let Some(message_send_a) = message_send_a.take() { + match handle_message_send(message_send_a, &mut constructor_b) { + Ok((msg_send, final_tx)) => { + message_send_b = msg_send; + final_tx_b = final_tx; + } + Err(abort_reason) => { + assert_eq!(Some(abort_reason), session.expect_error); + return; + }, + } + } + if let Some(message_send_b) = message_send_b.take() { + match handle_message_send(message_send_b, &mut constructor_a) { + Ok((msg_send, final_tx)) => { + message_send_a = msg_send; + final_tx_a = final_tx; + } + Err(abort_reason) => { + assert_eq!(Some(abort_reason), session.expect_error); + return; + }, + } + } + } + assert!(message_send_a.is_none()); + assert!(message_send_b.is_none()); + assert_eq!(final_tx_a, final_tx_b); + assert!(session.expect_error.is_none()); + } + + fn generate_tx(values: &[u64]) -> Transaction { + Transaction { + version: 2, + lock_time: LockTime::from_height(1337).unwrap(), + input: vec![TxIn { ..Default::default() }], + output: values.iter().map(|value| TxOut { + value: *value, + script_pubkey: Builder::new().push_opcode(opcodes::OP_TRUE).into_script().to_v0_p2wsh(), + }).collect(), + } + } + + fn generate_inputs(values: &[u64]) -> Vec<(TxIn, Transaction)> { + let tx = generate_tx(values); + let txid = tx.txid(); + tx.output.iter().enumerate().map(|(idx, _)| { + let input = TxIn { + previous_output: OutPoint { + txid: txid, + vout: idx as u32, + }, + script_sig: Default::default(), + sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, + witness: Default::default(), + }; + (input, tx.clone()) + }).collect() + } + + fn generate_output(value: u64) -> TxOut { + TxOut { value, script_pubkey: Builder::new().push_opcode(opcodes::OP_TRUE).into_script().to_v0_p2wsh() } + } + + #[test] + fn test_interactive_tx_constructor() { + // No contributions. + do_test_interactive_tx_constructor(TestSession { + inputs_a: vec![], + outputs_a: vec![], + inputs_b: vec![], + outputs_b: vec![], + expect_error: Some(AbortReason::InsufficientFees), + }); + // Single contribution, no initiator inputs. + do_test_interactive_tx_constructor(TestSession { + inputs_a: vec![], + outputs_a: vec![generate_output(1_000_000)], + inputs_b: vec![], + outputs_b: vec![], + expect_error: Some(AbortReason::InsufficientFees), + }); + // Single contribution, no initiator outputs. + do_test_interactive_tx_constructor(TestSession { + inputs_a: generate_inputs(&[1_000_000]), + outputs_a: vec![], + inputs_b: vec![], + outputs_b: vec![], + expect_error: None, + }); + // Single contribution, insufficient fees. + do_test_interactive_tx_constructor(TestSession { + inputs_a: generate_inputs(&[1_000_000]), + outputs_a: vec![generate_output(1_000_000)], + inputs_b: vec![], + outputs_b: vec![], + expect_error: Some(AbortReason::InsufficientFees), + }); + // Initiator contributes sufficient fees, but non-initiator does not. + do_test_interactive_tx_constructor(TestSession { + inputs_a: generate_inputs(&[1_000_000]), + outputs_a: vec![], + inputs_b: generate_inputs(&[100_000]), + outputs_b: vec![generate_output(100_000)], + expect_error: Some(AbortReason::InsufficientFees), + }); + // Multi-input-output contributions from both sides. + do_test_interactive_tx_constructor(TestSession { + inputs_a: generate_inputs(&[1_000_000, 1_000_000]), + outputs_a: vec![generate_output(1_000_000), generate_output(200_000)], + inputs_b: generate_inputs(&[1_000_000, 500_000]), + outputs_b: vec![generate_output(1_000_000), generate_output(400_000)], + expect_error: None, + }); + // Invalid input sequence from initiator. + let tx = generate_tx(&[1_000_000]); + let invalid_sequence_input = TxIn { + previous_output: OutPoint { txid: tx.txid(), vout: 0 }, + ..Default::default() + }; + do_test_interactive_tx_constructor(TestSession { + inputs_a: vec![(invalid_sequence_input, tx.clone())], + outputs_a: vec![generate_output(1_000_000)], + inputs_b: vec![], + outputs_b: vec![], + expect_error: Some(AbortReason::IncorrectInputSequenceValue), + }); + // Duplicate prevout from initiator. + let duplicate_input = TxIn { + previous_output: OutPoint { txid: tx.txid(), vout: 0 }, + sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, + ..Default::default() + }; + do_test_interactive_tx_constructor(TestSession { + inputs_a: vec![(duplicate_input.clone(), tx.clone()), (duplicate_input, tx.clone())], + outputs_a: vec![generate_output(1_000_000)], + inputs_b: vec![], + outputs_b: vec![], + expect_error: Some(AbortReason::PrevTxOutInvalid), + }); + // Non-initiator uses same prevout as initiator. + let duplicate_input = TxIn { + previous_output: OutPoint { txid: tx.txid(), vout: 0 }, + sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, + ..Default::default() + }; + do_test_interactive_tx_constructor(TestSession { + inputs_a: vec![(duplicate_input.clone(), tx.clone())], + outputs_a: vec![generate_output(1_000_000)], + inputs_b: vec![(duplicate_input.clone(), tx.clone())], + outputs_b: vec![], + expect_error: Some(AbortReason::PrevTxOutInvalid), + }); + } +} diff --git a/lightning/src/ln/mod.rs b/lightning/src/ln/mod.rs index 827d7741989..4267008520b 100644 --- a/lightning/src/ln/mod.rs +++ b/lightning/src/ln/mod.rs @@ -79,6 +79,8 @@ mod shutdown_tests; #[cfg(test)] #[allow(unused_mut)] mod async_signer_tests; +#[allow(unused_mut)] // TODO +pub(crate) mod interactivetxs; pub use self::peer_channel_encryptor::LN_MAX_MSG_LEN; diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index b877565e017..4f3c647ac8e 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -495,6 +495,10 @@ pub struct SpliceLocked { pub channel_id: ChannelId, } +/// A randomly chosen number that is used to identify inputs within an interactive transaction +/// construction. +pub type SerialId = u64; + /// A tx_add_input message for adding an input during interactive transaction construction /// // TODO(dual_funding): Add spec link for `tx_add_input`. @@ -504,7 +508,7 @@ pub struct TxAddInput { pub channel_id: ChannelId, /// A randomly chosen unique identifier for this input, which is even for initiators and odd for /// non-initiators. - pub serial_id: u64, + pub serial_id: SerialId, /// Serialized transaction that contains the output this input spends to verify that it is non /// malleable. pub prevtx: TransactionU16LenLimited, @@ -523,7 +527,7 @@ pub struct TxAddOutput { pub channel_id: ChannelId, /// A randomly chosen unique identifier for this output, which is even for initiators and odd for /// non-initiators. - pub serial_id: u64, + pub serial_id: SerialId, /// The satoshi value of the output pub sats: u64, /// The scriptPubKey for the output @@ -538,7 +542,7 @@ pub struct TxRemoveInput { /// The channel ID pub channel_id: ChannelId, /// The serial ID of the input to be removed - pub serial_id: u64, + pub serial_id: SerialId, } /// A tx_remove_output message for removing an output during interactive transaction construction. @@ -549,7 +553,7 @@ pub struct TxRemoveOutput { /// The channel ID pub channel_id: ChannelId, /// The serial ID of the output to be removed - pub serial_id: u64, + pub serial_id: SerialId, } /// A tx_complete message signalling the conclusion of a peer's transaction contributions during diff --git a/lightning/src/util/ser.rs b/lightning/src/util/ser.rs index 0d5cfc81906..1589329e7b6 100644 --- a/lightning/src/util/ser.rs +++ b/lightning/src/util/ser.rs @@ -1390,7 +1390,7 @@ impl Readable for Duration { /// /// Use [`TransactionU16LenLimited::into_transaction`] to convert into the contained `Transaction`. #[derive(Clone, Debug, Hash, PartialEq, Eq)] -pub struct TransactionU16LenLimited(Transaction); +pub struct TransactionU16LenLimited(pub Transaction); impl TransactionU16LenLimited { /// Constructs a new `TransactionU16LenLimited` from a `Transaction` only if it's consensus- From 906e01e0f337aae9b0ae2db00cb4fd141a97242d Mon Sep 17 00:00:00 2001 From: Jurvis Tan Date: Sat, 11 Nov 2023 15:59:29 -0800 Subject: [PATCH 015/101] Add more docs and tests --- lightning/src/ln/interactivetxs.rs | 222 ++++++++++++++++++++++++++--- 1 file changed, 206 insertions(+), 16 deletions(-) diff --git a/lightning/src/ln/interactivetxs.rs b/lightning/src/ln/interactivetxs.rs index 9d8eb4f1918..79c64f7f49a 100644 --- a/lightning/src/ln/interactivetxs.rs +++ b/lightning/src/ln/interactivetxs.rs @@ -369,10 +369,13 @@ impl NegotiationContext { // Channel states that can receive `(send|receive)_tx_(add|remove)_(input|output)` trait State {} +/// Category of states where we have sent some message to the counterparty, and we are waiting for +/// a response. trait LocalState: State { fn into_negotiation_context(self) -> NegotiationContext; } +/// Category of states that our counterparty has put us in after we receive a message from them. trait RemoteState: State { fn into_negotiation_context(self) -> NegotiationContext; } @@ -456,12 +459,16 @@ macro_rules! define_state_transitions { }; } +// State transitions when we have sent our counterparty some messages and are waiting for them +// to respond. define_state_transitions!(LOCAL_STATE, [ DATA &msgs::TxAddInput, TRANSITION remote_tx_add_input, DATA &msgs::TxRemoveInput, TRANSITION remote_tx_remove_input, DATA &msgs::TxAddOutput, TRANSITION remote_tx_add_output, DATA &msgs::TxRemoveOutput, TRANSITION remote_tx_remove_output ]); +// State transitions when we have received some messages from our counterparty and we should +// respond. define_state_transitions!(REMOTE_STATE, [ DATA &msgs::TxAddInput, TRANSITION local_tx_add_input, DATA &msgs::TxRemoveInput, TRANSITION local_tx_remove_input, @@ -631,6 +638,8 @@ impl InteractiveTxConstructor { } fn do_local_state_transition(&mut self) -> Result { + // We first attempt to send inputs we want to add, then outputs. Once we are done sending + // them both, then we always send tx_complete. if let Some((serial_id, input, prev_tx)) = self.inputs_to_contribute.pop() { let msg = msgs::TxAddInput { channel_id: self.channel_id, @@ -704,25 +713,48 @@ impl InteractiveTxConstructor { #[cfg(test)] mod tests { use core::default::Default; + use std::ops::Deref; use crate::chain::chaininterface::FEERATE_FLOOR_SATS_PER_KW; - use crate::ln::interactivetxs::{AbortReason, InteractiveTxConstructor, InteractiveTxMessageSend}; - use bitcoin::{OutPoint, Sequence, Transaction, TxIn, TxOut}; + use crate::ln::interactivetxs::{AbortReason, generate_local_serial_id, InteractiveTxConstructor, InteractiveTxMessageSend, MAX_INPUTS_OUTPUTS_COUNT, MAX_RECEIVED_TX_ADD_INPUT_COUNT, MAX_RECEIVED_TX_ADD_OUTPUT_COUNT}; + use bitcoin::{OutPoint, Sequence, Transaction, TxIn, TxOut, Witness}; use bitcoin::blockdata::opcodes; use bitcoin::blockdata::script::Builder; use bitcoin::locktime::absolute::LockTime; + use crate::ln::channel::TOTAL_BITCOIN_SUPPLY_SATOSHIS; use crate::ln::ChannelId; use crate::sign::EntropySource; use crate::util::atomic_counter::AtomicCounter; + // A simple entropy source that works based on an atomic counter. struct TestEntropySource(AtomicCounter); impl EntropySource for TestEntropySource { fn get_secure_random_bytes(&self) -> [u8; 32] { - let bytes = self.0.get_increment().to_be_bytes(); let mut res = [0u8; 32]; - res[0..8].copy_from_slice(&bytes); + let increment = self.0.get_increment(); + for i in 0..32 { + // Rotate the increment value by 'i' bits to the right, to avoid clashes + // when `generate_local_serial_id` does a parity flip on consecutive calls for the + // same party. + let rotated_increment = increment.rotate_right(i as u32); + res[i] = (rotated_increment & 0xff) as u8; + } + res + } + } + + // An entropy source that deliberately returns you the same seed every time. We use this + // to test if the constructor would catch inputs/outputs that are attempting to be added + // with duplicate serial ids. + struct DuplicateEntropySource; + impl EntropySource for DuplicateEntropySource { + fn get_secure_random_bytes(&self) -> [u8; 32] { + let mut res = [0u8; 32]; + let count = 1u64; + res[0..8].copy_from_slice(&count.to_be_bytes()); res } } + struct TestSession { inputs_a: Vec<(TxIn, Transaction)>, outputs_a: Vec, @@ -733,14 +765,23 @@ mod tests { fn do_test_interactive_tx_constructor(session: TestSession) { let entropy_source = TestEntropySource(AtomicCounter::new()); + do_test_interactive_tx_constructor_internal(session, &&entropy_source); + } + + fn do_test_interactive_tx_constructor_with_entropy_source(session: TestSession, entropy_source: ES) where ES::Target: EntropySource { + do_test_interactive_tx_constructor_internal(session, &entropy_source); + } + + fn do_test_interactive_tx_constructor_internal(session: TestSession, entropy_source: &ES) where ES::Target: EntropySource { + let channel_id = ChannelId(entropy_source.get_secure_random_bytes()); let channel_id = ChannelId(entropy_source.get_secure_random_bytes()); let tx_locktime = LockTime::from_height(1337).unwrap(); let (mut constructor_a, first_message_a) = InteractiveTxConstructor::new( - &&entropy_source, channel_id, FEERATE_FLOOR_SATS_PER_KW * 10, true, tx_locktime, session.inputs_a.clone(), session.outputs_a.clone() + entropy_source, channel_id, FEERATE_FLOOR_SATS_PER_KW * 10, true, tx_locktime, session.inputs_a.clone(), session.outputs_a.clone() ); let (mut constructor_b, first_message_b) = InteractiveTxConstructor::new( - &&entropy_source, channel_id, FEERATE_FLOOR_SATS_PER_KW * 10, false, tx_locktime, session.inputs_b.clone(), session.outputs_b.clone() + entropy_source, channel_id, FEERATE_FLOOR_SATS_PER_KW * 10, false, tx_locktime, session.inputs_b.clone(), session.outputs_b.clone() ); let handle_message_send = |msg: InteractiveTxMessageSend, for_constructor: &mut InteractiveTxConstructor| { @@ -823,8 +864,41 @@ mod tests { }).collect() } - fn generate_output(value: u64) -> TxOut { - TxOut { value, script_pubkey: Builder::new().push_opcode(opcodes::OP_TRUE).into_script().to_v0_p2wsh() } + fn generate_outputs(values: &[u64]) -> Vec { + values.iter().map(|value| { + TxOut { + value: *value, + script_pubkey: Builder::new().push_opcode(opcodes::OP_TRUE).into_script().to_v0_p2wsh() + } + }).collect() + } + + fn generate_fixed_number_of_inputs(count: u16) -> Vec<(TxIn, Transaction)> { + // Generate a transaction with `count` number of outputs. + let tx = generate_tx(&vec![1_000_000; count as usize]); + let txid = tx.txid(); + + tx.output.iter().enumerate().map(|(idx, _)| { + let input = TxIn { + previous_output: OutPoint { + txid: txid, + vout: idx as u32, + }, + script_sig: Default::default(), + sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, + witness: Default::default(), + }; + (input, tx.clone()) + }).collect() + } + + fn generate_fixed_number_of_outputs(count: u16) -> Vec { + // Set a constant value for each TxOut + generate_outputs(&vec![1_000_000; count as usize]) + } + + fn generate_non_witness_output(value: u64) -> TxOut { + TxOut { value, script_pubkey: Builder::new().push_opcode(opcodes::OP_TRUE).into_script().to_p2sh() } } #[test] @@ -840,7 +914,7 @@ mod tests { // Single contribution, no initiator inputs. do_test_interactive_tx_constructor(TestSession { inputs_a: vec![], - outputs_a: vec![generate_output(1_000_000)], + outputs_a: generate_outputs(&[1_000_000]), inputs_b: vec![], outputs_b: vec![], expect_error: Some(AbortReason::InsufficientFees), @@ -856,7 +930,7 @@ mod tests { // Single contribution, insufficient fees. do_test_interactive_tx_constructor(TestSession { inputs_a: generate_inputs(&[1_000_000]), - outputs_a: vec![generate_output(1_000_000)], + outputs_a: generate_outputs(&[1_000_000]), inputs_b: vec![], outputs_b: vec![], expect_error: Some(AbortReason::InsufficientFees), @@ -866,17 +940,40 @@ mod tests { inputs_a: generate_inputs(&[1_000_000]), outputs_a: vec![], inputs_b: generate_inputs(&[100_000]), - outputs_b: vec![generate_output(100_000)], + outputs_b: generate_outputs(&[100_000]), expect_error: Some(AbortReason::InsufficientFees), }); // Multi-input-output contributions from both sides. do_test_interactive_tx_constructor(TestSession { inputs_a: generate_inputs(&[1_000_000, 1_000_000]), - outputs_a: vec![generate_output(1_000_000), generate_output(200_000)], + outputs_a: generate_outputs(&[1_000_000, 200_000]), inputs_b: generate_inputs(&[1_000_000, 500_000]), - outputs_b: vec![generate_output(1_000_000), generate_output(400_000)], + outputs_b: generate_outputs(&[1_000_000, 400_000]), expect_error: None, }); + + // Prevout from initiator is not a witness program + let non_segwit_output_tx = { + let mut tx = generate_tx(&[1_000_000]); + tx.output.push(TxOut { + script_pubkey: Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script().to_p2sh(), + ..Default::default() + }); + tx + }; + let non_segwit_input = TxIn { + previous_output: OutPoint { txid: non_segwit_output_tx.txid(), vout: 1 }, + sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, + ..Default::default() + }; + do_test_interactive_tx_constructor(TestSession { + inputs_a: vec![(non_segwit_input, non_segwit_output_tx)], + outputs_a: vec![], + inputs_b: vec![], + outputs_b: vec![], + expect_error: Some(AbortReason::PrevTxOutInvalid), + }); + // Invalid input sequence from initiator. let tx = generate_tx(&[1_000_000]); let invalid_sequence_input = TxIn { @@ -885,7 +982,7 @@ mod tests { }; do_test_interactive_tx_constructor(TestSession { inputs_a: vec![(invalid_sequence_input, tx.clone())], - outputs_a: vec![generate_output(1_000_000)], + outputs_a: generate_outputs(&[1_000_000]), inputs_b: vec![], outputs_b: vec![], expect_error: Some(AbortReason::IncorrectInputSequenceValue), @@ -898,7 +995,7 @@ mod tests { }; do_test_interactive_tx_constructor(TestSession { inputs_a: vec![(duplicate_input.clone(), tx.clone()), (duplicate_input, tx.clone())], - outputs_a: vec![generate_output(1_000_000)], + outputs_a: generate_outputs(&[1_000_000]), inputs_b: vec![], outputs_b: vec![], expect_error: Some(AbortReason::PrevTxOutInvalid), @@ -911,10 +1008,103 @@ mod tests { }; do_test_interactive_tx_constructor(TestSession { inputs_a: vec![(duplicate_input.clone(), tx.clone())], - outputs_a: vec![generate_output(1_000_000)], + outputs_a: generate_outputs(&[1_000_000]), inputs_b: vec![(duplicate_input.clone(), tx.clone())], outputs_b: vec![], expect_error: Some(AbortReason::PrevTxOutInvalid), }); + // Initiator sends too many TxAddInputs + do_test_interactive_tx_constructor(TestSession { + inputs_a: generate_fixed_number_of_inputs(MAX_RECEIVED_TX_ADD_INPUT_COUNT + 1), + outputs_a: vec![], + inputs_b: vec![], + outputs_b: vec![], + expect_error: Some(AbortReason::ReceivedTooManyTxAddInputs), + }); + // Attempt to queue up two inputs with duplicate serial ids. We use a deliberately bad + // entropy source, `DuplicateEntropySource` to simulate this. + do_test_interactive_tx_constructor_with_entropy_source(TestSession { + inputs_a: generate_fixed_number_of_inputs(2), + outputs_a: vec![], + inputs_b: vec![], + outputs_b: vec![], + expect_error: Some(AbortReason::DuplicateSerialId), + }, &DuplicateEntropySource); + // Initiator sends too many TxAddOutputs. + do_test_interactive_tx_constructor(TestSession { + inputs_a: vec![], + outputs_a: generate_fixed_number_of_outputs(MAX_RECEIVED_TX_ADD_OUTPUT_COUNT + 1), + inputs_b: vec![], + outputs_b: vec![], + expect_error: Some(AbortReason::ReceivedTooManyTxAddOutputs), + }); + // Initiator sends an output below dust value. + do_test_interactive_tx_constructor(TestSession { + inputs_a: vec![], + outputs_a: generate_outputs(&[1]), + inputs_b: vec![], + outputs_b: vec![], + expect_error: Some(AbortReason::BelowDustLimit) + }); + // Initiator sends an output above maximum sats allowed. + do_test_interactive_tx_constructor(TestSession { + inputs_a: vec![], + outputs_a: generate_outputs(&[TOTAL_BITCOIN_SUPPLY_SATOSHIS + 1]), + inputs_b: vec![], + outputs_b: vec![], + expect_error: Some(AbortReason::ExceededMaximumSatsAllowed) + }); + // Initiator sends an output without a witness program. + do_test_interactive_tx_constructor(TestSession { + inputs_a: vec![], + outputs_a: vec![generate_non_witness_output(1_000_000)], + inputs_b: vec![], + outputs_b: vec![], + expect_error: Some(AbortReason::InvalidOutputScript) + }); + // Attempt to queue up two outputs with duplicate serial ids. We use a deliberately bad + // entropy source, `DuplicateEntropySource` to simulate this. + do_test_interactive_tx_constructor_with_entropy_source(TestSession { + inputs_a: vec![], + outputs_a: generate_fixed_number_of_outputs(2), + inputs_b: vec![], + outputs_b: vec![], + expect_error: Some(AbortReason::DuplicateSerialId) + }, &DuplicateEntropySource); + + // Peer contributed more output value than inputs + do_test_interactive_tx_constructor(TestSession { + inputs_a: generate_inputs(&[100_000]), + outputs_a: generate_outputs(&[1_000_000]), + inputs_b: vec![], + outputs_b: vec![], + expect_error: Some(AbortReason::InsufficientFees) + }); + + // Peer contributed more than allowed number of inputs. + do_test_interactive_tx_constructor(TestSession { + inputs_a: generate_fixed_number_of_inputs(MAX_INPUTS_OUTPUTS_COUNT as u16 + 1), + outputs_a: vec![], + inputs_b: vec![], + outputs_b: vec![], + expect_error: Some(AbortReason::ExceededNumberOfInputsOrOutputs) + }); + // Peer contributed more than allowed number of outputs. + do_test_interactive_tx_constructor(TestSession { + inputs_a: generate_inputs(&[TOTAL_BITCOIN_SUPPLY_SATOSHIS]), + outputs_a: generate_fixed_number_of_outputs(MAX_INPUTS_OUTPUTS_COUNT as u16 + 1), + inputs_b: vec![], + outputs_b: vec![], + expect_error: Some(AbortReason::ExceededNumberOfInputsOrOutputs) + }); + } + + #[test] + fn test_generate_local_serial_id() { + let entropy_source = TestEntropySource(AtomicCounter::new()); + + // Initiators should have even serial id, non-initiators should have odd serial id. + assert_eq!(generate_local_serial_id(&&entropy_source, true) % 2, 0); + assert_eq!(generate_local_serial_id(&&entropy_source, false) % 2, 1) } } From 6591432ce42315fa21f8f45014471b4669082be7 Mon Sep 17 00:00:00 2001 From: Jurvis Tan Date: Sun, 12 Nov 2023 11:17:17 -0800 Subject: [PATCH 016/101] Add comment on spec clarification --- lightning/src/ln/interactivetxs.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lightning/src/ln/interactivetxs.rs b/lightning/src/ln/interactivetxs.rs index 79c64f7f49a..6a329247964 100644 --- a/lightning/src/ln/interactivetxs.rs +++ b/lightning/src/ln/interactivetxs.rs @@ -220,6 +220,8 @@ impl NegotiationContext { // - the sats amount is less than the dust_limit return Err(AbortReason::BelowDustLimit); } + // TODO (Spec Clarification https://github.com/lightning/bolts/pull/851/files#r1390457718): + // Check if we should validate cumulative output value so far, instead of just the value of this input. if msg.sats > TOTAL_BITCOIN_SUPPLY_SATOSHIS { // The receiving node: // - MUST fail the negotiation if: From d3659fa8055c842a3e9d2ded3070c703148572fa Mon Sep 17 00:00:00 2001 From: Duncan Dean Date: Fri, 6 Oct 2023 13:05:17 +0200 Subject: [PATCH 017/101] Add interactive tx constructor to `ChannelContext` --- lightning/src/ln/channel.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index c22da6b1ef3..a1b64f6c059 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -26,6 +26,7 @@ use bitcoin::secp256k1; use crate::ln::{ChannelId, PaymentPreimage, PaymentHash}; use crate::ln::features::{ChannelTypeFeatures, InitFeatures}; +use crate::ln::interactivetxs::InteractiveTxConstructor; use crate::ln::msgs; use crate::ln::msgs::DecodeError; use crate::ln::script::{self, ShutdownScript}; @@ -1240,6 +1241,9 @@ pub(super) struct ChannelContext where SP::Target: SignerProvider { /// 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. blocked_monitor_updates: Vec, + + /// The current interactive transaction construction session under negotiation. + interactive_tx_constructor: Option, } impl ChannelContext where SP::Target: SignerProvider { @@ -1610,6 +1614,8 @@ impl ChannelContext where SP::Target: SignerProvider { channel_keys_id, blocked_monitor_updates: Vec::new(), + + interactive_tx_constructor: None, }; Ok(channel_context) @@ -1827,6 +1833,8 @@ impl ChannelContext where SP::Target: SignerProvider { channel_keys_id, blocked_monitor_updates: Vec::new(), + + interactive_tx_constructor: None, }) } @@ -8603,6 +8611,8 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch channel_keys_id, blocked_monitor_updates: blocked_monitor_updates.unwrap(), + + interactive_tx_constructor: None, }, #[cfg(dual_funding)] dual_funding_channel_context: None, From 4b9f03aeb472620d02f703539e0773bc6e76c777 Mon Sep 17 00:00:00 2001 From: Duncan Dean Date: Tue, 24 Oct 2023 10:05:36 +0200 Subject: [PATCH 018/101] Add FundingInputsContributionReady event --- lightning/src/events/mod.rs | 43 ++++++++++++++++++++++++++++++ lightning/src/ln/channelmanager.rs | 19 ++++++++++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index 76e5f25c0e5..dbe5a084cd8 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -977,6 +977,43 @@ pub enum Event { /// /// [`ChannelHandshakeConfig::negotiate_anchors_zero_fee_htlc_tx`]: crate::util::config::ChannelHandshakeConfig::negotiate_anchors_zero_fee_htlc_tx BumpTransaction(BumpTransactionEvent), + /// Used to indicate that the client should provide inputs to fund a dual-funded channel using + /// interactive transaction construction by calling [`ChannelManager::contribute_funding_inputs`]. + /// Generated in [`ChannelManager`] message handling. + /// Note that *all inputs* contributed must spend SegWit outputs or your counterparty can steal + /// your funds! + /// + /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager + /// [`ChannelManager::contribute_funding_inputs`]: crate::ln::channelmanager::ChannelManager::contribute_funding_inputs + FundingInputsContributionReady { + /// The channel_id of the channel that requires funding inputs which you'll need to pass into + /// [`ChannelManager::contribute_funding_inputs`]. + /// + /// [`ChannelManager::contribute_funding_inputs`]: crate::ln::channelmanager::ChannelManager::contribute_funding_inputs + channel_id: ChannelId, + /// The counterparty's node_id, which you'll need to pass back into + /// [`ChannelManager::contribute_funding_inputs`]. + /// + /// [`ChannelManager::contribute_funding_inputs`]: crate::ln::channelmanager::ChannelManager::contribute_funding_inputs + counterparty_node_id: PublicKey, + /// The value, in satoshis, that we commited to contribute to the channel value during + /// establishment. + holder_funding_satoshis: u64, + /// The value, in satoshis, that the counterparty commited to contribute to the channel value + /// during channel establishment. + counterparty_funding_satoshis: u64, + /// TODO(dual_funding): Update docs + /// The `user_channel_id` value passed in to [`ChannelManager::create_channel`] for outbound + /// channels, or to [`ChannelManager::accept_inbound_channel`] for inbound channels if + /// [`UserConfig::manually_accept_inbound_channels`] config flag is set to true. Otherwise + /// `user_channel_id` will be randomized for an inbound channel. This may be zero for objects + /// serialized with LDK versions prior to 0.0.113. + /// + /// [`ChannelManager::create_channel`]: crate::ln::channelmanager::ChannelManager::create_channel + /// [`ChannelManager::accept_inbound_channel`]: crate::ln::channelmanager::ChannelManager::accept_inbound_channel + /// [`UserConfig::manually_accept_inbound_channels`]: crate::util::config::UserConfig::manually_accept_inbound_channels + user_channel_id: u128, + }, } impl Writeable for Event { @@ -1213,6 +1250,12 @@ impl Writeable for Event { 35u8.write(writer)?; // Never write ConnectionNeeded events as buffered onion messages aren't serialized. }, + &Event::FundingInputsContributionReady { .. } => { + 37u8.write(writer)?; + // We never write out FundingInputsContributionReady events as, upon disconnection, peers + // drop any channels which have not yet exchanged the initial commitment_signed in V2 channel + // establishment. + }, // Note that, going forward, all new events must only write data inside of // `write_tlv_fields`. Versions 0.0.101+ will ignore odd-numbered events that write // data via `write_tlv_fields`. diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 39ce092489c..b13378662d1 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -29,7 +29,7 @@ use bitcoin::hash_types::{BlockHash, Txid}; use bitcoin::secp256k1::{SecretKey,PublicKey}; use bitcoin::secp256k1::Secp256k1; -use bitcoin::{secp256k1, Sequence}; +use bitcoin::{secp256k1, Sequence, TxIn}; use crate::blinded_path::BlindedPath; use crate::blinded_path::payment::{PaymentConstraints, ReceiveTlvs}; @@ -3961,6 +3961,23 @@ where result } + /// Call this to contribute inputs to a funding transaction for dual-funding. + /// + /// Returns an [`APIError::APIMisuseError`] if the contributed inputs spent non-SegWit outputs + /// or if the input amounts will not sufficiently cover the holder `funding_satoshis` and fees. + /// Any amount left over and above dust will be returned as change. + /// + /// Returns [`APIError::ChannelUnavailable`] if a inputs have already been provided for the + /// funding transaction of the channel or if the channel has been closed as indicated by + /// [`Event::ChannelClosed`]. + /// + /// [`Event::FundingInputsContributionReady`]: crate::events::Event::FundingInputsContributionReady + /// [`Event::ChannelClosed`]: crate::events::Event::ChannelClosed + pub fn contribute_funding_inputs(&self, channel_id: &ChannelId, counterparty_node_id: &PublicKey, + funding_inputs: Vec<(TxIn, Transaction)>) -> Result<(), APIError> { + Ok(()) + } + /// Atomically applies partial updates to the [`ChannelConfig`] of the given channels. /// /// Once the updates are applied, each eligible channel (advertised with a known short channel From 29e396c8995bb63f27d53ad39c157594c868cbe7 Mon Sep 17 00:00:00 2001 From: Duncan Dean Date: Fri, 15 Sep 2023 13:19:24 +0200 Subject: [PATCH 019/101] Implement support for accepting V2 channels --- lightning-net-tokio/src/lib.rs | 11 + lightning/src/events/mod.rs | 66 +++ lightning/src/ln/channel.rs | 249 +++++++++- lightning/src/ln/channelmanager.rs | 704 +++++++++++++++++++++++++---- lightning/src/ln/msgs.rs | 11 + lightning/src/ln/peer_handler.rs | 22 + lightning/src/ln/wire.rs | 44 ++ lightning/src/util/config.rs | 10 + lightning/src/util/test_utils.rs | 11 + 9 files changed, 1020 insertions(+), 108 deletions(-) diff --git a/lightning-net-tokio/src/lib.rs b/lightning-net-tokio/src/lib.rs index d02f23fdd2c..17ee8d466b7 100644 --- a/lightning-net-tokio/src/lib.rs +++ b/lightning-net-tokio/src/lib.rs @@ -615,20 +615,31 @@ mod tests { fn handle_update_fee(&self, _their_node_id: &PublicKey, _msg: &UpdateFee) {} fn handle_announcement_signatures(&self, _their_node_id: &PublicKey, _msg: &AnnouncementSignatures) {} fn handle_channel_update(&self, _their_node_id: &PublicKey, _msg: &ChannelUpdate) {} + #[cfg(dual_funding)] fn handle_open_channel_v2(&self, _their_node_id: &PublicKey, _msg: &OpenChannelV2) {} + #[cfg(dual_funding)] fn handle_accept_channel_v2(&self, _their_node_id: &PublicKey, _msg: &AcceptChannelV2) {} fn handle_stfu(&self, _their_node_id: &PublicKey, _msg: &Stfu) {} fn handle_splice(&self, _their_node_id: &PublicKey, _msg: &Splice) {} fn handle_splice_ack(&self, _their_node_id: &PublicKey, _msg: &SpliceAck) {} fn handle_splice_locked(&self, _their_node_id: &PublicKey, _msg: &SpliceLocked) {} + #[cfg(dual_funding)] fn handle_tx_add_input(&self, _their_node_id: &PublicKey, _msg: &TxAddInput) {} + #[cfg(dual_funding)] fn handle_tx_add_output(&self, _their_node_id: &PublicKey, _msg: &TxAddOutput) {} + #[cfg(dual_funding)] fn handle_tx_remove_input(&self, _their_node_id: &PublicKey, _msg: &TxRemoveInput) {} + #[cfg(dual_funding)] fn handle_tx_remove_output(&self, _their_node_id: &PublicKey, _msg: &TxRemoveOutput) {} + #[cfg(dual_funding)] fn handle_tx_complete(&self, _their_node_id: &PublicKey, _msg: &TxComplete) {} + #[cfg(dual_funding)] fn handle_tx_signatures(&self, _their_node_id: &PublicKey, _msg: &TxSignatures) {} + #[cfg(dual_funding)] fn handle_tx_init_rbf(&self, _their_node_id: &PublicKey, _msg: &TxInitRbf) {} + #[cfg(dual_funding)] fn handle_tx_ack_rbf(&self, _their_node_id: &PublicKey, _msg: &TxAckRbf) {} + #[cfg(dual_funding)] fn handle_tx_abort(&self, _their_node_id: &PublicKey, _msg: &TxAbort) {} fn peer_disconnected(&self, their_node_id: &PublicKey) { if *their_node_id == self.expected_pubkey { diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index dbe5a084cd8..560e040eda6 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -950,6 +950,64 @@ pub enum Event { /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager channel_type: ChannelTypeFeatures, }, + /// Indicates a request to open a new dual-funded channel by a peer. + /// + /// To accept the request without contributing funds, call [`ChannelManager::accept_inbound_channel`]. + /// To accept the request and contribute funds, call [`ChannelManager::accept_inbound_channel_with_contribution`]. + /// To reject the request, call [`ChannelManager::force_close_without_broadcasting_txn`]. + /// + /// The event is always triggered when a new open channel request is received for a dual-funded + /// channel, regardless of the value of the [`UserConfig::manually_accept_inbound_channels`] + /// config flag. This is so that funding inputs can be manually provided to contribute to the + /// overall channel capacity on the acceptor side. + /// + /// [`ChannelManager::accept_inbound_channel`]: crate::ln::channelmanager::ChannelManager::accept_inbound_channel + /// [`ChannelManager::accept_inbound_channel_with_contribution`]: crate::ln::channelmanager::ChannelManager::accept_inbound_channel_with_contribution + /// [`ChannelManager::force_close_without_broadcasting_txn`]: crate::ln::channelmanager::ChannelManager::force_close_without_broadcasting_txn + /// [`UserConfig::manually_accept_inbound_channels`]: crate::util::config::UserConfig::manually_accept_inbound_channels + OpenChannelV2Request { + /// The temporary channel ID of the channel requested to be opened. + /// + /// When responding to the request, the `temporary_channel_id` should be passed + /// back to the ChannelManager through [`ChannelManager::accept_inbound_channel`] or + /// [`ChannelManager::accept_inbound_channel_with_contribution`] to accept, or through + /// [`ChannelManager::force_close_without_broadcasting_txn`] to reject. + /// + /// [`ChannelManager::accept_inbound_channel`]: crate::ln::channelmanager::ChannelManager::accept_inbound_channel + /// [`ChannelManager::accept_inbound_channel_with_contribution`]: crate::ln::channelmanager::ChannelManager::accept_inbound_channel_with_contribution + /// [`ChannelManager::force_close_without_broadcasting_txn`]: crate::ln::channelmanager::ChannelManager::force_close_without_broadcasting_txn + temporary_channel_id: ChannelId, + /// The node_id of the counterparty requesting to open the channel. + /// + /// When responding to the request, the `counterparty_node_id` should be passed + /// back to the ChannelManager through [`ChannelManager::accept_inbound_channel`] or + /// [`ChannelManager::accept_inbound_channel_with_contribution`] to accept, or through + /// [`ChannelManager::force_close_without_broadcasting_txn`] to reject the request. + /// + /// [`ChannelManager::accept_inbound_channel`]: crate::ln::channelmanager::ChannelManager::accept_inbound_channel + /// [`ChannelManager::accept_inbound_channel_with_contribution`]: crate::ln::channelmanager::ChannelManager::accept_inbound_channel_with_contribution + /// [`ChannelManager::force_close_without_broadcasting_txn`]: crate::ln::channelmanager::ChannelManager::force_close_without_broadcasting_txn + counterparty_node_id: PublicKey, + /// The counterparty's contribution to the channel value in satoshis. + funding_satoshis: u64, + /// The features that this channel will operate with. If you reject the channel, a + /// well-behaved counterparty may automatically re-attempt the channel with a new set of + /// feature flags. + /// + /// Note that if [`ChannelTypeFeatures::supports_scid_privacy`] returns true on this type, + /// the resulting [`ChannelManager`] will not be readable by versions of LDK prior to + /// 0.0.106. + /// + /// Furthermore, note that if [`ChannelTypeFeatures::supports_zero_conf`] returns true on this type, + /// the resulting [`ChannelManager`] will not be readable by versions of LDK prior to + /// 0.0.107. + /// + /// NOTE: Zero-conf dual-funded channels are not currently accepted. + // TODO(dual_funding): Support zero-conf channels. + /// + /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager + channel_type: ChannelTypeFeatures, + }, /// Indicates that the HTLC was accepted, but could not be processed when or after attempting to /// forward it. /// @@ -977,6 +1035,7 @@ pub enum Event { /// /// [`ChannelHandshakeConfig::negotiate_anchors_zero_fee_htlc_tx`]: crate::util::config::ChannelHandshakeConfig::negotiate_anchors_zero_fee_htlc_tx BumpTransaction(BumpTransactionEvent), + #[cfg(dual_funding)] /// Used to indicate that the client should provide inputs to fund a dual-funded channel using /// interactive transaction construction by calling [`ChannelManager::contribute_funding_inputs`]. /// Generated in [`ChannelManager`] message handling. @@ -1250,12 +1309,19 @@ impl Writeable for Event { 35u8.write(writer)?; // Never write ConnectionNeeded events as buffered onion messages aren't serialized. }, + #[cfg(dual_funding)] &Event::FundingInputsContributionReady { .. } => { 37u8.write(writer)?; // We never write out FundingInputsContributionReady events as, upon disconnection, peers // drop any channels which have not yet exchanged the initial commitment_signed in V2 channel // establishment. }, + &Event::OpenChannelV2Request { .. } => { + 39u8.write(writer)?; + // We never write the OpenChannelV2Request events as, upon disconnection, peers + // drop any channels which have not yet completed any interactive funding transaction + // construction. + }, // Note that, going forward, all new events must only write data inside of // `write_tlv_fields`. Versions 0.0.101+ will ignore odd-numbered events that write // data via `write_tlv_fields`. diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index a1b64f6c059..b72b1d9c2d9 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -7,11 +7,10 @@ // You may not use this file except in accordance with one or both of these // licenses. -use bitcoin::blockdata::constants::ChainHash; +use bitcoin::blockdata::constants::{ChainHash, WITNESS_SCALE_FACTOR}; use bitcoin::blockdata::script::{Script, ScriptBuf, Builder}; use bitcoin::blockdata::transaction::Transaction; -use bitcoin::sighash; -use bitcoin::sighash::EcdsaSighashType; +use bitcoin::sighash::{self, EcdsaSighashType}; use bitcoin::consensus::encode; use bitcoin::hashes::Hash; @@ -22,11 +21,13 @@ use bitcoin::hash_types::{Txid, BlockHash}; use bitcoin::secp256k1::constants::PUBLIC_KEY_SIZE; use bitcoin::secp256k1::{PublicKey,SecretKey}; use bitcoin::secp256k1::{Secp256k1,ecdsa::Signature}; -use bitcoin::secp256k1; +use bitcoin::{secp256k1, TxIn, TxOut, VarInt}; +#[cfg(dual_funding)] +use bitcoin::locktime::absolute::LockTime; use crate::ln::{ChannelId, PaymentPreimage, PaymentHash}; use crate::ln::features::{ChannelTypeFeatures, InitFeatures}; -use crate::ln::interactivetxs::InteractiveTxConstructor; +use crate::ln::interactivetxs::{AbortReason, InteractiveTxConstructor, InteractiveTxMessageSend}; use crate::ln::msgs; use crate::ln::msgs::DecodeError; use crate::ln::script::{self, ShutdownScript}; @@ -35,7 +36,7 @@ use crate::ln::chan_utils::{CounterpartyCommitmentSecrets, TxCreationKeys, HTLCO use crate::ln::chan_utils; use crate::ln::onion_utils::HTLCFailReason; use crate::chain::BestBlock; -use crate::chain::chaininterface::{FeeEstimator, ConfirmationTarget, LowerBoundedFeeEstimator}; +use crate::chain::chaininterface::{FeeEstimator, ConfirmationTarget, LowerBoundedFeeEstimator, fee_for_weight}; use crate::chain::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate, ChannelMonitorUpdateStep, LATENCY_GRACE_PERIOD_BLOCKS, CLOSED_CHANNEL_UPDATE_ID}; use crate::chain::transaction::{OutPoint, TransactionData}; use crate::sign::ecdsa::{EcdsaChannelSigner, WriteableEcdsaChannelSigner}; @@ -3051,6 +3052,150 @@ impl ChannelContext where SP::Target: SignerProvider { _ => todo!() } } + + // Interactive transaction construction + + #[cfg(dual_funding)] + pub fn begin_interactive_funding_tx_construction( + &mut self, dual_funding_context: &DualFundingChannelContext, signer_provider: &SP, + entropy_source: &ES, is_initiator: bool, funding_inputs: Vec<(TxIn, Transaction)>, + ) -> Result, APIError> + where ES::Target: EntropySource + { + // Check that vouts exist for each TxIn in provided transactions. + for (idx, input) in funding_inputs.iter().enumerate() { + if input.1.output.get(input.0.previous_output.vout as usize).is_none() { + return Err(APIError::APIMisuseError { + err: format!("Transaction with txid {} does not have an output with vout of {} corresponding to TxIn at funding_inputs[{}]", + input.1.txid(), input.0.previous_output.vout, idx) }); + } + } + let total_input_satoshis: u64 = funding_inputs.iter().map(|input| input.1.output[input.0.previous_output.vout as usize].value).sum(); + if total_input_satoshis < dual_funding_context.our_funding_satoshis { + return Err(APIError::APIMisuseError { + err: format!("Total value of funding inputs must be at least funding amount. It was {} sats", + total_input_satoshis) }); + } + + let mut funding_outputs = Vec::new(); + if is_initiator { + funding_outputs.push(TxOut { + value: self.get_value_satoshis(), + script_pubkey: self.get_funding_redeemscript().to_v0_p2wsh(), + }); + } + + maybe_add_funding_change_output(signer_provider, is_initiator, dual_funding_context.our_funding_satoshis, + &funding_inputs, &mut funding_outputs, dual_funding_context.funding_feerate_sat_per_1000_weight, + total_input_satoshis, self.holder_dust_limit_satoshis, self.channel_keys_id).map_err( + |_| APIError::APIMisuseError { err: "Could not create change output".to_string() })?; + + let (tx_constructor, msg) = InteractiveTxConstructor::new( + entropy_source, self.channel_id(), dual_funding_context.funding_feerate_sat_per_1000_weight, + is_initiator, dual_funding_context.funding_tx_locktime, funding_inputs, + funding_outputs, + ); + self.interactive_tx_constructor = Some(tx_constructor); + + Ok(msg) + } + + fn get_tx_abort_msg_from_abort_reason(&self, reason: AbortReason) -> msgs::TxAbort { + let msg = match reason { + AbortReason::InvalidStateTransition => "State transition was invalid", + AbortReason::UnexpectedCounterpartyMessage => "Unexpected message", + AbortReason::ReceivedTooManyTxAddInputs => "Too many `tx_add_input`s received", + AbortReason::ReceivedTooManyTxAddOutputs => "Too many `tx_add_output`s received", + AbortReason::IncorrectInputSequenceValue => "Input has a sequence value greater than 0xFFFFFFFD", + AbortReason::IncorrectSerialIdParity => "Parity for `serial_id` was incorrect", + AbortReason::SerialIdUnknown => "The `serial_id` is unknown", + AbortReason::DuplicateSerialId => "The `serial_id` already exists", + AbortReason::PrevTxOutInvalid => "Invalid previous transaction output", + AbortReason::ExceededMaximumSatsAllowed => "Output amount exceeded total bitcoin supply", + AbortReason::ExceededNumberOfInputsOrOutputs => "Too many inputs or outputs", + AbortReason::TransactionTooLarge => "Transaction weight is too large", + AbortReason::BelowDustLimit => "Output amount is below the dust limit", + AbortReason::InvalidOutputScript => "The output script is non-standard", + AbortReason::InsufficientFees => "Insufficient fees paid", + }.to_string(); + + msgs::TxAbort { + channel_id: self.channel_id(), + data: msg.into_bytes(), + } + } + + pub fn tx_add_input(&mut self, msg: &msgs::TxAddInput) -> Result { + match self.interactive_tx_constructor { + Some(ref mut tx_constructor) => tx_constructor.handle_tx_add_input(msg).map_err( + |reason| self.get_tx_abort_msg_from_abort_reason(reason)), + None => Err(msgs::TxAbort { + channel_id: self.channel_id(), + data: "We do not have an interactive transaction negotiation in progress".to_string().into_bytes() + }), + } + } + + pub fn tx_add_output(&mut self, msg: &msgs::TxAddOutput)-> Result { + match self.interactive_tx_constructor { + Some(ref mut tx_constructor) => tx_constructor.handle_tx_add_output(msg).map_err( + |reason| self.get_tx_abort_msg_from_abort_reason(reason)), + None => Err(msgs::TxAbort { + channel_id: self.channel_id(), + data: "We do not have an interactive transaction negotiation in progress".to_string().into_bytes() + }), + } + } + + pub fn tx_remove_input(&mut self, msg: &msgs::TxRemoveInput)-> Result { + match self.interactive_tx_constructor { + Some(ref mut tx_constructor) => tx_constructor.handle_tx_remove_input(msg).map_err( + |reason| self.get_tx_abort_msg_from_abort_reason(reason)), + None => Err(msgs::TxAbort { + channel_id: self.channel_id(), + data: "We do not have an interactive transaction negotiation in progress".to_string().into_bytes() + }), + } + } + + pub fn tx_remove_output(&mut self, msg: &msgs::TxRemoveOutput)-> Result { + match self.interactive_tx_constructor { + Some(ref mut tx_constructor) => tx_constructor.handle_tx_remove_output(msg).map_err( + |reason| self.get_tx_abort_msg_from_abort_reason(reason)), + None => Err(msgs::TxAbort { + channel_id: self.channel_id(), + data: "We do not have an interactive transaction negotiation in progress".to_string().into_bytes() + }), + } + } + + pub fn tx_complete(&mut self, msg: &msgs::TxComplete) + -> Result<(Option, Option), msgs::TxAbort> { + match self.interactive_tx_constructor { + Some(ref mut tx_constructor) => tx_constructor.handle_tx_complete(msg).map_err( + |reason| self.get_tx_abort_msg_from_abort_reason(reason)), + None => Err(msgs::TxAbort { + channel_id: self.channel_id(), + data: "We do not have an interactive transaction negotiation in progress".to_string().into_bytes() + }), + } + } + + pub fn tx_signatures(&self, msg: &msgs::TxSignatures)-> Result { + todo!(); + } + + pub fn tx_init_rbf(&self, msg: &msgs::TxInitRbf)-> Result { + todo!(); + } + + pub fn tx_ack_rbf(&self, msg: &msgs::TxAckRbf)-> Result { + todo!(); + } + + pub fn tx_abort(&self, msg: &msgs::TxAbort)-> Result { + todo!(); + } } // Internal utility functions for channels @@ -3124,6 +3269,68 @@ 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 } +fn maybe_add_funding_change_output(signer_provider: &SP, is_initiator: bool, + our_funding_satoshis: u64, funding_inputs: &Vec<(TxIn, Transaction)>, + funding_outputs: &mut Vec, funding_feerate_sat_per_1000_weight: u32, + total_input_satoshis: u64, holder_dust_limit_satoshis: u64, channel_keys_id: [u8; 32], +) -> Result, ChannelError> where + SP::Target: SignerProvider, +{ + let our_funding_inputs_weight = funding_inputs.iter().fold(0u64, |weight, (txin, _) | { + // TODO(dual_funding): Use TxIn::segwit_weight when we upgrade rust-bitcoin to 0.30.x. + let script_sig_size = txin.script_sig.len(); + // previous_output (36) + script_sig varint len + script_sig push + sequence + let legacy_weight = (36 + VarInt(script_sig_size as u64).len() + script_sig_size + 4) * WITNESS_SCALE_FACTOR; + let input_weight = legacy_weight.saturating_add(txin.witness.serialized_len()); + weight.saturating_add(input_weight as u64) + }); + // TODO(dual_funding): Just use TxOut::weight when we upgrade rust-bitcoin to 0.30.x. + let calculate_output_weight = |txout: &TxOut| { + let script_len = txout.script_pubkey.len(); + // value (8) + script varint len + script push + (8 + VarInt(script_len as u64).len() + script_len) * WITNESS_SCALE_FACTOR + }; + let our_funding_outputs_weight = funding_outputs.iter().fold(0u64, |weight, txout | { + weight.saturating_add(calculate_output_weight(&txout) as u64) + }); + let our_contributed_weight = our_funding_outputs_weight.saturating_add(our_funding_outputs_weight); + let mut fees_sats = fee_for_weight(funding_feerate_sat_per_1000_weight, our_contributed_weight); + + // If we are the initiator, we must pay for weight of all common fields in the funding transaction. + if is_initiator { + let common_weight = + 4 /* version */ + + 2 /* segwit marker and flag */ + + 1 /* input count (limited to 252 inputs by interactive tx construction spec)*/ + + 1 /* output count (limited to 252 outputs by interactive tx construction spec)*/ + + 4 /* locktime */; + let common_fees = fee_for_weight(funding_feerate_sat_per_1000_weight, common_weight); + fees_sats = fees_sats.saturating_add(common_fees); + } + + let remaining_value = total_input_satoshis + .saturating_sub(our_funding_satoshis) + .saturating_sub(fees_sats); + + if remaining_value < holder_dust_limit_satoshis { + Ok(None) + } else { + let change_script = signer_provider.get_destination_script(channel_keys_id).map_err( + |_| ChannelError::Close("Failed to get change script as new destination script".to_owned()) + )?; + let mut change_output = TxOut { + value: remaining_value, + script_pubkey: change_script, + }; + let change_output_weight = calculate_output_weight(&change_output); + + let change_output_fee = fee_for_weight(funding_feerate_sat_per_1000_weight, change_output_weight as u64); + change_output.value = remaining_value.saturating_sub(change_output_fee); + funding_outputs.push(change_output.clone()); + Ok(Some(change_output)) + } +} + /// Context for dual-funded channels. #[cfg(dual_funding)] pub(super) struct DualFundingChannelContext { @@ -3133,7 +3340,7 @@ pub(super) struct DualFundingChannelContext { 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, + pub funding_tx_locktime: LockTime, /// The feerate set by the initiator to be used for the funding transaction. pub funding_feerate_sat_per_1000_weight: u32, } @@ -7453,7 +7660,11 @@ impl OutboundV2Channel where SP::Target: SignerProvider { 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 funding_tx_locktime = LockTime::from_height(current_chain_height) + .map_err(|_| APIError::APIMisuseError { + err: format!( + "Provided current chain height of {} doesn't make sense for a height-based timelock for the funding transaction", + current_chain_height) })?; let chan = Self { context: ChannelContext::new_for_outbound_channel( @@ -7526,10 +7737,19 @@ impl OutboundV2Channel where SP::Target: SignerProvider { None => Builder::new().into_script(), }), channel_type: Some(self.context.channel_type.clone()), - locktime: self.dual_funding_context.funding_tx_locktime, + locktime: self.dual_funding_context.funding_tx_locktime.to_consensus_u32(), require_confirmed_inputs: None, } } + + pub fn begin_interactive_funding_tx_construction(&mut self, signer_provider: &SP, + entropy_source: &ES, funding_inputs: Vec<(TxIn, Transaction)> + ) -> Result, APIError> + where ES::Target: EntropySource + { + self.context.begin_interactive_funding_tx_construction(&self.dual_funding_context, + signer_provider, entropy_source, true /* is_initiator */, funding_inputs) + } } // A not-yet-funded inbound (from counterparty) channel using V2 channel establishment. @@ -7612,7 +7832,7 @@ impl InboundV2Channel where SP::Target: SignerProvider { dual_funding_context: DualFundingChannelContext { our_funding_satoshis: funding_satoshis, their_funding_satoshis: msg.funding_satoshis, - funding_tx_locktime: msg.locktime, + funding_tx_locktime: LockTime::from_consensus(msg.locktime), funding_feerate_sat_per_1000_weight: msg.funding_feerate_sat_per_1000_weight, } }; @@ -7683,6 +7903,15 @@ impl InboundV2Channel where SP::Target: SignerProvider { pub fn get_accept_channel_v2_message(&self) -> msgs::AcceptChannelV2 { self.generate_accept_channel_v2_message() } + + pub fn begin_interactive_funding_tx_construction(&mut self, signer_provider: &SP, + entropy_source: &ES, funding_inputs: Vec<(TxIn, Transaction)> + ) -> Result, APIError> + where ES::Target: EntropySource + { + self.context.begin_interactive_funding_tx_construction(&self.dual_funding_context, + signer_provider, entropy_source, false /* is_initiator */, funding_inputs) + } } // Unfunded channel utilities diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index b13378662d1..1e3f93b5ba5 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -44,9 +44,13 @@ use crate::events::{Event, EventHandler, EventsProvider, MessageSendEvent, Messa // construct one themselves. use crate::ln::{inbound_payment, ChannelId, PaymentHash, PaymentPreimage, PaymentSecret}; use crate::ln::channel::{Channel, ChannelPhase, ChannelContext, ChannelError, ChannelUpdateStatus, ShutdownResult, UnfundedChannelContext, UpdateFulfillCommitFetch, OutboundV1Channel, InboundV1Channel, WithChannelContext}; +#[cfg(dual_funding)] +use crate::ln::channel::InboundV2Channel; use crate::ln::features::{Bolt12InvoiceFeatures, ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures}; #[cfg(any(feature = "_test_utils", test))] use crate::ln::features::Bolt11InvoiceFeatures; +#[cfg(dual_funding)] +use crate::ln::interactivetxs::InteractiveTxMessageSend; use crate::routing::gossip::NetworkGraph; use crate::routing::router::{BlindedTail, DefaultRouter, InFlightHtlcs, Path, Payee, PaymentParameters, Route, RouteParameters, Router}; use crate::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParameters}; @@ -890,11 +894,18 @@ impl PeerState where SP::Target: SignerProvider { } } +#[derive(Clone)] +pub(super) enum OpenChannelMessage { + V1(msgs::OpenChannel), + #[cfg(dual_funding)] + V2(msgs::OpenChannelV2), +} + /// A not-yet-accepted inbound (from counterparty) channel. Once /// accepted, the parameters will be used to construct a channel. pub(super) struct InboundChannelRequest { /// The original OpenChannel message. - pub open_channel_msg: msgs::OpenChannel, + pub open_channel_msg: OpenChannelMessage, /// The number of ticks remaining before the request expires. pub ticks_remaining: i32, } @@ -3973,8 +3984,47 @@ where /// /// [`Event::FundingInputsContributionReady`]: crate::events::Event::FundingInputsContributionReady /// [`Event::ChannelClosed`]: crate::events::Event::ChannelClosed + #[cfg(dual_funding)] pub fn contribute_funding_inputs(&self, channel_id: &ChannelId, counterparty_node_id: &PublicKey, funding_inputs: Vec<(TxIn, Transaction)>) -> Result<(), APIError> { + let per_peer_state = self.per_peer_state.read().unwrap(); + let peer_state_mutex = per_peer_state.get(counterparty_node_id) + .ok_or_else(|| APIError::ChannelUnavailable { err: format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id) })?; + + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + match peer_state.channel_by_id.entry(*channel_id) { + hash_map::Entry::Occupied(mut phase) => { + let tx_msg_opt = match phase.get_mut() { + ChannelPhase::UnfundedOutboundV2(chan) => { + chan.begin_interactive_funding_tx_construction(&self.signer_provider, + &self.entropy_source, funding_inputs)? + }, + ChannelPhase::UnfundedInboundV2(chan) => { + chan.begin_interactive_funding_tx_construction(&self.signer_provider, + &self.entropy_source, funding_inputs)? + }, + _ => { + return Err(APIError::ChannelUnavailable { + err: format!("Channel with ID {} is not an unfunded V2 channel", counterparty_node_id) }); + }, + }; + if let Some(tx_msg) = tx_msg_opt { + let msg_send_event = match tx_msg { + InteractiveTxMessageSend::TxAddInput(msg) => events::MessageSendEvent::SendTxAddInput { + node_id: *counterparty_node_id, msg }, + InteractiveTxMessageSend::TxAddOutput(msg) => events::MessageSendEvent::SendTxAddOutput { + node_id: *counterparty_node_id, msg }, + InteractiveTxMessageSend::TxComplete(msg) => events::MessageSendEvent::SendTxComplete { + node_id: *counterparty_node_id, msg }, + }; + peer_state.pending_msg_events.push(msg_send_event); + } + }, + hash_map::Entry::Vacant(_) => return Err(APIError::ChannelUnavailable {err: format!( + "Channel with id {} not found for the passed counterparty node_id {}", + channel_id, counterparty_node_id) }), + } Ok(()) } @@ -5935,7 +5985,8 @@ where handle_monitor_update_completion!(self, peer_state_lock, peer_state, per_peer_state, channel); } - /// Accepts a request to open a channel after a [`Event::OpenChannelRequest`]. + /// Accepts a request to open a channel after a [`Event::OpenChannelRequest`] or + /// [`Event::OpenChannelV2Request`] where we are not contributing any funds. /// /// The `temporary_channel_id` parameter indicates which inbound channel should be accepted, /// and the `counterparty_node_id` parameter is the id of the peer which has requested to open @@ -5950,13 +6001,15 @@ where /// used to accept such channels. /// /// [`Event::OpenChannelRequest`]: events::Event::OpenChannelRequest + /// [`Event::OpenChannelV2Request`]: events::Event::OpenChannelV2Request /// [`Event::ChannelClosed::user_channel_id`]: events::Event::ChannelClosed::user_channel_id pub fn accept_inbound_channel(&self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, user_channel_id: u128) -> Result<(), APIError> { - self.do_accept_inbound_channel(temporary_channel_id, counterparty_node_id, false, user_channel_id) + self.do_accept_inbound_channel(temporary_channel_id, counterparty_node_id, false, user_channel_id, 0) } - /// Accepts a request to open a channel after a [`events::Event::OpenChannelRequest`], treating - /// it as confirmed immediately. + /// Accepts a request to open a channel after a [`Event::OpenChannelRequest`] or + /// [`Event::OpenChannelV2Request`] where we are not contributing any funds, treating it as + /// confirmed immediately. /// /// The `user_channel_id` parameter will be provided back in /// [`Event::ChannelClosed::user_channel_id`] to allow tracking of which events correspond @@ -5972,12 +6025,56 @@ where /// does not pay to the correct script the correct amount, *you will lose funds*. /// /// [`Event::OpenChannelRequest`]: events::Event::OpenChannelRequest + /// [`Event::OpenChannelV2Request`]: events::Event::OpenChannelV2Request /// [`Event::ChannelClosed::user_channel_id`]: events::Event::ChannelClosed::user_channel_id pub fn accept_inbound_channel_from_trusted_peer_0conf(&self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, user_channel_id: u128) -> Result<(), APIError> { - self.do_accept_inbound_channel(temporary_channel_id, counterparty_node_id, true, user_channel_id) + self.do_accept_inbound_channel(temporary_channel_id, counterparty_node_id, true, user_channel_id, 0) } - fn do_accept_inbound_channel(&self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, accept_0conf: bool, user_channel_id: u128) -> Result<(), APIError> { + /// Accepts a request to open a dual-funded channel with a contribution provided by us after an + /// [`Event::OpenChannelV2Request`]. + /// + /// The `temporary_channel_id` parameter indicates which inbound channel should be accepted, + /// and the `counterparty_node_id` parameter is the id of the peer which has requested to open + /// the channel. + /// + /// The `user_channel_id` parameter will be provided back in + /// [`Event::ChannelClosed::user_channel_id`] to allow tracking of which events correspond + /// with which `accept_inbound_channel_*` call. + /// + /// `funnding_satoshis` is the amount we are contributing to the channel. + /// Raises [`APIError::APIMisuseError`] when `funding_satoshis` > 2**24. + /// + /// The `funding_inputs` parameter accepts ([`TxIn`], [`Transaction`]) pairs which will + /// be used to contribute `funding_satoshis` towards the channel (minus any mining fees due). + /// Raises [`APIError::APIMisuseError`] if the total value of the provided `funding_inputs` is + /// less than `funding_satoshis`. + // TODO(dual_funding): Describe error relating to inputs not being able to cover fees payable by us. + /// + /// LDK will create a change output for any non-dust amounts that remain after subtracting contribution + /// to channel capacity and fees from the provided input amounts. + // TODO(dual_funding): We could allow a list of such outputs to be provided so that the user may + /// be able to do some more interesting things at the same time as funding. + + /// Note that this method will return an error and reject the channel, if it requires support + /// for zero confirmations. + // TODO(dual_funding): Discussion on complications with 0conf dual-funded channels where "locking" + // of UTXOs used for funding would be required and other issues. + // See: https://lists.linuxfoundation.org/pipermail/lightning-dev/2023-May/003920.html + /// + /// + /// [`Event::OpenChannelV2Request`]: events::Event::OpenChannelV2Request + /// [`Event::ChannelClosed::user_channel_id`]: events::Event::ChannelClosed::user_channel_id + pub fn accept_inbound_channel_with_contribution(&self, temporary_channel_id: &ChannelId, + counterparty_node_id: &PublicKey, user_channel_id: u128, funding_satoshis: u64) -> Result<(), APIError> { + self.do_accept_inbound_channel(temporary_channel_id, counterparty_node_id, false, user_channel_id, + funding_satoshis) + } + + fn do_accept_inbound_channel( + &self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, accept_0conf: bool, + user_channel_id: u128, funding_satoshis: u64, + ) -> Result<(), APIError> { let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); let peers_without_funded_channels = @@ -5993,23 +6090,34 @@ where // happening and return an error. N.B. that we create channel with an outbound SCID of zero so // that we can delay allocating the SCID until after we're sure that the checks below will // succeed. - let mut channel = match peer_state.inbound_channel_request_by_id.remove(temporary_channel_id) { + let mut channel_phase = match peer_state.inbound_channel_request_by_id.remove(temporary_channel_id) { Some(unaccepted_channel) => { let best_block_height = self.best_block.read().unwrap().height(); - InboundV1Channel::new(&self.fee_estimator, &self.entropy_source, &self.signer_provider, - counterparty_node_id.clone(), &self.channel_type_features(), &peer_state.latest_features, - &unaccepted_channel.open_channel_msg, user_channel_id, &self.default_configuration, best_block_height, - &self.logger, accept_0conf).map_err(|e| APIError::ChannelUnavailable { err: e.to_string() }) + match unaccepted_channel.open_channel_msg { + OpenChannelMessage::V1(open_channel_msg) => { + ChannelPhase::UnfundedInboundV1(InboundV1Channel::new(&self.fee_estimator, &self.entropy_source, &self.signer_provider, + counterparty_node_id.clone(), &self.channel_type_features(), &peer_state.latest_features, + &open_channel_msg, user_channel_id, &self.default_configuration, best_block_height, + &self.logger, accept_0conf).map_err(|e| APIError::ChannelUnavailable { err: e.to_string() })?) + }, + #[cfg(dual_funding)] + OpenChannelMessage::V2(open_channel_msg) => { + ChannelPhase::UnfundedInboundV2(InboundV2Channel::new(&self.fee_estimator, &self.entropy_source, &self.signer_provider, + counterparty_node_id.clone(), &self.channel_type_features(), &peer_state.latest_features, + &open_channel_msg, funding_satoshis, user_channel_id, &self.default_configuration, best_block_height, + &self.logger).map_err(|e| APIError::ChannelUnavailable { err: e.to_string() })?) + }, + } } - _ => Err(APIError::APIMisuseError { err: "No such channel awaiting to be accepted.".to_owned() }) - }?; + _ => return Err(APIError::APIMisuseError { err: "No such channel awaiting to be accepted.".to_owned() }) + }; if accept_0conf { - // This should have been correctly configured by the call to InboundV1Channel::new. - debug_assert!(channel.context.minimum_depth().unwrap() == 0); - } else if channel.context.get_channel_type().requires_zero_conf() { + // This should have been correctly configured by the call to Inbound(V1/V2)Channel::new. + debug_assert!(channel_phase.context().minimum_depth().unwrap() == 0); + } else if channel_phase.context().get_channel_type().requires_zero_conf() { let send_msg_err_event = events::MessageSendEvent::HandleError { - node_id: channel.context.get_counterparty_node_id(), + node_id: channel_phase.context().get_counterparty_node_id(), action: msgs::ErrorAction::SendErrorMessage{ msg: msgs::ErrorMessage { channel_id: temporary_channel_id.clone(), data: "No zero confirmation channels accepted".to_owned(), } } @@ -6022,7 +6130,7 @@ where // channels per-peer we can accept channels from a peer with existing ones. if is_only_peer_channel && peers_without_funded_channels >= MAX_UNFUNDED_CHANNEL_PEERS { let send_msg_err_event = events::MessageSendEvent::HandleError { - node_id: channel.context.get_counterparty_node_id(), + node_id: channel_phase.context().get_counterparty_node_id(), action: msgs::ErrorAction::SendErrorMessage{ msg: msgs::ErrorMessage { channel_id: temporary_channel_id.clone(), data: "Have too many peers with unfunded channels, not accepting new ones".to_owned(), } } @@ -6034,18 +6142,65 @@ where // Now that we know we have a channel, assign an outbound SCID alias. let outbound_scid_alias = self.create_and_insert_outbound_scid_alias(); - channel.context.set_outbound_scid_alias(outbound_scid_alias); + channel_phase.context_mut().set_outbound_scid_alias(outbound_scid_alias); - peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel { - node_id: channel.context.get_counterparty_node_id(), - msg: channel.accept_inbound_channel(), - }); - - peer_state.channel_by_id.insert(temporary_channel_id.clone(), ChannelPhase::UnfundedInboundV1(channel)); + match channel_phase { + ChannelPhase::UnfundedInboundV1(mut channel) => { + peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel { + node_id: channel.context.get_counterparty_node_id(), + msg: channel.accept_inbound_channel() }); + peer_state.channel_by_id.insert(temporary_channel_id.clone(), + ChannelPhase::UnfundedInboundV1(channel)); + }, + #[cfg(dual_funding)] + ChannelPhase::UnfundedInboundV2(mut channel) => { + peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannelV2 { + node_id: channel.context.get_counterparty_node_id(), + msg: channel.accept_inbound_dual_funded_channel() }); + peer_state.channel_by_id.insert(channel.context.channel_id(), + ChannelPhase::UnfundedInboundV2(channel)); + }, + _ => { + debug_assert!(false); + // This should be unreachable, but if it is then we would have dropped the inbound channel request + // and there'd be nothing to clean up as we haven't added anything to the channel_by_id map yet. + return Err(APIError::APIMisuseError { + err: "Channel somehow changed to a non-inbound channel before accepting".to_owned() }) + } + } Ok(()) } + /// Checks related to inputs and their amounts related to establishing dual-funded channels. + fn dual_funding_amount_checks(funding_satoshis: u64, funding_inputs: &Vec<(TxIn, Transaction)>) + -> Result<(), APIError> { + if funding_satoshis < 1000 { + return Err(APIError::APIMisuseError { + err: format!("Funding amount must be at least 1000 satoshis. It was {} sats", funding_satoshis), + }); + } + + // Check that vouts exist for each TxIn in provided transactions. + for (idx, input) in funding_inputs.iter().enumerate() { + if input.1.output.get(input.0.previous_output.vout as usize).is_none() { + return Err(APIError::APIMisuseError { + err: format!("Transaction with txid {} does not have an output with vout of {} corresponding to TxIn at funding_inputs[{}]", + input.1.txid(), input.0.previous_output.vout, idx), + }); + } + } + + let total_input_satoshis: u64 = funding_inputs.iter().map(|input| input.1.output[input.0.previous_output.vout as usize].value).sum(); + if total_input_satoshis < funding_satoshis { + Err(APIError::APIMisuseError { + err: format!("Total value of funding inputs must be at least funding amount. It was {} sats", + total_input_satoshis) }) + } else { + Ok(()) + } + } + /// Gets the number of peers which match the given filter and do not have any funded, outbound, /// or 0-conf channels. /// @@ -6111,15 +6266,23 @@ where num_unfunded_channels + peer.inbound_channel_request_by_id.len() } - fn internal_open_channel(&self, counterparty_node_id: &PublicKey, msg: &msgs::OpenChannel) -> Result<(), MsgHandleErrInternal> { + fn internal_open_channel(&self, counterparty_node_id: &PublicKey, msg: OpenChannelMessage) -> Result<(), MsgHandleErrInternal> { + let (chain_hash, temporary_channel_id) = match msg.clone() { + OpenChannelMessage::V1(msg) => (msg.chain_hash, msg.temporary_channel_id), + #[cfg(dual_funding)] + OpenChannelMessage::V2(msg) => (msg.chain_hash, msg.temporary_channel_id), + }; + + // Do common open_channel(2) checks + // Note that the ChannelManager is NOT re-persisted on disk after this, so any changes are // likely to be lost on restart! - if msg.chain_hash != self.chain_hash { - return Err(MsgHandleErrInternal::send_err_msg_no_close("Unknown genesis block hash".to_owned(), msg.temporary_channel_id.clone())); + if chain_hash != self.chain_hash { + return Err(MsgHandleErrInternal::send_err_msg_no_close("Unknown genesis block hash".to_owned(), temporary_channel_id.clone())); } if !self.default_configuration.accept_inbound_channels { - return Err(MsgHandleErrInternal::send_err_msg_no_close("No inbound channels accepted".to_owned(), msg.temporary_channel_id.clone())); + return Err(MsgHandleErrInternal::send_err_msg_no_close("No inbound channels accepted".to_owned(), temporary_channel_id.clone())); } // Get the number of peers with channels, but without funded ones. We don't care too much @@ -6132,7 +6295,7 @@ where let peer_state_mutex = per_peer_state.get(counterparty_node_id) .ok_or_else(|| { debug_assert!(false); - MsgHandleErrInternal::send_err_msg_no_close(format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), msg.temporary_channel_id.clone()) + MsgHandleErrInternal::send_err_msg_no_close(format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), temporary_channel_id.clone()) })?; let mut peer_state_lock = peer_state_mutex.lock().unwrap(); let peer_state = &mut *peer_state_lock; @@ -6146,69 +6309,128 @@ where { return Err(MsgHandleErrInternal::send_err_msg_no_close( "Have too many peers with unfunded channels, not accepting new ones".to_owned(), - msg.temporary_channel_id.clone())); + temporary_channel_id.clone())); } let best_block_height = self.best_block.read().unwrap().height(); if Self::unfunded_channel_count(peer_state, best_block_height) >= MAX_UNFUNDED_CHANS_PER_PEER { return Err(MsgHandleErrInternal::send_err_msg_no_close( format!("Refusing more than {} unfunded channels.", MAX_UNFUNDED_CHANS_PER_PEER), - msg.temporary_channel_id.clone())); + temporary_channel_id.clone())); } - let channel_id = msg.temporary_channel_id; - let channel_exists = peer_state.has_channel(&channel_id); + let channel_exists = peer_state.has_channel(&temporary_channel_id); if channel_exists { - return Err(MsgHandleErrInternal::send_err_msg_no_close("temporary_channel_id collision for the same peer!".to_owned(), msg.temporary_channel_id.clone())); - } + return Err(MsgHandleErrInternal::send_err_msg_no_close("temporary_channel_id collision for the same peer!".to_owned(), temporary_channel_id.clone())); + } + + // Version-specific checks and logic + match msg { + OpenChannelMessage::V1(ref msg) => { + // 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 mut pending_events = self.pending_events.lock().unwrap(); + pending_events.push_back((events::Event::OpenChannelRequest { + temporary_channel_id: msg.temporary_channel_id.clone(), + counterparty_node_id: counterparty_node_id.clone(), + funding_satoshis: msg.funding_satoshis, + push_msat: msg.push_msat, + channel_type: msg.channel_type.clone().unwrap(), + }, None)); + peer_state.inbound_channel_request_by_id.insert(temporary_channel_id, InboundChannelRequest { + open_channel_msg: OpenChannelMessage::V1(msg.clone()), + ticks_remaining: UNACCEPTED_INBOUND_CHANNEL_AGE_LIMIT_TICKS, + }); + return Ok(()); + } - // 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 mut pending_events = self.pending_events.lock().unwrap(); - pending_events.push_back((events::Event::OpenChannelRequest { - temporary_channel_id: msg.temporary_channel_id.clone(), - counterparty_node_id: counterparty_node_id.clone(), - funding_satoshis: msg.funding_satoshis, - push_msat: msg.push_msat, - channel_type: msg.channel_type.clone().unwrap(), - }, None)); - peer_state.inbound_channel_request_by_id.insert(channel_id, InboundChannelRequest { - open_channel_msg: msg.clone(), - ticks_remaining: UNACCEPTED_INBOUND_CHANNEL_AGE_LIMIT_TICKS, - }); - return Ok(()); - } + // Otherwise create the channel right now. + let mut random_bytes = [0u8; 16]; + random_bytes.copy_from_slice(&self.entropy_source.get_secure_random_bytes()[..16]); + let user_channel_id = u128::from_be_bytes(random_bytes); + let mut channel = match InboundV1Channel::new(&self.fee_estimator, &self.entropy_source, &self.signer_provider, + counterparty_node_id.clone(), &self.channel_type_features(), &peer_state.latest_features, &msg, user_channel_id, + &self.default_configuration, best_block_height, &self.logger, /*is_0conf=*/false) + { + Err(e) => { + return Err(MsgHandleErrInternal::from_chan_no_close(e, msg.temporary_channel_id)); + }, + Ok(res) => res + }; - // Otherwise create the channel right now. - let mut random_bytes = [0u8; 16]; - random_bytes.copy_from_slice(&self.entropy_source.get_secure_random_bytes()[..16]); - let user_channel_id = u128::from_be_bytes(random_bytes); - let mut channel = match InboundV1Channel::new(&self.fee_estimator, &self.entropy_source, &self.signer_provider, - counterparty_node_id.clone(), &self.channel_type_features(), &peer_state.latest_features, msg, user_channel_id, - &self.default_configuration, best_block_height, &self.logger, /*is_0conf=*/false) - { - Err(e) => { - return Err(MsgHandleErrInternal::from_chan_no_close(e, msg.temporary_channel_id)); + let channel_type = channel.context.get_channel_type(); + if channel_type.requires_zero_conf() { + return Err(MsgHandleErrInternal::send_err_msg_no_close("No zero confirmation channels accepted".to_owned(), msg.temporary_channel_id.clone())); + } + if channel_type.requires_anchors_zero_fee_htlc_tx() { + return Err(MsgHandleErrInternal::send_err_msg_no_close("No channels with anchor outputs accepted".to_owned(), msg.temporary_channel_id.clone())); + } + + let outbound_scid_alias = self.create_and_insert_outbound_scid_alias(); + channel.context.set_outbound_scid_alias(outbound_scid_alias); + + peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel { + node_id: counterparty_node_id.clone(), + msg: channel.accept_inbound_channel(), + }); + peer_state.channel_by_id.insert(temporary_channel_id, ChannelPhase::UnfundedInboundV1(channel)); }, - Ok(res) => res - }; + #[cfg(dual_funding)] + OpenChannelMessage::V2(ref msg) => { + // If we're doing manual acceptance checks on the channel, then defer creation until we're sure + // we want to accept and, optionally, contribute to the channel value. + if self.default_configuration.manually_accept_inbound_channels { + let mut pending_events = self.pending_events.lock().unwrap(); + pending_events.push_back((events::Event::OpenChannelV2Request { + temporary_channel_id: msg.temporary_channel_id.clone(), + counterparty_node_id: counterparty_node_id.clone(), + funding_satoshis: msg.funding_satoshis, + channel_type: msg.channel_type.clone().unwrap(), + }, None)); + peer_state.inbound_channel_request_by_id.insert(temporary_channel_id, InboundChannelRequest { + open_channel_msg: OpenChannelMessage::V2(msg.clone()), + ticks_remaining: UNACCEPTED_INBOUND_CHANNEL_AGE_LIMIT_TICKS, + }); + return Ok(()); + } - let channel_type = channel.context.get_channel_type(); - if channel_type.requires_zero_conf() { - return Err(MsgHandleErrInternal::send_err_msg_no_close("No zero confirmation channels accepted".to_owned(), msg.temporary_channel_id.clone())); - } - if channel_type.requires_anchors_zero_fee_htlc_tx() { - return Err(MsgHandleErrInternal::send_err_msg_no_close("No channels with anchor outputs accepted".to_owned(), msg.temporary_channel_id.clone())); - } + // Otherwise create the channel right now. + let mut random_bytes = [0u8; 16]; + random_bytes.copy_from_slice(&self.entropy_source.get_secure_random_bytes()[..16]); + let user_channel_id = u128::from_be_bytes(random_bytes); + let mut channel = match InboundV2Channel::new(&self.fee_estimator, &self.entropy_source, + &self.signer_provider, counterparty_node_id.clone(), &self.channel_type_features(), + &peer_state.latest_features, &msg, 0, user_channel_id, &self.default_configuration, + best_block_height, &self.logger) + { + Err(e) => { + return Err(MsgHandleErrInternal::from_chan_no_close(e, msg.temporary_channel_id)); + }, + Ok(res) => res + }; - let outbound_scid_alias = self.create_and_insert_outbound_scid_alias(); - channel.context.set_outbound_scid_alias(outbound_scid_alias); + let channel_type = channel.context.get_channel_type(); + if channel_type.requires_zero_conf() { + return Err(MsgHandleErrInternal::send_err_msg_no_close("No zero confirmation channels accepted".to_owned(), msg.temporary_channel_id.clone())); + } + if channel_type.requires_anchors_zero_fee_htlc_tx() { + return Err(MsgHandleErrInternal::send_err_msg_no_close("No channels with anchor outputs accepted".to_owned(), msg.temporary_channel_id.clone())); + } - peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel { - node_id: counterparty_node_id.clone(), - msg: channel.accept_inbound_channel(), - }); - peer_state.channel_by_id.insert(channel_id, ChannelPhase::UnfundedInboundV1(channel)); + let outbound_scid_alias = self.create_and_insert_outbound_scid_alias(); + channel.context.set_outbound_scid_alias(outbound_scid_alias); + + channel.begin_interactive_funding_tx_construction(&self.signer_provider, &self.entropy_source, Vec::new()) + .map_err(|_| MsgHandleErrInternal::send_err_msg_no_close( + "Failed to start interactive transaction construction".to_owned(), msg.temporary_channel_id))?; + + peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannelV2 { + node_id: counterparty_node_id.clone(), + msg: channel.accept_inbound_dual_funded_channel(), + }); + peer_state.channel_by_id.insert(channel.context.channel_id(), ChannelPhase::UnfundedInboundV2(channel)); + }, + } Ok(()) } @@ -6397,6 +6619,249 @@ where } } + #[cfg(dual_funding)] + fn internal_tx_add_input(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxAddInput) -> Result<(), MsgHandleErrInternal> { + let per_peer_state = self.per_peer_state.read().unwrap(); + let peer_state_mutex = per_peer_state.get(counterparty_node_id) + .ok_or_else(|| { + debug_assert!(false); + MsgHandleErrInternal::send_err_msg_no_close( + format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), + msg.channel_id) + })?; + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + match peer_state.channel_by_id.entry(msg.channel_id) { + hash_map::Entry::Occupied(mut chan_phase_entry) => { + let channel_phase = chan_phase_entry.get_mut(); + match channel_phase { + ChannelPhase::UnfundedInboundV2(_) | ChannelPhase::UnfundedOutboundV2(_) => { + let tx_msg = channel_phase.context_mut().tx_add_input(msg); + let msg_send_event = match tx_msg { + Ok(InteractiveTxMessageSend::TxAddInput(msg)) => events::MessageSendEvent::SendTxAddInput { + node_id: *counterparty_node_id, msg }, + Ok(InteractiveTxMessageSend::TxAddOutput(msg)) => events::MessageSendEvent::SendTxAddOutput { + node_id: *counterparty_node_id, msg }, + Ok(InteractiveTxMessageSend::TxComplete(msg)) => events::MessageSendEvent::SendTxComplete { + node_id: *counterparty_node_id, msg }, + Err(tx_abort_msg) => events:: MessageSendEvent::SendTxAbort { + node_id: *counterparty_node_id, msg: tx_abort_msg } + }; + peer_state.pending_msg_events.push(msg_send_event); + }, + _ => try_chan_phase_entry!(self, Err(ChannelError::Warn( + "Got a tx_add_input message with no interactive transaction construction expected or in-progress" + .into())), chan_phase_entry) + } + Ok(()) + }, + hash_map::Entry::Vacant(_) => { + 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)) + } + } + } + + #[cfg(dual_funding)] + fn internal_tx_add_output(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxAddOutput) -> Result<(), MsgHandleErrInternal> { + let per_peer_state = self.per_peer_state.read().unwrap(); + let peer_state_mutex = per_peer_state.get(counterparty_node_id) + .ok_or_else(|| { + debug_assert!(false); + MsgHandleErrInternal::send_err_msg_no_close( + format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), + msg.channel_id) + })?; + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + match peer_state.channel_by_id.entry(msg.channel_id) { + hash_map::Entry::Occupied(mut chan_phase_entry) => { + let channel_phase = chan_phase_entry.get_mut(); + match channel_phase { + ChannelPhase::UnfundedInboundV2(_) | ChannelPhase::UnfundedOutboundV2(_) => { + let tx_msg = channel_phase.context_mut().tx_add_output(msg); + let msg_send_event = match tx_msg { + Ok(InteractiveTxMessageSend::TxAddInput(msg)) => events::MessageSendEvent::SendTxAddInput { + node_id: *counterparty_node_id, msg }, + Ok(InteractiveTxMessageSend::TxAddOutput(msg)) => events::MessageSendEvent::SendTxAddOutput { + node_id: *counterparty_node_id, msg }, + Ok(InteractiveTxMessageSend::TxComplete(msg)) => events::MessageSendEvent::SendTxComplete { + node_id: *counterparty_node_id, msg }, + Err(tx_abort_msg) => events:: MessageSendEvent::SendTxAbort { + node_id: *counterparty_node_id, msg: tx_abort_msg } + }; + peer_state.pending_msg_events.push(msg_send_event); + }, + _ => try_chan_phase_entry!(self, Err(ChannelError::Warn( + "Got a tx_add_output message with no interactive transaction construction expected or in-progress" + .into())), chan_phase_entry) + } + Ok(()) + }, + hash_map::Entry::Vacant(_) => { + 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)) + } + } + } + + #[cfg(dual_funding)] + fn internal_tx_remove_input(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxRemoveInput) -> Result<(), MsgHandleErrInternal> { + let per_peer_state = self.per_peer_state.read().unwrap(); + let peer_state_mutex = per_peer_state.get(counterparty_node_id) + .ok_or_else(|| { + debug_assert!(false); + MsgHandleErrInternal::send_err_msg_no_close( + format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), + msg.channel_id) + })?; + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + match peer_state.channel_by_id.entry(msg.channel_id) { + hash_map::Entry::Occupied(mut chan_phase_entry) => { + let channel_phase = chan_phase_entry.get_mut(); + match channel_phase { + ChannelPhase::UnfundedInboundV2(_) | ChannelPhase::UnfundedOutboundV2(_) => { + let tx_msg = channel_phase.context_mut().tx_remove_input(msg); + let msg_send_event = match tx_msg { + Ok(InteractiveTxMessageSend::TxAddInput(msg)) => events::MessageSendEvent::SendTxAddInput { + node_id: *counterparty_node_id, msg }, + Ok(InteractiveTxMessageSend::TxAddOutput(msg)) => events::MessageSendEvent::SendTxAddOutput { + node_id: *counterparty_node_id, msg }, + Ok(InteractiveTxMessageSend::TxComplete(msg)) => events::MessageSendEvent::SendTxComplete { + node_id: *counterparty_node_id, msg }, + Err(tx_abort_msg) => events:: MessageSendEvent::SendTxAbort { + node_id: *counterparty_node_id, msg: tx_abort_msg } + }; + peer_state.pending_msg_events.push(msg_send_event); + }, + _ => try_chan_phase_entry!(self, Err(ChannelError::Warn( + "Got a tx_remove_input message with no interactive transaction construction expected or in-progress" + .into())), chan_phase_entry) + } + Ok(()) + }, + hash_map::Entry::Vacant(_) => { + 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)) + } + } + } + + #[cfg(dual_funding)] + fn internal_tx_remove_output(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxRemoveOutput) -> Result<(), MsgHandleErrInternal> { + let per_peer_state = self.per_peer_state.read().unwrap(); + let peer_state_mutex = per_peer_state.get(counterparty_node_id) + .ok_or_else(|| { + debug_assert!(false); + MsgHandleErrInternal::send_err_msg_no_close( + format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), + msg.channel_id) + })?; + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + match peer_state.channel_by_id.entry(msg.channel_id) { + hash_map::Entry::Occupied(mut chan_phase_entry) => { + let channel_phase = chan_phase_entry.get_mut(); + match channel_phase { + ChannelPhase::UnfundedInboundV2(_) | ChannelPhase::UnfundedOutboundV2(_) => { + let tx_msg = channel_phase.context_mut().tx_remove_output(msg); + let msg_send_event = match tx_msg { + Ok(InteractiveTxMessageSend::TxAddInput(msg)) => events::MessageSendEvent::SendTxAddInput { + node_id: *counterparty_node_id, msg }, + Ok(InteractiveTxMessageSend::TxAddOutput(msg)) => events::MessageSendEvent::SendTxAddOutput { + node_id: *counterparty_node_id, msg }, + Ok(InteractiveTxMessageSend::TxComplete(msg)) => events::MessageSendEvent::SendTxComplete { + node_id: *counterparty_node_id, msg }, + Err(tx_abort_msg) => events:: MessageSendEvent::SendTxAbort { + node_id: *counterparty_node_id, msg: tx_abort_msg } + }; + peer_state.pending_msg_events.push(msg_send_event); + }, + _ => try_chan_phase_entry!(self, Err(ChannelError::Warn( + "Got a tx_remove_output message with no interactive transaction construction expected or in-progress" + .into())), chan_phase_entry) + } + Ok(()) + }, + hash_map::Entry::Vacant(_) => { + 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)) + } + } + } + + #[cfg(dual_funding)] + fn internal_tx_complete(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxComplete) -> Result<(), MsgHandleErrInternal> { + let per_peer_state = self.per_peer_state.read().unwrap(); + let peer_state_mutex = per_peer_state.get(counterparty_node_id) + .ok_or_else(|| { + debug_assert!(false); + MsgHandleErrInternal::send_err_msg_no_close( + format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), + msg.channel_id) + })?; + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + match peer_state.channel_by_id.entry(msg.channel_id) { + hash_map::Entry::Occupied(mut chan_phase_entry) => { + let channel_phase = chan_phase_entry.get_mut(); + match channel_phase { + ChannelPhase::UnfundedInboundV2(_) | ChannelPhase::UnfundedOutboundV2(_) => { + let result = channel_phase.context_mut().tx_complete(msg); + match result { + Ok((tx_msg_opt, tx_opt)) => { + if let Some(tx_msg) = tx_msg_opt { + let msg_send_event = match tx_msg { + InteractiveTxMessageSend::TxAddInput(msg) => events::MessageSendEvent::SendTxAddInput { + node_id: *counterparty_node_id, msg }, + InteractiveTxMessageSend::TxAddOutput(msg) => events::MessageSendEvent::SendTxAddOutput { + node_id: *counterparty_node_id, msg }, + InteractiveTxMessageSend::TxComplete(msg) => events::MessageSendEvent::SendTxComplete { + node_id: *counterparty_node_id, msg }, + }; + peer_state.pending_msg_events.push(msg_send_event); + } + if let Some(_tx) = tx_opt { + // TODO(dual_funding): Handle this unsigned transaction. + } + }, + Err(tx_abort_msg) => peer_state.pending_msg_events.push( + events:: MessageSendEvent::SendTxAbort { node_id: *counterparty_node_id, msg: tx_abort_msg }), + } + }, + _ => try_chan_phase_entry!(self, Err(ChannelError::Close( + "Got a tx_complete message with no interactive transaction construction expected or in-progress" + .into())), chan_phase_entry) + } + Ok(()) + }, + hash_map::Entry::Vacant(_) => { + 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)) + } + } + } + + fn internal_tx_signatures(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxSignatures) { + let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( + "Dual-funded channels not supported".to_owned(), + msg.channel_id.clone())), *counterparty_node_id); + } + + fn internal_tx_init_rbf(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxInitRbf) { + let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( + "Dual-funded channels not supported".to_owned(), + msg.channel_id.clone())), *counterparty_node_id); + } + + fn internal_tx_ack_rbf(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxAckRbf) { + let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( + "Dual-funded channels not supported".to_owned(), + msg.channel_id.clone())), *counterparty_node_id); + } + + fn internal_tx_abort(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxAbort) { + let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( + "Dual-funded channels not supported".to_owned(), + msg.channel_id.clone())), *counterparty_node_id); + } + fn internal_channel_ready(&self, counterparty_node_id: &PublicKey, msg: &msgs::ChannelReady) -> Result<(), MsgHandleErrInternal> { // Note that the ChannelManager is NOT re-persisted on disk after this (unless we error // closing a channel), so any changes are likely to be lost on restart! @@ -8563,7 +9028,7 @@ where // open_channel message - pre-funded channels are never written so there should be no // change to the contents. let _persistence_guard = PersistenceNotifierGuard::optionally_notify(self, || { - let res = self.internal_open_channel(counterparty_node_id, msg); + let res = self.internal_open_channel(counterparty_node_id, OpenChannelMessage::V1(msg.clone())); let persist = match &res { Err(e) if e.closes_channel() => { debug_assert!(false, "We shouldn't close a new channel"); @@ -8576,10 +9041,23 @@ where }); } + #[cfg(dual_funding)] fn handle_open_channel_v2(&self, counterparty_node_id: &PublicKey, msg: &msgs::OpenChannelV2) { - let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( - "Dual-funded channels not supported".to_owned(), - msg.temporary_channel_id.clone())), *counterparty_node_id); + // Note that we never need to persist the updated ChannelManager for an inbound + // open_channel message - pre-funded channels are never written so there should be no + // change to the contents. + let _persistence_guard = PersistenceNotifierGuard::optionally_notify(self, || { + let res = self.internal_open_channel(counterparty_node_id, OpenChannelMessage::V2(msg.clone())); + let persist = match &res { + Err(e) if e.closes_channel() => { + debug_assert!(false, "We shouldn't close a new channel"); + NotifyOption::DoPersist + }, + _ => NotifyOption::SkipPersistHandleEvents, + }; + let _ = handle_error!(self, res, *counterparty_node_id); + persist + }); } fn handle_accept_channel(&self, counterparty_node_id: &PublicKey, msg: &msgs::AcceptChannel) { @@ -8592,6 +9070,7 @@ where }); } + #[cfg(dual_funding)] fn handle_accept_channel_v2(&self, counterparty_node_id: &PublicKey, msg: &msgs::AcceptChannelV2) { let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( "Dual-funded channels not supported".to_owned(), @@ -9059,54 +9538,83 @@ where Some(vec![self.chain_hash]) } + #[cfg(dual_funding)] fn handle_tx_add_input(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxAddInput) { - let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( - "Dual-funded channels not supported".to_owned(), - msg.channel_id.clone())), *counterparty_node_id); + // Note that we never need to persist the updated ChannelManager for an inbound + // tx_add_input message - interactive transaction construction does not need to + // be persisted before any signatures are exchanged. + let _persistence_guard = PersistenceNotifierGuard::optionally_notify(self, || { + let _ = handle_error!(self, self.internal_tx_add_input(counterparty_node_id, msg), *counterparty_node_id); + NotifyOption::SkipPersistHandleEvents + }); } + #[cfg(dual_funding)] fn handle_tx_add_output(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxAddOutput) { - let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( - "Dual-funded channels not supported".to_owned(), - msg.channel_id.clone())), *counterparty_node_id); + // Note that we never need to persist the updated ChannelManager for an inbound + // tx_add_input message - interactive transaction construction does not need to + // be persisted before any signatures are exchanged. + let _persistence_guard = PersistenceNotifierGuard::optionally_notify(self, || { + let _ = handle_error!(self, self.internal_tx_add_output(counterparty_node_id, msg), *counterparty_node_id); + NotifyOption::SkipPersistHandleEvents + }); } + #[cfg(dual_funding)] fn handle_tx_remove_input(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxRemoveInput) { - let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( - "Dual-funded channels not supported".to_owned(), - msg.channel_id.clone())), *counterparty_node_id); + // Note that we never need to persist the updated ChannelManager for an inbound + // tx_add_input message - interactive transaction construction does not need to + // be persisted before any signatures are exchanged. + let _persistence_guard = PersistenceNotifierGuard::optionally_notify(self, || { + let _ = handle_error!(self, self.internal_tx_remove_input(counterparty_node_id, msg), *counterparty_node_id); + NotifyOption::SkipPersistHandleEvents + }); } + #[cfg(dual_funding)] fn handle_tx_remove_output(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxRemoveOutput) { - let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( - "Dual-funded channels not supported".to_owned(), - msg.channel_id.clone())), *counterparty_node_id); + // Note that we never need to persist the updated ChannelManager for an inbound + // tx_add_input message - interactive transaction construction does not need to + // be persisted before any signatures are exchanged. + let _persistence_guard = PersistenceNotifierGuard::optionally_notify(self, || { + let _ = handle_error!(self, self.internal_tx_remove_output(counterparty_node_id, msg), *counterparty_node_id); + NotifyOption::SkipPersistHandleEvents + }); } + #[cfg(dual_funding)] fn handle_tx_complete(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxComplete) { - let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( - "Dual-funded channels not supported".to_owned(), - msg.channel_id.clone())), *counterparty_node_id); + // Note that we never need to persist the updated ChannelManager for an inbound + // tx_add_input message - interactive transaction construction does not need to + // be persisted before any signatures are exchanged. + let _persistence_guard = PersistenceNotifierGuard::optionally_notify(self, || { + let _ = handle_error!(self, self.internal_tx_complete(counterparty_node_id, msg), *counterparty_node_id); + NotifyOption::SkipPersistHandleEvents + }); } + #[cfg(dual_funding)] fn handle_tx_signatures(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxSignatures) { let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( "Dual-funded channels not supported".to_owned(), msg.channel_id.clone())), *counterparty_node_id); } + #[cfg(dual_funding)] fn handle_tx_init_rbf(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxInitRbf) { let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( "Dual-funded channels not supported".to_owned(), msg.channel_id.clone())), *counterparty_node_id); } + #[cfg(dual_funding)] fn handle_tx_ack_rbf(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxAckRbf) { let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( "Dual-funded channels not supported".to_owned(), msg.channel_id.clone())), *counterparty_node_id); } + #[cfg(dual_funding)] fn handle_tx_abort(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxAbort) { let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( "Dual-funded channels not supported".to_owned(), diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 4f3c647ac8e..465a52c9904 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -1453,10 +1453,12 @@ pub trait ChannelMessageHandler : MessageSendEventsProvider { /// Handle an incoming `open_channel` message from the given peer. fn handle_open_channel(&self, their_node_id: &PublicKey, msg: &OpenChannel); /// Handle an incoming `open_channel2` message from the given peer. + #[cfg(dual_funding)] fn handle_open_channel_v2(&self, their_node_id: &PublicKey, msg: &OpenChannelV2); /// Handle an incoming `accept_channel` message from the given peer. fn handle_accept_channel(&self, their_node_id: &PublicKey, msg: &AcceptChannel); /// Handle an incoming `accept_channel2` message from the given peer. + #[cfg(dual_funding)] fn handle_accept_channel_v2(&self, their_node_id: &PublicKey, msg: &AcceptChannelV2); /// Handle an incoming `funding_created` message from the given peer. fn handle_funding_created(&self, their_node_id: &PublicKey, msg: &FundingCreated); @@ -1485,22 +1487,31 @@ pub trait ChannelMessageHandler : MessageSendEventsProvider { // Interactive channel construction /// Handle an incoming `tx_add_input message` from the given peer. + #[cfg(dual_funding)] fn handle_tx_add_input(&self, their_node_id: &PublicKey, msg: &TxAddInput); /// Handle an incoming `tx_add_output` message from the given peer. + #[cfg(dual_funding)] fn handle_tx_add_output(&self, their_node_id: &PublicKey, msg: &TxAddOutput); /// Handle an incoming `tx_remove_input` message from the given peer. + #[cfg(dual_funding)] fn handle_tx_remove_input(&self, their_node_id: &PublicKey, msg: &TxRemoveInput); /// Handle an incoming `tx_remove_output` message from the given peer. + #[cfg(dual_funding)] fn handle_tx_remove_output(&self, their_node_id: &PublicKey, msg: &TxRemoveOutput); /// Handle an incoming `tx_complete message` from the given peer. + #[cfg(dual_funding)] fn handle_tx_complete(&self, their_node_id: &PublicKey, msg: &TxComplete); /// Handle an incoming `tx_signatures` message from the given peer. + #[cfg(dual_funding)] fn handle_tx_signatures(&self, their_node_id: &PublicKey, msg: &TxSignatures); /// Handle an incoming `tx_init_rbf` message from the given peer. + #[cfg(dual_funding)] fn handle_tx_init_rbf(&self, their_node_id: &PublicKey, msg: &TxInitRbf); /// Handle an incoming `tx_ack_rbf` message from the given peer. + #[cfg(dual_funding)] fn handle_tx_ack_rbf(&self, their_node_id: &PublicKey, msg: &TxAckRbf); /// Handle an incoming `tx_abort message` from the given peer. + #[cfg(dual_funding)] fn handle_tx_abort(&self, their_node_id: &PublicKey, msg: &TxAbort); // HTLC handling: diff --git a/lightning/src/ln/peer_handler.rs b/lightning/src/ln/peer_handler.rs index 6a935acf00d..80b340f11a1 100644 --- a/lightning/src/ln/peer_handler.rs +++ b/lightning/src/ln/peer_handler.rs @@ -306,46 +306,57 @@ impl ChannelMessageHandler for ErroringMessageHandler { None } + #[cfg(dual_funding)] fn handle_open_channel_v2(&self, their_node_id: &PublicKey, msg: &msgs::OpenChannelV2) { ErroringMessageHandler::push_error(self, their_node_id, msg.temporary_channel_id); } + #[cfg(dual_funding)] fn handle_accept_channel_v2(&self, their_node_id: &PublicKey, msg: &msgs::AcceptChannelV2) { ErroringMessageHandler::push_error(self, their_node_id, msg.temporary_channel_id); } + #[cfg(dual_funding)] fn handle_tx_add_input(&self, their_node_id: &PublicKey, msg: &msgs::TxAddInput) { ErroringMessageHandler::push_error(self, their_node_id, msg.channel_id); } + #[cfg(dual_funding)] fn handle_tx_add_output(&self, their_node_id: &PublicKey, msg: &msgs::TxAddOutput) { ErroringMessageHandler::push_error(self, their_node_id, msg.channel_id); } + #[cfg(dual_funding)] fn handle_tx_remove_input(&self, their_node_id: &PublicKey, msg: &msgs::TxRemoveInput) { ErroringMessageHandler::push_error(self, their_node_id, msg.channel_id); } + #[cfg(dual_funding)] fn handle_tx_remove_output(&self, their_node_id: &PublicKey, msg: &msgs::TxRemoveOutput) { ErroringMessageHandler::push_error(self, their_node_id, msg.channel_id); } + #[cfg(dual_funding)] fn handle_tx_complete(&self, their_node_id: &PublicKey, msg: &msgs::TxComplete) { ErroringMessageHandler::push_error(self, their_node_id, msg.channel_id); } + #[cfg(dual_funding)] fn handle_tx_signatures(&self, their_node_id: &PublicKey, msg: &msgs::TxSignatures) { ErroringMessageHandler::push_error(self, their_node_id, msg.channel_id); } + #[cfg(dual_funding)] fn handle_tx_init_rbf(&self, their_node_id: &PublicKey, msg: &msgs::TxInitRbf) { ErroringMessageHandler::push_error(self, their_node_id, msg.channel_id); } + #[cfg(dual_funding)] fn handle_tx_ack_rbf(&self, their_node_id: &PublicKey, msg: &msgs::TxAckRbf) { ErroringMessageHandler::push_error(self, their_node_id, msg.channel_id); } + #[cfg(dual_funding)] fn handle_tx_abort(&self, their_node_id: &PublicKey, msg: &msgs::TxAbort) { ErroringMessageHandler::push_error(self, their_node_id, msg.channel_id); } @@ -1659,12 +1670,14 @@ impl { self.message_handler.chan_handler.handle_open_channel(&their_node_id, &msg); }, + #[cfg(dual_funding)] wire::Message::OpenChannelV2(msg) => { self.message_handler.chan_handler.handle_open_channel_v2(&their_node_id, &msg); }, wire::Message::AcceptChannel(msg) => { self.message_handler.chan_handler.handle_accept_channel(&their_node_id, &msg); }, + #[cfg(dual_funding)] wire::Message::AcceptChannelV2(msg) => { self.message_handler.chan_handler.handle_accept_channel_v2(&their_node_id, &msg); }, @@ -1696,30 +1709,39 @@ impl { self.message_handler.chan_handler.handle_tx_add_input(&their_node_id, &msg); }, + #[cfg(dual_funding)] wire::Message::TxAddOutput(msg) => { self.message_handler.chan_handler.handle_tx_add_output(&their_node_id, &msg); }, + #[cfg(dual_funding)] wire::Message::TxRemoveInput(msg) => { self.message_handler.chan_handler.handle_tx_remove_input(&their_node_id, &msg); }, + #[cfg(dual_funding)] wire::Message::TxRemoveOutput(msg) => { self.message_handler.chan_handler.handle_tx_remove_output(&their_node_id, &msg); }, + #[cfg(dual_funding)] wire::Message::TxComplete(msg) => { self.message_handler.chan_handler.handle_tx_complete(&their_node_id, &msg); }, + #[cfg(dual_funding)] wire::Message::TxSignatures(msg) => { self.message_handler.chan_handler.handle_tx_signatures(&their_node_id, &msg); }, + #[cfg(dual_funding)] wire::Message::TxInitRbf(msg) => { self.message_handler.chan_handler.handle_tx_init_rbf(&their_node_id, &msg); }, + #[cfg(dual_funding)] wire::Message::TxAckRbf(msg) => { self.message_handler.chan_handler.handle_tx_ack_rbf(&their_node_id, &msg); }, + #[cfg(dual_funding)] wire::Message::TxAbort(msg) => { self.message_handler.chan_handler.handle_tx_abort(&their_node_id, &msg); } diff --git a/lightning/src/ln/wire.rs b/lightning/src/ln/wire.rs index 5087df33b83..aee72677cee 100644 --- a/lightning/src/ln/wire.rs +++ b/lightning/src/ln/wire.rs @@ -54,8 +54,10 @@ pub(crate) enum Message where T: core::fmt::Debug + Type + TestEq { Ping(msgs::Ping), Pong(msgs::Pong), OpenChannel(msgs::OpenChannel), + #[cfg(dual_funding)] OpenChannelV2(msgs::OpenChannelV2), AcceptChannel(msgs::AcceptChannel), + #[cfg(dual_funding)] AcceptChannelV2(msgs::AcceptChannelV2), FundingCreated(msgs::FundingCreated), FundingSigned(msgs::FundingSigned), @@ -63,14 +65,23 @@ pub(crate) enum Message where T: core::fmt::Debug + Type + TestEq { Splice(msgs::Splice), SpliceAck(msgs::SpliceAck), SpliceLocked(msgs::SpliceLocked), + #[cfg(dual_funding)] TxAddInput(msgs::TxAddInput), + #[cfg(dual_funding)] TxAddOutput(msgs::TxAddOutput), + #[cfg(dual_funding)] TxRemoveInput(msgs::TxRemoveInput), + #[cfg(dual_funding)] TxRemoveOutput(msgs::TxRemoveOutput), + #[cfg(dual_funding)] TxComplete(msgs::TxComplete), + #[cfg(dual_funding)] TxSignatures(msgs::TxSignatures), + #[cfg(dual_funding)] TxInitRbf(msgs::TxInitRbf), + #[cfg(dual_funding)] TxAckRbf(msgs::TxAckRbf), + #[cfg(dual_funding)] TxAbort(msgs::TxAbort), ChannelReady(msgs::ChannelReady), Shutdown(msgs::Shutdown), @@ -109,8 +120,10 @@ impl Writeable for Message where T: core::fmt::Debug + Type + TestEq { &Message::Ping(ref msg) => msg.write(writer), &Message::Pong(ref msg) => msg.write(writer), &Message::OpenChannel(ref msg) => msg.write(writer), + #[cfg(dual_funding)] &Message::OpenChannelV2(ref msg) => msg.write(writer), &Message::AcceptChannel(ref msg) => msg.write(writer), + #[cfg(dual_funding)] &Message::AcceptChannelV2(ref msg) => msg.write(writer), &Message::FundingCreated(ref msg) => msg.write(writer), &Message::FundingSigned(ref msg) => msg.write(writer), @@ -118,14 +131,23 @@ impl Writeable for Message where T: core::fmt::Debug + Type + TestEq { &Message::Splice(ref msg) => msg.write(writer), &Message::SpliceAck(ref msg) => msg.write(writer), &Message::SpliceLocked(ref msg) => msg.write(writer), + #[cfg(dual_funding)] &Message::TxAddInput(ref msg) => msg.write(writer), + #[cfg(dual_funding)] &Message::TxAddOutput(ref msg) => msg.write(writer), + #[cfg(dual_funding)] &Message::TxRemoveInput(ref msg) => msg.write(writer), + #[cfg(dual_funding)] &Message::TxRemoveOutput(ref msg) => msg.write(writer), + #[cfg(dual_funding)] &Message::TxComplete(ref msg) => msg.write(writer), + #[cfg(dual_funding)] &Message::TxSignatures(ref msg) => msg.write(writer), + #[cfg(dual_funding)] &Message::TxInitRbf(ref msg) => msg.write(writer), + #[cfg(dual_funding)] &Message::TxAckRbf(ref msg) => msg.write(writer), + #[cfg(dual_funding)] &Message::TxAbort(ref msg) => msg.write(writer), &Message::ChannelReady(ref msg) => msg.write(writer), &Message::Shutdown(ref msg) => msg.write(writer), @@ -164,8 +186,10 @@ impl Type for Message where T: core::fmt::Debug + Type + TestEq { &Message::Ping(ref msg) => msg.type_id(), &Message::Pong(ref msg) => msg.type_id(), &Message::OpenChannel(ref msg) => msg.type_id(), + #[cfg(dual_funding)] &Message::OpenChannelV2(ref msg) => msg.type_id(), &Message::AcceptChannel(ref msg) => msg.type_id(), + #[cfg(dual_funding)] &Message::AcceptChannelV2(ref msg) => msg.type_id(), &Message::FundingCreated(ref msg) => msg.type_id(), &Message::FundingSigned(ref msg) => msg.type_id(), @@ -173,14 +197,23 @@ impl Type for Message where T: core::fmt::Debug + Type + TestEq { &Message::Splice(ref msg) => msg.type_id(), &Message::SpliceAck(ref msg) => msg.type_id(), &Message::SpliceLocked(ref msg) => msg.type_id(), + #[cfg(dual_funding)] &Message::TxAddInput(ref msg) => msg.type_id(), + #[cfg(dual_funding)] &Message::TxAddOutput(ref msg) => msg.type_id(), + #[cfg(dual_funding)] &Message::TxRemoveInput(ref msg) => msg.type_id(), + #[cfg(dual_funding)] &Message::TxRemoveOutput(ref msg) => msg.type_id(), + #[cfg(dual_funding)] &Message::TxComplete(ref msg) => msg.type_id(), + #[cfg(dual_funding)] &Message::TxSignatures(ref msg) => msg.type_id(), + #[cfg(dual_funding)] &Message::TxInitRbf(ref msg) => msg.type_id(), + #[cfg(dual_funding)] &Message::TxAckRbf(ref msg) => msg.type_id(), + #[cfg(dual_funding)] &Message::TxAbort(ref msg) => msg.type_id(), &Message::ChannelReady(ref msg) => msg.type_id(), &Message::Shutdown(ref msg) => msg.type_id(), @@ -255,12 +288,14 @@ fn do_read(buffer: &mut R, message_type: u1 msgs::OpenChannel::TYPE => { Ok(Message::OpenChannel(Readable::read(buffer)?)) }, + #[cfg(dual_funding)] msgs::OpenChannelV2::TYPE => { Ok(Message::OpenChannelV2(Readable::read(buffer)?)) }, msgs::AcceptChannel::TYPE => { Ok(Message::AcceptChannel(Readable::read(buffer)?)) }, + #[cfg(dual_funding)] msgs::AcceptChannelV2::TYPE => { Ok(Message::AcceptChannelV2(Readable::read(buffer)?)) }, @@ -282,30 +317,39 @@ fn do_read(buffer: &mut R, message_type: u1 msgs::SpliceLocked::TYPE => { Ok(Message::SpliceLocked(Readable::read(buffer)?)) }, + #[cfg(dual_funding)] msgs::TxAddInput::TYPE => { Ok(Message::TxAddInput(Readable::read(buffer)?)) }, + #[cfg(dual_funding)] msgs::TxAddOutput::TYPE => { Ok(Message::TxAddOutput(Readable::read(buffer)?)) }, + #[cfg(dual_funding)] msgs::TxRemoveInput::TYPE => { Ok(Message::TxRemoveInput(Readable::read(buffer)?)) }, + #[cfg(dual_funding)] msgs::TxRemoveOutput::TYPE => { Ok(Message::TxRemoveOutput(Readable::read(buffer)?)) }, + #[cfg(dual_funding)] msgs::TxComplete::TYPE => { Ok(Message::TxComplete(Readable::read(buffer)?)) }, + #[cfg(dual_funding)] msgs::TxSignatures::TYPE => { Ok(Message::TxSignatures(Readable::read(buffer)?)) }, + #[cfg(dual_funding)] msgs::TxInitRbf::TYPE => { Ok(Message::TxInitRbf(Readable::read(buffer)?)) }, + #[cfg(dual_funding)] msgs::TxAckRbf::TYPE => { Ok(Message::TxAckRbf(Readable::read(buffer)?)) }, + #[cfg(dual_funding)] msgs::TxAbort::TYPE => { Ok(Message::TxAbort(Readable::read(buffer)?)) }, diff --git a/lightning/src/util/config.rs b/lightning/src/util/config.rs index 13284113d2c..7922abf331e 100644 --- a/lightning/src/util/config.rs +++ b/lightning/src/util/config.rs @@ -737,11 +737,21 @@ pub struct UserConfig { /// [`msgs::AcceptChannel`] message will not be sent back to the counterparty node unless the /// user explicitly chooses to accept the request. /// + /// To be able to contribute to inbound dual-funded channels, this field must be set to true. + /// In that case the analogous [`Event::OpenChannelV2Request`] will be triggered once a request + /// to open a new dual-funded channel is received through a [`msgs::OpenChannelV2`] message. + /// A corresponding [`msgs::AcceptChannelV2`] message will not be sent back to the counterparty + /// node until the user explicitly chooses to accept the request, optionally contributing funds + /// to it. + /// /// Default value: false. /// /// [`Event::OpenChannelRequest`]: crate::events::Event::OpenChannelRequest /// [`msgs::OpenChannel`]: crate::ln::msgs::OpenChannel /// [`msgs::AcceptChannel`]: crate::ln::msgs::AcceptChannel + /// [`Event::OpenChannelV2Request`]: crate::events::Event::OpenChannelV2Request + /// [`msgs::OpenChannelV2`]: crate::ln::msgs::OpenChannelV2 + /// [`msgs::AcceptChannelV2`]: crate::ln::msgs::AcceptChannelV2 pub manually_accept_inbound_channels: bool, /// If this is set to true, LDK will intercept HTLCs that are attempting to be forwarded over /// fake short channel ids generated via [`ChannelManager::get_intercept_scid`]. Upon HTLC diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index 664deb9fa15..3a75e8e4009 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -744,46 +744,57 @@ impl msgs::ChannelMessageHandler for TestChannelMessageHandler { Some(vec![self.chain_hash]) } + #[cfg(dual_funding)] fn handle_open_channel_v2(&self, _their_node_id: &PublicKey, msg: &msgs::OpenChannelV2) { self.received_msg(wire::Message::OpenChannelV2(msg.clone())); } + #[cfg(dual_funding)] fn handle_accept_channel_v2(&self, _their_node_id: &PublicKey, msg: &msgs::AcceptChannelV2) { self.received_msg(wire::Message::AcceptChannelV2(msg.clone())); } + #[cfg(dual_funding)] fn handle_tx_add_input(&self, _their_node_id: &PublicKey, msg: &msgs::TxAddInput) { self.received_msg(wire::Message::TxAddInput(msg.clone())); } + #[cfg(dual_funding)] fn handle_tx_add_output(&self, _their_node_id: &PublicKey, msg: &msgs::TxAddOutput) { self.received_msg(wire::Message::TxAddOutput(msg.clone())); } + #[cfg(dual_funding)] fn handle_tx_remove_input(&self, _their_node_id: &PublicKey, msg: &msgs::TxRemoveInput) { self.received_msg(wire::Message::TxRemoveInput(msg.clone())); } + #[cfg(dual_funding)] fn handle_tx_remove_output(&self, _their_node_id: &PublicKey, msg: &msgs::TxRemoveOutput) { self.received_msg(wire::Message::TxRemoveOutput(msg.clone())); } + #[cfg(dual_funding)] fn handle_tx_complete(&self, _their_node_id: &PublicKey, msg: &msgs::TxComplete) { self.received_msg(wire::Message::TxComplete(msg.clone())); } + #[cfg(dual_funding)] fn handle_tx_signatures(&self, _their_node_id: &PublicKey, msg: &msgs::TxSignatures) { self.received_msg(wire::Message::TxSignatures(msg.clone())); } + #[cfg(dual_funding)] fn handle_tx_init_rbf(&self, _their_node_id: &PublicKey, msg: &msgs::TxInitRbf) { self.received_msg(wire::Message::TxInitRbf(msg.clone())); } + #[cfg(dual_funding)] fn handle_tx_ack_rbf(&self, _their_node_id: &PublicKey, msg: &msgs::TxAckRbf) { self.received_msg(wire::Message::TxAckRbf(msg.clone())); } + #[cfg(dual_funding)] fn handle_tx_abort(&self, _their_node_id: &PublicKey, msg: &msgs::TxAbort) { self.received_msg(wire::Message::TxAbort(msg.clone())); } From 7689e00aa1075b21b70be90f913d31bf8c81f2d8 Mon Sep 17 00:00:00 2001 From: Duncan Dean Date: Thu, 19 Oct 2023 15:47:33 +0200 Subject: [PATCH 020/101] Handle initial commitment_signed for V2 channels --- lightning/src/ln/channel.rs | 544 ++++++++++++++++++++++------- lightning/src/ln/channelmanager.rs | 307 +++++++++++++--- lightning/src/ln/msgs.rs | 1 + 3 files changed, 681 insertions(+), 171 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index b72b1d9c2d9..04bb37abbb0 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -1990,6 +1990,145 @@ impl ChannelContext where SP::Target: SignerProvider { } } + fn do_accept_channel_checks(&mut self, default_limits: &ChannelHandshakeLimits, + their_features: &InitFeatures, msg_dust_limit_satoshis: u64, msg_channel_reserve_satoshis: u64, + msg_to_self_delay: u16, msg_max_accepted_htlcs: u16, msg_htlc_minimum_msat: u64, + msg_max_htlc_value_in_flight_msat: u64, msg_minimum_depth: u32, msg_channel_type: &Option, + msg_shutdown_scriptpubkey: &Option, msg_funding_pubkey: PublicKey, msg_revocation_basepoint: PublicKey, + msg_payment_point: PublicKey, msg_delayed_payment_basepoint: PublicKey, msg_htlc_basepoint: PublicKey, + msg_first_per_commitment_point: PublicKey, + ) -> Result<(), ChannelError> { + let peer_limits = if let Some(ref limits) = self.inbound_handshake_limits_override { limits } else { default_limits }; + + // Check sanity of message fields: + if !self.is_outbound() { + return Err(ChannelError::Close("Got an accept_channel message from an inbound peer".to_owned())); + } + if !matches!(self.channel_state, ChannelState::NegotiatingFunding(flags) if flags == NegotiatingFundingFlags::OUR_INIT_SENT) { + return Err(ChannelError::Close("Got an accept_channel message at a strange time".to_owned())); + } + if msg_dust_limit_satoshis > 21000000 * 100000000 { + return Err(ChannelError::Close(format!("Peer never wants payout outputs? dust_limit_satoshis was {}", msg_dust_limit_satoshis))); + } + if msg_channel_reserve_satoshis > self.channel_value_satoshis { + return Err(ChannelError::Close(format!("Bogus channel_reserve_satoshis ({}). Must not be greater than ({})", msg_channel_reserve_satoshis, self.channel_value_satoshis))); + } + if msg_dust_limit_satoshis > self.holder_selected_channel_reserve_satoshis { + return Err(ChannelError::Close(format!("Dust limit ({}) is bigger than our channel reserve ({})", msg_dust_limit_satoshis, self.holder_selected_channel_reserve_satoshis))); + } + if msg_channel_reserve_satoshis > self.channel_value_satoshis - self.holder_selected_channel_reserve_satoshis { + return Err(ChannelError::Close(format!("Bogus channel_reserve_satoshis ({}). Must not be greater than channel value minus our reserve ({})", + msg_channel_reserve_satoshis, self.channel_value_satoshis - self.holder_selected_channel_reserve_satoshis))); + } + let full_channel_value_msat = (self.channel_value_satoshis - msg_channel_reserve_satoshis) * 1000; + if msg_htlc_minimum_msat >= full_channel_value_msat { + return Err(ChannelError::Close(format!("Minimum htlc value ({}) is full channel value ({})", msg_htlc_minimum_msat, full_channel_value_msat))); + } + let max_delay_acceptable = u16::min(peer_limits.their_to_self_delay, MAX_LOCAL_BREAKDOWN_TIMEOUT); + if msg_to_self_delay > max_delay_acceptable { + return Err(ChannelError::Close(format!("They wanted our payments to be delayed by a needlessly long period. Upper limit: {}. Actual: {}", max_delay_acceptable, msg_to_self_delay))); + } + if msg_max_accepted_htlcs < 1 { + return Err(ChannelError::Close("0 max_accepted_htlcs makes for a useless channel".to_owned())); + } + if msg_max_accepted_htlcs > MAX_HTLCS { + return Err(ChannelError::Close(format!("max_accepted_htlcs was {}. It must not be larger than {}", msg_max_accepted_htlcs, MAX_HTLCS))); + } + + // Now check against optional parameters as set by config... + if msg_htlc_minimum_msat > peer_limits.max_htlc_minimum_msat { + return Err(ChannelError::Close(format!("htlc_minimum_msat ({}) is higher than the user specified limit ({})", msg_htlc_minimum_msat, peer_limits.max_htlc_minimum_msat))); + } + if msg_max_htlc_value_in_flight_msat < peer_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_max_htlc_value_in_flight_msat, peer_limits.min_max_htlc_value_in_flight_msat))); + } + if msg_channel_reserve_satoshis > peer_limits.max_channel_reserve_satoshis { + return Err(ChannelError::Close(format!("channel_reserve_satoshis ({}) is higher than the user specified limit ({})", msg_channel_reserve_satoshis, peer_limits.max_channel_reserve_satoshis))); + } + if msg_max_accepted_htlcs < peer_limits.min_max_accepted_htlcs { + return Err(ChannelError::Close(format!("max_accepted_htlcs ({}) is less than the user specified limit ({})", msg_max_accepted_htlcs, peer_limits.min_max_accepted_htlcs))); + } + if msg_dust_limit_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS { + return Err(ChannelError::Close(format!("dust_limit_satoshis ({}) is less than the implementation limit ({})", msg_dust_limit_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS))); + } + if msg_dust_limit_satoshis > MAX_CHAN_DUST_LIMIT_SATOSHIS { + return Err(ChannelError::Close(format!("dust_limit_satoshis ({}) is greater than the implementation limit ({})", msg_dust_limit_satoshis, MAX_CHAN_DUST_LIMIT_SATOSHIS))); + } + if msg_minimum_depth > peer_limits.max_minimum_depth { + return Err(ChannelError::Close(format!("We consider the minimum depth to be unreasonably large. Expected minimum: ({}). Actual: ({})", peer_limits.max_minimum_depth, msg_minimum_depth))); + } + + if let Some(ty) = &msg_channel_type { + if *ty != self.channel_type { + return Err(ChannelError::Close("Channel Type in accept_channel didn't match the one sent in open_channel.".to_owned())); + } + } else if their_features.supports_channel_type() { + // Assume they've accepted the channel type as they said they understand it. + } else { + let channel_type = ChannelTypeFeatures::from_init(&their_features); + if channel_type != ChannelTypeFeatures::only_static_remote_key() { + return Err(ChannelError::Close("Only static_remote_key is supported for non-negotiated channel types".to_owned())); + } + self.channel_type = channel_type.clone(); + self.channel_transaction_parameters.channel_type_features = channel_type; + } + + let counterparty_shutdown_scriptpubkey = if their_features.supports_upfront_shutdown_script() { + match &msg_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 }; + + self.counterparty_dust_limit_satoshis = msg_dust_limit_satoshis; + self.counterparty_max_htlc_value_in_flight_msat = cmp::min(msg_max_htlc_value_in_flight_msat, self.channel_value_satoshis * 1000); + self.counterparty_selected_channel_reserve_satoshis = Some(msg_channel_reserve_satoshis); + self.counterparty_htlc_minimum_msat = msg_htlc_minimum_msat; + self.counterparty_max_accepted_htlcs = msg_max_accepted_htlcs; + + if peer_limits.trust_own_funding_0conf { + self.minimum_depth = Some(msg_minimum_depth); + } else { + self.minimum_depth = Some(cmp::max(1, msg_minimum_depth)); + } + + let counterparty_pubkeys = ChannelPublicKeys { + funding_pubkey: msg_funding_pubkey, + revocation_basepoint: RevocationBasepoint::from(msg_revocation_basepoint), + payment_point: msg_payment_point, + delayed_payment_basepoint: DelayedPaymentBasepoint::from(msg_delayed_payment_basepoint), + htlc_basepoint: HtlcBasepoint(msg_htlc_basepoint) + }; + + self.channel_transaction_parameters.counterparty_parameters = Some(CounterpartyChannelTransactionParameters { + selected_contest_delay: msg_to_self_delay, + pubkeys: counterparty_pubkeys, + }); + + self.counterparty_cur_commitment_point = Some(msg_first_per_commitment_point); + self.counterparty_shutdown_scriptpubkey = counterparty_shutdown_scriptpubkey; + + self.channel_state = ChannelState::NegotiatingFunding( + NegotiatingFundingFlags::OUR_INIT_SENT | NegotiatingFundingFlags::THEIR_INIT_SENT + ); + self.inbound_handshake_limits_override = None; // We're done enforcing limits on our peer's handshake now. + + Ok(()) + } + /// 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 @@ -3269,7 +3408,7 @@ 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 } -fn maybe_add_funding_change_output(signer_provider: &SP, is_initiator: bool, +pub(super) fn maybe_add_funding_change_output(signer_provider: &SP, is_initiator: bool, our_funding_satoshis: u64, funding_inputs: &Vec<(TxIn, Transaction)>, funding_outputs: &mut Vec, funding_feerate_sat_per_1000_weight: u32, total_input_satoshis: u64, holder_dust_limit_satoshis: u64, channel_keys_id: [u8; 32], @@ -4082,6 +4221,92 @@ impl Channel where Ok(()) } + #[cfg(dual_funding)] + pub fn commitment_signed_initial_v2(&mut self, msg: &msgs::CommitmentSigned, + best_block: BestBlock, signer_provider: &SP, logger: &L + ) -> Result::EcdsaSigner>, ChannelError> + where L::Target: Logger + { + if self.context.channel_state & !(ChannelState::MonitorUpdateInProgress as u32) != ChannelState::FundingCreated as u32 { + return Err(ChannelError::Close("Received initial commitment_signed before funding transaction constructed!".to_owned())); + } + if self.context.commitment_secrets.get_min_seen_secret() != (1 << 48) || + self.context.cur_counterparty_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER || + self.context.cur_holder_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER { + panic!("Should not have advanced channel commitment tx numbers prior to funding_created"); + } + let dual_funding_channel_context = self.dual_funding_channel_context.as_mut().ok_or( + ChannelError::Close("Have no context for dual-funded channel".to_owned()) + )?; + + let funding_script = self.context.get_funding_redeemscript(); + + let counterparty_keys = self.context.build_remote_transaction_keys(); + let counterparty_initial_commitment_tx = self.context.build_commitment_transaction(self.context.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, false, logger).tx; + let counterparty_trusted_tx = counterparty_initial_commitment_tx.trust(); + let counterparty_initial_bitcoin_tx = counterparty_trusted_tx.built_transaction(); + + log_trace!(logger, "Initial counterparty tx for channel {} is: txid {} tx {}", + &self.context.channel_id(), counterparty_initial_bitcoin_tx.txid, encode::serialize_hex(&counterparty_initial_bitcoin_tx.transaction)); + + let holder_signer = self.context.build_holder_transaction_keys(self.context.cur_holder_commitment_transaction_number); + let initial_commitment_tx = self.context.build_commitment_transaction(self.context.cur_holder_commitment_transaction_number, &holder_signer, true, false, logger).tx; + { + let trusted_tx = initial_commitment_tx.trust(); + let initial_commitment_bitcoin_tx = trusted_tx.built_transaction(); + let sighash = initial_commitment_bitcoin_tx.get_sighash_all(&funding_script, self.context.channel_value_satoshis); + // They sign our commitment transaction, allowing us to broadcast the tx if we wish. + if let Err(_) = self.context.secp_ctx.verify_ecdsa(&sighash, &msg.signature, &self.context.get_counterparty_pubkeys().funding_pubkey) { + return Err(ChannelError::Close("Invalid funding_signed signature from peer".to_owned())); + } + } + + let holder_commitment_tx = HolderCommitmentTransaction::new( + initial_commitment_tx, + msg.signature, + Vec::new(), + &self.context.get_holder_pubkeys().funding_pubkey, + self.context.counterparty_funding_pubkey() + ); + + self.context.holder_signer.as_ref().validate_holder_commitment(&holder_commitment_tx, Vec::new()) + .map_err(|_| ChannelError::Close("Failed to validate our commitment".to_owned()))?; + + let funding_redeemscript = self.context.get_funding_redeemscript(); + let funding_txo = self.context.get_funding_txo().unwrap(); + let funding_txo_script = funding_redeemscript.to_v0_p2wsh(); + let obscure_factor = get_commitment_transaction_number_obscure_factor(&self.context.get_holder_pubkeys().payment_point, &self.context.get_counterparty_pubkeys().payment_point, self.context.is_outbound()); + let shutdown_script = self.context.shutdown_scriptpubkey.clone().map(|script| script.into_inner()); + let mut monitor_signer = signer_provider.derive_channel_signer(self.context.channel_value_satoshis, self.context.channel_keys_id); + monitor_signer.provide_channel_parameters(&self.context.channel_transaction_parameters); + let channel_monitor = ChannelMonitor::new(self.context.secp_ctx.clone(), monitor_signer, + shutdown_script, self.context.get_holder_selected_contest_delay(), + &self.context.destination_script, (funding_txo, funding_txo_script), + &self.context.channel_transaction_parameters, + funding_redeemscript.clone(), self.context.channel_value_satoshis, + obscure_factor, + holder_commitment_tx, best_block, self.context.counterparty_node_id); + + channel_monitor.provide_initial_counterparty_commitment_tx( + counterparty_initial_bitcoin_tx.txid, Vec::new(), + self.context.cur_counterparty_commitment_transaction_number, + self.context.counterparty_cur_commitment_point.unwrap(), + counterparty_initial_commitment_tx.feerate_per_kw(), + counterparty_initial_commitment_tx.to_broadcaster_value_sat(), + counterparty_initial_commitment_tx.to_countersignatory_value_sat(), logger); + + assert_eq!(self.context.channel_state & (ChannelState::MonitorUpdateInProgress as u32), 0); // We have no had any monitor(s) yet to fail update! + self.context.cur_holder_commitment_transaction_number -= 1; + self.context.cur_counterparty_commitment_transaction_number -= 1; + + log_info!(logger, "Received initial commitment_signed from peer for channel {}", &self.context.channel_id()); + + let need_channel_ready = self.check_get_channel_ready(0).is_some(); + self.monitor_updating_paused(false, false, need_channel_ready, Vec::new(), Vec::new(), Vec::new()); + + Ok(channel_monitor) + } + pub fn commitment_signed(&mut self, msg: &msgs::CommitmentSigned, logger: &L) -> Result, ChannelError> where L::Target: Logger { @@ -7140,135 +7365,12 @@ impl OutboundV1Channel where SP::Target: SignerProvider { // Message handlers pub fn accept_channel(&mut self, msg: &msgs::AcceptChannel, default_limits: &ChannelHandshakeLimits, their_features: &InitFeatures) -> Result<(), ChannelError> { - let peer_limits = if let Some(ref limits) = self.context.inbound_handshake_limits_override { limits } else { default_limits }; - - // Check sanity of message fields: - 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) { - return Err(ChannelError::Close("Got an accept_channel message at a strange time".to_owned())); - } - if msg.dust_limit_satoshis > 21000000 * 100000000 { - return Err(ChannelError::Close(format!("Peer never wants payout outputs? dust_limit_satoshis was {}", msg.dust_limit_satoshis))); - } - if msg.channel_reserve_satoshis > self.context.channel_value_satoshis { - return Err(ChannelError::Close(format!("Bogus channel_reserve_satoshis ({}). Must not be greater than ({})", msg.channel_reserve_satoshis, self.context.channel_value_satoshis))); - } - if msg.dust_limit_satoshis > self.context.holder_selected_channel_reserve_satoshis { - return Err(ChannelError::Close(format!("Dust limit ({}) is bigger than our channel reserve ({})", msg.dust_limit_satoshis, self.context.holder_selected_channel_reserve_satoshis))); - } - if msg.channel_reserve_satoshis > self.context.channel_value_satoshis - self.context.holder_selected_channel_reserve_satoshis { - return Err(ChannelError::Close(format!("Bogus channel_reserve_satoshis ({}). Must not be greater than channel value minus our reserve ({})", - msg.channel_reserve_satoshis, self.context.channel_value_satoshis - self.context.holder_selected_channel_reserve_satoshis))); - } - let full_channel_value_msat = (self.context.channel_value_satoshis - msg.channel_reserve_satoshis) * 1000; - if msg.htlc_minimum_msat >= full_channel_value_msat { - return Err(ChannelError::Close(format!("Minimum htlc value ({}) is full channel value ({})", msg.htlc_minimum_msat, full_channel_value_msat))); - } - let max_delay_acceptable = u16::min(peer_limits.their_to_self_delay, MAX_LOCAL_BREAKDOWN_TIMEOUT); - if msg.to_self_delay > max_delay_acceptable { - return Err(ChannelError::Close(format!("They wanted our payments to be delayed by a needlessly long period. Upper limit: {}. Actual: {}", max_delay_acceptable, msg.to_self_delay))); - } - if msg.max_accepted_htlcs < 1 { - return Err(ChannelError::Close("0 max_accepted_htlcs makes for a useless channel".to_owned())); - } - if msg.max_accepted_htlcs > MAX_HTLCS { - return Err(ChannelError::Close(format!("max_accepted_htlcs was {}. It must not be larger than {}", msg.max_accepted_htlcs, MAX_HTLCS))); - } - - // Now check against optional parameters as set by config... - if msg.htlc_minimum_msat > peer_limits.max_htlc_minimum_msat { - return Err(ChannelError::Close(format!("htlc_minimum_msat ({}) is higher than the user specified limit ({})", msg.htlc_minimum_msat, peer_limits.max_htlc_minimum_msat))); - } - if msg.max_htlc_value_in_flight_msat < peer_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.max_htlc_value_in_flight_msat, peer_limits.min_max_htlc_value_in_flight_msat))); - } - if msg.channel_reserve_satoshis > peer_limits.max_channel_reserve_satoshis { - return Err(ChannelError::Close(format!("channel_reserve_satoshis ({}) is higher than the user specified limit ({})", msg.channel_reserve_satoshis, peer_limits.max_channel_reserve_satoshis))); - } - if msg.max_accepted_htlcs < peer_limits.min_max_accepted_htlcs { - return Err(ChannelError::Close(format!("max_accepted_htlcs ({}) is less than the user specified limit ({})", msg.max_accepted_htlcs, peer_limits.min_max_accepted_htlcs))); - } - if msg.dust_limit_satoshis < MIN_CHAN_DUST_LIMIT_SATOSHIS { - return Err(ChannelError::Close(format!("dust_limit_satoshis ({}) is less than the implementation limit ({})", msg.dust_limit_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS))); - } - if msg.dust_limit_satoshis > MAX_CHAN_DUST_LIMIT_SATOSHIS { - return Err(ChannelError::Close(format!("dust_limit_satoshis ({}) is greater than the implementation limit ({})", msg.dust_limit_satoshis, MAX_CHAN_DUST_LIMIT_SATOSHIS))); - } - if msg.minimum_depth > peer_limits.max_minimum_depth { - return Err(ChannelError::Close(format!("We consider the minimum depth to be unreasonably large. Expected minimum: ({}). Actual: ({})", peer_limits.max_minimum_depth, msg.minimum_depth))); - } - - if let Some(ty) = &msg.channel_type { - if *ty != self.context.channel_type { - return Err(ChannelError::Close("Channel Type in accept_channel didn't match the one sent in open_channel.".to_owned())); - } - } else if their_features.supports_channel_type() { - // Assume they've accepted the channel type as they said they understand it. - } else { - let channel_type = ChannelTypeFeatures::from_init(&their_features); - if channel_type != ChannelTypeFeatures::only_static_remote_key() { - return Err(ChannelError::Close("Only static_remote_key is supported for non-negotiated channel types".to_owned())); - } - self.context.channel_type = channel_type.clone(); - self.context.channel_transaction_parameters.channel_type_features = channel_type; - } - - let counterparty_shutdown_scriptpubkey = if their_features.supports_upfront_shutdown_script() { - match &msg.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 }; - - self.context.counterparty_dust_limit_satoshis = msg.dust_limit_satoshis; - self.context.counterparty_max_htlc_value_in_flight_msat = cmp::min(msg.max_htlc_value_in_flight_msat, self.context.channel_value_satoshis * 1000); - self.context.counterparty_selected_channel_reserve_satoshis = Some(msg.channel_reserve_satoshis); - self.context.counterparty_htlc_minimum_msat = msg.htlc_minimum_msat; - self.context.counterparty_max_accepted_htlcs = msg.max_accepted_htlcs; - - if peer_limits.trust_own_funding_0conf { - self.context.minimum_depth = Some(msg.minimum_depth); - } else { - self.context.minimum_depth = Some(cmp::max(1, msg.minimum_depth)); - } - - let counterparty_pubkeys = ChannelPublicKeys { - funding_pubkey: msg.funding_pubkey, - revocation_basepoint: RevocationBasepoint::from(msg.revocation_basepoint), - payment_point: msg.payment_point, - delayed_payment_basepoint: DelayedPaymentBasepoint::from(msg.delayed_payment_basepoint), - htlc_basepoint: HtlcBasepoint::from(msg.htlc_basepoint) - }; - - self.context.channel_transaction_parameters.counterparty_parameters = Some(CounterpartyChannelTransactionParameters { - selected_contest_delay: msg.to_self_delay, - pubkeys: counterparty_pubkeys, - }); - - self.context.counterparty_cur_commitment_point = Some(msg.first_per_commitment_point); - self.context.counterparty_shutdown_scriptpubkey = counterparty_shutdown_scriptpubkey; - - self.context.channel_state = ChannelState::NegotiatingFunding( - NegotiatingFundingFlags::OUR_INIT_SENT | NegotiatingFundingFlags::THEIR_INIT_SENT - ); - self.context.inbound_handshake_limits_override = None; // We're done enforcing limits on our peer's handshake now. - - Ok(()) + self.context.do_accept_channel_checks( + default_limits, their_features, msg.dust_limit_satoshis, msg.channel_reserve_satoshis, + msg.to_self_delay, msg.max_accepted_htlcs, msg.htlc_minimum_msat, + msg.max_htlc_value_in_flight_msat, msg.minimum_depth, &msg.channel_type, + &msg.shutdown_scriptpubkey, msg.funding_pubkey, msg.revocation_basepoint, msg.payment_point, + msg.delayed_payment_basepoint, msg.htlc_basepoint, msg.first_per_commitment_point) } /// Handles a funding_signed message from the remote end. @@ -7750,6 +7852,47 @@ impl OutboundV2Channel where SP::Target: SignerProvider { self.context.begin_interactive_funding_tx_construction(&self.dual_funding_context, signer_provider, entropy_source, true /* is_initiator */, funding_inputs) } + + pub fn funding_tx_constructed( + mut self, transaction: Transaction, est_block: BestBlock, signer_provider: &SP, logger: &L + ) -> Result<(Channel, msgs::CommitmentSigned), (Self, ChannelError)> + where + L::Target: Logger + { + let res = get_initial_commitment_signed(&mut self.context, transaction, est_block, + signer_provider, logger); + let commitment_signed = match res { + Ok(commitment_signed) => commitment_signed, + Err(err) => return Err((self, err)), + }; + + let channel = Channel { + context: self.context, + dual_funding_channel_context: Some(self.dual_funding_context), + }; + + Ok((channel, commitment_signed)) + } + + pub fn accept_channel_v2(&mut self, msg: &msgs::AcceptChannelV2, default_limits: &ChannelHandshakeLimits, + their_features: &InitFeatures) -> Result<(), ChannelError> { + // According to the spec we MUST fail the negotiation if `require_confirmed_inputs` is set in + // `accept_channel2` but we cannot provide confirmed inputs. We're not going to check if the user + // upheld this requirement, so we just defer the failure to the counterparty's checks during + // interactive transaction construction and remain blissfully unaware here. + + // Now we can generate the `channel_id` since we have our counterparty's `revocation_basepoint`. + self.context.channel_id = ChannelId::v2_from_revocation_basepoints( + &self.context.get_holder_pubkeys().revocation_basepoint, &RevocationBasepoint::from(msg.revocation_basepoint)); + self.dual_funding_context.their_funding_satoshis = msg.funding_satoshis; + self.context.do_accept_channel_checks( + default_limits, their_features, msg.dust_limit_satoshis, get_v2_channel_reserve_satoshis( + msg.funding_satoshis + self.dual_funding_context.our_funding_satoshis, msg.dust_limit_satoshis), + msg.to_self_delay, msg.max_accepted_htlcs, msg.htlc_minimum_msat, + msg.max_htlc_value_in_flight_msat, msg.minimum_depth, &msg.channel_type, + &msg.shutdown_scriptpubkey, msg.funding_pubkey, msg.revocation_basepoint, msg.payment_basepoint, + msg.delayed_payment_basepoint, msg.htlc_basepoint, msg.first_per_commitment_point) + } } // A not-yet-funded inbound (from counterparty) channel using V2 channel establishment. @@ -7912,6 +8055,27 @@ impl InboundV2Channel where SP::Target: SignerProvider { self.context.begin_interactive_funding_tx_construction(&self.dual_funding_context, signer_provider, entropy_source, false /* is_initiator */, funding_inputs) } + + pub fn funding_tx_constructed( + mut self, transaction: Transaction, est_block: BestBlock, signer_provider: &SP, logger: &L + ) -> Result<(Channel, msgs::CommitmentSigned), (Self, ChannelError)> + where + L::Target: Logger + { + let res = get_initial_commitment_signed(&mut self.context, transaction, est_block, + signer_provider, logger); + let commitment_signed = match res { + Ok(commitment_signed) => commitment_signed, + Err(err) => return Err((self, err)), + }; + + let channel = Channel { + context: self.context, + dual_funding_channel_context: Some(self.dual_funding_context), + }; + + Ok((channel, commitment_signed)) + } } // Unfunded channel utilities @@ -7939,6 +8103,126 @@ fn get_initial_channel_type(config: &UserConfig, their_features: &InitFeatures) ret } +/// If an Err is returned, it is a ChannelError::Close +fn get_initial_remote_commitment_tx_signature(context: &mut ChannelContext, logger: &L) +-> Result<(CommitmentTransaction, Signature), ChannelError> +where + SP::Target: SignerProvider, + L::Target: Logger +{ + let counterparty_keys = context.build_remote_transaction_keys(); + let counterparty_initial_commitment_tx = context.build_commitment_transaction( + context.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, false, logger).tx; + + let holder_keys = context.build_holder_transaction_keys(context.cur_holder_commitment_transaction_number); + let initial_commitment_tx = context.build_commitment_transaction( + context.cur_holder_commitment_transaction_number, &holder_keys, true, true, logger).tx; + + match &context.holder_signer { + ChannelSignerType::Ecdsa(ecdsa) => { + let signature = ecdsa.sign_counterparty_commitment(&counterparty_initial_commitment_tx, Vec::new(), Vec::new(), &context.secp_ctx) + .map_err(|_| ChannelError::Close("Failed to get signatures for new commitment_signed".to_owned()))?.0; + Ok((counterparty_initial_commitment_tx, signature)) + } + } +} + +fn get_initial_counterparty_commitment_signature( + context: &mut ChannelContext, logger: &L +) -> Result +where + SP::Target: SignerProvider, + L::Target: Logger +{ + let counterparty_keys = context.build_remote_transaction_keys(); + let counterparty_initial_commitment_tx = context.build_commitment_transaction( + context.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, false, logger).tx; + match context.holder_signer { + // TODO (taproot|arik): move match into calling method for Taproot + ChannelSignerType::Ecdsa(ref ecdsa) => { + Ok(ecdsa.sign_counterparty_commitment(&counterparty_initial_commitment_tx, Vec::new(), Vec::new(), &context.secp_ctx) + .map_err(|_| ChannelError::Close("Failed to get signatures for new commitment_signed".to_owned()))?.0) + } + } +} + +fn get_initial_commitment_signed( + context: &mut ChannelContext, transaction: Transaction, est_block: BestBlock, signer_provider: &SP, logger: &L +) -> Result +where + SP::Target: SignerProvider, + L::Target: Logger +{ + if !matches!( + context.channel_state, ChannelState::NegotiatingFunding(flags) + if flags == (NegotiatingFundingFlags::OUR_INIT_SENT | NegotiatingFundingFlags::THEIR_INIT_SENT)) { + panic!("Tried to get a funding_created messsage at a time other than immediately after initial handshake completion (or tried to get funding_created twice)"); + } + if context.commitment_secrets.get_min_seen_secret() != (1 << 48) || + context.cur_counterparty_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER || + context.cur_holder_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER { + panic!("Should not have advanced channel commitment tx numbers prior to initial commitment_signed"); + } + + let funding_redeemscript = context.get_funding_redeemscript().to_v0_p2wsh(); + let funding_outpoint_index = transaction.output.iter().enumerate().find_map( + |(idx, output)| { + if output.script_pubkey == funding_redeemscript { Some(idx as u16) } else { None } + }).expect("funding transaction contains funding output"); + let funding_txo = OutPoint { txid: transaction.txid(), index: funding_outpoint_index }; + context.channel_transaction_parameters.funding_outpoint = Some(funding_txo); + context.holder_signer.as_mut().provide_channel_parameters(&context.channel_transaction_parameters); + + let signature = match get_initial_counterparty_commitment_signature(context, logger) { + Ok(res) => res, + Err(e) => { + log_error!(logger, "Got bad signatures: {:?}!", e); + context.channel_transaction_parameters.funding_outpoint = None; + return Err(e); + } + }; + + // This is an externally observable change before we finish all our checks. In particular + // funding_created_signature may fail. + context.holder_signer.as_mut().provide_channel_parameters(&context.channel_transaction_parameters); + + let (counterparty_initial_commitment_tx, signature) = + match get_initial_remote_commitment_tx_signature(context, logger) { + Ok(res) => res, + Err(ChannelError::Close(e)) => { + context.channel_transaction_parameters.funding_outpoint = None; + return Err(ChannelError::Close(e)); + }, + Err(e) => { + // The only error we know how to handle is ChannelError::Close, so we fall over here + // to make sure we don't continue with an inconsistent state. + panic!("unexpected error type from funding_created_signature {:?}", e); + } + }; + + // Now that we're past error-generating stuff, update our local state: + + let funding_txo_script = context.get_funding_redeemscript().to_v0_p2wsh(); + let obscure_factor = get_commitment_transaction_number_obscure_factor(&context.get_holder_pubkeys().payment_point, &context.get_counterparty_pubkeys().payment_point, context.is_outbound()); + let shutdown_script = context.shutdown_scriptpubkey.clone().map(|script| script.into_inner()); + let mut monitor_signer = signer_provider.derive_channel_signer(context.channel_value_satoshis, context.channel_keys_id); + monitor_signer.provide_channel_parameters(&context.channel_transaction_parameters); + + context.channel_state = ChannelState::FundingNegotiated; + context.cur_counterparty_commitment_transaction_number -= 1; + context.cur_holder_commitment_transaction_number -= 1; + + log_info!(logger, "Generated commitment_signed for peer for channel {}", &context.channel_id()); + + Ok(msgs::CommitmentSigned { + channel_id: context.channel_id.clone(), + htlc_signatures: vec![], + signature, + #[cfg(taproot)] + partial_signature_with_nonce: None, + }) +} + const SERIALIZATION_VERSION: u8 = 3; const MIN_SERIALIZATION_VERSION: u8 = 3; diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 1e3f93b5ba5..7ee90a18cea 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -45,7 +45,7 @@ use crate::events::{Event, EventHandler, EventsProvider, MessageSendEvent, Messa use crate::ln::{inbound_payment, ChannelId, PaymentHash, PaymentPreimage, PaymentSecret}; use crate::ln::channel::{Channel, ChannelPhase, ChannelContext, ChannelError, ChannelUpdateStatus, ShutdownResult, UnfundedChannelContext, UpdateFulfillCommitFetch, OutboundV1Channel, InboundV1Channel, WithChannelContext}; #[cfg(dual_funding)] -use crate::ln::channel::InboundV2Channel; +use crate::ln::channel::{InboundV2Channel, OutboundV2Channel}; use crate::ln::features::{Bolt12InvoiceFeatures, ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures}; #[cfg(any(feature = "_test_utils", test))] use crate::ln::features::Bolt11InvoiceFeatures; @@ -59,6 +59,8 @@ use crate::ln::msgs; use crate::ln::onion_utils; use crate::ln::onion_utils::{HTLCFailReason, INVALID_ONION_BLINDING}; use crate::ln::msgs::{ChannelMessageHandler, DecodeError, LightningError}; +#[cfg(dual_funding)] +use crate::ln::msgs::CommitmentUpdate; #[cfg(test)] use crate::ln::outbound_payment; use crate::ln::outbound_payment::{Bolt12PaymentError, OutboundPayments, PaymentAttempts, PendingOutboundPayment, SendAlongPathArgs, StaleExpiration}; @@ -2526,8 +2528,70 @@ where /// [`Event::FundingGenerationReady::temporary_channel_id`]: events::Event::FundingGenerationReady::temporary_channel_id /// [`Event::ChannelClosed::channel_id`]: events::Event::ChannelClosed::channel_id pub fn create_channel(&self, their_network_key: PublicKey, channel_value_satoshis: u64, push_msat: u64, user_channel_id: u128, temporary_channel_id: Option, override_config: Option) -> Result { - if channel_value_satoshis < 1000 { - return Err(APIError::APIMisuseError { err: format!("Channel value must be at least 1000 satoshis. It was {}", channel_value_satoshis) }); + self.create_channel_internal(false, their_network_key, channel_value_satoshis, None, push_msat, + user_channel_id, temporary_channel_id, override_config) + } + + /// Creates a new outbound dual-funded channel to the given remote node and with the given value + /// contributed by us. + /// + /// `user_channel_id` will be provided back as in + /// [`Event::FundingGenerationReady::user_channel_id`] to allow tracking of which events + /// correspond with which `create_channel` call. Note that the `user_channel_id` defaults to a + /// randomized value for inbound channels. `user_channel_id` has no meaning inside of LDK, it + /// is simply copied to events and otherwise ignored. + /// + /// `funnding_satoshis` is the amount we are contributing to the channel. + /// Raises [`APIError::APIMisuseError`] when `funding_satoshis` > 2**24. + /// + /// The `funding_inputs` parameter accepts ([`TxIn`], [`Transaction`]) pairs which will + /// be used to contribute `funding_satoshis` towards the channel (minus any mining fees due). + /// Raises [`APIError::APIMisuseError`] if the total value of the provided `funding_inputs` is + /// less than `funding_satoshis`. + // TODO(dual_funding): Describe error relating to inputs not being able to cover fees payable by us. + /// + /// LDK will create a change output for any non-dust amounts that remain after subtracting contribution + /// to channel capacity and fees from the provided input amounts. + // TODO(dual_funding): We could allow a list of such outputs to be provided so that the user may + /// be able to do some more interesting things at the same time as funding a channel, like making + /// some low priority on-chain payment. + /// + /// The `funding_conf_target` parameter sets the priority of the funding transaction for appropriate + /// fee estimation. If `None`, then [`ConfirmationTarget::Normal`] is used. + /// + /// Raises [`APIError::ChannelUnavailable`] if the channel cannot be opened due to failing to + /// generate a shutdown scriptpubkey or destination script set by + /// [`SignerProvider::get_shutdown_scriptpubkey`] or [`SignerProvider::get_destination_script`]. + /// + /// Note that we do not check if you are currently connected to the given peer. If no + /// connection is available, the outbound `open_channel` message may fail to send, resulting in + /// the channel eventually being silently forgotten (dropped on reload). + /// + /// Returns the new Channel's temporary `channel_id`. This ID will appear as + /// [`Event::FundingGenerationReady::temporary_channel_id`] and in + /// [`ChannelDetails::channel_id`] until after + /// [`ChannelManager::funding_transaction_generated`] is called, swapping the Channel's ID for + /// one derived from the funding transaction's TXID. If the counterparty rejects the channel + /// immediately, this temporary ID will appear in [`Event::ChannelClosed::channel_id`]. + /// + /// [`Event::FundingGenerationReady::user_channel_id`]: events::Event::FundingGenerationReady::user_channel_id + /// [`Event::FundingGenerationReady::temporary_channel_id`]: events::Event::FundingGenerationReady::temporary_channel_id + /// [`Event::ChannelClosed::channel_id`]: events::Event::ChannelClosed::channel_id + /// [`ConfirmationTarget::Normal`]: chain::chaininterface::ConfirmationTarget + pub fn create_dual_funded_channel(&self, their_network_key: PublicKey, funding_satoshis: u64, + funding_conf_target: Option, user_channel_id: u128, + override_config: Option) -> Result + { + self.create_channel_internal(true, their_network_key, funding_satoshis, funding_conf_target, 0, + user_channel_id, None, override_config) + } + + fn create_channel_internal(&self, is_v2: bool, their_network_key: PublicKey, funding_satoshis: u64, + funding_conf_target: Option, push_msat: u64, user_channel_id: u128, + temporary_channel_id: Option, override_config: Option, + ) -> Result { + if funding_satoshis < 1000 { + return Err(APIError::APIMisuseError { err: format!("Channel value must be at least 1000 satoshis. It was {}", funding_satoshis) }); } let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); @@ -2547,24 +2611,75 @@ where } } - let channel = { - let outbound_scid_alias = self.create_and_insert_outbound_scid_alias(); - let their_features = &peer_state.latest_features; - let config = if override_config.is_some() { override_config.as_ref().unwrap() } else { &self.default_configuration }; - match OutboundV1Channel::new(&self.fee_estimator, &self.entropy_source, &self.signer_provider, their_network_key, - their_features, channel_value_satoshis, push_msat, user_channel_id, config, - self.best_block.read().unwrap().height(), outbound_scid_alias, temporary_channel_id) - { - Ok(res) => res, - Err(e) => { - self.outbound_scid_aliases.lock().unwrap().remove(&outbound_scid_alias); - return Err(e); - }, - } + let outbound_scid_alias = self.create_and_insert_outbound_scid_alias(); + let their_features = &peer_state.latest_features; + let config = if override_config.is_some() { override_config.as_ref().unwrap() } else { &self.default_configuration }; + + // TODO(dual_funding): Merge this with below when cfg is removed. + let (channel_phase, msg_send_event) = { + let channel = { + match OutboundV1Channel::new(&self.fee_estimator, &self.entropy_source, &self.signer_provider, their_network_key, + their_features, funding_satoshis, push_msat, user_channel_id, config, + self.best_block.read().unwrap().height(), outbound_scid_alias, temporary_channel_id) + { + Ok(res) => res, + Err(e) => { + self.outbound_scid_aliases.lock().unwrap().remove(&outbound_scid_alias); + return Err(e); + }, + } + }; + let res = channel.get_open_channel(self.chain_hash); + let event = events::MessageSendEvent::SendOpenChannel { + node_id: their_network_key, + msg: res, + }; + (ChannelPhase::UnfundedOutboundV1(channel), event) + }; + + #[cfg(dual_funding)] + let (channel_phase, msg_send_event) = if is_v2 { + let channel = { + match OutboundV2Channel::new(&self.fee_estimator, &self.entropy_source, &self.signer_provider, their_network_key, + their_features, funding_satoshis, user_channel_id, config, + self.best_block.read().unwrap().height(), outbound_scid_alias, + funding_conf_target.unwrap_or(ConfirmationTarget::NonAnchorChannelFee)) + { + Ok(res) => res, + Err(e) => { + self.outbound_scid_aliases.lock().unwrap().remove(&outbound_scid_alias); + return Err(e); + }, + } + }; + let res = channel.get_open_channel_v2(self.chain_hash); + let event = events::MessageSendEvent::SendOpenChannelV2 { + node_id: their_network_key, + msg: res, + }; + (ChannelPhase::UnfundedOutboundV2(channel), event) + } else { + let channel = { + match OutboundV1Channel::new(&self.fee_estimator, &self.entropy_source, &self.signer_provider, their_network_key, + their_features, funding_satoshis, push_msat, user_channel_id, config, + self.best_block.read().unwrap().height(), outbound_scid_alias, temporary_channel_id) + { + Ok(res) => res, + Err(e) => { + self.outbound_scid_aliases.lock().unwrap().remove(&outbound_scid_alias); + return Err(e); + }, + } + }; + let res = channel.get_open_channel(self.chain_hash); + let event = events::MessageSendEvent::SendOpenChannel { + node_id: their_network_key, + msg: res, + }; + (ChannelPhase::UnfundedOutboundV1(channel), event) }; - let res = channel.get_open_channel(self.chain_hash); - let temporary_channel_id = channel.context.channel_id(); + let temporary_channel_id = channel_phase.context().channel_id(); match peer_state.channel_by_id.entry(temporary_channel_id) { hash_map::Entry::Occupied(_) => { if cfg!(fuzzing) { @@ -2573,13 +2688,10 @@ where panic!("RNG is bad???"); } }, - hash_map::Entry::Vacant(entry) => { entry.insert(ChannelPhase::UnfundedOutboundV1(channel)); } + hash_map::Entry::Vacant(entry) => { entry.insert(channel_phase); } } - peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannel { - node_id: their_network_key, - msg: res, - }); + peer_state.pending_msg_events.push(msg_send_event); Ok(temporary_channel_id) } @@ -6472,6 +6584,55 @@ where Ok(()) } + #[cfg(dual_funding)] + fn internal_accept_channel_v2(&self, counterparty_node_id: &PublicKey, msg: &msgs::AcceptChannelV2) -> Result<(), MsgHandleErrInternal> { + // Note that the ChannelManager is NOT re-persisted on disk after this, so any changes are + // likely to be lost on restart! + let per_peer_state = self.per_peer_state.read().unwrap(); + let peer_state_mutex = per_peer_state.get(counterparty_node_id) + .ok_or_else(|| { + debug_assert!(false); + MsgHandleErrInternal::send_err_msg_no_close(format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), msg.temporary_channel_id) + })?; + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + + let (chan, channel_id, holder_funding_satoshis, counterparty_funding_satoshis, user_channel_id) = { + match peer_state.channel_by_id.remove(&msg.temporary_channel_id) { + Some(phase) => { + match phase { + ChannelPhase::UnfundedOutboundV2(mut chan) => { + if let Err(err) = chan.accept_channel_v2(&msg, &self.default_configuration.channel_handshake_limits, &peer_state.latest_features) { + let (_, res) = convert_chan_phase_err!(self, err, chan, &msg.temporary_channel_id, UNFUNDED_CHANNEL); + let _: Result<(), _> = handle_error!(self, Err(res), *counterparty_node_id); + } + let channel_id = chan.context.channel_id(); + let holder_funding_satoshis = chan.dual_funding_context.our_funding_satoshis; + let user_channel_id = chan.context.get_user_id(); + (chan, channel_id, holder_funding_satoshis, msg.funding_satoshis, user_channel_id) + }, + _ => { + peer_state.channel_by_id.insert(msg.temporary_channel_id, phase); + return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got an unexpected accept_channel2 message from peer with counterparty_node_id {}", counterparty_node_id), msg.temporary_channel_id)); + } + } + }, + None => 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.temporary_channel_id)) + } + }; + + peer_state.channel_by_id.insert(chan.context.channel_id(), ChannelPhase::UnfundedOutboundV2(chan)); + let mut pending_events = self.pending_events.lock().unwrap(); + pending_events.push_back((events::Event::FundingInputsContributionReady { + channel_id, + counterparty_node_id: *counterparty_node_id, + holder_funding_satoshis, + counterparty_funding_satoshis, + user_channel_id, + } , None)); + Ok(()) + } + fn internal_funding_created(&self, counterparty_node_id: &PublicKey, msg: &msgs::FundingCreated) -> Result<(), MsgHandleErrInternal> { let best_block = *self.best_block.read().unwrap(); @@ -6789,6 +6950,7 @@ where #[cfg(dual_funding)] fn internal_tx_complete(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxComplete) -> Result<(), MsgHandleErrInternal> { + let best_block = *self.best_block.read().unwrap(); let per_peer_state = self.per_peer_state.read().unwrap(); let peer_state_mutex = per_peer_state.get(counterparty_node_id) .ok_or_else(|| { @@ -6818,8 +6980,47 @@ where }; peer_state.pending_msg_events.push(msg_send_event); } - if let Some(_tx) = tx_opt { - // TODO(dual_funding): Handle this unsigned transaction. + if let Some(tx) = tx_opt { + let (channel_id, channel_phase) = chan_phase_entry.remove_entry(); + let res = match channel_phase { + ChannelPhase::UnfundedOutboundV2(chan) => { + chan.funding_tx_constructed(tx, best_block, &self.signer_provider, &self.logger).map_err( + |(chan, err)| { + (ChannelPhase::UnfundedOutboundV2(chan), err) + } + ) + }, + ChannelPhase::UnfundedInboundV2(chan) => { + chan.funding_tx_constructed(tx, best_block, &self.signer_provider, &self.logger).map_err( + |(chan, err)| { + (ChannelPhase::UnfundedInboundV2(chan), err) + } + ) + }, + _ => { + todo!(); + } + }; + match res { + Ok((channel, commitment_signed)) => { + peer_state.pending_msg_events.push(events::MessageSendEvent::UpdateHTLCs { + node_id: counterparty_node_id.clone(), + updates: CommitmentUpdate { + commitment_signed, + update_add_htlcs: vec![], + update_fulfill_htlcs: vec![], + update_fail_htlcs: vec![], + update_fail_malformed_htlcs: vec![], + update_fee: None, + }, + }); + peer_state.channel_by_id.insert(channel_id.clone(), ChannelPhase::Funded(channel)); + }, + Err((channel_phase, _channel_error)) => { + peer_state.channel_by_id.insert(channel_id, channel_phase); + // TODO(dual_funding): Handle the channel error. + }, + } } }, Err(tx_abort_msg) => peer_state.pending_msg_events.push( @@ -7223,6 +7424,7 @@ where } fn internal_commitment_signed(&self, counterparty_node_id: &PublicKey, msg: &msgs::CommitmentSigned) -> Result<(), MsgHandleErrInternal> { + let best_block = *self.best_block.read().unwrap(); let per_peer_state = self.per_peer_state.read().unwrap(); let peer_state_mutex = per_peer_state.get(counterparty_node_id) .ok_or_else(|| { @@ -7233,18 +7435,39 @@ where let peer_state = &mut *peer_state_lock; match peer_state.channel_by_id.entry(msg.channel_id) { hash_map::Entry::Occupied(mut chan_phase_entry) => { - if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() { - let logger = WithChannelContext::from(&self.logger, &chan.context); - let funding_txo = chan.context.get_funding_txo(); - let monitor_update_opt = try_chan_phase_entry!(self, chan.commitment_signed(&msg, &&logger), chan_phase_entry); - if let Some(monitor_update) = monitor_update_opt { - handle_new_monitor_update!(self, funding_txo.unwrap(), monitor_update, peer_state_lock, - peer_state, per_peer_state, chan); - } - Ok(()) - } else { - return try_chan_phase_entry!(self, Err(ChannelError::Close( - "Got a commitment_signed message for an unfunded channel!".into())), chan_phase_entry); + match chan_phase_entry.get_mut() { + ChannelPhase::Funded(chan) => { + let logger = WithChannelContext::from(&self.logger, &chan.context); + let funding_txo = chan.context.get_funding_txo(); + + let mut has_dual_funding_channel_context = false; + #[cfg(dual_funding)] + { + has_dual_funding_channel_context = chan.dual_funding_channel_context.is_some(); + } + + if has_dual_funding_channel_context { + #[cfg(dual_funding)] + { + let monitor = try_chan_phase_entry!(self, + chan.commitment_signed_initial_v2(&msg, best_block, &self.signer_provider, &&logger), chan_phase_entry); + if let Ok(persist_status) = self.chain_monitor.watch_channel(chan.context.get_funding_txo().unwrap(), monitor) { + handle_new_monitor_update!(self, persist_status, peer_state_lock, peer_state, per_peer_state, chan, INITIAL_MONITOR); + } else { + try_chan_phase_entry!(self, Err(ChannelError::Close("Channel funding outpoint was a duplicate".to_owned())), chan_phase_entry) + } + } + } else { + let monitor_update_opt = try_chan_phase_entry!(self, chan.commitment_signed(&msg, &self.logger), chan_phase_entry); + if let Some(monitor_update) = monitor_update_opt { + handle_new_monitor_update!(self, funding_txo.unwrap(), monitor_update, peer_state_lock, + peer_state, per_peer_state, chan); + } + } + Ok(()) + }, + _ => return try_chan_phase_entry!(self, Err(ChannelError::Close( + "Got a commitment_signed message for an unfunded channel!".into())), chan_phase_entry), } }, hash_map::Entry::Vacant(_) => 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)) @@ -9072,9 +9295,8 @@ where #[cfg(dual_funding)] fn handle_accept_channel_v2(&self, counterparty_node_id: &PublicKey, msg: &msgs::AcceptChannelV2) { - let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( - "Dual-funded channels not supported".to_owned(), - msg.temporary_channel_id.clone())), *counterparty_node_id); + let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); + let _ = handle_error!(self, self.internal_accept_channel_v2(counterparty_node_id, msg), *counterparty_node_id); } fn handle_funding_created(&self, counterparty_node_id: &PublicKey, msg: &msgs::FundingCreated) { @@ -12796,6 +13018,9 @@ mod tests { check_spends!(txn[0], funding_tx); } } + + // Dual-funding: V2 Channel Establishment Tests + // TODO(dual_funding): Complete these. } #[cfg(ldk_bench)] diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 465a52c9904..d66b9b67907 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -1457,6 +1457,7 @@ pub trait ChannelMessageHandler : MessageSendEventsProvider { fn handle_open_channel_v2(&self, their_node_id: &PublicKey, msg: &OpenChannelV2); /// Handle an incoming `accept_channel` message from the given peer. fn handle_accept_channel(&self, their_node_id: &PublicKey, msg: &AcceptChannel); + #[cfg(dual_funding)] /// Handle an incoming `accept_channel2` message from the given peer. #[cfg(dual_funding)] fn handle_accept_channel_v2(&self, their_node_id: &PublicKey, msg: &AcceptChannelV2); From d7b69d943a8fcba4de85a7b29912626404400d9d Mon Sep 17 00:00:00 2001 From: Duncan Dean Date: Thu, 19 Oct 2023 14:08:14 +0200 Subject: [PATCH 021/101] Handle interactive signing sessions --- lightning/src/events/mod.rs | 57 +++++ lightning/src/ln/channel.rs | 290 +++++++++++++-------- lightning/src/ln/channelmanager.rs | 295 ++++++++++++++++++++-- lightning/src/ln/functional_test_utils.rs | 27 +- lightning/src/ln/interactivetxs.rs | 217 +++++++++++++--- 5 files changed, 712 insertions(+), 174 deletions(-) diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index 560e040eda6..998ec961807 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -1073,6 +1073,55 @@ pub enum Event { /// [`UserConfig::manually_accept_inbound_channels`]: crate::util::config::UserConfig::manually_accept_inbound_channels user_channel_id: u128, }, + #[cfg(dual_funding)] + /// Indicates that a transaction constructed via interactive transaction construction for a + /// dual-funded (V2) channel is ready to be signed by the client. This event will only be triggered + /// if at least one input was contributed by the holder. + /// + /// The transaction contains all inputs provided by both parties when the channel was + /// created/accepted along with the channel's funding output and a change output if applicable. + /// + /// No part of the transaction should be changed before signing as the content of the transaction + /// has already been negotiated with the counterparty. + /// + /// Each signature MUST use the SIGHASH_ALL flag to avoid invalidation of initial commitment and + /// hence possible loss of funds. + /// + /// After signing, call [`ChannelManager::funding_transaction_signed`] with the (partially) signed + /// funding transaction. + /// + /// Generated in [`ChannelManager`] message handling. + /// + /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager + /// [`ChannelManager::funding_transaction_signed`]: crate::ln::channelmanager::ChannelManager::funding_transaction_signed + FundingTransactionReadyForSigning { + /// The channel_id of the V2 channel which you'll need to pass back into + /// [`ChannelManager::funding_transaction_signed`]. + /// + /// [`ChannelManager::funding_transaction_signed`]: crate::ln::channelmanager::ChannelManager::funding_transaction_signed + channel_id: ChannelId, + /// The counterparty's node_id, which you'll need to pass back into + /// [`ChannelManager::funding_transaction_signed`]. + /// + /// [`ChannelManager::funding_transaction_signed`]: crate::ln::channelmanager::ChannelManager::funding_transaction_signed + counterparty_node_id: PublicKey, + /// The `user_channel_id` value passed in to [`ChannelManager::create_dual_funded_channel`] for outbound + /// channels, or to [`ChannelManager::accept_inbound_channel`] or [`ChannelManager::accept_inbound_channel_with_contribution`] + /// for inbound channels if [`UserConfig::manually_accept_inbound_channels`] config flag is set to true. + /// Otherwise `user_channel_id` will be randomized for an inbound channel. + /// This may be zero for objects serialized with LDK versions prior to 0.0.113. + /// + /// [`ChannelManager::create_dual_funded_channel`]: crate::ln::channelmanager::ChannelManager::create_dual_funded_channel + /// [`ChannelManager::accept_inbound_channel`]: crate::ln::channelmanager::ChannelManager::accept_inbound_channel + /// [`ChannelManager::accept_inbound_channel_with_contribution`]: crate::ln::channelmanager::ChannelManager::accept_inbound_channel_with_contribution + /// [`UserConfig::manually_accept_inbound_channels`]: crate::util::config::UserConfig::manually_accept_inbound_channels + user_channel_id: u128, + /// The unsigned transaction to be signed and passed back to + /// [`ChannelManager::funding_transaction_signed`]. + /// + /// [`ChannelManager::funding_transaction_signed`]: crate::ln::channelmanager::ChannelManager::funding_transaction_signed + unsigned_transaction: Transaction, + }, } impl Writeable for Event { @@ -1322,6 +1371,14 @@ impl Writeable for Event { // drop any channels which have not yet completed any interactive funding transaction // construction. }, + #[cfg(dual_funding)] + &Event::FundingTransactionReadyForSigning { .. } => { + 39u8.write(writer)?; + // We never write out FundingTransactionReadyForSigning events as, upon disconnection, peers + // drop any V2-established channels which have not yet exchanged the initial `commitment_signed`. + // We only exhange the initial `commitment_signed` after the client calls + // `ChannelManager::funding_transaction_signed` and ALWAYS before we send a `tx_signatures` + }, // Note that, going forward, all new events must only write data inside of // `write_tlv_fields`. Versions 0.0.101+ will ignore odd-numbered events that write // data via `write_tlv_fields`. diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 04bb37abbb0..816df133e5b 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -11,7 +11,7 @@ use bitcoin::blockdata::constants::{ChainHash, WITNESS_SCALE_FACTOR}; use bitcoin::blockdata::script::{Script, ScriptBuf, Builder}; use bitcoin::blockdata::transaction::Transaction; use bitcoin::sighash::{self, EcdsaSighashType}; -use bitcoin::consensus::encode; +use bitcoin::consensus::{encode, Encodable}; use bitcoin::hashes::Hash; use bitcoin::hashes::sha256::Hash as Sha256; @@ -21,13 +21,16 @@ use bitcoin::hash_types::{Txid, BlockHash}; use bitcoin::secp256k1::constants::PUBLIC_KEY_SIZE; use bitcoin::secp256k1::{PublicKey,SecretKey}; use bitcoin::secp256k1::{Secp256k1,ecdsa::Signature}; -use bitcoin::{secp256k1, TxIn, TxOut, VarInt}; +use bitcoin::{secp256k1, TxIn, TxOut}; +#[cfg(dual_funding)] +use bitcoin::Witness; #[cfg(dual_funding)] use bitcoin::locktime::absolute::LockTime; +use crate::events::bump_transaction::{BASE_INPUT_WEIGHT, EMPTY_SCRIPT_SIG_WEIGHT}; use crate::ln::{ChannelId, PaymentPreimage, PaymentHash}; use crate::ln::features::{ChannelTypeFeatures, InitFeatures}; -use crate::ln::interactivetxs::{AbortReason, InteractiveTxConstructor, InteractiveTxMessageSend}; +use crate::ln::interactivetxs::{AbortReason, InteractiveTxConstructor, InteractiveTxMessageSend, InteractiveTxSigningSession}; use crate::ln::msgs; use crate::ln::msgs::DecodeError; use crate::ln::script::{self, ShutdownScript}; @@ -42,6 +45,8 @@ use crate::chain::transaction::{OutPoint, TransactionData}; use crate::sign::ecdsa::{EcdsaChannelSigner, WriteableEcdsaChannelSigner}; use crate::sign::{EntropySource, ChannelSigner, SignerProvider, NodeSigner, Recipient}; use crate::events::ClosureReason; +#[cfg(dual_funding)] +use crate::events::Event; use crate::routing::gossip::NodeId; use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer}; use crate::util::logger::{Logger, Record, WithContext}; @@ -50,6 +55,7 @@ use crate::util::config::{UserConfig, ChannelConfig, LegacyChannelConfig, Channe use crate::util::scid_utils::scid_from_parts; use crate::io; +use crate::io_extras::sink; use crate::prelude::*; use core::{cmp,mem,fmt}; use core::convert::TryInto; @@ -1243,7 +1249,7 @@ pub(super) struct ChannelContext where SP::Target: SignerProvider { /// store it here and only release it to the `ChannelManager` once it asks for it. blocked_monitor_updates: Vec, - /// The current interactive transaction construction session under negotiation. + /// The current interactive transaction construction session. interactive_tx_constructor: Option, } @@ -3197,7 +3203,8 @@ impl ChannelContext where SP::Target: SignerProvider { #[cfg(dual_funding)] pub fn begin_interactive_funding_tx_construction( &mut self, dual_funding_context: &DualFundingChannelContext, signer_provider: &SP, - entropy_source: &ES, is_initiator: bool, funding_inputs: Vec<(TxIn, Transaction)>, + entropy_source: &ES, holder_node_id: PublicKey, is_initiator: bool, + funding_inputs: Vec<(TxIn, Transaction)>, ) -> Result, APIError> where ES::Target: EntropySource { @@ -3231,8 +3238,8 @@ impl ChannelContext where SP::Target: SignerProvider { let (tx_constructor, msg) = InteractiveTxConstructor::new( entropy_source, self.channel_id(), dual_funding_context.funding_feerate_sat_per_1000_weight, - is_initiator, dual_funding_context.funding_tx_locktime, funding_inputs, - funding_outputs, + holder_node_id, self.counterparty_node_id, is_initiator, dual_funding_context.funding_tx_locktime, + funding_inputs, funding_outputs, ); self.interactive_tx_constructor = Some(tx_constructor); @@ -3309,7 +3316,7 @@ impl ChannelContext where SP::Target: SignerProvider { } pub fn tx_complete(&mut self, msg: &msgs::TxComplete) - -> Result<(Option, Option), msgs::TxAbort> { + -> Result<(Option, Option), msgs::TxAbort> { match self.interactive_tx_constructor { Some(ref mut tx_constructor) => tx_constructor.handle_tx_complete(msg).map_err( |reason| self.get_tx_abort_msg_from_abort_reason(reason)), @@ -3320,18 +3327,6 @@ impl ChannelContext where SP::Target: SignerProvider { } } - pub fn tx_signatures(&self, msg: &msgs::TxSignatures)-> Result { - todo!(); - } - - pub fn tx_init_rbf(&self, msg: &msgs::TxInitRbf)-> Result { - todo!(); - } - - pub fn tx_ack_rbf(&self, msg: &msgs::TxAckRbf)-> Result { - todo!(); - } - pub fn tx_abort(&self, msg: &msgs::TxAbort)-> Result { todo!(); } @@ -3416,35 +3411,29 @@ pub(super) fn maybe_add_funding_change_output(signer_provider: &SP, i SP::Target: SignerProvider, { let our_funding_inputs_weight = funding_inputs.iter().fold(0u64, |weight, (txin, _) | { - // TODO(dual_funding): Use TxIn::segwit_weight when we upgrade rust-bitcoin to 0.30.x. - let script_sig_size = txin.script_sig.len(); - // previous_output (36) + script_sig varint len + script_sig push + sequence - let legacy_weight = (36 + VarInt(script_sig_size as u64).len() + script_sig_size + 4) * WITNESS_SCALE_FACTOR; - let input_weight = legacy_weight.saturating_add(txin.witness.serialized_len()); - weight.saturating_add(input_weight as u64) + weight + BASE_INPUT_WEIGHT + EMPTY_SCRIPT_SIG_WEIGHT + txin.witness.serialized_len() as u64 }); - // TODO(dual_funding): Just use TxOut::weight when we upgrade rust-bitcoin to 0.30.x. let calculate_output_weight = |txout: &TxOut| { - let script_len = txout.script_pubkey.len(); - // value (8) + script varint len + script push - (8 + VarInt(script_len as u64).len() + script_len) * WITNESS_SCALE_FACTOR + (8 /* value */ + txout.script_pubkey.consensus_encode(&mut sink()).unwrap() as u64) * + WITNESS_SCALE_FACTOR as u64 }; let our_funding_outputs_weight = funding_outputs.iter().fold(0u64, |weight, txout | { weight.saturating_add(calculate_output_weight(&txout) as u64) }); - let our_contributed_weight = our_funding_outputs_weight.saturating_add(our_funding_outputs_weight); + let our_contributed_weight = our_funding_outputs_weight.saturating_add(our_funding_inputs_weight); let mut fees_sats = fee_for_weight(funding_feerate_sat_per_1000_weight, our_contributed_weight); // If we are the initiator, we must pay for weight of all common fields in the funding transaction. if is_initiator { - let common_weight = - 4 /* version */ + - 2 /* segwit marker and flag */ + - 1 /* input count (limited to 252 inputs by interactive tx construction spec)*/ + - 1 /* output count (limited to 252 outputs by interactive tx construction spec)*/ + - 4 /* locktime */; - let common_fees = fee_for_weight(funding_feerate_sat_per_1000_weight, common_weight); - fees_sats = fees_sats.saturating_add(common_fees); + let tx_common_fields_weight = + ( + 4 /* version */ + + 4 /* locktime */ + + 1 /* input count */ + + 1 /* output count */ + ) * WITNESS_SCALE_FACTOR as u64 + 2 /* segwit marker + flag */; + let tx_common_fields_fee = fee_for_weight(funding_feerate_sat_per_1000_weight, tx_common_fields_weight); + fees_sats = fees_sats.saturating_add(tx_common_fields_fee); } let remaining_value = total_input_satoshis @@ -3490,6 +3479,8 @@ pub(super) struct Channel where SP::Target: SignerProvider { pub context: ChannelContext, #[cfg(dual_funding)] pub dual_funding_channel_context: Option, + #[cfg(dual_funding)] + pub interactive_tx_signing_session: Option, } #[cfg(any(test, fuzzing))] @@ -4257,7 +4248,7 @@ impl Channel where let sighash = initial_commitment_bitcoin_tx.get_sighash_all(&funding_script, self.context.channel_value_satoshis); // They sign our commitment transaction, allowing us to broadcast the tx if we wish. if let Err(_) = self.context.secp_ctx.verify_ecdsa(&sighash, &msg.signature, &self.context.get_counterparty_pubkeys().funding_pubkey) { - return Err(ChannelError::Close("Invalid funding_signed signature from peer".to_owned())); + return Err(ChannelError::Close("Invalid commitment_signed signature from peer".to_owned())); } } @@ -4936,6 +4927,57 @@ impl Channel where } } + #[cfg(dual_funding)] + pub fn verify_interactive_tx_signatures(&mut self, witnesses: &Vec) { + if let Some(ref mut signing_session) = self.interactive_tx_signing_session { + // Check that sighash_all was used: + // TODO(dual_funding): Check sig for sighash + } + } + + #[cfg(dual_funding)] + pub fn tx_signatures(&mut self, msg: &msgs::TxSignatures) -> Result<(Option, Option), ChannelError> { + if let Some(ref mut signing_session) = self.interactive_tx_signing_session { + if msg.witnesses.len() != signing_session.counterparty_inputs_count() { + return Err(ChannelError::Close("Witness count did not match contributed input count".to_string())); + } + + for witness in &msg.witnesses { + if witness.is_empty() { + return Err(ChannelError::Close("Unexpected empty witness in tx_signatures received".to_string())); + } + + // TODO(dual_funding): Check all sigs are SIGHASH_ALL. + + // TODO(dual_funding): I don't see how we're going to be able to ensure witness-standardness + // for spending. Doesn't seem to be anything in rust-bitcoin. + } + + if msg.tx_hash != signing_session.constructed_transaction.txid() { + return Err(ChannelError::Close("The txid for the transaction does not match".to_string())); + } + + let (tx_signatures_opt, funding_tx_opt) = signing_session.received_tx_signatures(msg.clone()); + if funding_tx_opt.is_some() { + self.context.channel_state = ChannelState::FundingSent as u32; + } + self.context.funding_transaction = funding_tx_opt.clone(); + + Ok((tx_signatures_opt, funding_tx_opt)) + } else { + return Err(ChannelError::Close( + "Unexpected tx_signatures. No funding transaction awaiting signatures".to_string())); + } + } + + pub fn tx_init_rbf(&self, msg: &msgs::TxInitRbf)-> Result { + todo!(); + } + + pub fn tx_ack_rbf(&self, msg: &msgs::TxAckRbf)-> Result { + todo!(); + } + /// Queues up an outbound update fee by placing it in the holding cell. You should call /// [`Self::maybe_free_holding_cell_htlcs`] in order to actually generate and send the /// commitment update. @@ -7725,6 +7767,8 @@ impl InboundV1Channel where SP::Target: SignerProvider { context: self.context, #[cfg(dual_funding)] dual_funding_channel_context: None, + #[cfg(dual_funding)] + interactive_tx_signing_session: 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()); @@ -7845,33 +7889,64 @@ impl OutboundV2Channel where SP::Target: SignerProvider { } pub fn begin_interactive_funding_tx_construction(&mut self, signer_provider: &SP, - entropy_source: &ES, funding_inputs: Vec<(TxIn, Transaction)> + entropy_source: &ES, holder_node_id: PublicKey, funding_inputs: Vec<(TxIn, Transaction)> ) -> Result, APIError> where ES::Target: EntropySource { self.context.begin_interactive_funding_tx_construction(&self.dual_funding_context, - signer_provider, entropy_source, true /* is_initiator */, funding_inputs) + signer_provider, entropy_source, holder_node_id, true /* is_initiator */, funding_inputs) } pub fn funding_tx_constructed( - mut self, transaction: Transaction, est_block: BestBlock, signer_provider: &SP, logger: &L - ) -> Result<(Channel, msgs::CommitmentSigned), (Self, ChannelError)> + mut self, counterparty_node_id: &PublicKey, signing_session: InteractiveTxSigningSession, signer_provider: &SP, logger: &L + ) -> Result<(Channel, msgs::CommitmentSigned, Option), (Self, ChannelError)> where L::Target: Logger { - let res = get_initial_commitment_signed(&mut self.context, transaction, est_block, + let mut output_index = None; + let expected_spk = self.context.get_funding_redeemscript().to_v0_p2wsh(); + for (idx, outp) in signing_session.constructed_transaction.output.iter().enumerate() { + if outp.script_pubkey == expected_spk && outp.value == self.context.get_value_satoshis() { + if output_index.is_some() { + return Err((self, ChannelError::Close("Multiple outputs matched the expected script and value".to_owned()))); + } + output_index = Some(idx as u16); + } + } + if output_index.is_none() { + if output_index.is_some() { + return Err((self, ChannelError::Close("No output matched the script_pubkey and value in the FundingGenerationReady event".to_owned()))); + } + } + let outpoint = OutPoint { txid: signing_session.constructed_transaction.txid(), index: output_index.unwrap() }; + self.context.channel_transaction_parameters.funding_outpoint = Some(outpoint); + self.context.holder_signer.as_mut().provide_channel_parameters(&self.context.channel_transaction_parameters); + + let commitment_signed = get_initial_commitment_signed(&mut self.context, &signing_session.constructed_transaction, signer_provider, logger); - let commitment_signed = match res { - Ok(commitment_signed) => commitment_signed, - Err(err) => return Err((self, err)), + let commitment_signed = match commitment_signed { + Some(commitment_signed) => commitment_signed, + None => return Err((self, ChannelError::Close("Incorrect signature".to_string()))), + }; + + // Clear our the interactive tx constructor. + self.context.interactive_tx_constructor = None; + self.context.channel_state = ChannelState::FundingCreated as u32; + + let funding_ready_for_sig_event = Event::FundingTransactionReadyForSigning { + channel_id: self.context.channel_id, + counterparty_node_id: *counterparty_node_id, + user_channel_id: self.context.user_id, + unsigned_transaction: signing_session.constructed_transaction.clone(), }; let channel = Channel { context: self.context, dual_funding_channel_context: Some(self.dual_funding_context), + interactive_tx_signing_session: Some(signing_session), }; - Ok((channel, commitment_signed)) + Ok((channel, commitment_signed, Some(funding_ready_for_sig_event))) } pub fn accept_channel_v2(&mut self, msg: &msgs::AcceptChannelV2, default_limits: &ChannelHandshakeLimits, @@ -8048,33 +8123,69 @@ impl InboundV2Channel where SP::Target: SignerProvider { } pub fn begin_interactive_funding_tx_construction(&mut self, signer_provider: &SP, - entropy_source: &ES, funding_inputs: Vec<(TxIn, Transaction)> + entropy_source: &ES, holder_node_id: PublicKey, funding_inputs: Vec<(TxIn, Transaction)> ) -> Result, APIError> where ES::Target: EntropySource { self.context.begin_interactive_funding_tx_construction(&self.dual_funding_context, - signer_provider, entropy_source, false /* is_initiator */, funding_inputs) + signer_provider, entropy_source, holder_node_id, false /* is_initiator */, funding_inputs) } pub fn funding_tx_constructed( - mut self, transaction: Transaction, est_block: BestBlock, signer_provider: &SP, logger: &L - ) -> Result<(Channel, msgs::CommitmentSigned), (Self, ChannelError)> + mut self, counterparty_node_id: &PublicKey, mut signing_session: InteractiveTxSigningSession, signer_provider: &SP, logger: &L + ) -> Result<(Channel, msgs::CommitmentSigned, Option), (Self, ChannelError)> where L::Target: Logger { - let res = get_initial_commitment_signed(&mut self.context, transaction, est_block, + let mut output_index = None; + let expected_spk = self.context.get_funding_redeemscript().to_v0_p2wsh(); + for (idx, outp) in signing_session.constructed_transaction.output.iter().enumerate() { + if outp.script_pubkey == expected_spk && outp.value == self.context.get_value_satoshis() { + if output_index.is_some() { + return Err((self, ChannelError::Close("Multiple outputs matched the expected script and value".to_owned()))); + } + output_index = Some(idx as u16); + } + } + if output_index.is_none() { + if output_index.is_some() { + return Err((self, ChannelError::Close("No output matched the script_pubkey and value in the FundingGenerationReady event".to_owned()))); + } + } + let outpoint = OutPoint { txid: signing_session.constructed_transaction.txid(), index: output_index.unwrap() }; + self.context.channel_transaction_parameters.funding_outpoint = Some(outpoint); + self.context.holder_signer.as_mut().provide_channel_parameters(&self.context.channel_transaction_parameters); + + let commitment_signed = get_initial_commitment_signed(&mut self.context, &signing_session.constructed_transaction, signer_provider, logger); - let commitment_signed = match res { - Ok(commitment_signed) => commitment_signed, - Err(err) => return Err((self, err)), + let commitment_signed = match commitment_signed { + Some(commitment_signed) => commitment_signed, + None => return Err((self, ChannelError::Close("Incorrect signature".to_string()))), }; + let mut funding_ready_for_sig_event = None; + if self.dual_funding_context.our_funding_satoshis == 0 { + signing_session.provide_holder_witnesses(self.context.channel_id, Vec::new()); + } else { + funding_ready_for_sig_event = Some(Event::FundingTransactionReadyForSigning { + channel_id: self.context.channel_id, + counterparty_node_id: *counterparty_node_id, + user_channel_id: self.context.user_id, + unsigned_transaction: signing_session.constructed_transaction.clone(), + }); + } + + // Replace tx constructor session with signing session + self.context.interactive_tx_constructor = None; + self.context.channel_state = ChannelState::FundingCreated as u32; + let channel = Channel { context: self.context, dual_funding_channel_context: Some(self.dual_funding_context), + interactive_tx_signing_session: Some(signing_session), }; - Ok((channel, commitment_signed)) + Ok((channel, commitment_signed, funding_ready_for_sig_event)) } } @@ -8103,30 +8214,6 @@ fn get_initial_channel_type(config: &UserConfig, their_features: &InitFeatures) ret } -/// If an Err is returned, it is a ChannelError::Close -fn get_initial_remote_commitment_tx_signature(context: &mut ChannelContext, logger: &L) --> Result<(CommitmentTransaction, Signature), ChannelError> -where - SP::Target: SignerProvider, - L::Target: Logger -{ - let counterparty_keys = context.build_remote_transaction_keys(); - let counterparty_initial_commitment_tx = context.build_commitment_transaction( - context.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, false, logger).tx; - - let holder_keys = context.build_holder_transaction_keys(context.cur_holder_commitment_transaction_number); - let initial_commitment_tx = context.build_commitment_transaction( - context.cur_holder_commitment_transaction_number, &holder_keys, true, true, logger).tx; - - match &context.holder_signer { - ChannelSignerType::Ecdsa(ecdsa) => { - let signature = ecdsa.sign_counterparty_commitment(&counterparty_initial_commitment_tx, Vec::new(), Vec::new(), &context.secp_ctx) - .map_err(|_| ChannelError::Close("Failed to get signatures for new commitment_signed".to_owned()))?.0; - Ok((counterparty_initial_commitment_tx, signature)) - } - } -} - fn get_initial_counterparty_commitment_signature( context: &mut ChannelContext, logger: &L ) -> Result @@ -8147,7 +8234,7 @@ where } fn get_initial_commitment_signed( - context: &mut ChannelContext, transaction: Transaction, est_block: BestBlock, signer_provider: &SP, logger: &L + context: &mut ChannelContext, transaction: Transaction, signer_provider: &SP, logger: &L ) -> Result where SP::Target: SignerProvider, @@ -8182,35 +8269,10 @@ where } }; - // This is an externally observable change before we finish all our checks. In particular - // funding_created_signature may fail. - context.holder_signer.as_mut().provide_channel_parameters(&context.channel_transaction_parameters); - - let (counterparty_initial_commitment_tx, signature) = - match get_initial_remote_commitment_tx_signature(context, logger) { - Ok(res) => res, - Err(ChannelError::Close(e)) => { - context.channel_transaction_parameters.funding_outpoint = None; - return Err(ChannelError::Close(e)); - }, - Err(e) => { - // The only error we know how to handle is ChannelError::Close, so we fall over here - // to make sure we don't continue with an inconsistent state. - panic!("unexpected error type from funding_created_signature {:?}", e); - } - }; - - // Now that we're past error-generating stuff, update our local state: - - let funding_txo_script = context.get_funding_redeemscript().to_v0_p2wsh(); - let obscure_factor = get_commitment_transaction_number_obscure_factor(&context.get_holder_pubkeys().payment_point, &context.get_counterparty_pubkeys().payment_point, context.is_outbound()); - let shutdown_script = context.shutdown_scriptpubkey.clone().map(|script| script.into_inner()); - let mut monitor_signer = signer_provider.derive_channel_signer(context.channel_value_satoshis, context.channel_keys_id); - monitor_signer.provide_channel_parameters(&context.channel_transaction_parameters); - - context.channel_state = ChannelState::FundingNegotiated; - context.cur_counterparty_commitment_transaction_number -= 1; - context.cur_holder_commitment_transaction_number -= 1; + if context.signer_pending_funding { + log_trace!(logger, "Counterparty commitment signature ready for funding_created message: clearing signer_pending_funding"); + context.signer_pending_funding = false; + } log_info!(logger, "Generated commitment_signed for peer for channel {}", &context.channel_id()); @@ -9129,6 +9191,8 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch }, #[cfg(dual_funding)] dual_funding_channel_context: None, + #[cfg(dual_funding)] + interactive_tx_signing_session: None, }) } } @@ -9693,6 +9757,8 @@ mod tests { context: outbound_chan.context, #[cfg(dual_funding)] dual_funding_channel_context: None, + #[cfg(dual_funding)] + interactive_tx_signing_session: None, }; let dummy_htlc_source = HTLCSource::OutboundRoute { diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 7ee90a18cea..5d034533d9d 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -4110,11 +4110,11 @@ where let tx_msg_opt = match phase.get_mut() { ChannelPhase::UnfundedOutboundV2(chan) => { chan.begin_interactive_funding_tx_construction(&self.signer_provider, - &self.entropy_source, funding_inputs)? + &self.entropy_source, self.get_our_node_id(), funding_inputs)? }, ChannelPhase::UnfundedInboundV2(chan) => { chan.begin_interactive_funding_tx_construction(&self.signer_provider, - &self.entropy_source, funding_inputs)? + &self.entropy_source, self.get_our_node_id(), funding_inputs)? }, _ => { return Err(APIError::ChannelUnavailable { @@ -4140,6 +4140,50 @@ where Ok(()) } + /// Handles a signed funding transaction generated by interactive transaction construction and + /// provided by the client. + /// + /// Do NOT broadcast the funding transaction yourself. When we have safely received our + /// counterparty's signature(s) the funding transaction will automatically be broadcast via the + /// [`BroadcasterInterface`] provided when this `ChannelManager` was constructed. + #[cfg(dual_funding)] + pub fn funding_transaction_signed(&self, channel_id: &ChannelId, counterparty_node_id: &PublicKey, + transaction: Transaction) -> Result<(), APIError> { + let witnesses: Vec<_> = transaction.input.into_iter().enumerate().filter_map(|(idx, input)| { + if input.witness.is_empty() { None } else { Some(input.witness) } + }).collect(); + + let per_peer_state = self.per_peer_state.read().unwrap(); + let peer_state_mutex = per_peer_state.get(counterparty_node_id) + .ok_or_else(|| APIError::ChannelUnavailable { err: format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id) })?; + + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + + match peer_state.channel_by_id.get_mut(channel_id) { + Some(ChannelPhase::Funded(chan)) => { + chan.verify_interactive_tx_signatures(&witnesses); + if let Some(ref mut signing_session) = chan.interactive_tx_signing_session { + if let Some(tx_signatures) = signing_session.provide_holder_witnesses(*channel_id, witnesses) { + peer_state.pending_msg_events.push(events::MessageSendEvent::SendTxSignatures { + node_id: *counterparty_node_id, + msg: tx_signatures, + }); + } + } else { + todo!(); + } + }, + Some(_) => return Err(APIError::APIMisuseError { + err: format!("Channel with id {} not expecting funding signatures", channel_id)}), + None => return Err(APIError::ChannelUnavailable{ + err: format!("Channel with id {} not found for the passed counterparty node_id {}", channel_id, + counterparty_node_id) }), + } + + Ok(()) + } + /// Atomically applies partial updates to the [`ChannelConfig`] of the given channels. /// /// Once the updates are applied, each eligible channel (advertised with a known short channel @@ -6532,8 +6576,8 @@ where let outbound_scid_alias = self.create_and_insert_outbound_scid_alias(); channel.context.set_outbound_scid_alias(outbound_scid_alias); - channel.begin_interactive_funding_tx_construction(&self.signer_provider, &self.entropy_source, Vec::new()) - .map_err(|_| MsgHandleErrInternal::send_err_msg_no_close( + channel.begin_interactive_funding_tx_construction(&self.signer_provider, &self.entropy_source, + self.get_our_node_id(), Vec::new()).map_err(|_| MsgHandleErrInternal::send_err_msg_no_close( "Failed to start interactive transaction construction".to_owned(), msg.temporary_channel_id))?; peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannelV2 { @@ -6950,7 +6994,6 @@ where #[cfg(dual_funding)] fn internal_tx_complete(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxComplete) -> Result<(), MsgHandleErrInternal> { - let best_block = *self.best_block.read().unwrap(); let per_peer_state = self.per_peer_state.read().unwrap(); let peer_state_mutex = per_peer_state.get(counterparty_node_id) .ok_or_else(|| { @@ -6968,7 +7011,7 @@ where ChannelPhase::UnfundedInboundV2(_) | ChannelPhase::UnfundedOutboundV2(_) => { let result = channel_phase.context_mut().tx_complete(msg); match result { - Ok((tx_msg_opt, tx_opt)) => { + Ok((tx_msg_opt, signing_session_opt)) => { if let Some(tx_msg) = tx_msg_opt { let msg_send_event = match tx_msg { InteractiveTxMessageSend::TxAddInput(msg) => events::MessageSendEvent::SendTxAddInput { @@ -6980,18 +7023,18 @@ where }; peer_state.pending_msg_events.push(msg_send_event); } - if let Some(tx) = tx_opt { + if let Some(signing_session) = signing_session_opt { let (channel_id, channel_phase) = chan_phase_entry.remove_entry(); let res = match channel_phase { ChannelPhase::UnfundedOutboundV2(chan) => { - chan.funding_tx_constructed(tx, best_block, &self.signer_provider, &self.logger).map_err( + chan.funding_tx_constructed(counterparty_node_id, signing_session, &self.signer_provider, &self.logger).map_err( |(chan, err)| { (ChannelPhase::UnfundedOutboundV2(chan), err) } ) }, ChannelPhase::UnfundedInboundV2(chan) => { - chan.funding_tx_constructed(tx, best_block, &self.signer_provider, &self.logger).map_err( + chan.funding_tx_constructed(counterparty_node_id, signing_session, &self.signer_provider, &self.logger).map_err( |(chan, err)| { (ChannelPhase::UnfundedInboundV2(chan), err) } @@ -6999,10 +7042,14 @@ where }, _ => { todo!(); - } + }, }; match res { - Ok((channel, commitment_signed)) => { + Ok((channel, commitment_signed, funding_ready_for_sig_event_opt)) => { + if let Some(funding_ready_for_sig_event) = funding_ready_for_sig_event_opt { + let mut pending_events = self.pending_events.lock().unwrap(); + pending_events.push_back((funding_ready_for_sig_event, None)); + } peer_state.pending_msg_events.push(events::MessageSendEvent::UpdateHTLCs { node_id: counterparty_node_id.clone(), updates: CommitmentUpdate { @@ -7039,10 +7086,49 @@ where } } - fn internal_tx_signatures(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxSignatures) { - let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( - "Dual-funded channels not supported".to_owned(), - msg.channel_id.clone())), *counterparty_node_id); + #[cfg(dual_funding)] + fn internal_tx_signatures(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxSignatures) + -> Result<(), MsgHandleErrInternal> { + let per_peer_state = self.per_peer_state.read().unwrap(); + let peer_state_mutex = per_peer_state.get(counterparty_node_id) + .ok_or_else(|| { + debug_assert!(false); + MsgHandleErrInternal::send_err_msg_no_close( + format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), + msg.channel_id) + })?; + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + match peer_state.channel_by_id.entry(msg.channel_id) { + hash_map::Entry::Occupied(mut chan_phase_entry) => { + let channel_phase = chan_phase_entry.get_mut(); + match channel_phase { + ChannelPhase::Funded(chan) => { + let (tx_signatures_opt, funding_tx_opt) = try_chan_phase_entry!(self, chan.tx_signatures(&msg), chan_phase_entry); + if let Some(tx_signatures) = tx_signatures_opt { + peer_state.pending_msg_events.push(events::MessageSendEvent::SendTxSignatures { + node_id: *counterparty_node_id, + msg: tx_signatures, + }); + } + if let Some(ref funding_tx) = funding_tx_opt { + self.tx_broadcaster.broadcast_transactions(&[funding_tx]); + { + let mut pending_events = self.pending_events.lock().unwrap(); + emit_channel_pending_event!(pending_events, chan); + } + } + }, + _ => try_chan_phase_entry!(self, Err(ChannelError::Close( + "Got an unexpected tx_signatures message" + .into())), chan_phase_entry) + } + Ok(()) + }, + hash_map::Entry::Vacant(_) => { + 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)) + } + } } fn internal_tx_init_rbf(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxInitRbf) { @@ -7440,18 +7526,27 @@ where let logger = WithChannelContext::from(&self.logger, &chan.context); let funding_txo = chan.context.get_funding_txo(); - let mut has_dual_funding_channel_context = false; + let mut interactive_tx_signing_in_progress = false; #[cfg(dual_funding)] { - has_dual_funding_channel_context = chan.dual_funding_channel_context.is_some(); + interactive_tx_signing_in_progress = chan.interactive_tx_signing_session.is_some(); } - if has_dual_funding_channel_context { + + if interactive_tx_signing_in_progress { #[cfg(dual_funding)] { let monitor = try_chan_phase_entry!(self, chan.commitment_signed_initial_v2(&msg, best_block, &self.signer_provider, &&logger), chan_phase_entry); if let Ok(persist_status) = self.chain_monitor.watch_channel(chan.context.get_funding_txo().unwrap(), monitor) { + if let Some(tx_signatures) = chan.interactive_tx_signing_session.as_mut().map(|session| session.received_commitment_signed(msg.clone())).flatten() { + // At this point we have received a commitment signed and we are watching the channel so + // we're good to send our tx_signatures if we're up first. + peer_state.pending_msg_events.push(events::MessageSendEvent::SendTxSignatures { + node_id: *counterparty_node_id, + msg: tx_signatures, + }); + } handle_new_monitor_update!(self, persist_status, peer_state_lock, peer_state, per_peer_state, chan, INITIAL_MONITOR); } else { try_chan_phase_entry!(self, Err(ChannelError::Close("Channel funding outpoint was a duplicate".to_owned())), chan_phase_entry) @@ -9817,9 +9912,8 @@ where #[cfg(dual_funding)] fn handle_tx_signatures(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxSignatures) { - let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( - "Dual-funded channels not supported".to_owned(), - msg.channel_id.clone())), *counterparty_node_id); + let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); + let _ = handle_error!(self, self.internal_tx_signatures(counterparty_node_id, msg), *counterparty_node_id); } #[cfg(dual_funding)] @@ -11731,10 +11825,12 @@ where #[cfg(test)] mod tests { + use bitcoin::Witness; use bitcoin::hashes::Hash; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; use core::sync::atomic::Ordering; + use crate::chain::chaininterface::ConfirmationTarget; use crate::events::{Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, ClosureReason}; use crate::ln::{PaymentPreimage, PaymentHash, PaymentSecret}; use crate::ln::ChannelId; @@ -13020,7 +13116,162 @@ mod tests { } // Dual-funding: V2 Channel Establishment Tests - // TODO(dual_funding): Complete these. + #[test] + #[cfg(dual_funding)] + fn test_v2_channel_establishment_only_initiator_contributes() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + // Create a funding input for the new channel along with its previous transaction. + let funding_inputs = vec![create_dual_funding_utxo_with_prev_tx(&nodes[0], 100_000)]; + let funding_satoshis = 50_000; + + // nodes[0] creates a dual-funded channel as initiator. + nodes[0].node.create_dual_funded_channel( + nodes[1].node.get_our_node_id(), funding_satoshis, Some(ConfirmationTarget::NonAnchorChannelFee), + 42, None, + ).unwrap(); + let open_channel_v2_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannelV2, nodes[1].node.get_our_node_id()); + + assert_eq!(nodes[0].node.list_channels().len(), 1); + + // Since `manually_accept_inbound_channels` is false by default, nodes[1]'s node will accept the dual- + // funded channel immediately without allowing nodes[1] to contribute any inputs. + nodes[1].node.handle_open_channel_v2(&nodes[0].node.get_our_node_id(), &open_channel_v2_msg); + let accept_channel_v2_msg = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannelV2, nodes[0].node.get_our_node_id()); + + nodes[0].node.handle_accept_channel_v2(&nodes[1].node.get_our_node_id(), &accept_channel_v2_msg); + + // nodes[0] should get an event notifying her that channel establishment is awaiting funding inputs + // and that she should provide them. + if let Event::FundingInputsContributionReady { + channel_id, + counterparty_node_id, + holder_funding_satoshis, + counterparty_funding_satoshis, + .. + } = get_event!(nodes[0], Event::FundingInputsContributionReady) { + assert_eq!(holder_funding_satoshis, funding_satoshis); + assert_eq!(counterparty_funding_satoshis, 0); + nodes[0].node.contribute_funding_inputs(&channel_id, &counterparty_node_id, funding_inputs).unwrap(); + } else { panic!(); } + // nodes[0] will generate a TxAddInput message to kickstart the interactive transaction construction + // protocol with nodes[1]. + let tx_add_input_msg = get_event_msg!(&nodes[0], MessageSendEvent::SendTxAddInput, nodes[1].node.get_our_node_id()); + + nodes[1].node.handle_tx_add_input(&nodes[0].node.get_our_node_id(), &tx_add_input_msg); + let tx_complete_msg = get_event_msg!(nodes[1], MessageSendEvent::SendTxComplete, nodes[0].node.get_our_node_id()); + + let input_value = tx_add_input_msg.prevtx.0.output[tx_add_input_msg.prevtx_out as usize].value; + assert_eq!(input_value, 100_000); + + nodes[0].node.handle_tx_complete(&nodes[1].node.get_our_node_id(), &tx_complete_msg); + // First output we send is the change output. + let tx_add_output_msg = get_event_msg!(&nodes[0], MessageSendEvent::SendTxAddOutput, nodes[1].node.get_our_node_id()); + assert!(tx_add_output_msg.script.is_v0_p2wpkh()); + + nodes[1].node.handle_tx_add_output(&nodes[0].node.get_our_node_id(), &tx_add_output_msg); + let tx_complete_msg = get_event_msg!(&nodes[1], MessageSendEvent::SendTxComplete, nodes[0].node.get_our_node_id()); + + nodes[0].node.handle_tx_complete(&nodes[1].node.get_our_node_id(), &tx_complete_msg); + let tx_add_output_msg = get_event_msg!(&nodes[0], MessageSendEvent::SendTxAddOutput, nodes[1].node.get_our_node_id()); + + // Check we get the channel funding output. + assert!(tx_add_output_msg.script.is_v0_p2wsh()); + assert_eq!(tx_add_output_msg.sats, funding_satoshis); + + nodes[1].node.handle_tx_add_output(&nodes[0].node.get_our_node_id(), &tx_add_output_msg); + let tx_complete_msg = get_event_msg!(nodes[1], MessageSendEvent::SendTxComplete, nodes[0].node.get_our_node_id()); + + nodes[0].node.handle_tx_complete(&nodes[1].node.get_our_node_id(), &tx_complete_msg); + let msg_events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(msg_events.len(), 2); + let tx_complete_msg = match msg_events[0] { + MessageSendEvent::SendTxComplete { ref node_id, ref msg } => { + assert_eq!(*node_id, nodes[1].node.get_our_node_id()); + (*msg).clone() + }, + _ => panic!("Unexpected event"), + }; + let msg_commitment_signed_from_0 = match msg_events[1] { + MessageSendEvent::UpdateHTLCs { ref node_id, ref updates } => { + assert_eq!(*node_id, nodes[1].node.get_our_node_id()); + updates.commitment_signed.clone() + }, + _ => panic!("Unexpected event"), + }; + if let Event::FundingTransactionReadyForSigning { + channel_id, + counterparty_node_id, + mut unsigned_transaction, + .. + } = get_event!(nodes[0], Event::FundingTransactionReadyForSigning) { + assert_eq!(counterparty_node_id, nodes[1].node.get_our_node_id()); + + let mut witness = Witness::new(); + witness.push(vec![0]); + unsigned_transaction.input[0].witness = witness; + + nodes[0].node.funding_transaction_signed(&channel_id, &counterparty_node_id, unsigned_transaction).unwrap(); + } else { panic!(); } + + nodes[1].node.handle_tx_complete(&nodes[0].node.get_our_node_id(), &tx_complete_msg); + let msg_events = nodes[1].node.get_and_clear_pending_msg_events(); + // First messsage is commitment_signed, second is tx_signatures (see below for more) + assert_eq!(msg_events.len(), 1); + let msg_commitment_signed_from_1 = match msg_events[0] { + MessageSendEvent::UpdateHTLCs { ref node_id, ref updates } => { + assert_eq!(*node_id, nodes[0].node.get_our_node_id()); + updates.commitment_signed.clone() + }, + _ => panic!("Unexpected event"), + }; + + // Handle the initial commitment_signed exchange. Order is not important here. + nodes[1].node.handle_commitment_signed(&nodes[0].node.get_our_node_id(), &msg_commitment_signed_from_0); + nodes[0].node.handle_commitment_signed(&nodes[1].node.get_our_node_id(), &msg_commitment_signed_from_1); + check_added_monitors(&nodes[0], 1); + check_added_monitors(&nodes[1], 1); + + // The initiator is the only party that contributed any inputs so they should definitely be the one to send tx_signatures + // only after receiving tx_signatures from the non-initiator in this case. + let msg_events = nodes[0].node.get_and_clear_pending_msg_events(); + assert!(msg_events.is_empty()); + let tx_signatures_from_1 = get_event_msg!(nodes[1], MessageSendEvent::SendTxSignatures, nodes[0].node.get_our_node_id()); + + nodes[0].node.handle_tx_signatures(&nodes[1].node.get_our_node_id(), &tx_signatures_from_1); + let events_0 = nodes[0].node.get_and_clear_pending_events(); + assert_eq!(events_0.len(), 1); + match events_0[0] { + Event::ChannelPending{ ref counterparty_node_id, .. } => { + assert_eq!(*counterparty_node_id, nodes[1].node.get_our_node_id()); + }, + _ => panic!("Unexpected event"), + } + let tx_signatures_from_0 = get_event_msg!(nodes[0], MessageSendEvent::SendTxSignatures, nodes[1].node.get_our_node_id()); + nodes[1].node.handle_tx_signatures(&nodes[0].node.get_our_node_id(), &tx_signatures_from_0); + let events_1 = nodes[1].node.get_and_clear_pending_events(); + assert_eq!(events_1.len(), 1); + match events_1[0] { + Event::ChannelPending{ ref counterparty_node_id, .. } => { + assert_eq!(*counterparty_node_id, nodes[0].node.get_our_node_id()); + }, + _ => panic!("Unexpected event"), + } + + let tx = { + let tx_0 = &nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap()[0]; + let tx_1 = &nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap()[0]; + assert_eq!(tx_0, tx_1); + tx_0.clone() + }; + + let (channel_ready, _) = create_chan_between_nodes_with_value_confirm(&nodes[0], &nodes[1], &tx); + let (announcement, nodes_0_update, nodes_1_update) = create_chan_between_nodes_with_value_b(&nodes[0], &nodes[1], &channel_ready); + update_nodes_with_chan_announce(&nodes, 0, 1, &announcement, &nodes_0_update, &nodes_1_update); + } } #[cfg(ldk_bench)] diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index d3d2e3322ca..b8bec047c2b 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -35,8 +35,10 @@ use crate::util::logger::Logger; use bitcoin::blockdata::block::{Block, Header, Version}; use bitcoin::blockdata::locktime::absolute::LockTime; -use bitcoin::blockdata::transaction::{Transaction, TxIn, TxOut}; -use bitcoin::hash_types::{BlockHash, TxMerkleNode}; +use bitcoin::blockdata::script::ScriptBuf; +use bitcoin::blockdata::transaction::{Sequence, Transaction, TxIn, TxOut}; +use bitcoin::blockdata::witness::Witness; +use bitcoin::hash_types::{BlockHash, TxMerkleNode, WPubkeyHash}; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::Hash as _; use bitcoin::network::constants::Network; @@ -1074,6 +1076,27 @@ pub fn create_coinbase_funding_transaction<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, internal_create_funding_transaction(node, expected_counterparty_node_id, expected_chan_value, expected_user_chan_id, true) } +pub fn create_dual_funding_utxo_with_prev_tx<'a, 'b, 'c>( + node: &Node<'a, 'b, 'c>, value_satoshis: u64, +) -> (TxIn, Transaction) { + let chan_id = *node.network_chan_count.borrow(); + + let tx = Transaction { version: chan_id as i32, lock_time: LockTime::ZERO, input: vec![], + output: vec![TxOut { + value: value_satoshis, script_pubkey: ScriptBuf::new_v0_p2wpkh(&WPubkeyHash::all_zeros()), + }]}; + let funding_input = TxIn { + previous_output: OutPoint { + txid: tx.txid(), + index: 0, + }.into_bitcoin_outpoint(), + script_sig: ScriptBuf::new(), + sequence: Sequence::ZERO, + witness: Witness::new(), + }; + (funding_input, tx) +} + fn internal_create_funding_transaction<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, expected_counterparty_node_id: &PublicKey, expected_chan_value: u64, expected_user_chan_id: u128, coinbase: bool) -> (ChannelId, Transaction, OutPoint) { diff --git a/lightning/src/ln/interactivetxs.rs b/lightning/src/ln/interactivetxs.rs index 6a329247964..ac4e3f24ca1 100644 --- a/lightning/src/ln/interactivetxs.rs +++ b/lightning/src/ln/interactivetxs.rs @@ -15,13 +15,14 @@ use bitcoin::blockdata::constants::WITNESS_SCALE_FACTOR; use bitcoin::consensus::Encodable; use bitcoin::locktime::absolute::LockTime; use bitcoin::policy::MAX_STANDARD_TX_WEIGHT; -use bitcoin::{OutPoint, Sequence, Transaction, TxIn, TxOut}; +use bitcoin::secp256k1::PublicKey; +use bitcoin::{OutPoint, Sequence, Transaction, TxIn, TxOut, Witness}; use crate::chain::chaininterface::fee_for_weight; use crate::events::bump_transaction::{BASE_INPUT_WEIGHT, EMPTY_SCRIPT_SIG_WEIGHT}; use crate::ln::channel::TOTAL_BITCOIN_SUPPLY_SATOSHIS; use crate::ln::{ChannelId, msgs}; -use crate::ln::msgs::SerialId; +use crate::ln::msgs::{CommitmentSigned, SerialId, TxSignatures}; use crate::sign::EntropySource; use crate::util::ser::TransactionU16LenLimited; @@ -66,18 +67,109 @@ pub enum AbortReason { InsufficientFees, } -#[derive(Debug)] +#[derive(Debug, Clone, PartialEq)] pub struct TxInputWithPrevOutput { input: TxIn, prev_output: TxOut, } +#[derive(Debug, Clone, PartialEq)] +pub enum InteractiveTxInput { + HolderInput(TxInputWithPrevOutput), + CounterpartyInput(TxInputWithPrevOutput), +} + +#[derive(Debug, Clone, PartialEq)] +pub struct InteractiveTxSigningSession { + pub constructed_transaction: Transaction, + holder_sends_tx_signatures_first: bool, + inputs: Vec, + sent_commitment_signed: Option, + received_commitment_signed: Option, + holder_tx_signatures: Option, + counterparty_tx_signatures: Option, +} + +impl InteractiveTxSigningSession { + pub fn received_commitment_signed(&mut self, commitment_signed: CommitmentSigned) -> Option { + self.received_commitment_signed = Some(commitment_signed); + if self.holder_sends_tx_signatures_first { + self.holder_tx_signatures.clone() + } else { + None + } + } + + pub fn received_tx_signatures(&mut self, tx_signatures: TxSignatures) -> (Option, Option) { + if self.counterparty_tx_signatures.is_some() { + return (None, None); + }; + self.counterparty_tx_signatures = Some(tx_signatures.clone()); + self.inputs.iter_mut().filter_map(|input| match input { + InteractiveTxInput::CounterpartyInput(TxInputWithPrevOutput { input, .. }) => Some(input), + _ => None, + }).zip(tx_signatures.witnesses.into_iter()).for_each(|(input, witness)| input.witness = witness); + + let holder_tx_signatures = if !self.holder_sends_tx_signatures_first { + self.holder_tx_signatures.clone() + } else { + None + }; + + let funding_tx = if self.holder_tx_signatures.is_some() { + Some(self.finalize_funding_tx()) + } else { + None + }; + + (holder_tx_signatures, funding_tx) + } + + pub fn provide_holder_witnesses(&mut self, channel_id: ChannelId, witnesses: Vec) -> Option { + self.inputs.iter_mut().filter_map(|input| match input { + InteractiveTxInput::HolderInput(TxInputWithPrevOutput { input, .. }) => Some(input), + _ => None, + }).zip(witnesses.iter()).for_each(|(input, witness)| input.witness = witness.clone()); + self.holder_tx_signatures = Some(TxSignatures { + channel_id, + tx_hash: self.constructed_transaction.txid(), + witnesses: witnesses.into_iter().map(|witness| witness).collect(), + }); + if self.received_commitment_signed.is_some() && + (self.holder_sends_tx_signatures_first || self.counterparty_tx_signatures.is_some()) { + self.holder_tx_signatures.clone() + } else { + None + } + } + + pub fn counterparty_inputs_count(&self) -> usize { + self.inputs.iter().filter(|input| match input { + InteractiveTxInput::CounterpartyInput(_) => true, _ => false }).count() + } + + pub fn holder_inputs_count(&self) -> usize { + self.inputs.iter().filter(|input| match input { + InteractiveTxInput::HolderInput(_) => true, _ => false }).count() + } + + fn finalize_funding_tx(&mut self) -> Transaction { + self.constructed_transaction.input = self.inputs.iter().map(|interactive_tx_input| match interactive_tx_input { + InteractiveTxInput::HolderInput(input_with_prevout) => input_with_prevout.input.clone(), + InteractiveTxInput::CounterpartyInput(input_with_prevout) => input_with_prevout.input.clone(), + }).collect(); + self.constructed_transaction.clone() + } +} + #[derive(Debug)] struct NegotiationContext { + holder_node_id: PublicKey, + counterparty_node_id: PublicKey, holder_is_initiator: bool, received_tx_add_input_count: u16, received_tx_add_output_count: u16, - inputs: HashMap, + inputs: HashMap, prevtx_outpoints: HashSet, outputs: HashMap, tx_locktime: LockTime, @@ -90,10 +182,20 @@ impl NegotiationContext { self.holder_is_initiator == !serial_id.is_valid_for_initiator() } + fn holder_inputs_contributed(&self) -> impl Iterator + Clone { + self.inputs.iter() + .filter_map(move |(_, input)| match input { + InteractiveTxInput::HolderInput(tx_input_with_prev_output) => Some(tx_input_with_prev_output), + _ => None, + }) + } + fn counterparty_inputs_contributed(&self) -> impl Iterator + Clone { self.inputs.iter() - .filter(move |(serial_id, _)| self.is_serial_id_valid_for_counterparty(serial_id)) - .map(|(_, input_with_prevout)| input_with_prevout) + .filter_map(move |(_, input)| match input { + InteractiveTxInput::CounterpartyInput(tx_input_with_prev_output) => Some(tx_input_with_prev_output), + _ => None, + }) } fn counterparty_outputs_contributed(&self) -> impl Iterator + Clone{ @@ -102,6 +204,18 @@ impl NegotiationContext { .map(|(_, input_with_prevout)| input_with_prevout) } + fn should_holder_send_tx_signatures_first(&self, holder_inputs_value: u64, counterparty_inputs_value: u64) -> bool { + // There is a strict ordering for `tx_signatures` exchange to prevent deadlocks. + if holder_inputs_value == counterparty_inputs_value { + // If the amounts are the same then the peer with the lowest pubkey lexicographically sends its + // tx_signatures first + self.holder_node_id < self.counterparty_node_id + } else { + // Otherwise the peer with the lowest contributed input value sends its tx_signatures first. + holder_inputs_value < counterparty_inputs_value + } + } + fn remote_tx_add_input(&mut self, msg: &msgs::TxAddInput) -> Result<(), AbortReason> { // The interactive-txs spec calls for us to fail negotiation if the `prevtx` we receive is // invalid. However, we would not need to account for this explicit negotiation failure @@ -170,14 +284,15 @@ impl NegotiationContext { txid: transaction.txid(), vout: msg.prevtx_out, }; - self.inputs.insert(msg.serial_id, TxInputWithPrevOutput { - input: TxIn { - previous_output: prev_outpoint.clone(), - sequence: Sequence(msg.sequence), - ..Default::default() - }, - prev_output: prev_out, - }); + self.inputs.insert(msg.serial_id, InteractiveTxInput::CounterpartyInput( + TxInputWithPrevOutput { + input: TxIn { + previous_output: prev_outpoint.clone(), + sequence: Sequence(msg.sequence), + ..Default::default() + }, + prev_output: prev_out, + })); self.prevtx_outpoints.insert(prev_outpoint); Ok(()) } @@ -279,10 +394,10 @@ impl NegotiationContext { debug_assert!((msg.prevtx_out as usize) < tx.output.len()); let prev_output = &tx.output[msg.prevtx_out as usize]; self.prevtx_outpoints.insert(input.previous_output.clone()); - self.inputs.insert(msg.serial_id, TxInputWithPrevOutput { + self.inputs.insert(msg.serial_id, InteractiveTxInput::HolderInput(TxInputWithPrevOutput { input, prev_output: prev_output.clone(), - }); + })); } fn local_tx_add_output(&mut self, msg: &msgs::TxAddOutput) { @@ -300,7 +415,7 @@ impl NegotiationContext { self.outputs.remove(&msg.serial_id); } - fn build_transaction(mut self) -> Result { + fn build_transaction(mut self) -> Result<(Vec, Transaction), AbortReason> { // The receiving node: // MUST fail the negotiation if: @@ -327,10 +442,15 @@ impl NegotiationContext { inputs.sort_unstable_by_key(|(serial_id, _)| *serial_id); outputs.sort_unstable_by_key(|(serial_id, _)| *serial_id); + let tx_input = inputs.iter().filter_map(|(_, input)| match input { + InteractiveTxInput::HolderInput(TxInputWithPrevOutput { input, .. }) => Some(input.clone()), + InteractiveTxInput::CounterpartyInput(TxInputWithPrevOutput { input, .. }) => Some(input.clone()), + }).collect(); + let tx_to_validate = Transaction { version: 2, lock_time: self.tx_locktime, - input: inputs.into_iter().map(|(_, input)| input.input.clone()).collect(), + input: tx_input, output: outputs.into_iter().map(|(_, output)| output.clone()).collect(), }; if tx_to_validate.weight().to_wu() > MAX_STANDARD_TX_WEIGHT as u64 { @@ -364,7 +484,8 @@ impl NegotiationContext { return Err(AbortReason::InsufficientFees); } - Ok(tx_to_validate) + let interactive_tx_inputs = inputs.into_iter().map(|(_, input)| input.clone()).collect(); + Ok((interactive_tx_inputs, tx_to_validate)) } } @@ -411,7 +532,7 @@ define_state!(LOCAL_STATE, LocalChange, "We have sent a message to the counterpa define_state!(LOCAL_STATE, LocalTxComplete, "We have sent a `tx_complete` message and are awaiting the counterparty's."); define_state!(REMOTE_STATE, RemoteChange, "We have received a message from the counterparty that has affected our negotiation state."); define_state!(REMOTE_STATE, RemoteTxComplete, "We have received a `tx_complete` message and the counterparty is awaiting ours."); -define_state!(NegotiationComplete, Transaction, "We have exchanged consecutive `tx_complete` messages with the counterparty and the transaction negotiation is complete."); +define_state!(NegotiationComplete, InteractiveTxSigningSession, "We have exchanged consecutive `tx_complete` messages with the counterparty and the transaction negotiation is complete."); define_state!(NegotiationAborted, AbortReason, "The negotiation has failed and cannot be continued."); type StateTransitionResult = Result; @@ -454,8 +575,23 @@ macro_rules! define_state_transitions { impl StateTransition for $from_state { fn transition(self, _data: &msgs::TxComplete) -> StateTransitionResult { let context = self.into_negotiation_context(); - let tx = context.build_transaction()?; - Ok(NegotiationComplete(tx)) + let holder_inputs_value = context.holder_inputs_contributed() + .fold(0, |value, input| value + input.prev_output.value); + let counterparty_inputs_value = context.counterparty_inputs_contributed() + .fold(0, |value, input| value + input.prev_output.value); + let holder_sends_tx_signatures_first = context.should_holder_send_tx_signatures_first( + holder_inputs_value, counterparty_inputs_value); + let (inputs, tx) = context.build_transaction()?; + let signing_session = InteractiveTxSigningSession { + inputs, + holder_sends_tx_signatures_first, + constructed_transaction: tx, + sent_commitment_signed: None, + received_commitment_signed: None, + holder_tx_signatures: None, + counterparty_tx_signatures: None, + }; + Ok(NegotiationComplete(signing_session)) } } }; @@ -526,9 +662,12 @@ macro_rules! define_state_machine_transitions { } impl StateMachine { - fn new(feerate_sat_per_kw: u32, is_initiator: bool, tx_locktime: LockTime) -> Self { + fn new(holder_node_id: PublicKey, counterparty_node_id: PublicKey, feerate_sat_per_kw: u32, + is_initiator: bool, tx_locktime: LockTime) -> Self { let context = NegotiationContext { tx_locktime, + holder_node_id, + counterparty_node_id, holder_is_initiator: is_initiator, received_tx_add_input_count: 0, received_tx_add_output_count: 0, @@ -603,14 +742,14 @@ fn generate_local_serial_id(entropy_source: &ES, is_initiator: bool) impl InteractiveTxConstructor { pub fn new( - entropy_source: &ES, channel_id: ChannelId, feerate_sat_per_kw: u32, is_initiator: bool, - tx_locktime: LockTime, inputs_to_contribute: Vec<(TxIn, Transaction)>, - outputs_to_contribute: Vec, + entropy_source: &ES, channel_id: ChannelId, feerate_sat_per_kw: u32, holder_node_id: PublicKey, + counterparty_node_id: PublicKey, is_initiator: bool, tx_locktime: LockTime, + inputs_to_contribute: Vec<(TxIn, Transaction)>, outputs_to_contribute: Vec, ) -> (Self, Option) where ES::Target: EntropySource, { - let state_machine = StateMachine::new(feerate_sat_per_kw, is_initiator, tx_locktime); + let state_machine = StateMachine::new(holder_node_id, counterparty_node_id, feerate_sat_per_kw, is_initiator, tx_locktime); let inputs_to_contribute = inputs_to_contribute.into_iter().map(|(input, tx)| { let serial_id = generate_local_serial_id(entropy_source, is_initiator); (serial_id, input, tx) @@ -688,12 +827,12 @@ impl InteractiveTxConstructor { self.do_local_state_transition() } - pub fn handle_tx_complete(&mut self, msg: &msgs::TxComplete) -> Result<(Option, Option), AbortReason> { + pub fn handle_tx_complete(&mut self, msg: &msgs::TxComplete) -> Result<(Option, Option), AbortReason> { let _ = do_state_transition!(self, remote_tx_complete, msg)?; match &self.state_machine { StateMachine::RemoteTxComplete(_) => { let msg_send = self.do_local_state_transition()?; - let negotiated_tx = match &self.state_machine { + let success_result = match &self.state_machine { StateMachine::NegotiationComplete(s) => Some(s.0.clone()), StateMachine::LocalChange(_) => None, // We either had an input or output to contribute. _ => { @@ -701,7 +840,7 @@ impl InteractiveTxConstructor { return Err(AbortReason::InvalidStateTransition); } }; - Ok((Some(msg_send), negotiated_tx)) + Ok((Some(msg_send), success_result)) } StateMachine::NegotiationComplete(s) => Ok((None, Some(s.0.clone()))), _ => { @@ -718,7 +857,8 @@ mod tests { use std::ops::Deref; use crate::chain::chaininterface::FEERATE_FLOOR_SATS_PER_KW; use crate::ln::interactivetxs::{AbortReason, generate_local_serial_id, InteractiveTxConstructor, InteractiveTxMessageSend, MAX_INPUTS_OUTPUTS_COUNT, MAX_RECEIVED_TX_ADD_INPUT_COUNT, MAX_RECEIVED_TX_ADD_OUTPUT_COUNT}; - use bitcoin::{OutPoint, Sequence, Transaction, TxIn, TxOut, Witness}; + use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; + use bitcoin::{OutPoint, Sequence, Transaction, TxIn, TxOut}; use bitcoin::blockdata::opcodes; use bitcoin::blockdata::script::Builder; use bitcoin::locktime::absolute::LockTime; @@ -775,15 +915,16 @@ mod tests { } fn do_test_interactive_tx_constructor_internal(session: TestSession, entropy_source: &ES) where ES::Target: EntropySource { - let channel_id = ChannelId(entropy_source.get_secure_random_bytes()); let channel_id = ChannelId(entropy_source.get_secure_random_bytes()); let tx_locktime = LockTime::from_height(1337).unwrap(); + let holder_node_id = PublicKey::from_secret_key(&Secp256k1::signing_only(), &SecretKey::from_slice(&[42; 32]).unwrap()); + let counterparty_node_id = PublicKey::from_secret_key(&Secp256k1::signing_only(), &SecretKey::from_slice(&[43; 32]).unwrap()); let (mut constructor_a, first_message_a) = InteractiveTxConstructor::new( - entropy_source, channel_id, FEERATE_FLOOR_SATS_PER_KW * 10, true, tx_locktime, session.inputs_a.clone(), session.outputs_a.clone() + entropy_source, channel_id, FEERATE_FLOOR_SATS_PER_KW * 10, holder_node_id, counterparty_node_id, true, tx_locktime, session.inputs_a.clone(), session.outputs_a.clone() ); let (mut constructor_b, first_message_b) = InteractiveTxConstructor::new( - entropy_source, channel_id, FEERATE_FLOOR_SATS_PER_KW * 10, false, tx_locktime, session.inputs_b.clone(), session.outputs_b.clone() + entropy_source, channel_id, FEERATE_FLOOR_SATS_PER_KW * 10, holder_node_id, counterparty_node_id, false, tx_locktime, session.inputs_b.clone(), session.outputs_b.clone() ); let handle_message_send = |msg: InteractiveTxMessageSend, for_constructor: &mut InteractiveTxConstructor| { @@ -808,9 +949,9 @@ mod tests { while final_tx_a.is_none() || final_tx_b.is_none() { if let Some(message_send_a) = message_send_a.take() { match handle_message_send(message_send_a, &mut constructor_b) { - Ok((msg_send, final_tx)) => { + Ok((msg_send, interactive_signing_session)) => { message_send_b = msg_send; - final_tx_b = final_tx; + final_tx_b = interactive_signing_session.map(|session| session.constructed_transaction); } Err(abort_reason) => { assert_eq!(Some(abort_reason), session.expect_error); @@ -820,9 +961,9 @@ mod tests { } if let Some(message_send_b) = message_send_b.take() { match handle_message_send(message_send_b, &mut constructor_a) { - Ok((msg_send, final_tx)) => { + Ok((msg_send, interactive_signing_session)) => { message_send_a = msg_send; - final_tx_a = final_tx; + final_tx_a = interactive_signing_session.map(|session| session.constructed_transaction); } Err(abort_reason) => { assert_eq!(Some(abort_reason), session.expect_error); From 0ac3d9ada9ae317c73cdfd5d9da22e214ca7fc7e Mon Sep 17 00:00:00 2001 From: Duncan Dean Date: Wed, 13 Sep 2023 23:42:05 +0200 Subject: [PATCH 022/101] Add `option_dual_fund` feature --- lightning/src/ln/channelmanager.rs | 1 + lightning/src/ln/features.rs | 10 ++++++++-- lightning/src/ln/peer_handler.rs | 1 + 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 5d034533d9d..95dd467edc2 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -10116,6 +10116,7 @@ pub fn provided_init_features(config: &UserConfig) -> InitFeatures { if config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx { features.set_anchors_zero_fee_htlc_tx_optional(); } + features.set_dual_fund_optional(); features } diff --git a/lightning/src/ln/features.rs b/lightning/src/ln/features.rs index d10c3a71927..87b8ea26801 100644 --- a/lightning/src/ln/features.rs +++ b/lightning/src/ln/features.rs @@ -43,6 +43,9 @@ //! (see [BOLT-2](https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-open_channel-message) for more information). //! - `ShutdownAnySegwit` - requires/supports that future segwit versions are allowed in `shutdown` //! (see [BOLT-2](https://github.com/lightning/bolts/blob/master/02-peer-protocol.md) for more information). +//! - `DualFund` - requires/supports V2 channel establishment +//! (see [BOLT-2](https://github.com/lightning/bolts/pull/851/files) for more information). +// TODO: update link //! - `OnionMessages` - requires/supports forwarding onion messages //! (see [BOLT-7](https://github.com/lightning/bolts/pull/759/files) for more information). // TODO: update link @@ -143,7 +146,7 @@ mod sealed { // Byte 2 BasicMPP | Wumbo | AnchorsNonzeroFeeHtlcTx | AnchorsZeroFeeHtlcTx, // Byte 3 - ShutdownAnySegwit | Taproot, + ShutdownAnySegwit | DualFund | Taproot, // Byte 4 OnionMessages, // Byte 5 @@ -159,7 +162,7 @@ mod sealed { // Byte 2 BasicMPP | Wumbo | AnchorsNonzeroFeeHtlcTx | AnchorsZeroFeeHtlcTx, // Byte 3 - ShutdownAnySegwit | Taproot, + ShutdownAnySegwit | DualFund | Taproot, // Byte 4 OnionMessages, // Byte 5 @@ -394,6 +397,9 @@ mod sealed { define_feature!(27, ShutdownAnySegwit, [InitContext, NodeContext], "Feature flags for `opt_shutdown_anysegwit`.", set_shutdown_any_segwit_optional, set_shutdown_any_segwit_required, supports_shutdown_anysegwit, requires_shutdown_anysegwit); + define_feature!(29, DualFund, [InitContext, NodeContext], + "Feature flags for `option_dual_fund`.", set_dual_fund_optional, set_dual_fund_required, + supports_dual_fund, requires_dual_fund); define_feature!(31, Taproot, [InitContext, NodeContext, ChannelTypeContext], "Feature flags for `option_taproot`.", set_taproot_optional, set_taproot_required, supports_taproot, requires_taproot); diff --git a/lightning/src/ln/peer_handler.rs b/lightning/src/ln/peer_handler.rs index 80b340f11a1..5622476b0d2 100644 --- a/lightning/src/ln/peer_handler.rs +++ b/lightning/src/ln/peer_handler.rs @@ -293,6 +293,7 @@ impl ChannelMessageHandler for ErroringMessageHandler { features.set_basic_mpp_optional(); features.set_wumbo_optional(); features.set_shutdown_any_segwit_optional(); + features.set_dual_fund_optional(); features.set_channel_type_optional(); features.set_scid_privacy_optional(); features.set_zero_conf_optional(); From a146bbe90710deb78fcf7f25ff558333aaa2e95f Mon Sep 17 00:00:00 2001 From: optout <13562139+optout21@users.noreply.github.com> Date: Mon, 18 Dec 2023 14:15:06 +0100 Subject: [PATCH 023/101] Splice test: add checks to acceptor node as well --- lightning/src/ln/functional_tests.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index 85d6d438a41..9b03c02a386 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -506,6 +506,18 @@ fn test_splice_in_simple() { assert_eq!(channel.confirmations.unwrap(), 10); } + // do the checks on acceptor side as well + assert_eq!(nodes[a].node.list_channels().len(), 1); + { + let channel = &nodes[a].node.list_channels()[0]; + assert!(channel.is_usable); + assert!(channel.is_channel_ready); + assert_eq!(channel.channel_value_satoshis, post_splice_channel_value); + assert_eq!(channel.outbound_capacity_msat, 0); + assert_eq!(channel.funding_txo.unwrap().txid, splice_tx.txid()); + assert_eq!(channel.confirmations.unwrap(), 10); + } + // ... End of Splicing // close channel From 5db4b3f85586c0413ac2e747310562b02d2b535c Mon Sep 17 00:00:00 2001 From: optout <13562139+optout21@users.noreply.github.com> Date: Mon, 8 Jan 2024 10:24:24 +0100 Subject: [PATCH 024/101] Minor cleanup --- README.md | 2 +- lightning-net-tokio/src/lib.rs | 6 +- lightning/src/ln/channel.rs | 124 --------------------------------- 3 files changed, 4 insertions(+), 128 deletions(-) diff --git a/README.md b/README.md index 6782fc836d4..8d1d82e87c3 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Objective, Restrictions: - Only a single pending splicing is supported - The channel ID is not changed during splicing (which is incorrect) -Up-to-date with main branch as of v0.0.117 (Oct 4, commit 9de51f0; originally branched off v0.0.115). +Up-to-date with main branch as of v0.0.118 (Oct 24, commit d2242f6; originally branched off v0.0.115). See also `ldk-sample` https://github.com/catenocrypt/ldk-sample/tree/splicing-hapa2 diff --git a/lightning-net-tokio/src/lib.rs b/lightning-net-tokio/src/lib.rs index 50cce31f4a2..0eaec17edab 100644 --- a/lightning-net-tokio/src/lib.rs +++ b/lightning-net-tokio/src/lib.rs @@ -610,9 +610,9 @@ mod tests { fn handle_splice_ack(&self, _their_node_id: &PublicKey, _msg: &SpliceAck) {} fn handle_splice_locked(&self, _their_node_id: &PublicKey, _msg: &SpliceLocked) {} fn handle_splice_created(&self, _their_node_id: &PublicKey, _msg: &SpliceCreated) {} - fn handle_splice_comm_signed(&self, their_node_id: &PublicKey, msg: &SpliceCommSigned) {} - fn handle_splice_comm_ack(&self, their_node_id: &PublicKey, msg: &SpliceCommAck) {} - fn handle_splice_signed(&self, their_node_id: &PublicKey, msg: &SpliceSigned) {} + fn handle_splice_comm_signed(&self, _their_node_id: &PublicKey, _msg: &SpliceCommSigned) {} + fn handle_splice_comm_ack(&self, _their_node_id: &PublicKey, _msg: &SpliceCommAck) {} + fn handle_splice_signed(&self, _their_node_id: &PublicKey, _msg: &SpliceSigned) {} fn handle_splice_signed_ack(&self, _their_node_id: &PublicKey, _msg: &SpliceSignedAck) {} fn handle_tx_add_input(&self, _their_node_id: &PublicKey, _msg: &TxAddInput) {} fn handle_tx_add_output(&self, _their_node_id: &PublicKey, _msg: &TxAddOutput) {} diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index a744145bc1b..acc12d76877 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -6486,130 +6486,6 @@ impl Channel where channel_id: self.context.channel_id(), }) } - - /* - /// #SPLICING - /// Based on funding_created() - /// Note: there is no need to return Channel (as it is not re-created, channel ID not changed) - pub fn splice_created( - &mut self, msg: &msgs::SpliceCreated, best_block: BestBlock, signer_provider: &SP, logger: &L - ) -> Result<(msgs::SpliceSignedAck, ChannelMonitor<::Signer>), ChannelError> - where - L::Target: Logger - { - if self.context.is_outbound() { - return Err(ChannelError::Close("Received splice_created for an outbound channel?".to_owned())); - } - // TODO checks taken out - /* - if self.channel_state != (ChannelState::OurInitSent as u32 | ChannelState::TheirInitSent as u32) { - // BOLT 2 says that if we disconnect before we send funding_signed we SHOULD NOT - // remember the channel, so it's safe to just send an error_message here and drop the - // channel. - return Err(ChannelError::Close("Received splice_created after we got the channel!".to_owned())); - } - if self.inbound_awaiting_accept { - return Err(ChannelError::Close("SpliceCreated message received before the channel was accepted".to_owned())); - } - if self.commitment_secrets.get_min_seen_secret() != (1 << 48) || - self.cur_counterparty_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER || - self.cur_holder_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER { - panic!("Should not have advanced channel commitment tx numbers prior to funding_created"); - } - */ - - // Sign splice funding tx: create our signature on the funding tx - // #SPLICE-SIG - let funding_signature = match &mut self.context.holder_signer { - ChannelSignerType::Ecdsa(ecdsa) => { - ecdsa.sign_splicing_funding_input(&msg.splice_transaction, msg.splice_prev_funding_input_index, msg.splice_prev_funding_input_value, &msg.splice_tx_redeem_script, &self.context.secp_ctx) - .map_err(|_| ChannelError::Close("Failed to sign the previous funding input in the new splicing funding tx".to_owned()))? - } - }; - log_info!(logger, "Created signature for funding tx input / acceptor, input idx {} txid {} value {} sig {:?}", msg.splice_prev_funding_input_index, msg.splice_transaction.txid(), self.context.channel_value_satoshis, funding_signature.serialize_der().to_hex()); - - // Commit to the new channel value (capacity). The increase belongs to them. - let splice_txo = OutPoint { txid: msg.splice_txid, index: msg.funding_output_index }; - let _ = self.context.commit_pending_splice(splice_txo, logger)?; - - // This is an externally observable change before we finish all our checks. In particular - // funding_created_signature may fail. - self.context.holder_signer.as_mut().reprovide_channel_parameters(&self.context.channel_transaction_parameters, self.context.channel_value_satoshis); - let txoutp = &self.context.channel_transaction_parameters.funding_outpoint.unwrap(); - log_trace!(logger, "fund_tx_outpoint {} {}", log_bytes!(txoutp.txid), txoutp.index); - - let (counterparty_initial_commitment_tx, initial_commitment_tx, signature) = match self.context.funding_created_signature(&msg.signature, logger) { - Ok(res) => res, - Err(ChannelError::Close(e)) => { - self.context.channel_transaction_parameters.funding_outpoint = None; - return Err(ChannelError::Close(e)); - }, - Err(e) => { - // The only error we know how to handle is ChannelError::Close, so we fall over here - // to make sure we don't continue with an inconsistent state. - panic!("unexpected error type from splice_created_signature {:?}", e); - } - }; - - let holder_commitment_tx = HolderCommitmentTransaction::new( - initial_commitment_tx, - msg.signature, - Vec::new(), - &self.context.get_holder_pubkeys().funding_pubkey, - self.context.counterparty_funding_pubkey() - ); - - if let Err(_) = self.context.holder_signer.as_ref().validate_holder_commitment(&holder_commitment_tx, Vec::new()) { - return Err(ChannelError::Close("Failed to validate our commitment".to_owned())); - } - - // Now that we're past error-generating stuff, update our local state: - - let funding_redeemscript = self.context.get_funding_redeemscript(); - let funding_txo_script = funding_redeemscript.to_v0_p2wsh(); - let obscure_factor = get_commitment_transaction_number_obscure_factor(&self.context.get_holder_pubkeys().payment_point, &self.context.get_counterparty_pubkeys().payment_point, self.context.is_outbound()); - let shutdown_script = self.context.shutdown_scriptpubkey.clone().map(|script| script.into_inner()); - let mut monitor_signer = signer_provider.derive_channel_signer(self.context.channel_value_satoshis, self.context.channel_keys_id); - monitor_signer.provide_channel_parameters(&self.context.channel_transaction_parameters); - let channel_monitor = ChannelMonitor::new(self.context.secp_ctx.clone(), monitor_signer, - shutdown_script, self.context.get_holder_selected_contest_delay(), - &self.context.destination_script, (splice_txo, funding_txo_script.clone()), - &self.context.channel_transaction_parameters, - funding_redeemscript.clone(), self.context.channel_value_satoshis, - obscure_factor, - holder_commitment_tx, best_block, self.context.counterparty_node_id); - - // TODO: Is this initial or latest commitment? - channel_monitor.provide_latest_counterparty_commitment_tx( - counterparty_initial_commitment_tx.trust().txid(), Vec::new(), - self.context.cur_counterparty_commitment_transaction_number, - self.context.counterparty_cur_commitment_point.unwrap(), logger); - - self.context.channel_state = ChannelState::FundingSent as u32; - // Note: channel_id is not changed - // self.channel_id = splice_txo.to_channel_id(); - // TODO: Check if we reset transation number counters or not - // self.cur_counterparty_commitment_transaction_number -= 1; - // self.cur_holder_commitment_transaction_number -= 1; - - log_info!(logger, "Generated funding_signed for peer for channel {}", self.context.channel_id()); - - let need_channel_ready = self.check_get_channel_ready(0).is_some(); - self.monitor_updating_paused(false, false, need_channel_ready, Vec::new(), Vec::new(), Vec::new()); - - Ok((msgs::SpliceSignedAck { - channel_id: self.context.channel_id(), - funding_signature, - splice_prev_funding_input_index: msg.splice_prev_funding_input_index, - splice_prev_funding_input_value: msg.splice_prev_funding_input_value, - signature, - /* - #[cfg(taproot)] - partial_signature_with_nonce: None, - */ - }, channel_monitor)) - } - */ } /// A not-yet-funded outbound (from holder) channel using V1 channel establishment. From dfe53d707915d0a7aa4f8ed0fdc7dcea8469d0cf Mon Sep 17 00:00:00 2001 From: optout <13562139+optout21@users.noreply.github.com> Date: Mon, 8 Jan 2024 14:49:30 +0100 Subject: [PATCH 025/101] Update to main, up to Nov 23 70ea110, bitcoin 0.30.2, splice msgs in --- CONTRIBUTING.md | 9 +- fuzz/Cargo.toml | 4 +- fuzz/README.md | 60 +- fuzz/src/bin/gen_target.sh | 6 + fuzz/src/bin/msg_splice_ack_target.rs | 113 ++ fuzz/src/bin/msg_splice_locked_target.rs | 113 ++ fuzz/src/bin/msg_splice_target.rs | 113 ++ fuzz/src/bin/msg_stfu_target.rs | 113 ++ fuzz/src/chanmon_consistency.rs | 16 +- fuzz/src/full_stack.rs | 34 +- fuzz/src/msg_targets/gen_target.sh | 6 + fuzz/src/msg_targets/mod.rs | 4 + fuzz/src/msg_targets/msg_splice.rs | 25 + fuzz/src/msg_targets/msg_splice_ack.rs | 25 + fuzz/src/msg_targets/msg_splice_locked.rs | 25 + fuzz/src/msg_targets/msg_stfu.rs | 25 + fuzz/src/onion_message.rs | 178 ++- fuzz/src/peer_crypt.rs | 8 +- fuzz/targets.h | 4 + lightning-background-processor/Cargo.toml | 2 +- lightning-background-processor/src/lib.rs | 6 +- lightning-block-sync/Cargo.toml | 3 +- lightning-block-sync/src/convert.rs | 44 +- lightning-block-sync/src/init.rs | 10 +- lightning-block-sync/src/lib.rs | 13 +- lightning-block-sync/src/poll.rs | 11 +- lightning-block-sync/src/rest.rs | 11 +- lightning-block-sync/src/rpc.rs | 11 +- lightning-block-sync/src/test_utils.rs | 35 +- lightning-block-sync/src/utils.rs | 40 +- lightning-custom-message/Cargo.toml | 2 +- lightning-invoice/Cargo.toml | 8 +- lightning-invoice/src/de.rs | 12 +- lightning-invoice/src/lib.rs | 80 +- lightning-invoice/src/payment.rs | 397 ++---- lightning-invoice/src/ser.rs | 4 +- lightning-invoice/src/utils.rs | 27 +- lightning-invoice/tests/ser_de.rs | 76 +- lightning-net-tokio/Cargo.toml | 2 +- lightning-net-tokio/src/lib.rs | 33 +- lightning-persister/Cargo.toml | 4 +- lightning-persister/src/fs_store.rs | 6 +- lightning-rapid-gossip-sync/Cargo.toml | 2 +- lightning-transaction-sync/Cargo.toml | 8 +- lightning-transaction-sync/src/common.rs | 5 +- .../tests/integration_tests.rs | 26 +- lightning/Cargo.toml | 11 +- lightning/src/blinded_path/utils.rs | 12 +- lightning/src/chain/chaininterface.rs | 17 - lightning/src/chain/chainmonitor.rs | 21 +- lightning/src/chain/channelmonitor.rs | 162 +-- lightning/src/chain/mod.rs | 22 +- lightning/src/chain/onchaintx.rs | 24 +- lightning/src/chain/package.rs | 93 +- lightning/src/chain/transaction.rs | 11 +- lightning/src/events/bump_transaction.rs | 39 +- lightning/src/events/mod.rs | 35 +- lightning/src/lib.rs | 2 +- lightning/src/ln/async_signer_tests.rs | 323 +++++ lightning/src/ln/chan_utils.rs | 234 ++-- lightning/src/ln/chanmon_update_fail_tests.rs | 6 +- lightning/src/ln/channel.rs | 654 ++++++---- lightning/src/ln/channel_id.rs | 14 +- lightning/src/ln/channelmanager.rs | 1095 +++++++++++------ lightning/src/ln/functional_test_utils.rs | 250 +++- lightning/src/ln/functional_tests.rs | 563 ++++----- lightning/src/ln/inbound_payment.rs | 12 +- lightning/src/ln/mod.rs | 4 + lightning/src/ln/monitor_tests.rs | 75 +- lightning/src/ln/msgs.rs | 548 +++++---- lightning/src/ln/onion_route_tests.rs | 8 +- lightning/src/ln/onion_utils.rs | 144 ++- lightning/src/ln/outbound_payment.rs | 10 +- lightning/src/ln/payment_tests.rs | 201 +-- lightning/src/ln/peer_channel_encryptor.rs | 251 ++-- lightning/src/ln/peer_handler.rs | 79 +- lightning/src/ln/priv_short_conf_tests.rs | 16 +- lightning/src/ln/reload_tests.rs | 10 +- lightning/src/ln/script.rs | 85 +- lightning/src/ln/shutdown_tests.rs | 123 +- lightning/src/ln/wire.rs | 15 +- lightning/src/offers/invoice.rs | 75 +- lightning/src/offers/invoice_request.rs | 4 +- lightning/src/offers/merkle.rs | 82 +- lightning/src/offers/offer.rs | 259 +++- lightning/src/offers/parse.rs | 90 ++ lightning/src/offers/refund.rs | 8 + lightning/src/offers/signer.rs | 10 +- .../src/onion_message/functional_tests.rs | 21 +- lightning/src/onion_message/messenger.rs | 5 +- lightning/src/onion_message/mod.rs | 1 + lightning/src/onion_message/packet.rs | 2 +- lightning/src/routing/gossip.rs | 65 +- lightning/src/routing/router.rs | 32 +- lightning/src/routing/test_utils.rs | 8 +- lightning/src/routing/utxo.rs | 14 +- lightning/src/sign/mod.rs | 98 +- lightning/src/sign/type_resolver.rs | 1 + lightning/src/util/chacha20poly1305rfc.rs | 22 +- lightning/src/util/config.rs | 5 +- lightning/src/util/crypto.rs | 12 +- lightning/src/util/indexed_map.rs | 8 + lightning/src/util/message_signing.rs | 19 +- lightning/src/util/persist.rs | 18 +- lightning/src/util/ser.rs | 34 +- lightning/src/util/ser_macros.rs | 45 +- lightning/src/util/string.rs | 2 +- lightning/src/util/test_channel_signer.rs | 36 +- lightning/src/util/test_utils.rs | 47 +- lightning/src/util/transaction_utils.rs | 49 +- 110 files changed, 5051 insertions(+), 2949 deletions(-) create mode 100644 fuzz/src/bin/msg_splice_ack_target.rs create mode 100644 fuzz/src/bin/msg_splice_locked_target.rs create mode 100644 fuzz/src/bin/msg_splice_target.rs create mode 100644 fuzz/src/bin/msg_stfu_target.rs create mode 100644 fuzz/src/msg_targets/msg_splice.rs create mode 100644 fuzz/src/msg_targets/msg_splice_ack.rs create mode 100644 fuzz/src/msg_targets/msg_splice_locked.rs create mode 100644 fuzz/src/msg_targets/msg_stfu.rs create mode 100644 lightning/src/ln/async_signer_tests.rs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e795ecb9fba..350415af24c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -88,10 +88,11 @@ be covered by functional tests. When refactoring, structure your PR to make it easy to review and don't hesitate to split it into multiple small, focused PRs. -The Minimum Supported Rust Version (MSRV) currently is 1.41.1 (enforced by -our GitHub Actions). Also, the compatibility for LDK object serialization is -currently ensured back to and including crate version 0.0.99 (see the -[changelog](CHANGELOG.md)). +The Minimum Supported Rust Version (MSRV) currently is 1.48.0 (enforced by +our GitHub Actions). We support reading serialized LDK objects written by any +version of LDK 0.0.99 and above. We support LDK versions 0.0.113 and above +reading serialized LDK objects written by modern LDK. Any expected issues with +upgrades or downgrades should be mentioned in the [changelog](CHANGELOG.md). Commits should cover both the issue fixed and the solution's rationale. These [guidelines](https://chris.beams.io/posts/git-commit/) should be kept in mind. diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 5daebb35066..573096efdfc 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -20,8 +20,8 @@ stdin_fuzz = [] [dependencies] lightning = { path = "../lightning", features = ["regex", "hashbrown", "_test_utils"] } lightning-rapid-gossip-sync = { path = "../lightning-rapid-gossip-sync" } -bitcoin = { version = "0.29.0", features = ["secp-lowmemory"] } -hex = "0.3" +bitcoin = { version = "0.30.2", features = ["secp-lowmemory"] } +hex = { package = "hex-conservative", version = "0.1.1", default-features = false } hashbrown = "0.8" afl = { version = "0.12", optional = true } diff --git a/fuzz/README.md b/fuzz/README.md index bfc8fa5f4bf..987288b5d93 100644 --- a/fuzz/README.md +++ b/fuzz/README.md @@ -1,22 +1,23 @@ # Fuzzing -Fuzz tests generate a ton of random parameter arguments to the program and then validate that none cause it to crash. +Fuzz tests generate a ton of random parameter arguments to the program and then validate that none +cause it to crash. ## How does it work? -Typically, Travis CI will run `travis-fuzz.sh` on one of the environments the automated tests are configured for. -This is the most time-consuming component of the continuous integration workflow, so it is recommended that you detect -issues locally, and Travis merely acts as a sanity check. Fuzzing is further only effective with -a lot of CPU time, indicating that if crash scenarios are discovered on Travis with its low -runtime constraints, the crash is caused relatively easily. +Typically, CI will run `ci-fuzz.sh` on one of the environments the automated tests are +configured for. Fuzzing is further only effective with a lot of CPU time, indicating that if crash +scenarios are discovered on CI with its low runtime constraints, the crash is caused relatively +easily. ## How do I run fuzz tests locally? -You typically won't need to run the entire combination of different fuzzing tools. For local execution, `honggfuzz` -should be more than sufficient. +We support multiple fuzzing engines such as `honggfuzz`, `libFuzzer` and `AFL`. You typically won't +need to run the entire suite of different fuzzing tools. For local execution, `honggfuzz`should be +more than sufficient. ### Setup - +#### Honggfuzz To install `honggfuzz`, simply run ```shell @@ -31,9 +32,18 @@ cargo update -p honggfuzz --precise "0.5.52" cargo install --force honggfuzz --version "0.5.52" ``` +#### cargo-fuzz / libFuzzer +To install `cargo-fuzz`, simply run + +```shell +cargo update +cargo install --force cargo-fuzz +``` + ### Execution -To run the Hongg fuzzer, do +#### Honggfuzz +To run fuzzing using `honggfuzz`, do ```shell export CPU_COUNT=1 # replace as needed @@ -46,19 +56,39 @@ cargo hfuzz run $TARGET (Or, for a prettier output, replace the last line with `cargo --color always hfuzz run $TARGET`.) +#### cargo-fuzz / libFuzzer +To run fuzzing using `cargo-fuzz / libFuzzer`, run + +```shell +rustup install nightly # Note: libFuzzer requires a nightly version of rust. +cargo +nightly fuzz run --features "libfuzzer_fuzz" msg_ping_target +``` +Note: If you encounter a `SIGKILL` during run/build check for OOM in kernel logs and consider +increasing RAM size for VM. + +If you wish to just generate fuzzing binary executables for `libFuzzer` and not run them: +```shell +cargo +nightly fuzz build --features "libfuzzer_fuzz" msg_ping_target +# Generates binary artifact in path ./target/aarch64-unknown-linux-gnu/release/msg_ping_target +# Exact path depends on your system architecture. +``` +You can upload the build artifact generated above to `ClusterFuzz` for distributed fuzzing. + +### List Fuzzing Targets To see a list of available fuzzing targets, run: ```shell ls ./src/bin/ ``` -## A fuzz test failed on Travis, what do I do? +## A fuzz test failed, what do I do? -You're trying to create a PR, but need to find the underlying cause of that pesky fuzz failure blocking the merge? +You're trying to create a PR, but need to find the underlying cause of that pesky fuzz failure +blocking the merge? Worry not, for this is easily traced. -If your Travis output log looks like this: +If your output log looks like this: ``` Size:639 (i,b,hw,ed,ip,cmp): 0/0/0/0/0/1, Tot:0/0/0/2036/5/28604 @@ -66,13 +96,13 @@ Seen a crash. Terminating all fuzzing threads … # a lot of lines in between -<0x0000555555565559> [func:UNKNOWN file: line:0 module:/home/travis/build/rust-bitcoin/rust-lightning/fuzz/hfuzz_target/x86_64-unknown-linux-gnu/release/full_stack_target] +<0x0000555555565559> [func:UNKNOWN file: line:0 module:./rust-lightning/fuzz/hfuzz_target/x86_64-unknown-linux-gnu/release/full_stack_target] <0x0000000000000000> [func:UNKNOWN file: line:0 module:UNKNOWN] ===================================================================== 2d3136383734090101010101010101010101010101010101010101010101 010101010100040101010101010101010101010103010101010100010101 0069d07c319a4961 -The command "if [ "$(rustup show | grep default | grep stable)" != "" ]; then cd fuzz && cargo test --verbose && ./travis-fuzz.sh; fi" exited with 1. +The command "if [ "$(rustup show | grep default | grep stable)" != "" ]; then cd fuzz && cargo test --verbose && ./ci-fuzz.sh; fi" exited with 1. ``` Note that the penultimate stack trace line ends in `release/full_stack_target]`. That indicates that diff --git a/fuzz/src/bin/gen_target.sh b/fuzz/src/bin/gen_target.sh index 2fa7debdf46..62381622f6b 100755 --- a/fuzz/src/bin/gen_target.sh +++ b/fuzz/src/bin/gen_target.sh @@ -70,3 +70,9 @@ GEN_TEST msg_tx_signatures msg_targets:: GEN_TEST msg_tx_init_rbf msg_targets:: GEN_TEST msg_tx_ack_rbf msg_targets:: GEN_TEST msg_tx_abort msg_targets:: + +GEN_TEST msg_stfu msg_targets:: + +GEN_TEST msg_splice msg_targets:: +GEN_TEST msg_splice_ack msg_targets:: +GEN_TEST msg_splice_locked msg_targets:: diff --git a/fuzz/src/bin/msg_splice_ack_target.rs b/fuzz/src/bin/msg_splice_ack_target.rs new file mode 100644 index 00000000000..cff5675e191 --- /dev/null +++ b/fuzz/src/bin/msg_splice_ack_target.rs @@ -0,0 +1,113 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +// This file is auto-generated by gen_target.sh based on target_template.txt +// To modify it, modify target_template.txt and run gen_target.sh instead. + +#![cfg_attr(feature = "libfuzzer_fuzz", no_main)] + +#[cfg(not(fuzzing))] +compile_error!("Fuzz targets need cfg=fuzzing"); + +extern crate lightning_fuzz; +use lightning_fuzz::msg_targets::msg_splice_ack::*; + +#[cfg(feature = "afl")] +#[macro_use] extern crate afl; +#[cfg(feature = "afl")] +fn main() { + fuzz!(|data| { + msg_splice_ack_run(data.as_ptr(), data.len()); + }); +} + +#[cfg(feature = "honggfuzz")] +#[macro_use] extern crate honggfuzz; +#[cfg(feature = "honggfuzz")] +fn main() { + loop { + fuzz!(|data| { + msg_splice_ack_run(data.as_ptr(), data.len()); + }); + } +} + +#[cfg(feature = "libfuzzer_fuzz")] +#[macro_use] extern crate libfuzzer_sys; +#[cfg(feature = "libfuzzer_fuzz")] +fuzz_target!(|data: &[u8]| { + msg_splice_ack_run(data.as_ptr(), data.len()); +}); + +#[cfg(feature = "stdin_fuzz")] +fn main() { + use std::io::Read; + + let mut data = Vec::with_capacity(8192); + std::io::stdin().read_to_end(&mut data).unwrap(); + msg_splice_ack_run(data.as_ptr(), data.len()); +} + +#[test] +fn run_test_cases() { + use std::fs; + use std::io::Read; + use lightning_fuzz::utils::test_logger::StringBuffer; + + use std::sync::{atomic, Arc}; + { + let data: Vec = vec![0]; + msg_splice_ack_run(data.as_ptr(), data.len()); + } + let mut threads = Vec::new(); + let threads_running = Arc::new(atomic::AtomicUsize::new(0)); + if let Ok(tests) = fs::read_dir("test_cases/msg_splice_ack") { + for test in tests { + let mut data: Vec = Vec::new(); + let path = test.unwrap().path(); + fs::File::open(&path).unwrap().read_to_end(&mut data).unwrap(); + threads_running.fetch_add(1, atomic::Ordering::AcqRel); + + let thread_count_ref = Arc::clone(&threads_running); + let main_thread_ref = std::thread::current(); + threads.push((path.file_name().unwrap().to_str().unwrap().to_string(), + std::thread::spawn(move || { + let string_logger = StringBuffer::new(); + + let panic_logger = string_logger.clone(); + let res = if ::std::panic::catch_unwind(move || { + msg_splice_ack_test(&data, panic_logger); + }).is_err() { + Some(string_logger.into_string()) + } else { None }; + thread_count_ref.fetch_sub(1, atomic::Ordering::AcqRel); + main_thread_ref.unpark(); + res + }) + )); + while threads_running.load(atomic::Ordering::Acquire) > 32 { + std::thread::park(); + } + } + } + let mut failed_outputs = Vec::new(); + for (test, thread) in threads.drain(..) { + if let Some(output) = thread.join().unwrap() { + println!("\nOutput of {}:\n{}\n", test, output); + failed_outputs.push(test); + } + } + if !failed_outputs.is_empty() { + println!("Test cases which failed: "); + for case in failed_outputs { + println!("{}", case); + } + panic!(); + } +} diff --git a/fuzz/src/bin/msg_splice_locked_target.rs b/fuzz/src/bin/msg_splice_locked_target.rs new file mode 100644 index 00000000000..ea5a49bc16d --- /dev/null +++ b/fuzz/src/bin/msg_splice_locked_target.rs @@ -0,0 +1,113 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +// This file is auto-generated by gen_target.sh based on target_template.txt +// To modify it, modify target_template.txt and run gen_target.sh instead. + +#![cfg_attr(feature = "libfuzzer_fuzz", no_main)] + +#[cfg(not(fuzzing))] +compile_error!("Fuzz targets need cfg=fuzzing"); + +extern crate lightning_fuzz; +use lightning_fuzz::msg_targets::msg_splice_locked::*; + +#[cfg(feature = "afl")] +#[macro_use] extern crate afl; +#[cfg(feature = "afl")] +fn main() { + fuzz!(|data| { + msg_splice_locked_run(data.as_ptr(), data.len()); + }); +} + +#[cfg(feature = "honggfuzz")] +#[macro_use] extern crate honggfuzz; +#[cfg(feature = "honggfuzz")] +fn main() { + loop { + fuzz!(|data| { + msg_splice_locked_run(data.as_ptr(), data.len()); + }); + } +} + +#[cfg(feature = "libfuzzer_fuzz")] +#[macro_use] extern crate libfuzzer_sys; +#[cfg(feature = "libfuzzer_fuzz")] +fuzz_target!(|data: &[u8]| { + msg_splice_locked_run(data.as_ptr(), data.len()); +}); + +#[cfg(feature = "stdin_fuzz")] +fn main() { + use std::io::Read; + + let mut data = Vec::with_capacity(8192); + std::io::stdin().read_to_end(&mut data).unwrap(); + msg_splice_locked_run(data.as_ptr(), data.len()); +} + +#[test] +fn run_test_cases() { + use std::fs; + use std::io::Read; + use lightning_fuzz::utils::test_logger::StringBuffer; + + use std::sync::{atomic, Arc}; + { + let data: Vec = vec![0]; + msg_splice_locked_run(data.as_ptr(), data.len()); + } + let mut threads = Vec::new(); + let threads_running = Arc::new(atomic::AtomicUsize::new(0)); + if let Ok(tests) = fs::read_dir("test_cases/msg_splice_locked") { + for test in tests { + let mut data: Vec = Vec::new(); + let path = test.unwrap().path(); + fs::File::open(&path).unwrap().read_to_end(&mut data).unwrap(); + threads_running.fetch_add(1, atomic::Ordering::AcqRel); + + let thread_count_ref = Arc::clone(&threads_running); + let main_thread_ref = std::thread::current(); + threads.push((path.file_name().unwrap().to_str().unwrap().to_string(), + std::thread::spawn(move || { + let string_logger = StringBuffer::new(); + + let panic_logger = string_logger.clone(); + let res = if ::std::panic::catch_unwind(move || { + msg_splice_locked_test(&data, panic_logger); + }).is_err() { + Some(string_logger.into_string()) + } else { None }; + thread_count_ref.fetch_sub(1, atomic::Ordering::AcqRel); + main_thread_ref.unpark(); + res + }) + )); + while threads_running.load(atomic::Ordering::Acquire) > 32 { + std::thread::park(); + } + } + } + let mut failed_outputs = Vec::new(); + for (test, thread) in threads.drain(..) { + if let Some(output) = thread.join().unwrap() { + println!("\nOutput of {}:\n{}\n", test, output); + failed_outputs.push(test); + } + } + if !failed_outputs.is_empty() { + println!("Test cases which failed: "); + for case in failed_outputs { + println!("{}", case); + } + panic!(); + } +} diff --git a/fuzz/src/bin/msg_splice_target.rs b/fuzz/src/bin/msg_splice_target.rs new file mode 100644 index 00000000000..bff4dffbbc2 --- /dev/null +++ b/fuzz/src/bin/msg_splice_target.rs @@ -0,0 +1,113 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +// This file is auto-generated by gen_target.sh based on target_template.txt +// To modify it, modify target_template.txt and run gen_target.sh instead. + +#![cfg_attr(feature = "libfuzzer_fuzz", no_main)] + +#[cfg(not(fuzzing))] +compile_error!("Fuzz targets need cfg=fuzzing"); + +extern crate lightning_fuzz; +use lightning_fuzz::msg_targets::msg_splice::*; + +#[cfg(feature = "afl")] +#[macro_use] extern crate afl; +#[cfg(feature = "afl")] +fn main() { + fuzz!(|data| { + msg_splice_run(data.as_ptr(), data.len()); + }); +} + +#[cfg(feature = "honggfuzz")] +#[macro_use] extern crate honggfuzz; +#[cfg(feature = "honggfuzz")] +fn main() { + loop { + fuzz!(|data| { + msg_splice_run(data.as_ptr(), data.len()); + }); + } +} + +#[cfg(feature = "libfuzzer_fuzz")] +#[macro_use] extern crate libfuzzer_sys; +#[cfg(feature = "libfuzzer_fuzz")] +fuzz_target!(|data: &[u8]| { + msg_splice_run(data.as_ptr(), data.len()); +}); + +#[cfg(feature = "stdin_fuzz")] +fn main() { + use std::io::Read; + + let mut data = Vec::with_capacity(8192); + std::io::stdin().read_to_end(&mut data).unwrap(); + msg_splice_run(data.as_ptr(), data.len()); +} + +#[test] +fn run_test_cases() { + use std::fs; + use std::io::Read; + use lightning_fuzz::utils::test_logger::StringBuffer; + + use std::sync::{atomic, Arc}; + { + let data: Vec = vec![0]; + msg_splice_run(data.as_ptr(), data.len()); + } + let mut threads = Vec::new(); + let threads_running = Arc::new(atomic::AtomicUsize::new(0)); + if let Ok(tests) = fs::read_dir("test_cases/msg_splice") { + for test in tests { + let mut data: Vec = Vec::new(); + let path = test.unwrap().path(); + fs::File::open(&path).unwrap().read_to_end(&mut data).unwrap(); + threads_running.fetch_add(1, atomic::Ordering::AcqRel); + + let thread_count_ref = Arc::clone(&threads_running); + let main_thread_ref = std::thread::current(); + threads.push((path.file_name().unwrap().to_str().unwrap().to_string(), + std::thread::spawn(move || { + let string_logger = StringBuffer::new(); + + let panic_logger = string_logger.clone(); + let res = if ::std::panic::catch_unwind(move || { + msg_splice_test(&data, panic_logger); + }).is_err() { + Some(string_logger.into_string()) + } else { None }; + thread_count_ref.fetch_sub(1, atomic::Ordering::AcqRel); + main_thread_ref.unpark(); + res + }) + )); + while threads_running.load(atomic::Ordering::Acquire) > 32 { + std::thread::park(); + } + } + } + let mut failed_outputs = Vec::new(); + for (test, thread) in threads.drain(..) { + if let Some(output) = thread.join().unwrap() { + println!("\nOutput of {}:\n{}\n", test, output); + failed_outputs.push(test); + } + } + if !failed_outputs.is_empty() { + println!("Test cases which failed: "); + for case in failed_outputs { + println!("{}", case); + } + panic!(); + } +} diff --git a/fuzz/src/bin/msg_stfu_target.rs b/fuzz/src/bin/msg_stfu_target.rs new file mode 100644 index 00000000000..1cabf8ded48 --- /dev/null +++ b/fuzz/src/bin/msg_stfu_target.rs @@ -0,0 +1,113 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +// This file is auto-generated by gen_target.sh based on target_template.txt +// To modify it, modify target_template.txt and run gen_target.sh instead. + +#![cfg_attr(feature = "libfuzzer_fuzz", no_main)] + +#[cfg(not(fuzzing))] +compile_error!("Fuzz targets need cfg=fuzzing"); + +extern crate lightning_fuzz; +use lightning_fuzz::msg_targets::msg_stfu::*; + +#[cfg(feature = "afl")] +#[macro_use] extern crate afl; +#[cfg(feature = "afl")] +fn main() { + fuzz!(|data| { + msg_stfu_run(data.as_ptr(), data.len()); + }); +} + +#[cfg(feature = "honggfuzz")] +#[macro_use] extern crate honggfuzz; +#[cfg(feature = "honggfuzz")] +fn main() { + loop { + fuzz!(|data| { + msg_stfu_run(data.as_ptr(), data.len()); + }); + } +} + +#[cfg(feature = "libfuzzer_fuzz")] +#[macro_use] extern crate libfuzzer_sys; +#[cfg(feature = "libfuzzer_fuzz")] +fuzz_target!(|data: &[u8]| { + msg_stfu_run(data.as_ptr(), data.len()); +}); + +#[cfg(feature = "stdin_fuzz")] +fn main() { + use std::io::Read; + + let mut data = Vec::with_capacity(8192); + std::io::stdin().read_to_end(&mut data).unwrap(); + msg_stfu_run(data.as_ptr(), data.len()); +} + +#[test] +fn run_test_cases() { + use std::fs; + use std::io::Read; + use lightning_fuzz::utils::test_logger::StringBuffer; + + use std::sync::{atomic, Arc}; + { + let data: Vec = vec![0]; + msg_stfu_run(data.as_ptr(), data.len()); + } + let mut threads = Vec::new(); + let threads_running = Arc::new(atomic::AtomicUsize::new(0)); + if let Ok(tests) = fs::read_dir("test_cases/msg_stfu") { + for test in tests { + let mut data: Vec = Vec::new(); + let path = test.unwrap().path(); + fs::File::open(&path).unwrap().read_to_end(&mut data).unwrap(); + threads_running.fetch_add(1, atomic::Ordering::AcqRel); + + let thread_count_ref = Arc::clone(&threads_running); + let main_thread_ref = std::thread::current(); + threads.push((path.file_name().unwrap().to_str().unwrap().to_string(), + std::thread::spawn(move || { + let string_logger = StringBuffer::new(); + + let panic_logger = string_logger.clone(); + let res = if ::std::panic::catch_unwind(move || { + msg_stfu_test(&data, panic_logger); + }).is_err() { + Some(string_logger.into_string()) + } else { None }; + thread_count_ref.fetch_sub(1, atomic::Ordering::AcqRel); + main_thread_ref.unpark(); + res + }) + )); + while threads_running.load(atomic::Ordering::Acquire) > 32 { + std::thread::park(); + } + } + } + let mut failed_outputs = Vec::new(); + for (test, thread) in threads.drain(..) { + if let Some(output) = thread.join().unwrap() { + println!("\nOutput of {}:\n{}\n", test, output); + failed_outputs.push(test); + } + } + if !failed_outputs.is_empty() { + println!("Test cases which failed: "); + for case in failed_outputs { + println!("{}", case); + } + panic!(); + } +} diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index dddd97cae45..af0c64d88ae 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -20,9 +20,9 @@ use bitcoin::blockdata::constants::genesis_block; use bitcoin::blockdata::transaction::{Transaction, TxOut}; -use bitcoin::blockdata::script::{Builder, Script}; +use bitcoin::blockdata::script::{Builder, ScriptBuf}; use bitcoin::blockdata::opcodes; -use bitcoin::blockdata::locktime::PackedLockTime; +use bitcoin::blockdata::locktime::absolute::LockTime; use bitcoin::network::constants::Network; use bitcoin::hashes::Hash as TraitImport; @@ -80,7 +80,6 @@ impl FeeEstimator for FuzzEstimator { // always return a HighPriority feerate here which is >= the maximum Normal feerate and a // Background feerate which is <= the minimum Normal feerate. match conf_target { - ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee => MAX_FEE * 10, ConfirmationTarget::OnChainSweep => MAX_FEE, ConfirmationTarget::ChannelCloseMinimum|ConfirmationTarget::AnchorChannelFee|ConfirmationTarget::MinAllowedAnchorChannelRemoteFee|ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => 253, ConfirmationTarget::NonAnchorChannelFee => cmp::min(self.ret_val.load(atomic::Ordering::Acquire), MAX_FEE), @@ -267,14 +266,15 @@ impl SignerProvider for KeyProvider { inner, state, disable_revocation_policy_check: false, + available: Arc::new(Mutex::new(true)), }) } - fn get_destination_script(&self) -> Result { + fn get_destination_script(&self) -> Result { let secp_ctx = Secp256k1::signing_only(); let channel_monitor_claim_key = SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, self.node_secret[31]]).unwrap(); let our_channel_monitor_claim_key_hash = WPubkeyHash::hash(&PublicKey::from_secret_key(&secp_ctx, &channel_monitor_claim_key).serialize()); - Ok(Builder::new().push_opcode(opcodes::all::OP_PUSHBYTES_0).push_slice(&our_channel_monitor_claim_key_hash[..]).into_script()) + Ok(Builder::new().push_opcode(opcodes::all::OP_PUSHBYTES_0).push_slice(our_channel_monitor_claim_key_hash).into_script()) } fn get_shutdown_scriptpubkey(&self) -> Result { @@ -343,7 +343,7 @@ type ChanMan<'a> = ChannelManager, Arc, A fn get_payment_secret_hash(dest: &ChanMan, payment_id: &mut u8) -> Option<(PaymentSecret, PaymentHash)> { let mut payment_hash; for _ in 0..256 { - payment_hash = PaymentHash(Sha256::hash(&[*payment_id; 1]).into_inner()); + payment_hash = PaymentHash(Sha256::hash(&[*payment_id; 1]).to_byte_array()); if let Ok(payment_secret) = dest.create_inbound_payment_for_hash(payment_hash, None, 3600, None) { return Some((payment_secret, payment_hash)); } @@ -525,7 +525,7 @@ pub fn do_test(data: &[u8], underlying_out: Out, anchors: bool) { features: $source.init_features(), networks: None, remote_network_address: None }, false).unwrap(); - $source.create_channel($dest.get_our_node_id(), 100_000, 42, 0, None).unwrap(); + $source.create_channel($dest.get_our_node_id(), 100_000, 42, 0, None, None).unwrap(); let open_channel = { let events = $source.get_and_clear_pending_msg_events(); assert_eq!(events.len(), 1); @@ -565,7 +565,7 @@ pub fn do_test(data: &[u8], underlying_out: Out, anchors: bool) { let events = $source.get_and_clear_pending_events(); assert_eq!(events.len(), 1); if let events::Event::FundingGenerationReady { ref temporary_channel_id, ref channel_value_satoshis, ref output_script, .. } = events[0] { - let tx = Transaction { version: $chan_id, lock_time: PackedLockTime::ZERO, input: Vec::new(), output: vec![TxOut { + let tx = Transaction { version: $chan_id, lock_time: LockTime::ZERO, input: Vec::new(), output: vec![TxOut { value: *channel_value_satoshis, script_pubkey: output_script.clone(), }]}; funding_output = OutPoint { txid: tx.txid(), index: 0 }; diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs index 9ed58dd949d..8a597a61477 100644 --- a/fuzz/src/full_stack.rs +++ b/fuzz/src/full_stack.rs @@ -15,12 +15,13 @@ use bitcoin::blockdata::constants::genesis_block; use bitcoin::blockdata::transaction::{Transaction, TxOut}; -use bitcoin::blockdata::script::{Builder, Script}; +use bitcoin::blockdata::script::{Builder, ScriptBuf}; use bitcoin::blockdata::opcodes; -use bitcoin::blockdata::locktime::PackedLockTime; +use bitcoin::blockdata::locktime::absolute::LockTime; use bitcoin::consensus::encode::deserialize; use bitcoin::network::constants::Network; +use bitcoin::hashes::hex::FromHex; use bitcoin::hashes::Hash as TraitImport; use bitcoin::hashes::HashEngine as TraitImportEngine; use bitcoin::hashes::sha256::Hash as Sha256; @@ -391,11 +392,11 @@ impl SignerProvider for KeyProvider { )) } - fn get_destination_script(&self) -> Result { + fn get_destination_script(&self) -> Result { let secp_ctx = Secp256k1::signing_only(); - let channel_monitor_claim_key = SecretKey::from_slice(&hex::decode("0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap()[..]).unwrap(); + let channel_monitor_claim_key = SecretKey::from_slice(&>::from_hex("0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap()[..]).unwrap(); let our_channel_monitor_claim_key_hash = WPubkeyHash::hash(&PublicKey::from_secret_key(&secp_ctx, &channel_monitor_claim_key).serialize()); - Ok(Builder::new().push_opcode(opcodes::all::OP_PUSHBYTES_0).push_slice(&our_channel_monitor_claim_key_hash[..]).into_script()) + Ok(Builder::new().push_opcode(opcodes::all::OP_PUSHBYTES_0).push_slice(our_channel_monitor_claim_key_hash).into_script()) } fn get_shutdown_scriptpubkey(&self) -> Result { @@ -481,7 +482,7 @@ pub fn do_test(data: &[u8], logger: &Arc) { let mut should_forward = false; let mut payments_received: Vec = Vec::new(); let mut payments_sent = 0; - let mut pending_funding_generation: Vec<(ChannelId, PublicKey, u64, Script)> = Vec::new(); + let mut pending_funding_generation: Vec<(ChannelId, PublicKey, u64, ScriptBuf)> = Vec::new(); let mut pending_funding_signatures = HashMap::new(); loop { @@ -531,9 +532,7 @@ pub fn do_test(data: &[u8], logger: &Arc) { payment_params, final_value_msat); let mut payment_hash = PaymentHash([0; 32]); payment_hash.0[0..8].copy_from_slice(&be64_to_array(payments_sent)); - let mut sha = Sha256::engine(); - sha.input(&payment_hash.0[..]); - payment_hash.0 = Sha256::from_engine(sha).into_inner(); + payment_hash.0 = Sha256::hash(&payment_hash.0[..]).to_byte_array(); payments_sent += 1; match channelmanager.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0), params, @@ -550,9 +549,7 @@ pub fn do_test(data: &[u8], logger: &Arc) { payment_params, final_value_msat); let mut payment_hash = PaymentHash([0; 32]); payment_hash.0[0..8].copy_from_slice(&be64_to_array(payments_sent)); - let mut sha = Sha256::engine(); - sha.input(&payment_hash.0[..]); - payment_hash.0 = Sha256::from_engine(sha).into_inner(); + payment_hash.0 = Sha256::hash(&payment_hash.0[..]).to_byte_array(); payments_sent += 1; let mut payment_secret = PaymentSecret([0; 32]); payment_secret.0[0..8].copy_from_slice(&be64_to_array(payments_sent)); @@ -571,7 +568,7 @@ pub fn do_test(data: &[u8], logger: &Arc) { let their_key = get_pubkey!(); let chan_value = slice_to_be24(get_slice!(3)) as u64; let push_msat_value = slice_to_be24(get_slice!(3)) as u64; - if channelmanager.create_channel(their_key, chan_value, push_msat_value, 0, None).is_err() { return; } + if channelmanager.create_channel(their_key, chan_value, push_msat_value, 0, None, None).is_err() { return; } }, 6 => { let mut channels = channelmanager.list_channels(); @@ -603,9 +600,7 @@ pub fn do_test(data: &[u8], logger: &Arc) { }, 16 => { let payment_preimage = PaymentPreimage(keys_manager.get_secure_random_bytes()); - let mut sha = Sha256::engine(); - sha.input(&payment_preimage.0[..]); - let payment_hash = PaymentHash(Sha256::from_engine(sha).into_inner()); + let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0[..]).to_byte_array()); // Note that this may fail - our hashes may collide and we'll end up trying to // double-register the same payment_hash. let _ = channelmanager.create_inbound_payment_for_hash(payment_hash, None, 1, None); @@ -617,7 +612,7 @@ pub fn do_test(data: &[u8], logger: &Arc) { }, 10 => { 'outer_loop: for funding_generation in pending_funding_generation.drain(..) { - let mut tx = Transaction { version: 0, lock_time: PackedLockTime::ZERO, input: Vec::new(), output: vec![TxOut { + let mut tx = Transaction { version: 0, lock_time: LockTime::ZERO, input: Vec::new(), output: vec![TxOut { value: funding_generation.2, script_pubkey: funding_generation.3, }] }; let funding_output = 'search_loop: loop { @@ -721,6 +716,7 @@ pub extern "C" fn full_stack_run(data: *const u8, datalen: usize) { #[cfg(test)] mod tests { + use bitcoin::hashes::hex::FromHex; use lightning::util::logger::{Logger, Record}; use std::collections::HashMap; use std::sync::{Arc, Mutex}; @@ -776,7 +772,7 @@ mod tests { // 030059 - inbound read from peer id 0 of len 89 // 030000000000000000000000000000000000000000000000000000000000000005 020900000000000000000000000000000000000000000000000000000000000000 01 0000 01021000 03000000000000000000000000000000 - rest of open_channel and mac // - // 00fd00fd - Two feerate requests (all returning min feerate, which our open_channel also uses) (gonna be ingested by FuzzEstimator) + // 00fd - Two feerate requests (all returning min feerate, which our open_channel also uses) (gonna be ingested by FuzzEstimator) // - client should now respond with accept_channel (CHECK 1: type 33 to peer 03000000) // // 030012 - inbound read from peer id 0 of len 18 @@ -1033,7 +1029,7 @@ mod tests { // - client now fails the HTLC backwards as it was unable to extract the payment preimage (CHECK 9 duplicate and CHECK 10) let logger = Arc::new(TrackingLogger { lines: Mutex::new(HashMap::new()) }); - super::do_test(&::hex::decode("01000000000000000000000000000000000000000000000000000000000000000000000001000300000000000000000000000000000000000000000000000000000000000000020300320003000000000000000000000000000000000000000000000000000000000000000203000000000000000000000000000000030012001003000000000000000000000000000000030020001000021aaa0008aaaaaaaaaaaa9aaa030000000000000000000000000000000300120147030000000000000000000000000000000300fe00206fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000ff4f00f805273c1b203bb5ebf8436bfde57b3be8c2f5e95d9491dbb181909679000000000000c35000000000000000000000000000000162ffffffffffffffff00000000000002220000000000000000000000fd000601e3030000000000000000000000000000000000000000000000000000000000000001030000000000000000000000000000000000000000000000000000000000000002030000000000000000000000000000000000000000000000000000000000000003030000000000000000000000000000000000000000000000000000000000000004030059030000000000000000000000000000000000000000000000000000000000000005020900000000000000000000000000000000000000000000000000000000000000010000010210000300000000000000000000000000000000fd00fd0300120084030000000000000000000000000000000300940022ff4f00f805273c1b203bb5ebf8436bfde57b3be8c2f5e95d9491dbb1819096793d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000210100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000c005e020000000100000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0150c3000000000000220020ae00000000000000000000000000000000000000000000000000000000000000000000000c00000c00000c00000c00000c00000c00000c00000c00000c00000c00000c00000c000003001200430300000000000000000000000000000003005300243d0000000000000000000000000000000000000000000000000000000000000002080000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000010301320003000000000000000000000000000000000000000000000000000000000000000703000000000000000000000000000000030142000302000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003000000000000000000000000000000030112001001000000000000000000000000000000030120001000021aaa0008aaaaaaaaaaaa9aaa01000000000000000000000000000000050103020000000000000000000000000000000000000000000000000000000000000000c3500003e800fd0301120112010000000000000000000000000000000301ff00210000000000000000000000000000000000000000000000000000000000000e05000000000000016200000000004c4b4000000000000003e800000000000003e80000000203f000050300000000000000000000000000000000000000000000000000000000000001000300000000000000000000000000000000000000000000000000000000000002000300000000000000000000000000000000000000000000000000000000000003000300000000000000000000000000000000000000000000000000000000000004000300000000000000000000000000000000000000000000000000000000000005000266000000000000000000000000000003012300000000000000000000000000000000000000010000000000000000000000000000000a00fd00fd03011200620100000000000000000000000000000003017200233a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007c0001000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000b03011200430100000000000000000000000000000003015300243a000000000000000000000000000000000000000000000000000000000000000267000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003001205ac030000000000000000000000000000000300ff00803d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e80ff00000000000000000000000000000000000000000000000000000000000000000003f00003000000000000000000000000000000000000000000000000000000000000055511020203e80401a0060800000e00000100000a00000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300c1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffab000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000fd03001200640300000000000000000000000000000003007400843d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030010000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000900000000000000000000000000000000000000000000000000000000000000020b00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000700fd00fd03011200640100000000000000000000000000000003017400843a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006a000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a00000000000000000000000000000000000000000000000000000000000000660000000000000000000000000000000000000000000000000000000000000002640000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000030112004a0100000000000000000000000000000003015a00823a000000000000000000000000000000000000000000000000000000000000000000000000000000ff008888888888888888888888888888888888888888888888888888888888880100000000000000000000000000000003011200640100000000000000000000000000000003017400843a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a0000000000000000000000000000000000000000000000000000000000000067000000000000000000000000000000000000000000000000000000000000000265000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003001205ac030000000000000000000000000000000300ff00803d0000000000000000000000000000000000000000000000000000000000000000000000000000010000000000003e80ff00000000000000000000000000000000000000000000000000000000000000000003f00003000000000000000000000000000000000000000000000000000000000000055511020203e80401a0060800000e00000100000a00000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300c1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffab000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000fd03001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000020a000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200640300000000000000000000000000000003007400843d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c3010000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000b00000000000000000000000000000000000000000000000000000000000000020d00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000700fd00fd03011200640100000000000000000000000000000003017400843a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000039000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a00000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000002700000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000030112002c0100000000000000000000000000000003013c00833a00000000000000000000000000000000000000000000000000000000000000000000000000000100000100000000000000000000000000000003011200640100000000000000000000000000000003017400843a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000039000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a000000000000000000000000000000000000000000000000000000000000006500000000000000000000000000000000000000000000000000000000000000027100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000703001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000020c000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200640300000000000000000000000000000003007400843d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000032010000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001205ac030000000000000000000000000000000300ff00803d00000000000000000000000000000000000000000000000000000000000000000000000000000200000000000b0838ff00000000000000000000000000000000000000000000000000000000000000000003f0000300000000000000000000000000000000000000000000000000000000000005551202030927c00401a0060800000e00000100000a00000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300c1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff53000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000fd03001200a4030000000000000000000000000000000300b400843d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007501000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006705000000000000000000000000000000000000000000000000000000000000060300000000000000000000000000000003001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000d00000000000000000000000000000000000000000000000000000000000000020f00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000700fd00fd0c007d02000000013a000000000000000000000000000000000000000000000000000000000000000000000000000000800258020000000000002200204b0000000000000000000000000000000000000000000000000000000000000014c00000000000001600142800000000000000000000000000000000000000050000200c005e0200000001730000000000000000000000000000000000000000000000000000000000000000000000000000000001a701000000000000220020b200000000000000000000000000000000000000000000000000000000000000000000000c00000c00000c00000c00000c000007").unwrap(), &(Arc::clone(&logger) as Arc)); + super::do_test(&>::from_hex("01000000000000000000000000000000000000000000000000000000000000000000000001000300000000000000000000000000000000000000000000000000000000000000020300320003000000000000000000000000000000000000000000000000000000000000000203000000000000000000000000000000030012001003000000000000000000000000000000030020001000021aaa0008aaaaaaaaaaaa9aaa030000000000000000000000000000000300120147030000000000000000000000000000000300fe00206fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000ff4f00f805273c1b203bb5ebf8436bfde57b3be8c2f5e95d9491dbb181909679000000000000c35000000000000000000000000000000162ffffffffffffffff00000000000002220000000000000000000000fd000601e3030000000000000000000000000000000000000000000000000000000000000001030000000000000000000000000000000000000000000000000000000000000002030000000000000000000000000000000000000000000000000000000000000003030000000000000000000000000000000000000000000000000000000000000004030059030000000000000000000000000000000000000000000000000000000000000005020900000000000000000000000000000000000000000000000000000000000000010000010210000300000000000000000000000000000000fd0300120084030000000000000000000000000000000300940022ff4f00f805273c1b203bb5ebf8436bfde57b3be8c2f5e95d9491dbb1819096793d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000210100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000c005e020000000100000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0150c3000000000000220020ae00000000000000000000000000000000000000000000000000000000000000000000000c00000c00000c00000c00000c00000c00000c00000c00000c00000c00000c00000c000003001200430300000000000000000000000000000003005300243d0000000000000000000000000000000000000000000000000000000000000002080000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000010301320003000000000000000000000000000000000000000000000000000000000000000703000000000000000000000000000000030142000302000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003000000000000000000000000000000030112001001000000000000000000000000000000030120001000021aaa0008aaaaaaaaaaaa9aaa01000000000000000000000000000000050103020000000000000000000000000000000000000000000000000000000000000000c3500003e800fd0301120112010000000000000000000000000000000301ff00210000000000000000000000000000000000000000000000000000000000000e05000000000000016200000000004c4b4000000000000003e800000000000003e80000000203f000050300000000000000000000000000000000000000000000000000000000000001000300000000000000000000000000000000000000000000000000000000000002000300000000000000000000000000000000000000000000000000000000000003000300000000000000000000000000000000000000000000000000000000000004000300000000000000000000000000000000000000000000000000000000000005000266000000000000000000000000000003012300000000000000000000000000000000000000010000000000000000000000000000000a00fd00fd03011200620100000000000000000000000000000003017200233a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007c0001000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000b03011200430100000000000000000000000000000003015300243a000000000000000000000000000000000000000000000000000000000000000267000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003001205ac030000000000000000000000000000000300ff00803d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e80ff00000000000000000000000000000000000000000000000000000000000000000003f00003000000000000000000000000000000000000000000000000000000000000055511020203e80401a0060800000e00000100000a00000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300c1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffab000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000fd03001200640300000000000000000000000000000003007400843d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030010000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000900000000000000000000000000000000000000000000000000000000000000020b00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000700fd00fd03011200640100000000000000000000000000000003017400843a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006a000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a00000000000000000000000000000000000000000000000000000000000000660000000000000000000000000000000000000000000000000000000000000002640000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000030112004a0100000000000000000000000000000003015a00823a000000000000000000000000000000000000000000000000000000000000000000000000000000ff008888888888888888888888888888888888888888888888888888888888880100000000000000000000000000000003011200640100000000000000000000000000000003017400843a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a0000000000000000000000000000000000000000000000000000000000000067000000000000000000000000000000000000000000000000000000000000000265000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003001205ac030000000000000000000000000000000300ff00803d0000000000000000000000000000000000000000000000000000000000000000000000000000010000000000003e80ff00000000000000000000000000000000000000000000000000000000000000000003f00003000000000000000000000000000000000000000000000000000000000000055511020203e80401a0060800000e00000100000a00000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300c1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffab000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000fd03001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000020a000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200640300000000000000000000000000000003007400843d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c3010000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000b00000000000000000000000000000000000000000000000000000000000000020d00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000700fd00fd03011200640100000000000000000000000000000003017400843a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000039000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a00000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000002700000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000030112002c0100000000000000000000000000000003013c00833a00000000000000000000000000000000000000000000000000000000000000000000000000000100000100000000000000000000000000000003011200640100000000000000000000000000000003017400843a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000039000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000003011200630100000000000000000000000000000003017300853a000000000000000000000000000000000000000000000000000000000000006500000000000000000000000000000000000000000000000000000000000000027100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000703001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000020c000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001200640300000000000000000000000000000003007400843d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000032010000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000003001205ac030000000000000000000000000000000300ff00803d00000000000000000000000000000000000000000000000000000000000000000000000000000200000000000b0838ff00000000000000000000000000000000000000000000000000000000000000000003f0000300000000000000000000000000000000000000000000000000000000000005551202030927c00401a0060800000e00000100000a00000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0300c1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff53000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000fd03001200a4030000000000000000000000000000000300b400843d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007501000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006705000000000000000000000000000000000000000000000000000000000000060300000000000000000000000000000003001200630300000000000000000000000000000003007300853d000000000000000000000000000000000000000000000000000000000000000d00000000000000000000000000000000000000000000000000000000000000020f00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000700fd00fd0c007d02000000013a000000000000000000000000000000000000000000000000000000000000000000000000000000800258020000000000002200204b0000000000000000000000000000000000000000000000000000000000000014c00000000000001600142800000000000000000000000000000000000000050000200c005e0200000001730000000000000000000000000000000000000000000000000000000000000000000000000000000001a701000000000000220020b200000000000000000000000000000000000000000000000000000000000000000000000c00000c00000c00000c00000c000007").unwrap(), &(Arc::clone(&logger) as Arc)); let log_entries = logger.lines.lock().unwrap(); assert_eq!(log_entries.get(&("lightning::ln::peer_handler".to_string(), "Handling SendAcceptChannel event in peer_handler for node 030000000000000000000000000000000000000000000000000000000000000002 for channel ff4f00f805273c1b203bb5ebf8436bfde57b3be8c2f5e95d9491dbb181909679".to_string())), Some(&1)); // 1 diff --git a/fuzz/src/msg_targets/gen_target.sh b/fuzz/src/msg_targets/gen_target.sh index d89df19d99a..cb24aa919db 100755 --- a/fuzz/src/msg_targets/gen_target.sh +++ b/fuzz/src/msg_targets/gen_target.sh @@ -58,3 +58,9 @@ GEN_TEST lightning::ln::msgs::TxSignatures test_msg_simple "" GEN_TEST lightning::ln::msgs::TxInitRbf test_msg_simple "" GEN_TEST lightning::ln::msgs::TxAckRbf test_msg_simple "" GEN_TEST lightning::ln::msgs::TxAbort test_msg_simple "" + +GEN_TEST lightning::ln::msgs::Stfu test_msg_simple "" + +GEN_TEST lightning::ln::msgs::Splice test_msg_simple "" +GEN_TEST lightning::ln::msgs::SpliceAck test_msg_simple "" +GEN_TEST lightning::ln::msgs::SpliceLocked test_msg_simple "" diff --git a/fuzz/src/msg_targets/mod.rs b/fuzz/src/msg_targets/mod.rs index 302dda440cb..837bee65dec 100644 --- a/fuzz/src/msg_targets/mod.rs +++ b/fuzz/src/msg_targets/mod.rs @@ -41,3 +41,7 @@ pub mod msg_tx_signatures; pub mod msg_tx_init_rbf; pub mod msg_tx_ack_rbf; pub mod msg_tx_abort; +pub mod msg_stfu; +pub mod msg_splice; +pub mod msg_splice_ack; +pub mod msg_splice_locked; diff --git a/fuzz/src/msg_targets/msg_splice.rs b/fuzz/src/msg_targets/msg_splice.rs new file mode 100644 index 00000000000..e6a18d2561c --- /dev/null +++ b/fuzz/src/msg_targets/msg_splice.rs @@ -0,0 +1,25 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +// This file is auto-generated by gen_target.sh based on msg_target_template.txt +// To modify it, modify msg_target_template.txt and run gen_target.sh instead. + +use crate::msg_targets::utils::VecWriter; +use crate::utils::test_logger; + +#[inline] +pub fn msg_splice_test(data: &[u8], _out: Out) { + test_msg_simple!(lightning::ln::msgs::Splice, data); +} + +#[no_mangle] +pub extern "C" fn msg_splice_run(data: *const u8, datalen: usize) { + let data = unsafe { std::slice::from_raw_parts(data, datalen) }; + test_msg_simple!(lightning::ln::msgs::Splice, data); +} diff --git a/fuzz/src/msg_targets/msg_splice_ack.rs b/fuzz/src/msg_targets/msg_splice_ack.rs new file mode 100644 index 00000000000..cc77c198bb7 --- /dev/null +++ b/fuzz/src/msg_targets/msg_splice_ack.rs @@ -0,0 +1,25 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +// This file is auto-generated by gen_target.sh based on msg_target_template.txt +// To modify it, modify msg_target_template.txt and run gen_target.sh instead. + +use crate::msg_targets::utils::VecWriter; +use crate::utils::test_logger; + +#[inline] +pub fn msg_splice_ack_test(data: &[u8], _out: Out) { + test_msg_simple!(lightning::ln::msgs::SpliceAck, data); +} + +#[no_mangle] +pub extern "C" fn msg_splice_ack_run(data: *const u8, datalen: usize) { + let data = unsafe { std::slice::from_raw_parts(data, datalen) }; + test_msg_simple!(lightning::ln::msgs::SpliceAck, data); +} diff --git a/fuzz/src/msg_targets/msg_splice_locked.rs b/fuzz/src/msg_targets/msg_splice_locked.rs new file mode 100644 index 00000000000..39e340fd40b --- /dev/null +++ b/fuzz/src/msg_targets/msg_splice_locked.rs @@ -0,0 +1,25 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +// This file is auto-generated by gen_target.sh based on msg_target_template.txt +// To modify it, modify msg_target_template.txt and run gen_target.sh instead. + +use crate::msg_targets::utils::VecWriter; +use crate::utils::test_logger; + +#[inline] +pub fn msg_splice_locked_test(data: &[u8], _out: Out) { + test_msg_simple!(lightning::ln::msgs::SpliceLocked, data); +} + +#[no_mangle] +pub extern "C" fn msg_splice_locked_run(data: *const u8, datalen: usize) { + let data = unsafe { std::slice::from_raw_parts(data, datalen) }; + test_msg_simple!(lightning::ln::msgs::SpliceLocked, data); +} diff --git a/fuzz/src/msg_targets/msg_stfu.rs b/fuzz/src/msg_targets/msg_stfu.rs new file mode 100644 index 00000000000..be2029194a7 --- /dev/null +++ b/fuzz/src/msg_targets/msg_stfu.rs @@ -0,0 +1,25 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +// This file is auto-generated by gen_target.sh based on msg_target_template.txt +// To modify it, modify msg_target_template.txt and run gen_target.sh instead. + +use crate::msg_targets::utils::VecWriter; +use crate::utils::test_logger; + +#[inline] +pub fn msg_stfu_test(data: &[u8], _out: Out) { + test_msg_simple!(lightning::ln::msgs::Stfu, data); +} + +#[no_mangle] +pub extern "C" fn msg_stfu_run(data: *const u8, datalen: usize) { + let data = unsafe { std::slice::from_raw_parts(data, datalen) }; + test_msg_simple!(lightning::ln::msgs::Stfu, data); +} diff --git a/fuzz/src/onion_message.rs b/fuzz/src/onion_message.rs index fcc8dc3cad2..c071d806e93 100644 --- a/fuzz/src/onion_message.rs +++ b/fuzz/src/onion_message.rs @@ -1,12 +1,13 @@ // Imports that need to be added manually use bitcoin::bech32::u5; -use bitcoin::blockdata::script::Script; +use bitcoin::blockdata::script::ScriptBuf; use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey}; use bitcoin::secp256k1::ecdh::SharedSecret; use bitcoin::secp256k1::ecdsa::RecoverableSignature; use bitcoin::secp256k1::schnorr; use lightning::sign::{Recipient, KeyMaterial, EntropySource, NodeSigner, SignerProvider}; +use lightning::ln::features::InitFeatures; use lightning::ln::msgs::{self, DecodeError, OnionMessageHandler}; use lightning::ln::script::ShutdownScript; use lightning::offers::invoice::UnsignedBolt12Invoice; @@ -39,9 +40,20 @@ pub fn do_test(data: &[u8], logger: &L) { &keys_manager, &keys_manager, logger, &message_router, &offers_msg_handler, &custom_msg_handler ); - let mut pk = [2; 33]; pk[1] = 0xff; - let peer_node_id_not_used = PublicKey::from_slice(&pk).unwrap(); - onion_messenger.handle_onion_message(&peer_node_id_not_used, &msg); + + let peer_node_id = { + let mut secret_bytes = [0; 32]; + secret_bytes[31] = 2; + let secret = SecretKey::from_slice(&secret_bytes).unwrap(); + PublicKey::from_secret_key(&Secp256k1::signing_only(), &secret) + }; + + let mut features = InitFeatures::empty(); + features.set_onion_messages_optional(); + let init = msgs::Init { features, networks: None, remote_network_address: None }; + + onion_messenger.peer_connected(&peer_node_id, &init, false).unwrap(); + onion_messenger.handle_onion_message(&peer_node_id, &msg); } } @@ -187,13 +199,14 @@ impl SignerProvider for KeyProvider { fn read_chan_signer(&self, _data: &[u8]) -> Result { unreachable!() } - fn get_destination_script(&self) -> Result { unreachable!() } + fn get_destination_script(&self) -> Result { unreachable!() } fn get_shutdown_scriptpubkey(&self) -> Result { unreachable!() } } #[cfg(test)] mod tests { + use bitcoin::hashes::hex::FromHex; use lightning::util::logger::{Logger, Record}; use std::collections::HashMap; use std::sync::Mutex; @@ -211,25 +224,168 @@ mod tests { #[test] fn test_no_onion_message_breakage() { - let two_unblinded_hops_om = "020000000000000000000000000000000000000000000000000000000000000e01055600020000000000000000000000000000000000000000000000000000000000000e0135043304210202020202020202020202020202020202020202020202020202020202020202026d000000000000000000000000000000eb0000000000000000000000000000000000000000000000000000000000000036041096000000000000000000000000000000fd1092202a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004800000000000000000000000000000000000000000000000000000000000000"; + let one_hop_om = "\ + 020000000000000000000000000000000000000000000000000000000000000e01055600020000000000000\ + 000000000000000000000000000000000000000000000000e01ae0276020000000000000000000000000000\ + 000000000000000000000000000000000002020000000000000000000000000000000000000000000000000\ + 000000000000e0101022a0000000000000000000000000000014551231950b75fc4402da1732fc9bebf0010\ + 9500000000000000000000000000000004106d000000000000000000000000000000fd1092202a2a2a2a2a2\ + a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a0000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000005600000000000000000000000000000000000000000000\ + 000000000000000000"; + let logger = TrackingLogger { lines: Mutex::new(HashMap::new()) }; + super::do_test(&>::from_hex(one_hop_om).unwrap(), &logger); + { + let log_entries = logger.lines.lock().unwrap(); + assert_eq!(log_entries.get(&("lightning::onion_message::messenger".to_string(), + "Received an onion message with path_id None and a reply_path".to_string())), Some(&1)); + assert_eq!(log_entries.get(&("lightning::onion_message::messenger".to_string(), + "Sending onion message when responding to Custom onion message with path_id None".to_string())), Some(&1)); + } + + let two_unblinded_hops_om = "\ + 020000000000000000000000000000000000000000000000000000000000000e01055600020000000000000\ + 000000000000000000000000000000000000000000000000e01350433042102020202020202020202020202\ + 02020202020202020202020202020202020202026d000000000000000000000000000000eb0000000000000\ + 000000000000000000000000000000000000000000000000036041096000000000000000000000000000000\ + fd1092202a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000004800000000000000000000000000000000000000000000\ + 000000000000000000"; let logger = TrackingLogger { lines: Mutex::new(HashMap::new()) }; - super::do_test(&::hex::decode(two_unblinded_hops_om).unwrap(), &logger); + super::do_test(&>::from_hex(two_unblinded_hops_om).unwrap(), &logger); { let log_entries = logger.lines.lock().unwrap(); assert_eq!(log_entries.get(&("lightning::onion_message::messenger".to_string(), "Forwarding an onion message to peer 020202020202020202020202020202020202020202020202020202020202020202".to_string())), Some(&1)); } - let two_unblinded_two_blinded_om = "020000000000000000000000000000000000000000000000000000000000000e01055600020000000000000000000000000000000000000000000000000000000000000e0135043304210202020202020202020202020202020202020202020202020202020202020202026d0000000000000000000000000000009e0000000000000000000000000000000000000000000000000000000000000058045604210203030303030303030303030303030303030303030303030303030303030303020821020000000000000000000000000000000000000000000000000000000000000e0196000000000000000000000000000000e9000000000000000000000000000000000000000000000000000000000000003504330421020404040404040404040404040404040404040404040404040404040404040402ca00000000000000000000000000000042000000000000000000000000000000000000000000000000000000000000003604103f000000000000000000000000000000fd1092202a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004800000000000000000000000000000000000000000000000000000000000000"; + let two_unblinded_two_blinded_om = "\ + 020000000000000000000000000000000000000000000000000000000000000e01055600020000000000000\ + 000000000000000000000000000000000000000000000000e01350433042102020202020202020202020202\ + 02020202020202020202020202020202020202026d0000000000000000000000000000009e0000000000000\ + 000000000000000000000000000000000000000000000000058045604210203030303030303030303030303\ + 030303030303030303030303030303030303020821020000000000000000000000000000000000000000000\ + 000000000000000000e0196000000000000000000000000000000e900000000000000000000000000000000\ + 000000000000000000000000000000350433042102040404040404040404040404040404040404040404040\ + 4040404040404040402ca000000000000000000000000000000420000000000000000000000000000000000\ + 00000000000000000000000000003604103f000000000000000000000000000000fd1092202a2a2a2a2a2a2\ + a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000004800000000000000000000000000000000000000000000\ + 000000000000000000"; let logger = TrackingLogger { lines: Mutex::new(HashMap::new()) }; - super::do_test(&::hex::decode(two_unblinded_two_blinded_om).unwrap(), &logger); + super::do_test(&>::from_hex(two_unblinded_two_blinded_om).unwrap(), &logger); { let log_entries = logger.lines.lock().unwrap(); assert_eq!(log_entries.get(&("lightning::onion_message::messenger".to_string(), "Forwarding an onion message to peer 020202020202020202020202020202020202020202020202020202020202020202".to_string())), Some(&1)); } - let three_blinded_om = "020000000000000000000000000000000000000000000000000000000000000e01055600020000000000000000000000000000000000000000000000000000000000000e0135043304210202020202020202020202020202020202020202020202020202020202020202026d000000000000000000000000000000b20000000000000000000000000000000000000000000000000000000000000035043304210203030303030303030303030303030303030303030303030303030303030303029600000000000000000000000000000033000000000000000000000000000000000000000000000000000000000000003604104e000000000000000000000000000000fd1092202a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004800000000000000000000000000000000000000000000000000000000000000"; + let three_blinded_om = "\ + 020000000000000000000000000000000000000000000000000000000000000e01055600020000000000000\ + 000000000000000000000000000000000000000000000000e01350433042102020202020202020202020202\ + 02020202020202020202020202020202020202026d000000000000000000000000000000b20000000000000\ + 000000000000000000000000000000000000000000000000035043304210203030303030303030303030303\ + 030303030303030303030303030303030303029600000000000000000000000000000033000000000000000\ + 000000000000000000000000000000000000000000000003604104e000000000000000000000000000000fd\ + 1092202a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a00000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000004800000000000000000000000000000000000000000000\ + 000000000000000000"; let logger = TrackingLogger { lines: Mutex::new(HashMap::new()) }; - super::do_test(&::hex::decode(three_blinded_om).unwrap(), &logger); + super::do_test(&>::from_hex(three_blinded_om).unwrap(), &logger); { let log_entries = logger.lines.lock().unwrap(); assert_eq!(log_entries.get(&("lightning::onion_message::messenger".to_string(), "Forwarding an onion message to peer 020202020202020202020202020202020202020202020202020202020202020202".to_string())), Some(&1)); diff --git a/fuzz/src/peer_crypt.rs b/fuzz/src/peer_crypt.rs index f6df392fcef..3acf4d664f6 100644 --- a/fuzz/src/peer_crypt.rs +++ b/fuzz/src/peer_crypt.rs @@ -7,7 +7,7 @@ // You may not use this file except in accordance with one or both of these // licenses. -use lightning::ln::peer_channel_encryptor::PeerChannelEncryptor; +use lightning::ln::peer_channel_encryptor::{PeerChannelEncryptor, MessageBuf}; use lightning::util::test_utils::TestNodeSigner; use bitcoin::secp256k1::{Secp256k1, PublicKey, SecretKey}; @@ -74,15 +74,17 @@ pub fn do_test(data: &[u8]) { assert!(crypter.is_ready_for_encryption()); crypter }; + let mut buf = [0; 65536 + 16]; loop { if get_slice!(1)[0] == 0 { - crypter.encrypt_buffer(get_slice!(slice_to_be16(get_slice!(2)))); + crypter.encrypt_buffer(MessageBuf::from_encoded(&get_slice!(slice_to_be16(get_slice!(2))))); } else { let len = match crypter.decrypt_length_header(get_slice!(16+2)) { Ok(len) => len, Err(_) => return, }; - match crypter.decrypt_message(get_slice!(len as usize + 16)) { + buf.copy_from_slice(&get_slice!(len as usize + 16)); + match crypter.decrypt_message(&mut buf[..len as usize + 16]) { Ok(_) => {}, Err(_) => return, } diff --git a/fuzz/targets.h b/fuzz/targets.h index cad0ac4d822..841ed55cea3 100644 --- a/fuzz/targets.h +++ b/fuzz/targets.h @@ -57,3 +57,7 @@ void msg_tx_signatures_run(const unsigned char* data, size_t data_len); void msg_tx_init_rbf_run(const unsigned char* data, size_t data_len); void msg_tx_ack_rbf_run(const unsigned char* data, size_t data_len); void msg_tx_abort_run(const unsigned char* data, size_t data_len); +void msg_stfu_run(const unsigned char* data, size_t data_len); +void msg_splice_run(const unsigned char* data, size_t data_len); +void msg_splice_ack_run(const unsigned char* data, size_t data_len); +void msg_splice_locked_run(const unsigned char* data, size_t data_len); diff --git a/lightning-background-processor/Cargo.toml b/lightning-background-processor/Cargo.toml index 59f77ac5fa4..933cbc466a6 100644 --- a/lightning-background-processor/Cargo.toml +++ b/lightning-background-processor/Cargo.toml @@ -21,7 +21,7 @@ no-std = ["bitcoin/no-std", "lightning/no-std", "lightning-rapid-gossip-sync/no- default = ["std"] [dependencies] -bitcoin = { version = "0.29.0", default-features = false } +bitcoin = { version = "0.30.2", default-features = false } lightning = { version = "0.0.118", path = "../lightning", default-features = false } lightning-rapid-gossip-sync = { version = "0.0.118", path = "../lightning-rapid-gossip-sync", default-features = false } diff --git a/lightning-background-processor/src/lib.rs b/lightning-background-processor/src/lib.rs index aa6d0b0615e..76252324efc 100644 --- a/lightning-background-processor/src/lib.rs +++ b/lightning-background-processor/src/lib.rs @@ -845,7 +845,7 @@ impl Drop for BackgroundProcessor { #[cfg(all(feature = "std", test))] mod tests { use bitcoin::blockdata::constants::{genesis_block, ChainHash}; - use bitcoin::blockdata::locktime::PackedLockTime; + use bitcoin::blockdata::locktime::absolute::LockTime; use bitcoin::blockdata::transaction::{Transaction, TxOut}; use bitcoin::network::constants::Network; use bitcoin::secp256k1::{SecretKey, PublicKey, Secp256k1}; @@ -1241,7 +1241,7 @@ mod tests { macro_rules! begin_open_channel { ($node_a: expr, $node_b: expr, $channel_value: expr) => {{ - $node_a.node.create_channel($node_b.node.get_our_node_id(), $channel_value, 100, 42, None).unwrap(); + $node_a.node.create_channel($node_b.node.get_our_node_id(), $channel_value, 100, 42, None, None).unwrap(); $node_b.node.handle_open_channel(&$node_a.node.get_our_node_id(), &get_event_msg!($node_a, MessageSendEvent::SendOpenChannel, $node_b.node.get_our_node_id())); $node_a.node.handle_accept_channel(&$node_b.node.get_our_node_id(), &get_event_msg!($node_b, MessageSendEvent::SendAcceptChannel, $node_a.node.get_our_node_id())); }} @@ -1254,7 +1254,7 @@ mod tests { assert_eq!(channel_value_satoshis, $channel_value); assert_eq!(user_channel_id, 42); - let tx = Transaction { version: 1 as i32, lock_time: PackedLockTime(0), input: Vec::new(), output: vec![TxOut { + let tx = Transaction { version: 1 as i32, lock_time: LockTime::ZERO, input: Vec::new(), output: vec![TxOut { value: channel_value_satoshis, script_pubkey: output_script.clone(), }]}; (temporary_channel_id, tx) diff --git a/lightning-block-sync/Cargo.toml b/lightning-block-sync/Cargo.toml index d9bb4ee7b1e..5a8f887c1d8 100644 --- a/lightning-block-sync/Cargo.toml +++ b/lightning-block-sync/Cargo.toml @@ -18,7 +18,8 @@ rest-client = [ "serde_json", "chunked_transfer" ] rpc-client = [ "serde_json", "chunked_transfer" ] [dependencies] -bitcoin = "0.29.0" +bitcoin = "0.30.2" +hex = { package = "hex-conservative", version = "0.1.1", default-features = false } lightning = { version = "0.0.118", path = "../lightning" } tokio = { version = "1.0", features = [ "io-util", "net", "time" ], optional = true } serde_json = { version = "1.0", optional = true } diff --git a/lightning-block-sync/src/convert.rs b/lightning-block-sync/src/convert.rs index bf9e9577619..0f9ab8c43ba 100644 --- a/lightning-block-sync/src/convert.rs +++ b/lightning-block-sync/src/convert.rs @@ -1,8 +1,8 @@ use crate::http::{BinaryResponse, JsonResponse}; -use crate::utils::hex_to_uint256; +use crate::utils::hex_to_work; use crate::{BlockHeaderData, BlockSourceError}; -use bitcoin::blockdata::block::{Block, BlockHeader}; +use bitcoin::blockdata::block::{Block, Header}; use bitcoin::consensus::encode; use bitcoin::hash_types::{BlockHash, TxMerkleNode, Txid}; use bitcoin::hashes::hex::FromHex; @@ -88,17 +88,21 @@ impl TryFrom for BlockHeaderData { } } Ok(BlockHeaderData { - header: BlockHeader { - version: get_field!("version", as_i64).try_into().map_err(|_| ())?, + header: Header { + version: bitcoin::blockdata::block::Version::from_consensus( + get_field!("version", as_i64).try_into().map_err(|_| ())? + ), prev_blockhash: if let Some(hash_str) = response.get("previousblockhash") { - BlockHash::from_hex(hash_str.as_str().ok_or(())?).map_err(|_| ())? + BlockHash::from_str(hash_str.as_str().ok_or(())?).map_err(|_| ())? } else { BlockHash::all_zeros() }, - merkle_root: TxMerkleNode::from_hex(get_field!("merkleroot", as_str)).map_err(|_| ())?, + merkle_root: TxMerkleNode::from_str(get_field!("merkleroot", as_str)).map_err(|_| ())?, time: get_field!("time", as_u64).try_into().map_err(|_| ())?, - bits: u32::from_be_bytes(<[u8; 4]>::from_hex(get_field!("bits", as_str)).map_err(|_| ())?), + bits: bitcoin::CompactTarget::from_consensus( + u32::from_be_bytes(<[u8; 4]>::from_hex(get_field!("bits", as_str)).map_err(|_| ())?) + ), nonce: get_field!("nonce", as_u64).try_into().map_err(|_| ())?, }, - chainwork: hex_to_uint256(get_field!("chainwork", as_str)).map_err(|_| ())?, + chainwork: hex_to_work(get_field!("chainwork", as_str)).map_err(|_| ())?, height: get_field!("height", as_u64).try_into().map_err(|_| ())?, }) } @@ -132,7 +136,7 @@ impl TryInto<(BlockHash, Option)> for JsonResponse { } let hash = match &self.0["bestblockhash"] { - serde_json::Value::String(hex_data) => match BlockHash::from_hex(&hex_data) { + serde_json::Value::String(hex_data) => match BlockHash::from_str(&hex_data) { Err(_) => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid hex data")), Ok(block_hash) => block_hash, }, @@ -288,8 +292,8 @@ pub(crate) mod tests { use super::*; use bitcoin::blockdata::constants::genesis_block; use bitcoin::hashes::Hash; - use bitcoin::hashes::hex::ToHex; use bitcoin::network::constants::Network; + use hex::DisplayHex; use serde_json::value::Number; use serde_json::Value; @@ -298,14 +302,14 @@ pub(crate) mod tests { fn from(data: BlockHeaderData) -> Self { let BlockHeaderData { chainwork, height, header } = data; serde_json::json!({ - "chainwork": chainwork.to_string()["0x".len()..], + "chainwork": chainwork.to_be_bytes().as_hex().to_string(), "height": height, - "version": header.version, - "merkleroot": header.merkle_root.to_hex(), + "version": header.version.to_consensus(), + "merkleroot": header.merkle_root.to_string(), "time": header.time, "nonce": header.nonce, - "bits": header.bits.to_hex(), - "previousblockhash": header.prev_blockhash.to_hex(), + "bits": header.bits.to_consensus().to_be_bytes().as_hex().to_string(), + "previousblockhash": header.prev_blockhash.to_string(), }) } } @@ -394,7 +398,7 @@ pub(crate) mod tests { #[test] fn into_block_header_from_json_response_with_valid_header_array() { let genesis_block = genesis_block(Network::Bitcoin); - let best_block_header = BlockHeader { + let best_block_header = Header { prev_blockhash: genesis_block.block_hash(), ..genesis_block.header }; @@ -541,7 +545,7 @@ pub(crate) mod tests { fn into_block_hash_from_json_response_without_height() { let block = genesis_block(Network::Bitcoin); let response = JsonResponse(serde_json::json!({ - "bestblockhash": block.block_hash().to_hex(), + "bestblockhash": block.block_hash().to_string(), })); match TryInto::<(BlockHash, Option)>::try_into(response) { Err(e) => panic!("Unexpected error: {:?}", e), @@ -556,7 +560,7 @@ pub(crate) mod tests { fn into_block_hash_from_json_response_with_unexpected_blocks_type() { let block = genesis_block(Network::Bitcoin); let response = JsonResponse(serde_json::json!({ - "bestblockhash": block.block_hash().to_hex(), + "bestblockhash": block.block_hash().to_string(), "blocks": "foo", })); match TryInto::<(BlockHash, Option)>::try_into(response) { @@ -572,7 +576,7 @@ pub(crate) mod tests { fn into_block_hash_from_json_response_with_invalid_height() { let block = genesis_block(Network::Bitcoin); let response = JsonResponse(serde_json::json!({ - "bestblockhash": block.block_hash().to_hex(), + "bestblockhash": block.block_hash().to_string(), "blocks": std::u64::MAX, })); match TryInto::<(BlockHash, Option)>::try_into(response) { @@ -588,7 +592,7 @@ pub(crate) mod tests { fn into_block_hash_from_json_response_with_height() { let block = genesis_block(Network::Bitcoin); let response = JsonResponse(serde_json::json!({ - "bestblockhash": block.block_hash().to_hex(), + "bestblockhash": block.block_hash().to_string(), "blocks": 1, })); match TryInto::<(BlockHash, Option)>::try_into(response) { diff --git a/lightning-block-sync/src/init.rs b/lightning-block-sync/src/init.rs index 5423bba5182..df113fb012a 100644 --- a/lightning-block-sync/src/init.rs +++ b/lightning-block-sync/src/init.rs @@ -4,7 +4,7 @@ use crate::{BlockSource, BlockSourceResult, Cache, ChainNotifier}; use crate::poll::{ChainPoller, Validate, ValidatedBlockHeader}; -use bitcoin::blockdata::block::BlockHeader; +use bitcoin::blockdata::block::Header; use bitcoin::hash_types::BlockHash; use bitcoin::network::constants::Network; @@ -211,11 +211,11 @@ impl<'a, C: Cache> Cache for ReadOnlyCache<'a, C> { struct DynamicChainListener<'a, L: chain::Listen + ?Sized>(&'a L); impl<'a, L: chain::Listen + ?Sized> chain::Listen for DynamicChainListener<'a, L> { - fn filtered_block_connected(&self, _header: &BlockHeader, _txdata: &chain::transaction::TransactionData, _height: u32) { + fn filtered_block_connected(&self, _header: &Header, _txdata: &chain::transaction::TransactionData, _height: u32) { unreachable!() } - fn block_disconnected(&self, header: &BlockHeader, height: u32) { + fn block_disconnected(&self, header: &Header, height: u32) { self.0.block_disconnected(header, height) } } @@ -234,7 +234,7 @@ impl<'a, L: chain::Listen + ?Sized> chain::Listen for ChainListenerSet<'a, L> { } } - fn filtered_block_connected(&self, header: &BlockHeader, txdata: &chain::transaction::TransactionData, height: u32) { + fn filtered_block_connected(&self, header: &Header, txdata: &chain::transaction::TransactionData, height: u32) { for (starting_height, chain_listener) in self.0.iter() { if height > *starting_height { chain_listener.filtered_block_connected(header, txdata, height); @@ -242,7 +242,7 @@ impl<'a, L: chain::Listen + ?Sized> chain::Listen for ChainListenerSet<'a, L> { } } - fn block_disconnected(&self, _header: &BlockHeader, _height: u32) { + fn block_disconnected(&self, _header: &Header, _height: u32) { unreachable!() } } diff --git a/lightning-block-sync/src/lib.rs b/lightning-block-sync/src/lib.rs index 3561a1b5d76..77ff3f0810b 100644 --- a/lightning-block-sync/src/lib.rs +++ b/lightning-block-sync/src/lib.rs @@ -47,9 +47,9 @@ mod utils; use crate::poll::{ChainTip, Poll, ValidatedBlockHeader}; -use bitcoin::blockdata::block::{Block, BlockHeader}; +use bitcoin::blockdata::block::{Block, Header}; use bitcoin::hash_types::BlockHash; -use bitcoin::util::uint::Uint256; +use bitcoin::pow::Work; use lightning::chain; use lightning::chain::Listen; @@ -147,14 +147,13 @@ impl BlockSourceError { #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct BlockHeaderData { /// The block header itself. - pub header: BlockHeader, + pub header: Header, /// The block height where the genesis block has height 0. pub height: u32, - /// The total chain work in expected number of double-SHA256 hashes required to build a chain - /// of equivalent weight. - pub chainwork: Uint256, + /// The total chain work required to build a chain of equivalent weight. + pub chainwork: Work, } /// A block including either all its transactions or only the block header. @@ -166,7 +165,7 @@ pub enum BlockData { /// A block containing all its transactions. FullBlock(Block), /// A block header for when the block does not contain any pertinent transactions. - HeaderOnly(BlockHeader), + HeaderOnly(Header), } /// A lightweight client for keeping a listener in sync with the chain, allowing for Simplified diff --git a/lightning-block-sync/src/poll.rs b/lightning-block-sync/src/poll.rs index e7171cf3656..dcc19a4969d 100644 --- a/lightning-block-sync/src/poll.rs +++ b/lightning-block-sync/src/poll.rs @@ -60,7 +60,7 @@ impl Validate for BlockHeaderData { fn validate(self, block_hash: BlockHash) -> BlockSourceResult { let pow_valid_block_hash = self.header - .validate_pow(&self.header.target()) + .validate_pow(self.header.target()) .map_err(BlockSourceError::persistent)?; if pow_valid_block_hash != block_hash { @@ -81,7 +81,7 @@ impl Validate for BlockData { }; let pow_valid_block_hash = header - .validate_pow(&header.target()) + .validate_pow(header.target()) .map_err(BlockSourceError::persistent)?; if pow_valid_block_hash != block_hash { @@ -138,8 +138,8 @@ impl ValidatedBlockHeader { 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; + let min_target = previous_target.min_difficulty_transition_threshold(); + let max_target = previous_target.max_difficulty_transition_threshold(); if target > max_target || target < min_target { return Err(BlockSourceError::persistent("invalid difficulty transition")) } @@ -262,7 +262,6 @@ mod tests { use crate::*; use crate::test_utils::Blockchain; use super::*; - use bitcoin::util::uint::Uint256; #[tokio::test] async fn poll_empty_chain() { @@ -302,7 +301,7 @@ mod tests { // Invalidate the tip by changing its target. chain.blocks.last_mut().unwrap().header.bits = - BlockHeader::compact_target_from_u256(&Uint256::from_be_bytes([0; 32])); + bitcoin::Target::from_be_bytes([0x01; 32]).to_compact_lossy(); let poller = ChainPoller::new(&chain, Network::Bitcoin); match poller.poll_chain_tip(best_known_chain_tip).await { diff --git a/lightning-block-sync/src/rest.rs b/lightning-block-sync/src/rest.rs index 5690da12ea0..74a460a7ab5 100644 --- a/lightning-block-sync/src/rest.rs +++ b/lightning-block-sync/src/rest.rs @@ -8,7 +8,6 @@ use crate::convert::GetUtxosResponse; use bitcoin::OutPoint; use bitcoin::hash_types::BlockHash; -use bitcoin::hashes::hex::ToHex; use std::convert::TryFrom; use std::convert::TryInto; @@ -44,14 +43,14 @@ impl RestClient { impl BlockSource for RestClient { fn get_header<'a>(&'a self, header_hash: &'a BlockHash, _height: Option) -> AsyncBlockSourceResult<'a, BlockHeaderData> { Box::pin(async move { - let resource_path = format!("headers/1/{}.json", header_hash.to_hex()); + let resource_path = format!("headers/1/{}.json", header_hash.to_string()); Ok(self.request_resource::(&resource_path).await?) }) } fn get_block<'a>(&'a self, header_hash: &'a BlockHash) -> AsyncBlockSourceResult<'a, BlockData> { Box::pin(async move { - let resource_path = format!("block/{}.bin", header_hash.to_hex()); + let resource_path = format!("block/{}.bin", header_hash.to_string()); Ok(BlockData::FullBlock(self.request_resource::(&resource_path).await?)) }) } @@ -73,7 +72,7 @@ impl UtxoSource for RestClient { fn is_output_unspent<'a>(&'a self, outpoint: OutPoint) -> AsyncBlockSourceResult<'a, bool> { Box::pin(async move { - let resource_path = format!("getutxos/{}-{}.json", outpoint.txid.to_hex(), outpoint.vout); + let resource_path = format!("getutxos/{}-{}.json", outpoint.txid.to_string(), outpoint.vout); let utxo_result = self.request_resource::(&resource_path).await?; Ok(utxo_result.hit_bitmap_nonempty) @@ -145,7 +144,7 @@ mod tests { )); let client = RestClient::new(server.endpoint()).unwrap(); - let outpoint = OutPoint::new(bitcoin::Txid::from_inner([0; 32]), 0); + let outpoint = OutPoint::new(bitcoin::Txid::from_byte_array([0; 32]), 0); let unspent_output = client.is_output_unspent(outpoint).await.unwrap(); assert_eq!(unspent_output, false); } @@ -159,7 +158,7 @@ mod tests { )); let client = RestClient::new(server.endpoint()).unwrap(); - let outpoint = OutPoint::new(bitcoin::Txid::from_inner([0; 32]), 0); + let outpoint = OutPoint::new(bitcoin::Txid::from_byte_array([0; 32]), 0); let unspent_output = client.is_output_unspent(outpoint).await.unwrap(); assert_eq!(unspent_output, true); } diff --git a/lightning-block-sync/src/rpc.rs b/lightning-block-sync/src/rpc.rs index 0ad94040aca..d296088ae7e 100644 --- a/lightning-block-sync/src/rpc.rs +++ b/lightning-block-sync/src/rpc.rs @@ -6,7 +6,6 @@ use crate::http::{HttpClient, HttpEndpoint, HttpError, JsonResponse}; use crate::gossip::UtxoSource; use bitcoin::hash_types::BlockHash; -use bitcoin::hashes::hex::ToHex; use bitcoin::OutPoint; use std::sync::Mutex; @@ -120,14 +119,14 @@ impl RpcClient { impl BlockSource for RpcClient { fn get_header<'a>(&'a self, header_hash: &'a BlockHash, _height: Option) -> AsyncBlockSourceResult<'a, BlockHeaderData> { Box::pin(async move { - let header_hash = serde_json::json!(header_hash.to_hex()); + let header_hash = serde_json::json!(header_hash.to_string()); Ok(self.call_method("getblockheader", &[header_hash]).await?) }) } fn get_block<'a>(&'a self, header_hash: &'a BlockHash) -> AsyncBlockSourceResult<'a, BlockData> { Box::pin(async move { - let header_hash = serde_json::json!(header_hash.to_hex()); + let header_hash = serde_json::json!(header_hash.to_string()); let verbosity = serde_json::json!(0); Ok(BlockData::FullBlock(self.call_method("getblock", &[header_hash, verbosity]).await?)) }) @@ -150,7 +149,7 @@ impl UtxoSource for RpcClient { fn is_output_unspent<'a>(&'a self, outpoint: OutPoint) -> AsyncBlockSourceResult<'a, bool> { Box::pin(async move { - let txid_param = serde_json::json!(outpoint.txid.to_hex()); + let txid_param = serde_json::json!(outpoint.txid.to_string()); let vout_param = serde_json::json!(outpoint.vout); let include_mempool = serde_json::json!(false); let utxo_opt: serde_json::Value = self.call_method( @@ -275,7 +274,7 @@ mod tests { let response = serde_json::json!({ "result": null }); let server = HttpServer::responding_with_ok(MessageBody::Content(response)); let client = RpcClient::new(CREDENTIALS, server.endpoint()).unwrap(); - let outpoint = OutPoint::new(bitcoin::Txid::from_inner([0; 32]), 0); + let outpoint = OutPoint::new(bitcoin::Txid::from_byte_array([0; 32]), 0); let unspent_output = client.is_output_unspent(outpoint).await.unwrap(); assert_eq!(unspent_output, false); } @@ -285,7 +284,7 @@ mod tests { let response = serde_json::json!({ "result": {"bestblock": 1, "confirmations": 42}}); let server = HttpServer::responding_with_ok(MessageBody::Content(response)); let client = RpcClient::new(CREDENTIALS, server.endpoint()).unwrap(); - let outpoint = OutPoint::new(bitcoin::Txid::from_inner([0; 32]), 0); + let outpoint = OutPoint::new(bitcoin::Txid::from_byte_array([0; 32]), 0); let unspent_output = client.is_output_unspent(outpoint).await.unwrap(); assert_eq!(unspent_output, true); } diff --git a/lightning-block-sync/src/test_utils.rs b/lightning-block-sync/src/test_utils.rs index 597d2a85fd5..b6fa6617c84 100644 --- a/lightning-block-sync/src/test_utils.rs +++ b/lightning-block-sync/src/test_utils.rs @@ -1,13 +1,12 @@ use crate::{AsyncBlockSourceResult, BlockData, BlockHeaderData, BlockSource, BlockSourceError, UnboundedCache}; use crate::poll::{Validate, ValidatedBlockHeader}; -use bitcoin::blockdata::block::{Block, BlockHeader}; +use bitcoin::blockdata::block::{Block, Header, Version}; use bitcoin::blockdata::constants::genesis_block; -use bitcoin::hash_types::BlockHash; +use bitcoin::blockdata::locktime::absolute::LockTime; +use bitcoin::hash_types::{BlockHash, TxMerkleNode}; use bitcoin::network::constants::Network; -use bitcoin::util::uint::Uint256; -use bitcoin::util::hash::bitcoin_merkle_root; -use bitcoin::{PackedLockTime, Transaction}; +use bitcoin::Transaction; use lightning::chain; @@ -35,7 +34,7 @@ impl Blockchain { pub fn with_height(mut self, height: usize) -> Self { self.blocks.reserve_exact(height); - let bits = BlockHeader::compact_target_from_u256(&Uint256::from_be_bytes([0xff; 32])); + let bits = bitcoin::Target::from_be_bytes([0xff; 32]).to_compact_lossy(); for i in 1..=height { let prev_block = &self.blocks[i - 1]; let prev_blockhash = prev_block.block_hash(); @@ -46,16 +45,16 @@ impl Blockchain { // but that's OK because those tests don't trigger the check. let coinbase = Transaction { version: 0, - lock_time: PackedLockTime::ZERO, + lock_time: LockTime::ZERO, input: vec![], output: vec![] }; - let merkle_root = bitcoin_merkle_root(vec![coinbase.txid().as_hash()].into_iter()).unwrap(); + let merkle_root = TxMerkleNode::from_raw_hash(coinbase.txid().to_raw_hash()); self.blocks.push(Block { - header: BlockHeader { - version: 0, + header: Header { + version: Version::NO_SOFT_FORK_SIGNALLING, prev_blockhash, - merkle_root: merkle_root.into(), + merkle_root, time, bits, nonce: 0, @@ -103,8 +102,12 @@ impl Blockchain { fn at_height_unvalidated(&self, height: usize) -> BlockHeaderData { assert!(!self.blocks.is_empty()); assert!(height < self.blocks.len()); + let mut total_work = self.blocks[0].header.work(); + for i in 1..=height { + total_work = total_work + self.blocks[i].header.work(); + } BlockHeaderData { - chainwork: self.blocks[0].header.work() + Uint256::from_u64(height as u64).unwrap(), + chainwork: total_work, height: height as u32, header: self.blocks[height].header, } @@ -188,8 +191,8 @@ impl BlockSource for Blockchain { pub struct NullChainListener; impl chain::Listen for NullChainListener { - fn filtered_block_connected(&self, _header: &BlockHeader, _txdata: &chain::transaction::TransactionData, _height: u32) {} - fn block_disconnected(&self, _header: &BlockHeader, _height: u32) {} + fn filtered_block_connected(&self, _header: &Header, _txdata: &chain::transaction::TransactionData, _height: u32) {} + fn block_disconnected(&self, _header: &Header, _height: u32) {} } pub struct MockChainListener { @@ -236,7 +239,7 @@ impl chain::Listen for MockChainListener { } } - fn filtered_block_connected(&self, header: &BlockHeader, _txdata: &chain::transaction::TransactionData, height: u32) { + fn filtered_block_connected(&self, header: &Header, _txdata: &chain::transaction::TransactionData, height: u32) { match self.expected_filtered_blocks_connected.borrow_mut().pop_front() { None => { panic!("Unexpected filtered block connected: {:?}", header.block_hash()); @@ -248,7 +251,7 @@ impl chain::Listen for MockChainListener { } } - fn block_disconnected(&self, header: &BlockHeader, height: u32) { + fn block_disconnected(&self, header: &Header, height: u32) { match self.expected_blocks_disconnected.borrow_mut().pop_front() { None => { panic!("Unexpected block disconnected: {:?}", header.block_hash()); diff --git a/lightning-block-sync/src/utils.rs b/lightning-block-sync/src/utils.rs index 96a2e578877..b841f5f9d41 100644 --- a/lightning-block-sync/src/utils.rs +++ b/lightning-block-sync/src/utils.rs @@ -1,54 +1,54 @@ use bitcoin::hashes::hex::FromHex; -use bitcoin::util::uint::Uint256; +use bitcoin::pow::Work; -pub fn hex_to_uint256(hex: &str) -> Result { +pub fn hex_to_work(hex: &str) -> Result { let bytes = <[u8; 32]>::from_hex(hex)?; - Ok(Uint256::from_be_bytes(bytes)) + Ok(Work::from_be_bytes(bytes)) } #[cfg(test)] mod tests { use super::*; - use bitcoin::util::uint::Uint256; + use bitcoin::pow::Work; #[test] - fn hex_to_uint256_empty_str() { - assert!(hex_to_uint256("").is_err()); + fn hex_to_work_empty_str() { + assert!(hex_to_work("").is_err()); } #[test] - fn hex_to_uint256_too_short_str() { + fn hex_to_work_too_short_str() { let hex = String::from_utf8(vec![b'0'; 32]).unwrap(); - assert_eq!(hex_to_uint256(&hex), Err(bitcoin::hashes::hex::Error::InvalidLength(64, 32))); + assert_eq!(hex_to_work(&hex), Err(bitcoin::hashes::hex::Error::InvalidLength(64, 32))); } #[test] - fn hex_to_uint256_too_long_str() { + fn hex_to_work_too_long_str() { let hex = String::from_utf8(vec![b'0'; 128]).unwrap(); - assert_eq!(hex_to_uint256(&hex), Err(bitcoin::hashes::hex::Error::InvalidLength(64, 128))); + assert_eq!(hex_to_work(&hex), Err(bitcoin::hashes::hex::Error::InvalidLength(64, 128))); } #[test] - fn hex_to_uint256_odd_length_str() { + fn hex_to_work_odd_length_str() { let hex = String::from_utf8(vec![b'0'; 65]).unwrap(); - assert_eq!(hex_to_uint256(&hex), Err(bitcoin::hashes::hex::Error::OddLengthString(65))); + assert_eq!(hex_to_work(&hex), Err(bitcoin::hashes::hex::Error::OddLengthString(65))); } #[test] - fn hex_to_uint256_invalid_char() { + fn hex_to_work_invalid_char() { let hex = String::from_utf8(vec![b'G'; 64]).unwrap(); - assert_eq!(hex_to_uint256(&hex), Err(bitcoin::hashes::hex::Error::InvalidChar(b'G'))); + assert_eq!(hex_to_work(&hex), Err(bitcoin::hashes::hex::Error::InvalidChar(b'G'))); } #[test] - fn hex_to_uint256_lowercase_str() { - let hex: String = std::iter::repeat("0123456789abcdef").take(4).collect(); - assert_eq!(hex_to_uint256(&hex).unwrap(), Uint256([0x0123456789abcdefu64; 4])); + fn hex_to_work_lowercase_str() { + let hex: String = std::iter::repeat("1a").take(32).collect(); + assert_eq!(hex_to_work(&hex).unwrap(), Work::from_be_bytes([0x1a; 32])); } #[test] - fn hex_to_uint256_uppercase_str() { - let hex: String = std::iter::repeat("0123456789ABCDEF").take(4).collect(); - assert_eq!(hex_to_uint256(&hex).unwrap(), Uint256([0x0123456789abcdefu64; 4])); + fn hex_to_work_uppercase_str() { + let hex: String = std::iter::repeat("1A").take(32).collect(); + assert_eq!(hex_to_work(&hex).unwrap(), Work::from_be_bytes([0x1A; 32])); } } diff --git a/lightning-custom-message/Cargo.toml b/lightning-custom-message/Cargo.toml index 2efce6bcd2c..182ff498512 100644 --- a/lightning-custom-message/Cargo.toml +++ b/lightning-custom-message/Cargo.toml @@ -14,5 +14,5 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [dependencies] -bitcoin = "0.29.0" +bitcoin = "0.30.2" lightning = { version = "0.0.118", path = "../lightning" } diff --git a/lightning-invoice/Cargo.toml b/lightning-invoice/Cargo.toml index a34bd63b7f2..0b45fa41e6c 100644 --- a/lightning-invoice/Cargo.toml +++ b/lightning-invoice/Cargo.toml @@ -22,14 +22,14 @@ std = ["bitcoin_hashes/std", "num-traits/std", "lightning/std", "bech32/std"] [dependencies] bech32 = { version = "0.9.0", default-features = false } lightning = { version = "0.0.118", path = "../lightning", default-features = false } -secp256k1 = { version = "0.24.0", default-features = false, features = ["recovery", "alloc"] } +secp256k1 = { version = "0.27.0", default-features = false, features = ["recovery", "alloc"] } num-traits = { version = "0.2.8", default-features = false } -bitcoin_hashes = { version = "0.11", default-features = false } +bitcoin_hashes = { version = "0.12.0", default-features = false } hashbrown = { version = "0.8", optional = true } serde = { version = "1.0.118", optional = true } -bitcoin = { version = "0.29.0", default-features = false } +bitcoin = { version = "0.30.2", default-features = false } [dev-dependencies] lightning = { version = "0.0.118", path = "../lightning", default-features = false, features = ["_test_utils"] } -hex = "0.4" +hex = { package = "hex-conservative", version = "0.1.1", default-features = false } serde_json = { version = "1"} diff --git a/lightning-invoice/src/de.rs b/lightning-invoice/src/de.rs index 5bfa9a042c3..0276b742f28 100644 --- a/lightning-invoice/src/de.rs +++ b/lightning-invoice/src/de.rs @@ -10,7 +10,7 @@ use core::str::FromStr; use bech32::{u5, FromBase32}; use bitcoin::{PubkeyHash, ScriptHash}; -use bitcoin::util::address::WitnessVersion; +use bitcoin::address::WitnessVersion; use bitcoin_hashes::Hash; use bitcoin_hashes::sha256; use crate::prelude::*; @@ -726,8 +726,8 @@ mod test { use crate::de::Bolt11ParseError; use secp256k1::PublicKey; use bech32::u5; - use bitcoin_hashes::hex::FromHex; use bitcoin_hashes::sha256; + use std::str::FromStr; const CHARSET_REV: [i8; 128] = [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, @@ -778,7 +778,7 @@ mod test { "qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypq".as_bytes() ); - let hash = sha256::Hash::from_hex( + let hash = sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap(); let expected = Ok(Sha256(hash)); @@ -855,7 +855,7 @@ mod test { use crate::Fallback; use bech32::FromBase32; use bitcoin::{PubkeyHash, ScriptHash}; - use bitcoin::util::address::WitnessVersion; + use bitcoin::address::WitnessVersion; use bitcoin_hashes::Hash; let cases = vec![ @@ -988,7 +988,7 @@ mod test { data: RawDataPart { timestamp: PositiveTimestamp::from_unix_timestamp(1496314658).unwrap(), tagged_fields: vec ! [ - PaymentHash(Sha256(sha256::Hash::from_hex( + PaymentHash(Sha256(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap())).into(), Description(crate::Description::new("coffee beans".to_owned()).unwrap()).into(), @@ -1035,7 +1035,7 @@ mod test { data: RawDataPart { timestamp: PositiveTimestamp::from_unix_timestamp(1496314658).unwrap(), tagged_fields: vec ! [ - PaymentHash(Sha256(sha256::Hash::from_hex( + PaymentHash(Sha256(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap())).into(), Description( diff --git a/lightning-invoice/src/lib.rs b/lightning-invoice/src/lib.rs index d953795cf8e..d30fb638d34 100644 --- a/lightning-invoice/src/lib.rs +++ b/lightning-invoice/src/lib.rs @@ -46,7 +46,7 @@ use std::time::SystemTime; use bech32::u5; use bitcoin::{Address, Network, PubkeyHash, ScriptHash}; -use bitcoin::util::address::{Payload, WitnessVersion}; +use bitcoin::address::{Payload, WitnessProgram, WitnessVersion}; use bitcoin_hashes::{Hash, sha256}; use lightning::ln::features::Bolt11InvoiceFeatures; use lightning::util::invoice::construct_invoice_preimage; @@ -73,6 +73,7 @@ pub use lightning::ln::PaymentSecret; pub use lightning::routing::router::{RouteHint, RouteHintHop}; #[doc(no_inline)] pub use lightning::routing::gossip::RoutingFees; +use lightning::util::string::UntrustedString; mod de; mod ser; @@ -82,9 +83,9 @@ mod prelude { #[cfg(feature = "hashbrown")] extern crate hashbrown; - pub use alloc::{vec, vec::Vec, string::String, collections::VecDeque, boxed::Box}; + pub use alloc::{vec, vec::Vec, string::String}; #[cfg(not(feature = "hashbrown"))] - pub use std::collections::{HashMap, HashSet, hash_map}; + pub use std::collections::{HashMap, hash_map}; #[cfg(feature = "hashbrown")] pub use self::hashbrown::{HashMap, HashSet, hash_map}; @@ -93,12 +94,6 @@ mod prelude { use crate::prelude::*; -/// Sync compat for std/no_std -#[cfg(feature = "std")] -mod sync { - pub use ::std::sync::{Mutex, MutexGuard}; -} - /// Sync compat for std/no_std #[cfg(not(feature = "std"))] mod sync; @@ -269,6 +264,15 @@ pub enum Bolt11InvoiceDescription<'f> { Hash(&'f Sha256), } +impl<'f> Display for Bolt11InvoiceDescription<'f> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Bolt11InvoiceDescription::Direct(desc) => write!(f, "{}", desc.0), + Bolt11InvoiceDescription::Hash(hash) => write!(f, "{}", hash.0), + } + } +} + /// Represents a signed [`RawBolt11Invoice`] with cached hash. The signature is not checked and may be /// invalid. /// @@ -403,6 +407,7 @@ impl From for Currency { Network::Testnet => Currency::BitcoinTestnet, Network::Regtest => Currency::Regtest, Network::Signet => Currency::Signet, + _ => unreachable!(), } } } @@ -470,8 +475,8 @@ impl Sha256 { /// /// # Invariants /// The description can be at most 639 __bytes__ long -#[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] -pub struct Description(String); +#[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Default)] +pub struct Description(UntrustedString); /// Payee public key #[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] @@ -520,7 +525,7 @@ impl Ord for Bolt11InvoiceSignature { /// The encoded route has to be <1024 5bit characters long (<=639 bytes or <=12 hops) /// #[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] -pub struct PrivateRoute(RouteHint); +pub struct PrivateRoute(pub RouteHint); /// Tag constants as specified in BOLT11 #[allow(missing_docs)] @@ -675,7 +680,7 @@ impl InvoiceBui pub fn invoice_description(self, description: Bolt11InvoiceDescription) -> InvoiceBuilder { match description { Bolt11InvoiceDescription::Direct(desc) => { - self.description(desc.clone().into_inner()) + self.description(desc.clone().into_inner().0) } Bolt11InvoiceDescription::Hash(hash) => { self.description_hash(hash.0) @@ -1136,6 +1141,12 @@ impl PositiveTimestamp { } } +impl From for Duration { + fn from(val: PositiveTimestamp) -> Self { + val.0 + } +} + #[cfg(feature = "std")] impl From for SystemTime { fn from(val: PositiveTimestamp) -> Self { @@ -1413,10 +1424,13 @@ impl Bolt11Invoice { /// Returns a list of all fallback addresses as [`Address`]es pub fn fallback_addresses(&self) -> Vec
{ - self.fallbacks().iter().map(|fallback| { + self.fallbacks().iter().filter_map(|fallback| { let payload = match fallback { Fallback::SegWitProgram { version, program } => { - Payload::WitnessProgram { version: *version, program: program.to_vec() } + match WitnessProgram::new(*version, program.clone()) { + Ok(witness_program) => Payload::WitnessProgram(witness_program), + Err(_) => return None, + } } Fallback::PubKeyHash(pkh) => { Payload::PubkeyHash(*pkh) @@ -1426,7 +1440,7 @@ impl Bolt11Invoice { } }; - Address { payload, network: self.network() } + Some(Address::new(self.network(), payload)) }).collect() } @@ -1502,27 +1516,19 @@ impl Description { if description.len() > 639 { Err(CreationError::DescriptionTooLong) } else { - Ok(Description(description)) + Ok(Description(UntrustedString(description))) } } - /// Returns the underlying description [`String`] - pub fn into_inner(self) -> String { + /// Returns the underlying description [`UntrustedString`] + pub fn into_inner(self) -> UntrustedString { self.0 } } -impl From for String { - fn from(val: Description) -> Self { - val.into_inner() - } -} - -impl Deref for Description { - type Target = str; - - fn deref(&self) -> &str { - &self.0 +impl Display for Description { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) } } @@ -1747,9 +1753,9 @@ impl<'de> Deserialize<'de> for Bolt11Invoice { #[cfg(test)] mod test { - use bitcoin::Script; - use bitcoin_hashes::hex::FromHex; + use bitcoin::ScriptBuf; use bitcoin_hashes::sha256; + use std::str::FromStr; #[test] fn test_system_time_bounds_assumptions() { @@ -1773,7 +1779,7 @@ mod test { data: RawDataPart { timestamp: PositiveTimestamp::from_unix_timestamp(1496314658).unwrap(), tagged_fields: vec![ - PaymentHash(crate::Sha256(sha256::Hash::from_hex( + PaymentHash(crate::Sha256(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap())).into(), Description(crate::Description::new( @@ -1811,7 +1817,7 @@ mod test { data: RawDataPart { timestamp: PositiveTimestamp::from_unix_timestamp(1496314658).unwrap(), tagged_fields: vec ! [ - PaymentHash(Sha256(sha256::Hash::from_hex( + PaymentHash(Sha256(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap())).into(), Description( @@ -1867,7 +1873,7 @@ mod test { use lightning::ln::features::Bolt11InvoiceFeatures; use secp256k1::Secp256k1; use secp256k1::SecretKey; - use crate::{Bolt11Invoice, RawBolt11Invoice, RawHrp, RawDataPart, Currency, Sha256, PositiveTimestamp, + use crate::{Bolt11Invoice, RawBolt11Invoice, RawHrp, RawDataPart, Currency, Sha256, PositiveTimestamp, Bolt11SemanticError}; let private_key = SecretKey::from_slice(&[42; 32]).unwrap(); @@ -1881,7 +1887,7 @@ mod test { data: RawDataPart { timestamp: PositiveTimestamp::from_unix_timestamp(1496314658).unwrap(), tagged_fields: vec ! [ - PaymentHash(Sha256(sha256::Hash::from_hex( + PaymentHash(Sha256(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap())).into(), Description( @@ -2138,7 +2144,7 @@ mod test { assert_eq!(invoice.expiry_time(), Duration::from_secs(54321)); assert_eq!(invoice.min_final_cltv_expiry_delta(), 144); assert_eq!(invoice.fallbacks(), vec![&Fallback::PubKeyHash(PubkeyHash::from_slice(&[0;20]).unwrap())]); - let address = Address::from_script(&Script::new_p2pkh(&PubkeyHash::from_slice(&[0;20]).unwrap()), Network::Testnet).unwrap(); + let address = Address::from_script(&ScriptBuf::new_p2pkh(&PubkeyHash::from_slice(&[0;20]).unwrap()), Network::Testnet).unwrap(); assert_eq!(invoice.fallback_addresses(), vec![address]); assert_eq!(invoice.private_routes(), vec![&PrivateRoute(route_1), &PrivateRoute(route_2)]); assert_eq!( diff --git a/lightning-invoice/src/payment.rs b/lightning-invoice/src/payment.rs index 89842591fde..7306d12f5e6 100644 --- a/lightning-invoice/src/payment.rs +++ b/lightning-invoice/src/payment.rs @@ -10,237 +10,74 @@ //! Convenient utilities for paying Lightning invoices. use crate::Bolt11Invoice; -use crate::prelude::*; +use crate::bitcoin_hashes::Hash; -use bitcoin_hashes::Hash; - -use lightning::chain; -use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator}; -use lightning::sign::{NodeSigner, SignerProvider, EntropySource}; use lightning::ln::PaymentHash; -use lightning::ln::channelmanager::{AChannelManager, ChannelManager, PaymentId, Retry, RetryableSendFailure, RecipientOnionFields, ProbeSendFailure}; -use lightning::routing::router::{PaymentParameters, RouteParameters, Router}; -use lightning::util::logger::Logger; - -use core::fmt::Debug; -use core::ops::Deref; -use core::time::Duration; - -/// Pays the given [`Bolt11Invoice`], retrying if needed based on [`Retry`]. -/// -/// [`Bolt11Invoice::payment_hash`] is used as the [`PaymentId`], which ensures idempotency as long -/// as the payment is still pending. If the payment succeeds, you must ensure that a second payment -/// with the same [`PaymentHash`] is never sent. -/// -/// If you wish to use a different payment idempotency token, see [`pay_invoice_with_id`]. -pub fn pay_invoice( - invoice: &Bolt11Invoice, retry_strategy: Retry, channelmanager: C -) -> Result -where C::Target: AChannelManager, -{ - let payment_id = PaymentId(invoice.payment_hash().into_inner()); - pay_invoice_with_id(invoice, payment_id, retry_strategy, channelmanager.get_cm()) - .map(|()| payment_id) -} +use lightning::ln::channelmanager::RecipientOnionFields; +use lightning::routing::router::{PaymentParameters, RouteParameters}; -/// Pays the given [`Bolt11Invoice`] with a custom idempotency key, retrying if needed based on -/// [`Retry`]. +/// Builds the necessary parameters to pay or pre-flight probe the given zero-amount +/// [`Bolt11Invoice`] using [`ChannelManager::send_payment`] or +/// [`ChannelManager::send_preflight_probes`]. /// -/// Note that idempotency is only guaranteed as long as the payment is still pending. Once the -/// payment completes or fails, no idempotency guarantees are made. +/// Prior to paying, you must ensure that the [`Bolt11Invoice::payment_hash`] is unique and the +/// same [`PaymentHash`] has never been paid before. /// -/// You should ensure that the [`Bolt11Invoice::payment_hash`] is unique and the same -/// [`PaymentHash`] has never been paid before. +/// Will always succeed unless the invoice has an amount specified, in which case +/// [`payment_parameters_from_invoice`] should be used. /// -/// See [`pay_invoice`] for a variant which uses the [`PaymentHash`] for the idempotency token. -pub fn pay_invoice_with_id( - invoice: &Bolt11Invoice, payment_id: PaymentId, retry_strategy: Retry, channelmanager: C -) -> Result<(), PaymentError> -where C::Target: AChannelManager, -{ - let amt_msat = invoice.amount_milli_satoshis().ok_or(PaymentError::Invoice("amount missing"))?; - pay_invoice_using_amount(invoice, amt_msat, payment_id, retry_strategy, channelmanager.get_cm()) -} - -/// Pays the given zero-value [`Bolt11Invoice`] using the given amount, retrying if needed based on -/// [`Retry`]. -/// -/// [`Bolt11Invoice::payment_hash`] is used as the [`PaymentId`], which ensures idempotency as long -/// as the payment is still pending. If the payment succeeds, you must ensure that a second payment -/// with the same [`PaymentHash`] is never sent. -/// -/// If you wish to use a different payment idempotency token, see -/// [`pay_zero_value_invoice_with_id`]. -pub fn pay_zero_value_invoice( - invoice: &Bolt11Invoice, amount_msats: u64, retry_strategy: Retry, channelmanager: C -) -> Result -where C::Target: AChannelManager, -{ - let payment_id = PaymentId(invoice.payment_hash().into_inner()); - pay_zero_value_invoice_with_id(invoice, amount_msats, payment_id, retry_strategy, - channelmanager) - .map(|()| payment_id) +/// [`ChannelManager::send_payment`]: lightning::ln::channelmanager::ChannelManager::send_payment +/// [`ChannelManager::send_preflight_probes`]: lightning::ln::channelmanager::ChannelManager::send_preflight_probes +pub fn payment_parameters_from_zero_amount_invoice(invoice: &Bolt11Invoice, amount_msat: u64) +-> Result<(PaymentHash, RecipientOnionFields, RouteParameters), ()> { + if invoice.amount_milli_satoshis().is_some() { + Err(()) + } else { + Ok(params_from_invoice(invoice, amount_msat)) + } } -/// Pays the given zero-value [`Bolt11Invoice`] using the given amount and custom idempotency key, -/// retrying if needed based on [`Retry`]. +/// Builds the necessary parameters to pay or pre-flight probe the given [`Bolt11Invoice`] using +/// [`ChannelManager::send_payment`] or [`ChannelManager::send_preflight_probes`]. /// -/// Note that idempotency is only guaranteed as long as the payment is still pending. Once the -/// payment completes or fails, no idempotency guarantees are made. +/// Prior to paying, you must ensure that the [`Bolt11Invoice::payment_hash`] is unique and the +/// same [`PaymentHash`] has never been paid before. /// -/// You should ensure that the [`Bolt11Invoice::payment_hash`] is unique and the same -/// [`PaymentHash`] has never been paid before. +/// Will always succeed unless the invoice has no amount specified, in which case +/// [`payment_parameters_from_zero_amount_invoice`] should be used. /// -/// See [`pay_zero_value_invoice`] for a variant which uses the [`PaymentHash`] for the -/// idempotency token. -pub fn pay_zero_value_invoice_with_id( - invoice: &Bolt11Invoice, amount_msats: u64, payment_id: PaymentId, retry_strategy: Retry, - channelmanager: C -) -> Result<(), PaymentError> -where C::Target: AChannelManager, -{ - if invoice.amount_milli_satoshis().is_some() { - Err(PaymentError::Invoice("amount unexpected")) +/// [`ChannelManager::send_payment`]: lightning::ln::channelmanager::ChannelManager::send_payment +/// [`ChannelManager::send_preflight_probes`]: lightning::ln::channelmanager::ChannelManager::send_preflight_probes +pub fn payment_parameters_from_invoice(invoice: &Bolt11Invoice) +-> Result<(PaymentHash, RecipientOnionFields, RouteParameters), ()> { + if let Some(amount_msat) = invoice.amount_milli_satoshis() { + Ok(params_from_invoice(invoice, amount_msat)) } else { - pay_invoice_using_amount(invoice, amount_msats, payment_id, retry_strategy, - channelmanager.get_cm()) + Err(()) } } -fn pay_invoice_using_amount( - invoice: &Bolt11Invoice, amount_msats: u64, payment_id: PaymentId, retry_strategy: Retry, - payer: P -) -> Result<(), PaymentError> where P::Target: Payer { - let payment_hash = PaymentHash((*invoice.payment_hash()).into_inner()); +fn params_from_invoice(invoice: &Bolt11Invoice, amount_msat: u64) +-> (PaymentHash, RecipientOnionFields, RouteParameters) { + let payment_hash = PaymentHash((*invoice.payment_hash()).to_byte_array()); + let mut recipient_onion = RecipientOnionFields::secret_only(*invoice.payment_secret()); recipient_onion.payment_metadata = invoice.payment_metadata().map(|v| v.clone()); - let mut payment_params = PaymentParameters::from_node_id(invoice.recover_payee_pub_key(), - invoice.min_final_cltv_expiry_delta() as u32) - .with_expiry_time(expiry_time_from_unix_epoch(invoice).as_secs()) - .with_route_hints(invoice.route_hints()).unwrap(); - if let Some(features) = invoice.features() { - payment_params = payment_params.with_bolt11_features(features.clone()).unwrap(); - } - let route_params = RouteParameters::from_payment_params_and_value(payment_params, amount_msats); - - payer.send_payment(payment_hash, recipient_onion, payment_id, route_params, retry_strategy) -} - -/// Sends payment probes over all paths of a route that would be used to pay the given invoice. -/// -/// See [`ChannelManager::send_preflight_probes`] for more information. -pub fn preflight_probe_invoice( - invoice: &Bolt11Invoice, channelmanager: C, liquidity_limit_multiplier: Option, -) -> Result, ProbingError> -where C::Target: AChannelManager, -{ - let amount_msat = if let Some(invoice_amount_msat) = invoice.amount_milli_satoshis() { - invoice_amount_msat - } else { - return Err(ProbingError::Invoice("Failed to send probe as no amount was given in the invoice.")); - }; let mut payment_params = PaymentParameters::from_node_id( - invoice.recover_payee_pub_key(), - invoice.min_final_cltv_expiry_delta() as u32, - ) - .with_expiry_time(expiry_time_from_unix_epoch(invoice).as_secs()) - .with_route_hints(invoice.route_hints()) - .unwrap(); - - if let Some(features) = invoice.features() { - payment_params = payment_params.with_bolt11_features(features.clone()).unwrap(); - } - let route_params = RouteParameters::from_payment_params_and_value(payment_params, amount_msat); - - channelmanager.get_cm().send_preflight_probes(route_params, liquidity_limit_multiplier) - .map_err(ProbingError::Sending) -} - -/// Sends payment probes over all paths of a route that would be used to pay the given zero-value -/// invoice using the given amount. -/// -/// See [`ChannelManager::send_preflight_probes`] for more information. -pub fn preflight_probe_zero_value_invoice( - invoice: &Bolt11Invoice, amount_msat: u64, channelmanager: C, - liquidity_limit_multiplier: Option, -) -> Result, ProbingError> -where C::Target: AChannelManager, -{ - if invoice.amount_milli_satoshis().is_some() { - return Err(ProbingError::Invoice("amount unexpected")); + invoice.recover_payee_pub_key(), + invoice.min_final_cltv_expiry_delta() as u32 + ) + .with_route_hints(invoice.route_hints()).unwrap(); + if let Some(expiry) = invoice.expires_at() { + payment_params = payment_params.with_expiry_time(expiry.as_secs()); } - - let mut payment_params = PaymentParameters::from_node_id( - invoice.recover_payee_pub_key(), - invoice.min_final_cltv_expiry_delta() as u32, - ) - .with_expiry_time(expiry_time_from_unix_epoch(invoice).as_secs()) - .with_route_hints(invoice.route_hints()) - .unwrap(); - if let Some(features) = invoice.features() { payment_params = payment_params.with_bolt11_features(features.clone()).unwrap(); } - let route_params = RouteParameters::from_payment_params_and_value(payment_params, amount_msat); - - channelmanager.get_cm().send_preflight_probes(route_params, liquidity_limit_multiplier) - .map_err(ProbingError::Sending) -} - -fn expiry_time_from_unix_epoch(invoice: &Bolt11Invoice) -> Duration { - invoice.signed_invoice.raw_invoice.data.timestamp.0 + invoice.expiry_time() -} - -/// An error that may occur when making a payment. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum PaymentError { - /// An error resulting from the provided [`Bolt11Invoice`] or payment hash. - Invoice(&'static str), - /// An error occurring when sending a payment. - Sending(RetryableSendFailure), -} -/// An error that may occur when sending a payment probe. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum ProbingError { - /// An error resulting from the provided [`Bolt11Invoice`]. - Invoice(&'static str), - /// An error occurring when sending a payment probe. - Sending(ProbeSendFailure), -} - -/// A trait defining behavior of a [`Bolt11Invoice`] payer. -/// -/// Useful for unit testing internal methods. -trait Payer { - /// Sends a payment over the Lightning Network using the given [`Route`]. - /// - /// [`Route`]: lightning::routing::router::Route - fn send_payment( - &self, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields, - payment_id: PaymentId, route_params: RouteParameters, retry_strategy: Retry - ) -> Result<(), PaymentError>; -} - -impl Payer for ChannelManager -where - M::Target: chain::Watch<::Signer>, - T::Target: BroadcasterInterface, - ES::Target: EntropySource, - NS::Target: NodeSigner, - SP::Target: SignerProvider, - F::Target: FeeEstimator, - R::Target: Router, - L::Target: Logger, -{ - fn send_payment( - &self, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields, - payment_id: PaymentId, route_params: RouteParameters, retry_strategy: Retry - ) -> Result<(), PaymentError> { - self.send_payment(payment_hash, recipient_onion, payment_id, route_params, retry_strategy) - .map_err(PaymentError::Sending) - } + let route_params = RouteParameters::from_payment_params_and_value(payment_params, amount_msat); + (payment_hash, recipient_onion, route_params) } #[cfg(test)] @@ -249,64 +86,14 @@ mod tests { use crate::{InvoiceBuilder, Currency}; use bitcoin_hashes::sha256::Hash as Sha256; use lightning::events::Event; + use lightning::ln::channelmanager::{Retry, PaymentId}; use lightning::ln::msgs::ChannelMessageHandler; - use lightning::ln::{PaymentPreimage, PaymentSecret}; + use lightning::ln::PaymentSecret; use lightning::ln::functional_test_utils::*; - use secp256k1::{SecretKey, Secp256k1}; - use std::collections::VecDeque; + use lightning::routing::router::Payee; + use secp256k1::{SecretKey, PublicKey, Secp256k1}; use std::time::{SystemTime, Duration}; - struct TestPayer { - expectations: core::cell::RefCell>, - } - - impl TestPayer { - fn new() -> Self { - Self { - expectations: core::cell::RefCell::new(VecDeque::new()), - } - } - - fn expect_send(self, value_msat: Amount) -> Self { - self.expectations.borrow_mut().push_back(value_msat); - self - } - - fn check_value_msats(&self, actual_value_msats: Amount) { - let expected_value_msats = self.expectations.borrow_mut().pop_front(); - if let Some(expected_value_msats) = expected_value_msats { - assert_eq!(actual_value_msats, expected_value_msats); - } else { - panic!("Unexpected amount: {:?}", actual_value_msats); - } - } - } - - #[derive(Clone, Debug, PartialEq, Eq)] - struct Amount(u64); // msat - - impl Payer for TestPayer { - fn send_payment( - &self, _payment_hash: PaymentHash, _recipient_onion: RecipientOnionFields, - _payment_id: PaymentId, route_params: RouteParameters, _retry_strategy: Retry - ) -> Result<(), PaymentError> { - self.check_value_msats(Amount(route_params.final_value_msat)); - Ok(()) - } - } - - impl Drop for TestPayer { - fn drop(&mut self) { - if std::thread::panicking() { - return; - } - - if !self.expectations.borrow().is_empty() { - panic!("Unsatisfied payment expectations: {:?}", self.expectations.borrow()); - } - } - } - fn duration_since_epoch() -> Duration { #[cfg(feature = "std")] let duration_since_epoch = @@ -316,11 +103,14 @@ mod tests { duration_since_epoch } - fn invoice(payment_preimage: PaymentPreimage) -> Bolt11Invoice { - let payment_hash = Sha256::hash(&payment_preimage.0); + #[test] + fn invoice_test() { + let payment_hash = Sha256::hash(&[0; 32]); let private_key = SecretKey::from_slice(&[42; 32]).unwrap(); + let secp_ctx = Secp256k1::new(); + let public_key = PublicKey::from_secret_key(&secp_ctx, &private_key); - InvoiceBuilder::new(Currency::Bitcoin) + let invoice = InvoiceBuilder::new(Currency::Bitcoin) .description("test".into()) .payment_hash(payment_hash) .payment_secret(PaymentSecret([0; 32])) @@ -328,63 +118,53 @@ mod tests { .min_final_cltv_expiry_delta(144) .amount_milli_satoshis(128) .build_signed(|hash| { - Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key) + secp_ctx.sign_ecdsa_recoverable(hash, &private_key) }) - .unwrap() + .unwrap(); + + assert!(payment_parameters_from_zero_amount_invoice(&invoice, 42).is_err()); + + let (hash, onion, params) = payment_parameters_from_invoice(&invoice).unwrap(); + assert_eq!(&hash.0[..], &payment_hash[..]); + assert_eq!(onion.payment_secret, Some(PaymentSecret([0; 32]))); + assert_eq!(params.final_value_msat, 128); + match params.payment_params.payee { + Payee::Clear { node_id, .. } => { + assert_eq!(node_id, public_key); + }, + _ => panic!(), + } } - fn zero_value_invoice(payment_preimage: PaymentPreimage) -> Bolt11Invoice { - let payment_hash = Sha256::hash(&payment_preimage.0); + #[test] + fn zero_value_invoice_test() { + let payment_hash = Sha256::hash(&[0; 32]); let private_key = SecretKey::from_slice(&[42; 32]).unwrap(); + let secp_ctx = Secp256k1::new(); + let public_key = PublicKey::from_secret_key(&secp_ctx, &private_key); - InvoiceBuilder::new(Currency::Bitcoin) + let invoice = InvoiceBuilder::new(Currency::Bitcoin) .description("test".into()) .payment_hash(payment_hash) .payment_secret(PaymentSecret([0; 32])) .duration_since_epoch(duration_since_epoch()) .min_final_cltv_expiry_delta(144) .build_signed(|hash| { - Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key) + secp_ctx.sign_ecdsa_recoverable(hash, &private_key) }) - .unwrap() - } + .unwrap(); - #[test] - fn pays_invoice() { - let payment_id = PaymentId([42; 32]); - let payment_preimage = PaymentPreimage([1; 32]); - let invoice = invoice(payment_preimage); - let final_value_msat = invoice.amount_milli_satoshis().unwrap(); - - let payer = TestPayer::new().expect_send(Amount(final_value_msat)); - pay_invoice_using_amount(&invoice, final_value_msat, payment_id, Retry::Attempts(0), &payer).unwrap(); - } + assert!(payment_parameters_from_invoice(&invoice).is_err()); - #[test] - fn pays_zero_value_invoice() { - let payment_id = PaymentId([42; 32]); - let payment_preimage = PaymentPreimage([1; 32]); - let invoice = zero_value_invoice(payment_preimage); - let amt_msat = 10_000; - - let payer = TestPayer::new().expect_send(Amount(amt_msat)); - pay_invoice_using_amount(&invoice, amt_msat, payment_id, Retry::Attempts(0), &payer).unwrap(); - } - - #[test] - fn fails_paying_zero_value_invoice_with_amount() { - let chanmon_cfgs = create_chanmon_cfgs(1); - let node_cfgs = create_node_cfgs(1, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(1, &node_cfgs, &[None]); - let nodes = create_network(1, &node_cfgs, &node_chanmgrs); - - let payment_preimage = PaymentPreimage([1; 32]); - let invoice = invoice(payment_preimage); - let amt_msat = 10_000; - - match pay_zero_value_invoice(&invoice, amt_msat, Retry::Attempts(0), nodes[0].node) { - Err(PaymentError::Invoice("amount unexpected")) => {}, - _ => panic!() + let (hash, onion, params) = payment_parameters_from_zero_amount_invoice(&invoice, 42).unwrap(); + assert_eq!(&hash.0[..], &payment_hash[..]); + assert_eq!(onion.payment_secret, Some(PaymentSecret([0; 32]))); + assert_eq!(params.final_value_msat, 42); + match params.payment_params.payee { + Payee::Clear { node_id, .. } => { + assert_eq!(node_id, public_key); + }, + _ => panic!(), } } @@ -418,7 +198,8 @@ mod tests { }) .unwrap(); - pay_invoice(&invoice, Retry::Attempts(0), nodes[0].node).unwrap(); + let (hash, onion, params) = payment_parameters_from_invoice(&invoice).unwrap(); + nodes[0].node.send_payment(hash, onion, PaymentId(hash.0), params, Retry::Attempts(0)).unwrap(); check_added_monitors(&nodes[0], 1); let send_event = SendEvent::from_node(&nodes[0]); nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &send_event.msgs[0]); diff --git a/lightning-invoice/src/ser.rs b/lightning-invoice/src/ser.rs index dc5dba45da0..fe42f72b653 100644 --- a/lightning-invoice/src/ser.rs +++ b/lightning-invoice/src/ser.rs @@ -279,13 +279,13 @@ impl Base32Len for Sha256 { impl ToBase32 for Description { fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { - self.as_bytes().write_base32(writer) + self.0.0.as_bytes().write_base32(writer) } } impl Base32Len for Description { fn base32_len(&self) -> usize { - self.0.as_bytes().base32_len() + self.0.0.as_bytes().base32_len() } } diff --git a/lightning-invoice/src/utils.rs b/lightning-invoice/src/utils.rs index a512b2de05d..9930b545662 100644 --- a/lightning-invoice/src/utils.rs +++ b/lightning-invoice/src/utils.rs @@ -158,7 +158,7 @@ where let invoice = match description { Bolt11InvoiceDescription::Direct(description) => { - InvoiceBuilder::new(network).description(description.0.clone()) + InvoiceBuilder::new(network).description(description.0.0.clone()) } Bolt11InvoiceDescription::Hash(hash) => InvoiceBuilder::new(network).description_hash(hash.0), }; @@ -538,7 +538,7 @@ fn _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_has let invoice = match description { Bolt11InvoiceDescription::Direct(description) => { - InvoiceBuilder::new(network).description(description.0.clone()) + InvoiceBuilder::new(network).description(description.0.0.clone()) } Bolt11InvoiceDescription::Hash(hash) => InvoiceBuilder::new(network).description_hash(hash.0), }; @@ -808,6 +808,7 @@ mod test { use lightning::util::config::UserConfig; use crate::utils::{create_invoice_from_channelmanager_and_duration_since_epoch, rotate_through_iterators}; use std::collections::HashSet; + use lightning::util::string::UntrustedString; #[test] fn test_prefer_current_channel() { @@ -852,7 +853,7 @@ mod test { assert_eq!(invoice.amount_pico_btc(), Some(100_000)); // If no `min_final_cltv_expiry_delta` is specified, then it should be `MIN_FINAL_CLTV_EXPIRY_DELTA`. assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); - assert_eq!(invoice.description(), Bolt11InvoiceDescription::Direct(&Description("test".to_string()))); + assert_eq!(invoice.description(), Bolt11InvoiceDescription::Direct(&Description(UntrustedString("test".to_string())))); assert_eq!(invoice.expiry_time(), Duration::from_secs(non_default_invoice_expiry_secs.into())); // Invoice SCIDs should always use inbound SCID aliases over the real channel ID, if one is @@ -872,8 +873,7 @@ mod test { let route_params = RouteParameters::from_payment_params_and_value( payment_params, invoice.amount_milli_satoshis().unwrap()); let payment_event = { - let mut payment_hash = PaymentHash([0; 32]); - payment_hash.0.copy_from_slice(&invoice.payment_hash().as_ref()[0..32]); + let payment_hash = PaymentHash(invoice.payment_hash().to_byte_array()); nodes[0].node.send_payment(payment_hash, RecipientOnionFields::secret_only(*invoice.payment_secret()), PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap(); @@ -963,7 +963,7 @@ mod test { ).unwrap(); assert_eq!(invoice.amount_pico_btc(), Some(100_000)); assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); - assert_eq!(invoice.description(), Bolt11InvoiceDescription::Direct(&Description("test".to_string()))); + assert_eq!(invoice.description(), Bolt11InvoiceDescription::Direct(&Description(UntrustedString("test".to_string())))); assert_eq!(invoice.payment_hash(), &sha256::Hash::from_slice(&payment_hash.0[..]).unwrap()); } @@ -1141,7 +1141,7 @@ mod test { // is never handled, the `channel.counterparty.forwarding_info` is never assigned. let mut private_chan_cfg = UserConfig::default(); private_chan_cfg.channel_handshake_config.announced_channel = false; - let temporary_channel_id = nodes[2].node.create_channel(nodes[0].node.get_our_node_id(), 1_000_000, 500_000_000, 42, Some(private_chan_cfg)).unwrap(); + let temporary_channel_id = nodes[2].node.create_channel(nodes[0].node.get_our_node_id(), 1_000_000, 500_000_000, 42, None, Some(private_chan_cfg)).unwrap(); let open_channel = get_event_msg!(nodes[2], MessageSendEvent::SendOpenChannel, nodes[0].node.get_our_node_id()); nodes[0].node.handle_open_channel(&nodes[2].node.get_our_node_id(), &open_channel); let accept_channel = get_event_msg!(nodes[0], MessageSendEvent::SendAcceptChannel, nodes[2].node.get_our_node_id()); @@ -1294,7 +1294,7 @@ mod test { let user_payment_preimage = PaymentPreimage([1; 32]); let payment_hash = if user_generated_pmt_hash { - Some(PaymentHash(Sha256::hash(&user_payment_preimage.0[..]).into_inner())) + Some(PaymentHash(Sha256::hash(&user_payment_preimage.0[..]).to_byte_array())) } else { None }; @@ -1307,7 +1307,7 @@ mod test { route_hints, nodes[1].keys_manager, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet, None, Duration::from_secs(genesis_timestamp) ).unwrap(); - let (payment_hash, payment_secret) = (PaymentHash(invoice.payment_hash().into_inner()), *invoice.payment_secret()); + let (payment_hash, payment_secret) = (PaymentHash(invoice.payment_hash().to_byte_array()), *invoice.payment_secret()); let payment_preimage = if user_generated_pmt_hash { user_payment_preimage } else { @@ -1315,7 +1315,7 @@ mod test { }; assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); - assert_eq!(invoice.description(), Bolt11InvoiceDescription::Direct(&Description("test".to_string()))); + assert_eq!(invoice.description(), Bolt11InvoiceDescription::Direct(&Description(UntrustedString("test".to_string())))); assert_eq!(invoice.route_hints().len(), 2); assert_eq!(invoice.expiry_time(), Duration::from_secs(non_default_invoice_expiry_secs.into())); assert!(!invoice.features().unwrap().supports_basic_mpp()); @@ -1327,8 +1327,7 @@ mod test { let params = RouteParameters::from_payment_params_and_value( payment_params, invoice.amount_milli_satoshis().unwrap()); let (payment_event, fwd_idx) = { - let mut payment_hash = PaymentHash([0; 32]); - payment_hash.0.copy_from_slice(&invoice.payment_hash().as_ref()[0..32]); + let payment_hash = PaymentHash(invoice.payment_hash().to_byte_array()); nodes[0].node.send_payment(payment_hash, RecipientOnionFields::secret_only(*invoice.payment_secret()), PaymentId(payment_hash.0), params, Retry::Attempts(0)).unwrap(); @@ -1453,7 +1452,7 @@ mod test { nodes[2].node.get_phantom_route_hints(), ]; let user_payment_preimage = PaymentPreimage([1; 32]); - let payment_hash = Some(PaymentHash(Sha256::hash(&user_payment_preimage.0[..]).into_inner())); + let payment_hash = Some(PaymentHash(Sha256::hash(&user_payment_preimage.0[..]).to_byte_array())); let non_default_invoice_expiry_secs = 4200; let min_final_cltv_expiry_delta = Some(100); let duration_since_epoch = Duration::from_secs(1234567); @@ -1547,7 +1546,7 @@ mod test { // is never handled, the `channel.counterparty.forwarding_info` is never assigned. let mut private_chan_cfg = UserConfig::default(); private_chan_cfg.channel_handshake_config.announced_channel = false; - let temporary_channel_id = nodes[1].node.create_channel(nodes[3].node.get_our_node_id(), 1_000_000, 500_000_000, 42, Some(private_chan_cfg)).unwrap(); + let temporary_channel_id = nodes[1].node.create_channel(nodes[3].node.get_our_node_id(), 1_000_000, 500_000_000, 42, None, Some(private_chan_cfg)).unwrap(); let open_channel = get_event_msg!(nodes[1], MessageSendEvent::SendOpenChannel, nodes[3].node.get_our_node_id()); nodes[3].node.handle_open_channel(&nodes[1].node.get_our_node_id(), &open_channel); let accept_channel = get_event_msg!(nodes[3], MessageSendEvent::SendAcceptChannel, nodes[1].node.get_our_node_id()); diff --git a/lightning-invoice/tests/ser_de.rs b/lightning-invoice/tests/ser_de.rs index e21b82eae3c..98886bef788 100644 --- a/lightning-invoice/tests/ser_de.rs +++ b/lightning-invoice/tests/ser_de.rs @@ -5,9 +5,9 @@ extern crate lightning_invoice; extern crate secp256k1; extern crate hex; -use bitcoin::util::address::WitnessVersion; +use bitcoin::address::WitnessVersion; use bitcoin::{PubkeyHash, ScriptHash}; -use bitcoin_hashes::hex::FromHex; +use bitcoin::hashes::hex::FromHex; use bitcoin_hashes::{sha256, Hash}; use lightning::ln::PaymentSecret; use lightning::routing::gossip::RoutingFees; @@ -26,7 +26,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { InvoiceBuilder::new(Currency::Bitcoin) .duration_since_epoch(Duration::from_secs(1496314658)) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_hex( + .payment_hash(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap()) .description("Please consider supporting this project".to_owned()) @@ -34,7 +34,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .unwrap() .sign(|_| { RecoverableSignature::from_compact( - &hex::decode("8d3ce9e28357337f62da0162d9454df827f83cfe499aeb1c1db349d4d81127425e434ca29929406c23bba1ae8ac6ca32880b38d4bf6ff874024cac34ba9625f1").unwrap(), + &>::from_hex("8d3ce9e28357337f62da0162d9454df827f83cfe499aeb1c1db349d4d81127425e434ca29929406c23bba1ae8ac6ca32880b38d4bf6ff874024cac34ba9625f1").unwrap(), RecoveryId::from_i32(1).unwrap() ) }).unwrap(), @@ -47,7 +47,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .amount_milli_satoshis(250_000_000) .duration_since_epoch(Duration::from_secs(1496314658)) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_hex( + .payment_hash(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap()) .description("1 cup coffee".to_owned()) @@ -56,7 +56,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .unwrap() .sign(|_| { RecoverableSignature::from_compact( - &hex::decode("e59e3ffbd3945e4334879158d31e89b076dff54f3fa7979ae79df2db9dcaf5896cbfe1a478b8d2307e92c88139464cb7e6ef26e414c4abe33337961ddc5e8ab1").unwrap(), + &>::from_hex("e59e3ffbd3945e4334879158d31e89b076dff54f3fa7979ae79df2db9dcaf5896cbfe1a478b8d2307e92c88139464cb7e6ef26e414c4abe33337961ddc5e8ab1").unwrap(), RecoveryId::from_i32(1).unwrap() ) }).unwrap(), @@ -69,7 +69,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .amount_milli_satoshis(250_000_000) .duration_since_epoch(Duration::from_secs(1496314658)) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_hex( + .payment_hash(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap()) .description("ナンセンス 1杯".to_owned()) @@ -78,7 +78,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .unwrap() .sign(|_| { RecoverableSignature::from_compact( - &hex::decode("bae41ef385e0fc972977c7ea42b12cbd76577d2412919da8a8a22f9577b6507710c0e96dd78c821dea16453037f717f44aa7e3d196ebb18fbb97307dcb7336c3").unwrap(), + &>::from_hex("bae41ef385e0fc972977c7ea42b12cbd76577d2412919da8a8a22f9577b6507710c0e96dd78c821dea16453037f717f44aa7e3d196ebb18fbb97307dcb7336c3").unwrap(), RecoveryId::from_i32(1).unwrap() ) }).unwrap(), @@ -92,14 +92,14 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .duration_since_epoch(Duration::from_secs(1496314658)) .description_hash(sha256::Hash::hash(b"One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon")) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_hex( + .payment_hash(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap()) .build_raw() .unwrap() .sign(|_| { RecoverableSignature::from_compact( - &hex::decode("f67a5f696648fa4fb102e1a07b230e54722f8e024cee71e80b4847ac191da3fb2d2cdb28cc32344d7e9a9cf5c9b6a0ee0582ae46e9938b9c81e344a4dbb5289d").unwrap(), + &>::from_hex("f67a5f696648fa4fb102e1a07b230e54722f8e024cee71e80b4847ac191da3fb2d2cdb28cc32344d7e9a9cf5c9b6a0ee0582ae46e9938b9c81e344a4dbb5289d").unwrap(), RecoveryId::from_i32(1).unwrap() ) }).unwrap(), @@ -113,7 +113,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .duration_since_epoch(Duration::from_secs(1496314658)) .description_hash(sha256::Hash::hash(b"One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon")) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_hex( + .payment_hash(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap()) .fallback(Fallback::PubKeyHash(PubkeyHash::from_slice(&[49, 114, 181, 101, 79, 102, 131, 200, 251, 20, 105, 89, 211, 71, 206, 48, 60, 174, 76, 167]).unwrap())) @@ -121,7 +121,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .unwrap() .sign(|_| { RecoverableSignature::from_compact( - &hex::decode("6ca95a74dc32e69ced6175b15a5cc56a92bf19f5dace0f134b7d94d464b9f5cf6090a18d48b243f289394d17bdf89466d8e6b37df5981f696bc3dd5986e1bee1").unwrap(), + &>::from_hex("6ca95a74dc32e69ced6175b15a5cc56a92bf19f5dace0f134b7d94d464b9f5cf6090a18d48b243f289394d17bdf89466d8e6b37df5981f696bc3dd5986e1bee1").unwrap(), RecoveryId::from_i32(1).unwrap() ) }).unwrap(), @@ -135,12 +135,12 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .duration_since_epoch(Duration::from_secs(1496314658)) .description_hash(sha256::Hash::hash(b"One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon")) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_hex( + .payment_hash(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap()) .fallback(Fallback::PubKeyHash(PubkeyHash::from_slice(&[4, 182, 31, 125, 193, 234, 13, 201, 148, 36, 70, 76, 196, 6, 77, 197, 100, 217, 30, 137]).unwrap())) .private_route(RouteHint(vec![RouteHintHop { - src_node_id: PublicKey::from_slice(&hex::decode( + src_node_id: PublicKey::from_slice(&>::from_hex( "029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255" ).unwrap()).unwrap(), short_channel_id: (66051 << 40) | (263430 << 16) | 1800, @@ -148,7 +148,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { cltv_expiry_delta: 3, htlc_maximum_msat: None, htlc_minimum_msat: None, }, RouteHintHop { - src_node_id: PublicKey::from_slice(&hex::decode( + src_node_id: PublicKey::from_slice(&>::from_hex( "039e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255" ).unwrap()).unwrap(), short_channel_id: (197637 << 40) | (395016 << 16) | 2314, @@ -160,7 +160,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .unwrap() .sign(|_| { RecoverableSignature::from_compact( - &hex::decode("6a6586db4e8f6d40e3a5bb92e4df5110c627e9ce493af237e20a046b4e86ea200178c59564ecf892f33a9558bf041b6ad2cb8292d7a6c351fbb7f2ae2d16b54e").unwrap(), + &>::from_hex("6a6586db4e8f6d40e3a5bb92e4df5110c627e9ce493af237e20a046b4e86ea200178c59564ecf892f33a9558bf041b6ad2cb8292d7a6c351fbb7f2ae2d16b54e").unwrap(), RecoveryId::from_i32(0).unwrap() ) }).unwrap(), @@ -174,7 +174,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .duration_since_epoch(Duration::from_secs(1496314658)) .description_hash(sha256::Hash::hash(b"One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon")) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_hex( + .payment_hash(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap()) .fallback(Fallback::ScriptHash(ScriptHash::from_slice(&[143, 85, 86, 59, 154, 25, 243, 33, 194, 17, 233, 185, 243, 140, 223, 104, 110, 160, 120, 69]).unwrap())) @@ -182,7 +182,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .unwrap() .sign(|_| { RecoverableSignature::from_compact( - &hex::decode("16810439d1a9bfd5a65acc61340dc92448bb2d456a80b58ce012b73cb5202438020500c9ab7ef5573a4d174c811f669885ae27f895bb3a3be52c243589f87518").unwrap(), + &>::from_hex("16810439d1a9bfd5a65acc61340dc92448bb2d456a80b58ce012b73cb5202438020500c9ab7ef5573a4d174c811f669885ae27f895bb3a3be52c243589f87518").unwrap(), RecoveryId::from_i32(1).unwrap() ) }).unwrap(), @@ -196,7 +196,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .duration_since_epoch(Duration::from_secs(1496314658)) .description_hash(sha256::Hash::hash(b"One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon")) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_hex( + .payment_hash(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap()) .fallback(Fallback::SegWitProgram { version: WitnessVersion::V0, @@ -206,7 +206,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .unwrap() .sign(|_| { RecoverableSignature::from_compact( - &hex::decode("5a8bd7b97c1cc9055ee60cf2356621f8752248e037a953886a1782b44a58f5ff2d94e6bc89b7b514541a3603bb33722b6c08aa1a3639d34becc549a99fea6eae").unwrap(), + &>::from_hex("5a8bd7b97c1cc9055ee60cf2356621f8752248e037a953886a1782b44a58f5ff2d94e6bc89b7b514541a3603bb33722b6c08aa1a3639d34becc549a99fea6eae").unwrap(), RecoveryId::from_i32(0).unwrap() ) }).unwrap(), @@ -220,7 +220,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .duration_since_epoch(Duration::from_secs(1496314658)) .description_hash(sha256::Hash::hash(b"One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon")) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_hex( + .payment_hash(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap()) .fallback(Fallback::SegWitProgram { version: WitnessVersion::V0, @@ -230,7 +230,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .unwrap() .sign(|_| { RecoverableSignature::from_compact( - &hex::decode("2b3ec248f80301a421817369194f012cdd8af8df1c279981420f9e901e20fa3309d791e11355e609b59ce4a220852a0cd55ab862b1785a83b206c90fa74d01c8").unwrap(), + &>::from_hex("2b3ec248f80301a421817369194f012cdd8af8df1c279981420f9e901e20fa3309d791e11355e609b59ce4a220852a0cd55ab862b1785a83b206c90fa74d01c8").unwrap(), RecoveryId::from_i32(1).unwrap() ) }).unwrap(), @@ -243,14 +243,14 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .amount_milli_satoshis(967878534) .duration_since_epoch(Duration::from_secs(1572468703)) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_hex( + .payment_hash(sha256::Hash::from_str( "462264ede7e14047e9b249da94fefc47f41f7d02ee9b091815a5506bc8abf75f" ).unwrap()) .expiry_time(Duration::from_secs(604800)) .min_final_cltv_expiry_delta(10) .description("Blockstream Store: 88.85 USD for Blockstream Ledger Nano S x 1, \"Back In My Day\" Sticker x 2, \"I Got Lightning Working\" Sticker x 2 and 1 more items".to_owned()) .private_route(RouteHint(vec![RouteHintHop { - src_node_id: PublicKey::from_slice(&hex::decode( + src_node_id: PublicKey::from_slice(&>::from_hex( "03d06758583bb5154774a6eb221b1276c9e82d65bbaceca806d90e20c108f4b1c7" ).unwrap()).unwrap(), short_channel_id: (589390 << 40) | (3312 << 16) | 1, @@ -262,7 +262,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .unwrap() .sign(|_| { RecoverableSignature::from_compact( - &hex::decode("1b1160cf6186b55722c1ac7ea502086baaccaabdc76b326e666b7f309d972b15069bfca11cd365304b36f48230cc12f3f13a017aab65f7c165a169df32282a58").unwrap(), + &>::from_hex("1b1160cf6186b55722c1ac7ea502086baaccaabdc76b326e666b7f309d972b15069bfca11cd365304b36f48230cc12f3f13a017aab65f7c165a169df32282a58").unwrap(), RecoveryId::from_i32(1).unwrap() ) }).unwrap(), @@ -275,7 +275,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .amount_milli_satoshis(2_500_000_000) .duration_since_epoch(Duration::from_secs(1496314658)) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_hex( + .payment_hash(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap()) .description("coffee beans".to_owned()) @@ -283,7 +283,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .unwrap() .sign(|_| { RecoverableSignature::from_compact( - &hex::decode("5755469bf4b8e6b6ae7a1308d5f9bad5c82812e0855cd24fac242aa323fa820c5c551ede4faeabcb7fb6d5a464ad0e35c86f615589ee0e0c250c216a662198c1").unwrap(), + &>::from_hex("5755469bf4b8e6b6ae7a1308d5f9bad5c82812e0855cd24fac242aa323fa820c5c551ede4faeabcb7fb6d5a464ad0e35c86f615589ee0e0c250c216a662198c1").unwrap(), RecoveryId::from_i32(1).unwrap() ) }).unwrap(), @@ -296,7 +296,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .amount_milli_satoshis(2_500_000_000) .duration_since_epoch(Duration::from_secs(1496314658)) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_hex( + .payment_hash(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap()) .description("coffee beans".to_owned()) @@ -304,7 +304,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .unwrap() .sign(|_| { RecoverableSignature::from_compact( - &hex::decode("5755469bf4b8e6b6ae7a1308d5f9bad5c82812e0855cd24fac242aa323fa820c5c551ede4faeabcb7fb6d5a464ad0e35c86f615589ee0e0c250c216a662198c1").unwrap(), + &>::from_hex("5755469bf4b8e6b6ae7a1308d5f9bad5c82812e0855cd24fac242aa323fa820c5c551ede4faeabcb7fb6d5a464ad0e35c86f615589ee0e0c250c216a662198c1").unwrap(), RecoveryId::from_i32(1).unwrap() ) }).unwrap(), @@ -317,7 +317,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .amount_milli_satoshis(2_500_000_000) .duration_since_epoch(Duration::from_secs(1496314658)) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_hex( + .payment_hash(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap()) .description("coffee beans".to_owned()) @@ -325,7 +325,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .unwrap() .sign(|_| { RecoverableSignature::from_compact( - &hex::decode("150a5252308f25bc2641a186de87470189bb003774326beee33b9a2a720d1584386631c5dda6fc3195f97464bfc93d2574868eadd767d6da1078329c4349c837").unwrap(), + &>::from_hex("150a5252308f25bc2641a186de87470189bb003774326beee33b9a2a720d1584386631c5dda6fc3195f97464bfc93d2574868eadd767d6da1078329c4349c837").unwrap(), RecoveryId::from_i32(0).unwrap() ) }).unwrap(), @@ -337,13 +337,13 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { InvoiceBuilder::new(Currency::Bitcoin) .amount_milli_satoshis(1_000_000_000) .duration_since_epoch(Duration::from_secs(1496314658)) - .payment_hash(sha256::Hash::from_hex( + .payment_hash(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap()) .description("payment metadata inside".to_owned()) - .payment_metadata(hex::decode("01fafaf0").unwrap()) + .payment_metadata(>::from_hex("01fafaf0").unwrap()) .require_payment_metadata() - .payee_pub_key(PublicKey::from_slice(&hex::decode( + .payee_pub_key(PublicKey::from_slice(&>::from_hex( "03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad" ).unwrap()).unwrap()) .payment_secret(PaymentSecret([0x11; 32])) @@ -351,7 +351,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .unwrap() .sign(|_| { RecoverableSignature::from_compact( - &hex::decode("2150ed137ddb54f9736c6a0290ded709d22bddb7261d1d6518dffb467c6b1eef02afc182491bdacd00b65c83554c914a1c53c61b0a4ef04eccccdfb4365ed259").unwrap(), + &>::from_hex("2150ed137ddb54f9736c6a0290ded709d22bddb7261d1d6518dffb467c6b1eef02afc182491bdacd00b65c83554c914a1c53c61b0a4ef04eccccdfb4365ed259").unwrap(), RecoveryId::from_i32(1).unwrap() ) }).unwrap(), @@ -363,18 +363,18 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { InvoiceBuilder::new(Currency::Bitcoin) .amount_milli_satoshis(1_000_000_000) .duration_since_epoch(Duration::from_secs(1496314658)) - .payment_hash(sha256::Hash::from_hex( + .payment_hash(sha256::Hash::from_str( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap()) .description("payment metadata inside".to_owned()) - .payment_metadata(hex::decode("01fafaf0").unwrap()) + .payment_metadata(>::from_hex("01fafaf0").unwrap()) .require_payment_metadata() .payment_secret(PaymentSecret([0x11; 32])) .build_raw() .unwrap() .sign(|_| { RecoverableSignature::from_compact( - &hex::decode("f5d27be7d9c27d3aa521bc35d77cabd6bda18f1f61716445b19e27e4e17a887508ea8de5a8e1d94f561248f65434e61a221160dac1f1991b9c0f1057b269d898").unwrap(), + &>::from_hex("f5d27be7d9c27d3aa521bc35d77cabd6bda18f1f61716445b19e27e4e17a887508ea8de5a8e1d94f561248f65434e61a221160dac1f1991b9c0f1057b269d898").unwrap(), RecoveryId::from_i32(1).unwrap() ) }).unwrap(), diff --git a/lightning-net-tokio/Cargo.toml b/lightning-net-tokio/Cargo.toml index 6cd3a1fa382..247481fcfc8 100644 --- a/lightning-net-tokio/Cargo.toml +++ b/lightning-net-tokio/Cargo.toml @@ -15,7 +15,7 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [dependencies] -bitcoin = "0.29.0" +bitcoin = "0.30.2" lightning = { version = "0.0.118", path = "../lightning" } tokio = { version = "1.0", features = [ "rt", "sync", "net", "time" ] } diff --git a/lightning-net-tokio/src/lib.rs b/lightning-net-tokio/src/lib.rs index 0eaec17edab..b85b0080a17 100644 --- a/lightning-net-tokio/src/lib.rs +++ b/lightning-net-tokio/src/lib.rs @@ -422,7 +422,11 @@ const SOCK_WAKER_VTABLE: task::RawWakerVTable = task::RawWakerVTable::new(clone_socket_waker, wake_socket_waker, wake_socket_waker_by_ref, drop_socket_waker); fn clone_socket_waker(orig_ptr: *const ()) -> task::RawWaker { - write_avail_to_waker(orig_ptr as *const mpsc::Sender<()>) + let new_waker = unsafe { Arc::from_raw(orig_ptr as *const mpsc::Sender<()>) }; + let res = write_avail_to_waker(&new_waker); + // Don't decrement the refcount when dropping new_waker by turning it back `into_raw`. + let _ = Arc::into_raw(new_waker); + res } // When waking, an error should be fine. Most likely we got two send_datas in a row, both of which // failed to fully write, but we only need to call write_buffer_space_avail() once. Otherwise, the @@ -435,16 +439,15 @@ fn wake_socket_waker(orig_ptr: *const ()) { } fn wake_socket_waker_by_ref(orig_ptr: *const ()) { let sender_ptr = orig_ptr as *const mpsc::Sender<()>; - let sender = unsafe { (*sender_ptr).clone() }; + let sender = unsafe { &*sender_ptr }; let _ = sender.try_send(()); } fn drop_socket_waker(orig_ptr: *const ()) { - let _orig_box = unsafe { Box::from_raw(orig_ptr as *mut mpsc::Sender<()>) }; - // _orig_box is now dropped + let _orig_arc = unsafe { Arc::from_raw(orig_ptr as *mut mpsc::Sender<()>) }; + // _orig_arc is now dropped } -fn write_avail_to_waker(sender: *const mpsc::Sender<()>) -> task::RawWaker { - let new_box = Box::leak(Box::new(unsafe { (*sender).clone() })); - let new_ptr = new_box as *const mpsc::Sender<()>; +fn write_avail_to_waker(sender: &Arc>) -> task::RawWaker { + let new_ptr = Arc::into_raw(Arc::clone(&sender)); task::RawWaker::new(new_ptr as *const (), &SOCK_WAKER_VTABLE) } @@ -452,12 +455,20 @@ fn write_avail_to_waker(sender: *const mpsc::Sender<()>) -> task::RawWaker { /// type in the template of PeerHandler. pub struct SocketDescriptor { conn: Arc>, + // We store a copy of the mpsc::Sender to wake the read task in an Arc here. While we can + // simply clone the sender and store a copy in each waker, that would require allocating for + // each waker. Instead, we can simply `Arc::clone`, creating a new reference and store the + // pointer in the waker. + write_avail_sender: Arc>, id: u64, } impl SocketDescriptor { fn new(conn: Arc>) -> Self { - let id = conn.lock().unwrap().id; - Self { conn, id } + let (id, write_avail_sender) = { + let us = conn.lock().unwrap(); + (us.id, Arc::new(us.write_avail.clone())) + }; + Self { conn, id, write_avail_sender } } } impl peer_handler::SocketDescriptor for SocketDescriptor { @@ -480,7 +491,7 @@ impl peer_handler::SocketDescriptor for SocketDescriptor { let _ = us.read_waker.try_send(()); } if data.is_empty() { return 0; } - let waker = unsafe { task::Waker::from_raw(write_avail_to_waker(&us.write_avail)) }; + let waker = unsafe { task::Waker::from_raw(write_avail_to_waker(&self.write_avail_sender)) }; let mut ctx = task::Context::from_waker(&waker); let mut written_len = 0; loop { @@ -522,6 +533,7 @@ impl Clone for SocketDescriptor { Self { conn: Arc::clone(&self.conn), id: self.id, + write_avail_sender: Arc::clone(&self.write_avail_sender), } } } @@ -605,6 +617,7 @@ mod tests { fn handle_channel_update(&self, _their_node_id: &PublicKey, _msg: &ChannelUpdate) {} fn handle_open_channel_v2(&self, _their_node_id: &PublicKey, _msg: &OpenChannelV2) {} fn handle_accept_channel_v2(&self, _their_node_id: &PublicKey, _msg: &AcceptChannelV2) {} + fn handle_stfu(&self, _their_node_id: &PublicKey, _msg: &Stfu) {} // #SPLICING fn handle_splice(&self, _their_node_id: &PublicKey, _msg: &Splice) {} fn handle_splice_ack(&self, _their_node_id: &PublicKey, _msg: &SpliceAck) {} diff --git a/lightning-persister/Cargo.toml b/lightning-persister/Cargo.toml index 525b9507329..387366bff0e 100644 --- a/lightning-persister/Cargo.toml +++ b/lightning-persister/Cargo.toml @@ -14,7 +14,7 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [dependencies] -bitcoin = "0.29.0" +bitcoin = "0.30.2" lightning = { version = "0.0.118", path = "../lightning" } [target.'cfg(windows)'.dependencies] @@ -25,4 +25,4 @@ criterion = { version = "0.4", optional = true, default-features = false } [dev-dependencies] lightning = { version = "0.0.118", path = "../lightning", features = ["_test_utils"] } -bitcoin = { version = "0.29.0", default-features = false } +bitcoin = { version = "0.30.2", default-features = false } diff --git a/lightning-persister/src/fs_store.rs b/lightning-persister/src/fs_store.rs index c665d8083cb..118cf9af7ba 100644 --- a/lightning-persister/src/fs_store.rs +++ b/lightning-persister/src/fs_store.rs @@ -369,7 +369,6 @@ mod tests { use super::*; use crate::test_utils::{do_read_write_remove_list_persist, do_test_store}; - use bitcoin::hashes::hex::FromHex; use bitcoin::Txid; use lightning::chain::ChannelMonitorUpdateStatus; @@ -381,6 +380,7 @@ mod tests { use lightning::util::test_utils; use lightning::util::persist::read_channel_monitors; use std::fs; + use std::str::FromStr; #[cfg(target_os = "windows")] use { lightning::get_event_msg, @@ -466,7 +466,7 @@ mod tests { fs::set_permissions(path, perms).unwrap(); let test_txo = OutPoint { - txid: Txid::from_hex("8984484a580b825b9972d7adb15050b3ab624ccd731946b3eeddb92f4e7ef6be").unwrap(), + txid: Txid::from_str("8984484a580b825b9972d7adb15050b3ab624ccd731946b3eeddb92f4e7ef6be").unwrap(), index: 0 }; match store.persist_new_channel(test_txo, &added_monitors[0].1, update_id.2) { @@ -503,7 +503,7 @@ mod tests { let store = FilesystemStore::new(":<>/".into()); let test_txo = OutPoint { - txid: Txid::from_hex("8984484a580b825b9972d7adb15050b3ab624ccd731946b3eeddb92f4e7ef6be").unwrap(), + txid: Txid::from_str("8984484a580b825b9972d7adb15050b3ab624ccd731946b3eeddb92f4e7ef6be").unwrap(), index: 0 }; match store.persist_new_channel(test_txo, &added_monitors[0].1, update_id.2) { diff --git a/lightning-rapid-gossip-sync/Cargo.toml b/lightning-rapid-gossip-sync/Cargo.toml index 7853c32e70c..2018e3b2483 100644 --- a/lightning-rapid-gossip-sync/Cargo.toml +++ b/lightning-rapid-gossip-sync/Cargo.toml @@ -16,7 +16,7 @@ std = ["lightning/std"] [dependencies] lightning = { version = "0.0.118", path = "../lightning", default-features = false } -bitcoin = { version = "0.29.0", default-features = false } +bitcoin = { version = "0.30.2", default-features = false } [target.'cfg(ldk_bench)'.dependencies] criterion = { version = "0.4", optional = true, default-features = false } diff --git a/lightning-transaction-sync/Cargo.toml b/lightning-transaction-sync/Cargo.toml index 24f9c687a08..782c4b7033e 100644 --- a/lightning-transaction-sync/Cargo.toml +++ b/lightning-transaction-sync/Cargo.toml @@ -22,14 +22,14 @@ async-interface = [] [dependencies] lightning = { version = "0.0.118", path = "../lightning", default-features = false } -bitcoin = { version = "0.29.0", default-features = false } +bitcoin = { version = "0.30.2", default-features = false } bdk-macros = "0.6" futures = { version = "0.3", optional = true } -esplora-client = { version = "0.4", default-features = false, optional = true } +esplora-client = { version = "0.6", default-features = false, optional = true } reqwest = { version = "0.11", optional = true, default-features = false, features = ["json"] } [dev-dependencies] lightning = { version = "0.0.118", path = "../lightning", features = ["std"] } -electrsd = { version = "0.22.0", features = ["legacy", "esplora_a33e97e1", "bitcoind_23_0"] } -electrum-client = "0.12.0" +electrsd = { version = "0.26.0", features = ["legacy", "esplora_a33e97e1", "bitcoind_25_0"] } +electrum-client = "0.18.0" tokio = { version = "1.14.0", features = ["full"] } diff --git a/lightning-transaction-sync/src/common.rs b/lightning-transaction-sync/src/common.rs index a6ee61e90f2..45f18afb99b 100644 --- a/lightning-transaction-sync/src/common.rs +++ b/lightning-transaction-sync/src/common.rs @@ -1,5 +1,6 @@ use lightning::chain::WatchedOutput; -use bitcoin::{Txid, BlockHash, Transaction, BlockHeader, OutPoint}; +use bitcoin::{Txid, BlockHash, Transaction, OutPoint}; +use bitcoin::blockdata::block::Header; use std::collections::{HashSet, HashMap}; @@ -69,7 +70,7 @@ impl FilterQueue { pub(crate) struct ConfirmedTx { pub tx: Transaction, - pub block_header: BlockHeader, + pub block_header: Header, pub block_height: u32, pub pos: usize, } diff --git a/lightning-transaction-sync/tests/integration_tests.rs b/lightning-transaction-sync/tests/integration_tests.rs index 617b1213e89..8a69dce3f3d 100644 --- a/lightning-transaction-sync/tests/integration_tests.rs +++ b/lightning-transaction-sync/tests/integration_tests.rs @@ -5,12 +5,13 @@ use lightning::chain::transaction::TransactionData; use lightning::util::logger::{Logger, Record}; use electrsd::{bitcoind, bitcoind::BitcoinD, ElectrsD}; -use bitcoin::{Amount, Txid, BlockHash, BlockHeader}; +use bitcoin::{Amount, Txid, BlockHash}; +use bitcoin::blockdata::block::Header; use bitcoin::blockdata::constants::genesis_block; use bitcoin::network::constants::Network; use electrsd::bitcoind::bitcoincore_rpc::bitcoincore_rpc_json::AddressType; use bitcoind::bitcoincore_rpc::RpcApi; -use electrum_client::ElectrumApi; +use electrsd::electrum_client::ElectrumApi; use std::env; use std::sync::Mutex; @@ -42,21 +43,22 @@ pub fn generate_blocks_and_wait(bitcoind: &BitcoinD, electrsd: &ElectrsD, num: u let address = bitcoind .client .get_new_address(Some("test"), Some(AddressType::Legacy)) - .expect("failed to get new address"); + .expect("failed to get new address") + .assume_checked(); // TODO: expect this Result once the WouldBlock issue is resolved upstream. let _block_hashes_res = bitcoind.client.generate_to_address(num as u64, &address); wait_for_block(electrsd, cur_height as usize + num); } pub fn wait_for_block(electrsd: &ElectrsD, min_height: usize) { - let mut header = match electrsd.client.block_headers_subscribe() { + let mut header = match electrsd.client.block_headers_subscribe_raw() { Ok(header) => header, Err(_) => { // While subscribing should succeed the first time around, we ran into some cases where // it didn't. Since we can't proceed without subscribing, we try again after a delay // and panic if it still fails. std::thread::sleep(Duration::from_secs(1)); - electrsd.client.block_headers_subscribe().expect("failed to subscribe to block headers") + electrsd.client.block_headers_subscribe_raw().expect("failed to subscribe to block headers") } }; loop { @@ -66,7 +68,7 @@ pub fn wait_for_block(electrsd: &ElectrsD, min_height: usize) { header = exponential_backoff_poll(|| { electrsd.trigger().expect("failed to trigger electrsd"); electrsd.client.ping().expect("failed to ping electrsd"); - electrsd.client.block_headers_pop().expect("failed to pop block header") + electrsd.client.block_headers_pop_raw().expect("failed to pop block header") }); } } @@ -119,7 +121,7 @@ impl TestConfirmable { } impl Confirm for TestConfirmable { - fn transactions_confirmed(&self, header: &BlockHeader, txdata: &TransactionData<'_>, height: u32) { + fn transactions_confirmed(&self, header: &Header, txdata: &TransactionData<'_>, height: u32) { for (_, tx) in txdata { let txid = tx.txid(); let block_hash = header.block_hash(); @@ -135,7 +137,7 @@ impl Confirm for TestConfirmable { self.events.lock().unwrap().push(TestConfirmableEvent::Unconfirmed(*txid)); } - fn best_block_updated(&self, header: &BlockHeader, height: u32) { + fn best_block_updated(&self, header: &Header, height: u32) { let block_hash = header.block_hash(); *self.best_block.lock().unwrap() = (block_hash, height); self.events.lock().unwrap().push(TestConfirmableEvent::BestBlockUpdated(block_hash, height)); @@ -176,9 +178,9 @@ fn test_esplora_syncs() { assert_eq!(events.len(), 1); // Check registered confirmed transactions are marked confirmed - let new_address = bitcoind.client.get_new_address(Some("test"), Some(AddressType::Legacy)).unwrap(); + let new_address = bitcoind.client.get_new_address(Some("test"), Some(AddressType::Legacy)).unwrap().assume_checked(); let txid = bitcoind.client.send_to_address(&new_address, Amount::from_sat(5000), None, None, None, None, None, None).unwrap(); - tx_sync.register_tx(&txid, &new_address.script_pubkey()); + tx_sync.register_tx(&txid, &new_address.payload.script_pubkey()); tx_sync.sync(vec![&confirmable]).unwrap(); @@ -259,9 +261,9 @@ async fn test_esplora_syncs() { assert_eq!(events.len(), 1); // Check registered confirmed transactions are marked confirmed - let new_address = bitcoind.client.get_new_address(Some("test"), Some(AddressType::Legacy)).unwrap(); + let new_address = bitcoind.client.get_new_address(Some("test"), Some(AddressType::Legacy)).unwrap().assume_checked(); let txid = bitcoind.client.send_to_address(&new_address, Amount::from_sat(5000), None, None, None, None, None, None).unwrap(); - tx_sync.register_tx(&txid, &new_address.script_pubkey()); + tx_sync.register_tx(&txid, &new_address.payload.script_pubkey()); tx_sync.sync(vec![&confirmable]).await.unwrap(); diff --git a/lightning/Cargo.toml b/lightning/Cargo.toml index 11ac2967eeb..76d751af391 100644 --- a/lightning/Cargo.toml +++ b/lightning/Cargo.toml @@ -17,7 +17,7 @@ rustdoc-args = ["--cfg", "docsrs"] [features] # Internal test utilities exposed to other repo crates -_test_utils = ["hex", "regex", "bitcoin/bitcoinconsensus"] +_test_utils = ["regex", "bitcoin/bitcoinconsensus"] # Unlog messages superior at targeted level. max_level_off = [] max_level_error = [] @@ -40,21 +40,20 @@ grind_signatures = [] default = ["std", "grind_signatures"] [dependencies] -bitcoin = { version = "0.29.0", default-features = false, features = ["secp-recovery"] } +bitcoin = { version = "0.30.2", default-features = false, features = ["secp-recovery"] } hashbrown = { version = "0.8", optional = true } -hex = { version = "0.4", optional = true } +hex = { package = "hex-conservative", version = "0.1.1", default-features = false } regex = { version = "1.5.6", optional = true } backtrace = { version = "0.3", optional = true } core2 = { version = "0.3.0", optional = true, default-features = false } [dev-dependencies] -hex = "0.4" regex = "1.5.6" [dev-dependencies.bitcoin] -version = "0.29.0" +version = "0.30.2" default-features = false features = ["bitcoinconsensus", "secp-recovery"] @@ -62,4 +61,4 @@ features = ["bitcoinconsensus", "secp-recovery"] criterion = { version = "0.4", optional = true, default-features = false } [target.'cfg(taproot)'.dependencies] -musig2 = { git = "https://github.com/arik-so/rust-musig2", rev = "27797d7" } +musig2 = { git = "https://github.com/arik-so/rust-musig2", rev = "cff11e3" } diff --git a/lightning/src/blinded_path/utils.rs b/lightning/src/blinded_path/utils.rs index c62b4e6c261..2e691898dc2 100644 --- a/lightning/src/blinded_path/utils.rs +++ b/lightning/src/blinded_path/utils.rs @@ -20,7 +20,7 @@ use crate::ln::msgs::DecodeError; use crate::ln::onion_utils; use crate::onion_message::Destination; use crate::util::chacha20poly1305rfc::ChaChaPolyWriteAdapter; -use crate::util::ser::{Readable, VecWriter, Writeable}; +use crate::util::ser::{Readable, Writeable}; use crate::io; use crate::prelude::*; @@ -49,7 +49,7 @@ where let hop_pk_blinding_factor = { let mut hmac = HmacEngine::::new(b"blinded_node_id"); hmac.input(encrypted_data_ss.as_ref()); - Hmac::from_engine(hmac).into_inner() + Hmac::from_engine(hmac).to_byte_array() }; $pk.mul_tweak(secp_ctx, &Scalar::from_be_bytes(hop_pk_blinding_factor).unwrap())? }; @@ -70,7 +70,7 @@ where let mut sha = Sha256::engine(); sha.input(&msg_blinding_point.serialize()[..]); sha.input(encrypted_data_ss.as_ref()); - Sha256::from_engine(sha).into_inner() + Sha256::from_engine(sha).to_byte_array() }; msg_blinding_point_priv = msg_blinding_point_priv.mul_tweak(&Scalar::from_be_bytes(msg_blinding_point_blinding_factor).unwrap())?; @@ -80,7 +80,7 @@ where let mut sha = Sha256::engine(); sha.input(&onion_packet_pubkey.serialize()[..]); sha.input(onion_packet_ss.as_ref()); - Sha256::from_engine(sha).into_inner() + Sha256::from_engine(sha).to_byte_array() }; onion_packet_pubkey_priv = onion_packet_pubkey_priv.mul_tweak(&Scalar::from_be_bytes(onion_packet_pubkey_blinding_factor).unwrap())?; onion_packet_pubkey = PublicKey::from_secret_key(secp_ctx, &onion_packet_pubkey_priv); @@ -129,10 +129,8 @@ where /// Encrypt TLV payload to be used as a [`crate::blinded_path::BlindedHop::encrypted_payload`]. fn encrypt_payload(payload: P, encrypted_tlvs_rho: [u8; 32]) -> Vec { - let mut writer = VecWriter(Vec::new()); let write_adapter = ChaChaPolyWriteAdapter::new(encrypted_tlvs_rho, &payload); - write_adapter.write(&mut writer).expect("In-memory writes cannot fail"); - writer.0 + write_adapter.encode() } /// Blinded path encrypted payloads may be padded to ensure they are equal length. diff --git a/lightning/src/chain/chaininterface.rs b/lightning/src/chain/chaininterface.rs index 73707f05236..2d7f0c18af3 100644 --- a/lightning/src/chain/chaininterface.rs +++ b/lightning/src/chain/chaininterface.rs @@ -53,23 +53,6 @@ pub enum ConfirmationTarget { /// to low hundreds of blocks to get our transaction on-chain, but we shouldn't risk too low a /// fee - this should be a relatively high priority feerate. OnChainSweep, - /// The highest feerate we will allow our channel counterparty to have in a non-anchor channel. - /// - /// This is the feerate on the transaction which we (or our counterparty) will broadcast in - /// order to close the channel unilaterally. Because our counterparty must ensure they can - /// always broadcast the latest state, this value being too low will cause immediate - /// force-closures. - /// - /// Allowing this value to be too high can allow our counterparty to burn our HTLC outputs to - /// dust, which can result in HTLCs failing or force-closures (when the dust HTLCs exceed - /// [`ChannelConfig::max_dust_htlc_exposure`]). - /// - /// Because most nodes use a feerate estimate which is based on a relatively high priority - /// transaction entering the current mempool, setting this to a small multiple of your current - /// high priority feerate estimate should suffice. - /// - /// [`ChannelConfig::max_dust_htlc_exposure`]: crate::util::config::ChannelConfig::max_dust_htlc_exposure - MaxAllowedNonAnchorChannelRemoteFee, /// This is the lowest feerate we will allow our channel counterparty to have in an anchor /// channel in order to close the channel if a channel party goes away. /// diff --git a/lightning/src/chain/chainmonitor.rs b/lightning/src/chain/chainmonitor.rs index e87d082d9a7..0186ac37488 100644 --- a/lightning/src/chain/chainmonitor.rs +++ b/lightning/src/chain/chainmonitor.rs @@ -23,7 +23,7 @@ //! events. The remote server would make use of [`ChainMonitor`] for block processing and for //! servicing [`ChannelMonitor`] updates from the client. -use bitcoin::blockdata::block::BlockHeader; +use bitcoin::blockdata::block::Header; use bitcoin::hash_types::{Txid, BlockHash}; use crate::chain; @@ -312,7 +312,7 @@ where C::Target: chain::Filter, /// updated `txdata`. /// /// Calls which represent a new blockchain tip height should set `best_height`. - fn process_chain_data(&self, header: &BlockHeader, best_height: Option, txdata: &TransactionData, process: FN) + fn process_chain_data(&self, header: &Header, best_height: Option, txdata: &TransactionData, process: FN) where FN: Fn(&ChannelMonitor, &TransactionData) -> Vec { @@ -355,7 +355,7 @@ where C::Target: chain::Filter, } fn update_monitor_with_chain_data( - &self, header: &BlockHeader, best_height: Option, txdata: &TransactionData, + &self, header: &Header, best_height: Option, txdata: &TransactionData, process: FN, funding_outpoint: &OutPoint, monitor_state: &MonitorHolder ) -> Result<(), ()> where FN: Fn(&ChannelMonitor, &TransactionData) -> Vec { let monitor = &monitor_state.monitor; @@ -635,7 +635,7 @@ where L::Target: Logger, P::Target: Persist, { - fn filtered_block_connected(&self, header: &BlockHeader, txdata: &TransactionData, height: u32) { + fn filtered_block_connected(&self, header: &Header, txdata: &TransactionData, height: u32) { log_debug!(self.logger, "New best block {} at height {} provided via block_connected", header.block_hash(), height); self.process_chain_data(header, Some(height), &txdata, |monitor, txdata| { monitor.block_connected( @@ -643,7 +643,7 @@ where }); } - fn block_disconnected(&self, header: &BlockHeader, height: u32) { + fn block_disconnected(&self, header: &Header, height: u32) { let monitor_states = self.monitors.read().unwrap(); log_debug!(self.logger, "Latest block {} at height {} removed via block_disconnected", header.block_hash(), height); for monitor_state in monitor_states.values() { @@ -662,7 +662,7 @@ where L::Target: Logger, P::Target: Persist, { - fn transactions_confirmed(&self, header: &BlockHeader, txdata: &TransactionData, height: u32) { + fn transactions_confirmed(&self, header: &Header, txdata: &TransactionData, height: u32) { log_debug!(self.logger, "{} provided transactions confirmed at height {} in block {}", txdata.len(), height, header.block_hash()); self.process_chain_data(header, None, txdata, |monitor, txdata| { monitor.transactions_confirmed( @@ -678,7 +678,7 @@ where } } - fn best_block_updated(&self, header: &BlockHeader, height: u32) { + fn best_block_updated(&self, header: &Header, height: u32) { log_debug!(self.logger, "New best block {} at height {} provided via best_block_updated", header.block_hash(), height); self.process_chain_data(header, Some(height), &[], |monitor, txdata| { // While in practice there shouldn't be any recursive calls when given empty txdata, @@ -814,12 +814,7 @@ where C::Target: chain::Filter, let mut pending_monitor_events = self.pending_monitor_events.lock().unwrap().split_off(0); for monitor_state in self.monitors.read().unwrap().values() { let is_pending_monitor_update = monitor_state.has_pending_chainsync_updates(&monitor_state.pending_monitor_updates.lock().unwrap()); - if is_pending_monitor_update && - monitor_state.last_chain_persist_height.load(Ordering::Acquire) + LATENCY_GRACE_PERIOD_BLOCKS as usize - > self.highest_chain_height.load(Ordering::Acquire) - { - log_debug!(self.logger, "A Channel Monitor sync is still in progress, refusing to provide monitor events!"); - } else { + if !is_pending_monitor_update || monitor_state.last_chain_persist_height.load(Ordering::Acquire) + LATENCY_GRACE_PERIOD_BLOCKS as usize <= self.highest_chain_height.load(Ordering::Acquire) { if is_pending_monitor_update { log_error!(self.logger, "A ChannelMonitor sync took longer than {} blocks to complete.", LATENCY_GRACE_PERIOD_BLOCKS); log_error!(self.logger, " To avoid funds-loss, we are allowing monitor updates to be released."); diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 0696ad7ed73..76741a61249 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -20,9 +20,9 @@ //! security-domain-separated system design, you should consider having multiple paths for //! ChannelMonitors to get out of the HSM and onto monitoring devices. -use bitcoin::blockdata::block::BlockHeader; +use bitcoin::blockdata::block::Header; use bitcoin::blockdata::transaction::{OutPoint as BitcoinOutPoint, TxOut, Transaction}; -use bitcoin::blockdata::script::Script; +use bitcoin::blockdata::script::{Script, ScriptBuf}; use bitcoin::hashes::Hash; use bitcoin::hashes::sha256::Hash as Sha256; @@ -30,7 +30,8 @@ use bitcoin::hash_types::{Txid, BlockHash}; use bitcoin::secp256k1::{Secp256k1, ecdsa::Signature}; use bitcoin::secp256k1::{SecretKey, PublicKey}; -use bitcoin::{secp256k1, EcdsaSighashType}; +use bitcoin::secp256k1; +use bitcoin::sighash::EcdsaSighashType; use crate::ln::channel::INITIAL_COMMITMENT_NUMBER; use crate::ln::{PaymentHash, PaymentPreimage}; @@ -132,7 +133,8 @@ pub enum MonitorEvent { /// A monitor event containing an HTLCUpdate. HTLCEvent(HTLCUpdate), - /// A monitor event that the Channel's commitment transaction was confirmed. + /// Indicates we broadcasted the channel's latest commitment transaction and thus closed the + /// channel. HolderForceClosed(OutPoint), /// Indicates a [`ChannelMonitor`] update has completed. See @@ -515,7 +517,7 @@ pub(crate) enum ChannelMonitorUpdateStep { should_broadcast: bool, }, ShutdownScript { - scriptpubkey: Script, + scriptpubkey: ScriptBuf, }, } @@ -749,19 +751,19 @@ pub(crate) struct ChannelMonitorImpl { latest_update_id: u64, commitment_transaction_number_obscure_factor: u64, - destination_script: Script, - broadcasted_holder_revokable_script: Option<(Script, PublicKey, PublicKey)>, - counterparty_payment_script: Script, - shutdown_script: Option