diff --git a/Cargo.toml b/Cargo.toml index 1c0523a5..62b6e2ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,6 @@ members = [ ] [patch.crates-io] -lightning = { git = "https://github.com/p2pderivatives/rust-lightning/", rev = "420d961d" } -lightning-net-tokio = { git = "https://github.com/p2pderivatives/rust-lightning/", rev = "420d961d" } -lightning-persister = { git = "https://github.com/p2pderivatives/rust-lightning/", rev = "420d961d" } +lightning = { git = "https://github.com/p2pderivatives/rust-lightning/", rev = "47c8479" } +lightning-net-tokio = { git = "https://github.com/p2pderivatives/rust-lightning/", rev = "47c8479" } +lightning-persister = { git = "https://github.com/p2pderivatives/rust-lightning/", rev = "47c8479" } diff --git a/dlc-manager/src/sub_channel_manager.rs b/dlc-manager/src/sub_channel_manager.rs index ec8a22b6..9d23ce28 100644 --- a/dlc-manager/src/sub_channel_manager.rs +++ b/dlc-manager/src/sub_channel_manager.rs @@ -1,13 +1,10 @@ //! # Module containing a manager enabling set up and update of DLC channels embedded within //! Lightning Network channels. -use std::{marker::PhantomData, ops::Deref, sync::Mutex}; +use std::{collections::HashMap, marker::PhantomData, ops::Deref, sync::Mutex}; use bitcoin::{OutPoint, PackedLockTime, Script, Sequence, Transaction}; -use dlc::{ - channel::{get_tx_adaptor_signature, sub_channel::LN_GLUE_TX_WEIGHT}, - PartyParams, -}; +use dlc::{channel::sub_channel::LN_GLUE_TX_WEIGHT, PartyParams}; use dlc_messages::{ channel::{AcceptChannel, OfferChannel}, oracle_msgs::OracleAnnouncement, @@ -28,14 +25,17 @@ use lightning::{ channelmanager::ChannelDetails, msgs::{ChannelMessageHandler, DecodeError, RevokeAndACK}, }, - util::ser::{Readable, Writeable, Writer}, util::{errors::APIError, events::MessageSendEventsProvider}, + util::{ + events::ClosureReason, + ser::{Readable, Writeable, Writer}, + }, }; use log::{error, info, trace, warn}; use secp256k1_zkp::{ecdsa::Signature, PublicKey, SecretKey}; use crate::{ - chain_monitor::{ChannelInfo, RevokedTxType, TxType}, + chain_monitor::{ChainMonitor, ChannelInfo, RevokedTxType, TxType}, channel::{ generate_temporary_contract_id, offered_channel::OfferedChannel, party_points::PartyBasePoints, Channel, ClosedChannel, @@ -49,8 +49,8 @@ use crate::{ subchannel::{ self, generate_temporary_channel_id, AcceptedSubChannel, CloseAcceptedSubChannel, CloseConfirmedSubChannel, CloseOfferedSubChannel, ClosingSubChannel, ConfirmedSubChannel, - LNChannelManager, OfferedSubChannel, ReestablishFlag, SignedSubChannel, SubChannel, - SubChannelState, + LNChainMonitor, LNChannelManager, LnDlcChannelSigner, LnDlcSignerProvider, + OfferedSubChannel, ReestablishFlag, SignedSubChannel, SubChannel, SubChannelState, }, Blockchain, ChannelId, ContractId, Oracle, Signer, Storage, Time, Wallet, }; @@ -157,56 +157,77 @@ impl_dlc_writeable_enum!(Action, pub struct SubChannelManager< W: Deref, M: Deref, + C: Deref, S: Deref, B: Deref, O: Deref, T: Deref, F: Deref, D: Deref>, - SP: ChannelSigner, + CS: ChannelSigner, + SP: Deref, + LCS: LnDlcChannelSigner, > where W::Target: Wallet, - M::Target: LNChannelManager, + M::Target: LNChannelManager, + C::Target: LNChainMonitor, S::Target: Storage, B::Target: Blockchain, O::Target: Oracle, T::Target: Time, F::Target: FeeEstimator, + SP::Target: LnDlcSignerProvider, { ln_channel_manager: M, dlc_channel_manager: D, actions: Mutex>, - phantom: std::marker::PhantomData, + ln_chain_monitor: C, + phantom: std::marker::PhantomData, + signer_provider: SP, + ln_channel_signers: Mutex>, } impl< W: Deref, M: Deref, + C: Deref, S: Deref, B: Deref, O: Deref, T: Deref, F: Deref, D: Deref>, - SP: ChannelSigner, - > SubChannelManager + CS: ChannelSigner, + SP: Deref, + LCS: LnDlcChannelSigner, + > SubChannelManager where W::Target: Wallet, - M::Target: LNChannelManager, + M::Target: LNChannelManager, + C::Target: LNChainMonitor, S::Target: Storage, B::Target: Blockchain, O::Target: Oracle, T::Target: Time, F::Target: FeeEstimator, + SP::Target: LnDlcSignerProvider, { /// Creates a new [`SubChannelManager`]. - pub fn new(ln_channel_manager: M, dlc_channel_manager: D) -> Result { + pub fn new( + ln_channel_manager: M, + dlc_channel_manager: D, + ln_chain_monitor: C, + signer_provider: SP, + ) -> Result { let actions = dlc_channel_manager.get_store().get_sub_channel_actions()?; Ok(Self { ln_channel_manager, dlc_channel_manager, actions: Mutex::new(actions), phantom: PhantomData, + ln_chain_monitor, + signer_provider, + ln_channel_signers: Mutex::new(HashMap::new()), }) } @@ -386,6 +407,7 @@ where counter_fund_pk: channel_details.counter_funding_pubkey.ok_or_else(|| { Error::InvalidState("Counter funding PK is missing".to_string()) })?, + channel_keys_id: channel_details.channel_keys_id, } } }; @@ -586,8 +608,8 @@ where ); let commitment_transactions = self - .ln_channel_manager - .get_latest_holder_commitment_txn(channel_lock)?; + .ln_chain_monitor + .get_latest_holder_commitment_txn(&funding_txo)?; let commitment_signed = self .ln_channel_manager @@ -697,7 +719,7 @@ where /// Start force closing the sub channel with given [`ChannelId`]. pub fn force_close_sub_channel(&self, channel_id: &ChannelId) -> Result<(), Error> { - let mut sub_channel = self + let sub_channel = self .dlc_channel_manager .get_store() .get_sub_channel(*channel_id)? @@ -705,23 +727,39 @@ where "Unknown sub channel {:?}", channel_id )))?; + let mut chain_monitor = self.dlc_channel_manager.get_chain_monitor().lock().unwrap(); + self.force_close_sub_channel_internal(sub_channel, &mut chain_monitor) + } + fn force_close_sub_channel_internal( + &self, + mut sub_channel: SubChannel, + chain_monitor: &mut ChainMonitor, + ) -> Result<(), Error> { match sub_channel.state { // Force close by simply asking LDK to force close as the channel funding outpoint has // not yet been updated (also updated sub-channel/channel/contract state). - SubChannelState::Offered(_) => self.force_close_with_ldk(sub_channel)?, + SubChannelState::Offered(_) => self.force_close_with_ldk(sub_channel, chain_monitor)?, // Force close by using the saved LN commitment transactions from before the spliting of the // channel. SubChannelState::Accepted(ref a) => { let commitment_transactions = a.commitment_transactions.clone(); - self.force_close_with_saved_commitment(sub_channel, &commitment_transactions)?; + self.force_close_with_saved_commitment( + sub_channel, + &commitment_transactions, + chain_monitor, + )?; } // Force close by using the saved LN commitment transactions from before the spliting of the // channel. SubChannelState::Confirmed(ref c) => { let commitment_transactions = c.commitment_transactions.clone(); - self.force_close_with_saved_commitment(sub_channel, &commitment_transactions)?; + self.force_close_with_saved_commitment( + sub_channel, + &commitment_transactions, + chain_monitor, + )?; } // Force close by broadcasting the split transaction and marking the sub-channel as // closing, which will trigger the periodic check to watch when the split transaction @@ -741,7 +779,7 @@ where // reacts properly during the closing process. SubChannelState::CloseAccepted(c) => { self.ln_channel_manager.with_channel_lock_no_check( - channel_id, + &sub_channel.channel_id, &sub_channel.counter_party, |channel_lock| { self.ln_channel_manager.set_funding_outpoint( @@ -762,7 +800,7 @@ where // Once we are in `CloseConfirmed`, we can simply use LDK to close the Lightning // channel. SubChannelState::CloseConfirmed(_) => { - self.force_close_with_ldk(sub_channel)?; + self.force_close_with_ldk(sub_channel, chain_monitor)?; } // In these states, either the channel is already closed, or it should be force closed // through LDK directly. @@ -782,7 +820,11 @@ where Ok(()) } - fn force_close_with_ldk(&self, mut sub_channel: SubChannel) -> Result<(), Error> { + fn force_close_with_ldk( + &self, + mut sub_channel: SubChannel, + chain_monitor: &mut ChainMonitor, + ) -> Result<(), Error> { let dlc_channel_id = sub_channel .get_dlc_channel_id(0) .expect("to have a channel id in offered state"); @@ -799,18 +841,18 @@ where .upsert_channel(closed_channel, Some(closed_contract))?; //TODO(tibo): this is actually unsafe, we shouldn't clean up the chain monitor before //having the commitment transaction confirmed on chain. - let mut chain_monitor = self.dlc_channel_manager.get_chain_monitor().lock().unwrap(); chain_monitor.cleanup_channel(sub_channel.channel_id); chain_monitor.cleanup_channel(dlc_channel_id); self.dlc_channel_manager .get_store() - .persist_chain_monitor(&chain_monitor) + .persist_chain_monitor(chain_monitor) } fn force_close_with_saved_commitment( &self, mut sub_channel: SubChannel, commitment_transactions: &Vec, + chain_monitor: &mut ChainMonitor, ) -> Result<(), Error> { // The Lightning commitment transaction is always first in the vector so this is safe. for tx in commitment_transactions { @@ -831,12 +873,12 @@ where self.dlc_channel_manager .get_store() .upsert_sub_channel(&sub_channel)?; - let mut chain_monitor = self.dlc_channel_manager.get_chain_monitor().lock().unwrap(); chain_monitor.cleanup_channel(sub_channel.channel_id); chain_monitor.cleanup_channel(dlc_channel_id); self.dlc_channel_manager .get_store() - .persist_chain_monitor(&chain_monitor) + .persist_chain_monitor(chain_monitor)?; + Ok(()) } fn force_close_signed_channel( @@ -847,78 +889,63 @@ where if let SubChannelState::Signed(state) | SubChannelState::Finalized(state) = sub_channel.state.clone() { - let channel_id = sub_channel.channel_id; - let counter_party = sub_channel.counter_party; - self.ln_channel_manager.with_channel_lock_no_check( - &channel_id, - &counter_party, - |channel_lock| { - let publish_base_secret = self - .dlc_channel_manager - .get_wallet() - .get_secret_key_for_pubkey( - &sub_channel.own_base_points.publish_basepoint, - )?; + let publish_base_secret = self + .dlc_channel_manager + .get_wallet() + .get_secret_key_for_pubkey(&sub_channel.own_base_points.publish_basepoint)?; - let publish_sk = derive_private_key( - self.dlc_channel_manager.get_secp(), - &state.own_per_split_point, - &publish_base_secret, - ); + let publish_sk = derive_private_key( + self.dlc_channel_manager.get_secp(), + &state.own_per_split_point, + &publish_base_secret, + ); - let counter_split_signature = state - .counter_split_adaptor_signature - .decrypt(&publish_sk) - .map_err(|e| APIError::ExternalError { err: e.to_string() })?; + let counter_split_signature = state + .counter_split_adaptor_signature + .decrypt(&publish_sk) + .map_err(|e| APIError::ExternalError { err: e.to_string() })?; - let mut split_tx = state.split_tx.transaction.clone(); + let mut split_tx = state.split_tx.transaction.clone(); - let mut own_sig = None; + let mut signers = self.ln_channel_signers.lock().unwrap(); - self.ln_channel_manager - .sign_with_fund_key_cb(channel_lock, &mut |fund_sk| { - own_sig = Some( - dlc::util::get_raw_sig_for_tx_input( - self.dlc_channel_manager.get_secp(), - &split_tx, - 0, - &sub_channel.original_funding_redeemscript, - sub_channel.fund_value_satoshis, - fund_sk, - ) - .unwrap(), - ); - dlc::util::sign_multi_sig_input( - self.dlc_channel_manager.get_secp(), - &mut split_tx, - &counter_split_signature, - &sub_channel.counter_fund_pk, - fund_sk, - &sub_channel.original_funding_redeemscript, - sub_channel.fund_value_satoshis, - 0, - ) - .unwrap(); - }); - self.dlc_channel_manager - .get_blockchain() - .send_transaction(&split_tx)?; + let signer = signers.entry(sub_channel.channel_id).or_insert( + self.signer_provider.derive_ln_dlc_channel_signer( + sub_channel.fund_value_satoshis, + sub_channel.channel_keys_id, + ), + ); + let own_split_sig = signer.get_holder_split_tx_signature( + self.dlc_channel_manager.get_secp(), + &split_tx, + &sub_channel.original_funding_redeemscript, + sub_channel.fund_value_satoshis, + )?; + dlc::util::finalize_multi_sign_input_transaction( + &mut split_tx, + vec![ + (sub_channel.own_fund_pk, own_split_sig), + (sub_channel.counter_fund_pk, counter_split_signature), + ], + &sub_channel.original_funding_redeemscript, + 0, + ); - let closing_sub_channel = ClosingSubChannel { - signed_sub_channel: state, - is_initiator: true, - commitment_transactions, - }; + self.dlc_channel_manager + .get_blockchain() + .send_transaction(&split_tx)?; - sub_channel.state = SubChannelState::Closing(closing_sub_channel); + let closing_sub_channel = ClosingSubChannel { + signed_sub_channel: state, + is_initiator: true, + commitment_transactions, + }; - self.dlc_channel_manager - .get_store() - .upsert_sub_channel(&sub_channel)?; + sub_channel.state = SubChannelState::Closing(closing_sub_channel); - Ok(()) - }, - )?; + self.dlc_channel_manager + .get_store() + .upsert_sub_channel(&sub_channel)?; } else { unreachable!("Should not call this method if not in Signed or Finalized state"); } @@ -1067,7 +1094,11 @@ where /// Notify that LDK has decided to close the channel with given id. This MUST be called within /// the event handler provided to LDK in reaction to the `ChannelClosed` event. - pub fn notify_ln_channel_closed(&self, channel_id: ChannelId) -> Result<(), Error> { + pub fn notify_ln_channel_closed( + &self, + channel_id: ChannelId, + closure_reason: &ClosureReason, + ) -> Result<(), Error> { let mut sub_channel = self .dlc_channel_manager .get_store() @@ -1079,50 +1110,116 @@ where let mut chain_monitor = self.dlc_channel_manager.get_chain_monitor().lock().unwrap(); - let (updated_channel, updated_contract) = match sub_channel.state { - SubChannelState::Offered(_) - | SubChannelState::Accepted(_) - | SubChannelState::Confirmed(_) - | SubChannelState::CloseAccepted(_) - | SubChannelState::CloseConfirmed(_) - | SubChannelState::Finalized(_) => { - let dlc_channel_id = sub_channel - .get_dlc_channel_id(0) - .expect("to have a channel id"); - let (closed_channel, closed_contract) = - self.get_closed_dlc_channel_and_contract(dlc_channel_id, true)?; - sub_channel.state = SubChannelState::CounterOnChainClosed; - chain_monitor.cleanup_channel(sub_channel.channel_id); - chain_monitor.cleanup_channel(dlc_channel_id); - (Some(closed_channel), Some(closed_contract)) - } - SubChannelState::OffChainClosed => { - chain_monitor.cleanup_channel(sub_channel.channel_id); - sub_channel.state = SubChannelState::CounterOnChainClosed; - (None, None) - } - SubChannelState::Signed(_) - | SubChannelState::Closing(_) - | SubChannelState::CloseOffered(_) => { - error!("Got close notification from LDK in a state that we don't handle yet"); - return Ok(()); - } - SubChannelState::OnChainClosed | SubChannelState::CounterOnChainClosed => { - info!( - "Channel close notification received for channel: {:?}", - sub_channel.channel_id - ); - return Ok(()); - } - SubChannelState::ClosedPunished(_) => { - warn!("Got close notification while in ClosedPunished."); - return Ok(()); - } - SubChannelState::Rejected => { - info!("Counterparty closed channel in rejected state, marking as counter closed"); - sub_channel.state = SubChannelState::CounterOnChainClosed; - (None, None) + let (updated_channel, updated_contract) = match closure_reason { + ClosureReason::CounterpartyForceClosed { .. } + | ClosureReason::CommitmentTxConfirmed => match sub_channel.state { + SubChannelState::Offered(_) + | SubChannelState::Accepted(_) + | SubChannelState::Confirmed(_) + | SubChannelState::CloseAccepted(_) + | SubChannelState::CloseConfirmed(_) + | SubChannelState::Finalized(_) => { + let dlc_channel_id = sub_channel + .get_dlc_channel_id(0) + .expect("to have a channel id"); + let (closed_channel, closed_contract) = + self.get_closed_dlc_channel_and_contract(dlc_channel_id, true)?; + sub_channel.state = SubChannelState::CounterOnChainClosed; + chain_monitor.cleanup_channel(sub_channel.channel_id); + chain_monitor.cleanup_channel(dlc_channel_id); + (Some(closed_channel), Some(closed_contract)) + } + SubChannelState::OffChainClosed => { + chain_monitor.cleanup_channel(sub_channel.channel_id); + sub_channel.state = SubChannelState::CounterOnChainClosed; + (None, None) + } + SubChannelState::Signed(_) + | SubChannelState::Closing(_) + | SubChannelState::CloseOffered(_) => { + error!("Got notification of LN channel closure by counter party in a state where we do not expect it."); + return Ok(()); + } + SubChannelState::OnChainClosed | SubChannelState::CounterOnChainClosed => { + info!( + "Channel close notification received for channel: {:?}", + sub_channel.channel_id + ); + return Ok(()); + } + SubChannelState::ClosedPunished(_) => { + warn!("Got close notification while in ClosedPunished."); + return Ok(()); + } + SubChannelState::Rejected => { + info!( + "Counterparty closed channel in rejected state, marking as counter closed" + ); + sub_channel.state = SubChannelState::CounterOnChainClosed; + (None, None) + } + }, + ClosureReason::HolderForceClosed | ClosureReason::ProcessingError { .. } => { + match sub_channel.state { + SubChannelState::Offered(_) | SubChannelState::CloseConfirmed(_) => { + let dlc_channel_id = sub_channel + .get_dlc_channel_id(0) + .expect("to have a channel id"); + let (closed_channel, closed_contract) = + self.get_closed_dlc_channel_and_contract(dlc_channel_id, false)?; + sub_channel.state = SubChannelState::OnChainClosed; + chain_monitor.cleanup_channel(sub_channel.channel_id); + chain_monitor.cleanup_channel(dlc_channel_id); + (Some(closed_channel), Some(closed_contract)) + } + SubChannelState::OffChainClosed => { + chain_monitor.cleanup_channel(sub_channel.channel_id); + sub_channel.state = SubChannelState::OnChainClosed; + (None, None) + } + SubChannelState::Accepted(_) + | SubChannelState::Confirmed(_) + | SubChannelState::CloseAccepted(_) + | SubChannelState::Finalized(_) => { + return self + .force_close_sub_channel_internal(sub_channel, &mut chain_monitor); + } + SubChannelState::CloseOffered(ref c) => { + let split_input = &c.signed_subchannel.split_tx.transaction.input[0]; + let funding_txo = lightning::chain::transaction::OutPoint { + txid: split_input.previous_output.txid, + index: split_input.previous_output.vout as u16, + }; + let commitment_transactions = Some( + self.ln_chain_monitor + .get_latest_holder_commitment_txn(&funding_txo)?, + ); + let mut signed_subchannel = sub_channel.clone(); + signed_subchannel.state = + SubChannelState::Signed(c.signed_subchannel.clone()); + self.force_close_signed_channel( + signed_subchannel, + commitment_transactions, + )?; + return Ok(()); + } + SubChannelState::Signed(ref s) => { + let split_input = &s.split_tx.transaction.input[0]; + let funding_txo = lightning::chain::transaction::OutPoint { + txid: split_input.previous_output.txid, + index: split_input.previous_output.vout as u16, + }; + let commitment_transactions = Some( + self.ln_chain_monitor + .get_latest_holder_commitment_txn(&funding_txo)?, + ); + self.force_close_signed_channel(sub_channel, commitment_transactions)?; + return Ok(()); + } + _ => return Ok(()), + } } + _ => return Ok(()), }; if let Some(channel) = updated_channel { @@ -1133,11 +1230,10 @@ where self.dlc_channel_manager .get_store() - .upsert_sub_channel(&sub_channel)?; - + .persist_chain_monitor(&chain_monitor)?; self.dlc_channel_manager .get_store() - .persist_chain_monitor(&chain_monitor)?; + .upsert_sub_channel(&sub_channel)?; Ok(()) } @@ -1261,9 +1357,17 @@ where Signed, None:: )?; + let funding_txo = lightning::chain::transaction::OutPoint { + txid: state.signed_subchannel.split_tx.transaction.input[0] + .previous_output + .txid, + index: state.signed_subchannel.split_tx.transaction.input[0] + .previous_output + .vout as u16, + }; let commitment_transactions = self - .ln_channel_manager - .get_latest_holder_commitment_txn(channel_lock)?; + .ln_chain_monitor + .get_latest_holder_commitment_txn(&funding_txo)?; let total_collateral = dlc_channel.own_params.collateral + dlc_channel.counter_params.collateral; @@ -1477,6 +1581,7 @@ where counter_fund_pk: channel_details.counter_funding_pubkey.ok_or_else(|| { Error::InvalidState("Counter funding PK is missing".to_string()) })?, + channel_keys_id: channel_details.channel_keys_id, }, }; @@ -1655,27 +1760,26 @@ where channel_id, counter_party, |channel_lock| { - let mut split_tx_adaptor_signature = None; - self.ln_channel_manager - .sign_with_fund_key_cb(channel_lock, &mut |sk| { - split_tx_adaptor_signature = Some( - get_tx_adaptor_signature( - self.dlc_channel_manager.get_secp(), - &split_tx.transaction, - channel_details.channel_value_satoshis, - &funding_redeemscript, - sk, - &accept_revoke_params.publish_pk.inner, - ) - .unwrap(), - ); - }); + let mut signers = self.ln_channel_signers.lock().unwrap(); - let split_tx_adaptor_signature = split_tx_adaptor_signature.unwrap(); + let signer = signers.entry(*channel_id).or_insert( + self.signer_provider.derive_ln_dlc_channel_signer( + channel_details.channel_value_satoshis, + channel_details.channel_keys_id, + ), + ); + + let split_tx_adaptor_signature = signer.get_holder_split_tx_adaptor_signature( + self.dlc_channel_manager.get_secp(), + &split_tx.transaction, + channel_details.channel_value_satoshis, + &funding_redeemscript, + &accept_revoke_params.publish_pk.inner, + )?; let commitment_transactions = self - .ln_channel_manager - .get_latest_holder_commitment_txn(channel_lock)?; + .ln_chain_monitor + .get_latest_holder_commitment_txn(&funding_txo)?; let commitment_signed = self .ln_channel_manager @@ -1941,23 +2045,22 @@ where self.dlc_channel_manager.get_chain_monitor(), )?; - let mut split_tx_adaptor_signature = None; - self.ln_channel_manager - .sign_with_fund_key_cb(channel_lock, &mut |sk| { - split_tx_adaptor_signature = Some( - get_tx_adaptor_signature( - self.dlc_channel_manager.get_secp(), - &state.split_tx.transaction, - accepted_sub_channel.fund_value_satoshis, - funding_redeemscript, - sk, - &offer_revoke_params.publish_pk.inner, - ) - .unwrap(), - ); - }); + let mut signers = self.ln_channel_signers.lock().unwrap(); - let split_adaptor_signature = split_tx_adaptor_signature.unwrap(); + let signer = signers.entry(sub_channel_confirm.channel_id).or_insert( + self.signer_provider.derive_ln_dlc_channel_signer( + channel_details.channel_value_satoshis, + channel_details.channel_keys_id, + ), + ); + + let split_adaptor_signature = signer.get_holder_split_tx_adaptor_signature( + self.dlc_channel_manager.get_secp(), + &state.split_tx.transaction, + accepted_sub_channel.fund_value_satoshis, + funding_redeemscript, + &offer_revoke_params.publish_pk.inner, + )?; let signed_sub_channel = SignedSubChannel { own_per_split_point: state.accept_per_split_point, @@ -2309,9 +2412,17 @@ where next_per_commitment_point: raa.next_per_commitment_point, }; + let funding_txo = lightning::chain::transaction::OutPoint { + txid: state.signed_subchannel.split_tx.transaction.input[0] + .previous_output + .txid, + index: state.signed_subchannel.split_tx.transaction.input[0] + .previous_output + .vout as u16, + }; let commitment_transactions = self - .ln_channel_manager - .get_latest_holder_commitment_txn(channel_lock)?; + .ln_chain_monitor + .get_latest_holder_commitment_txn(&funding_txo)?; let updated_channel = CloseConfirmedSubChannel { signed_subchannel: state.signed_subchannel, @@ -3532,22 +3643,27 @@ where impl< W: Deref, M: Deref, + C: Deref, S: Deref, B: Deref, O: Deref, T: Deref, F: Deref, D: Deref>, - SP: ChannelSigner, - > ChannelMessageHandler for SubChannelManager + CS: ChannelSigner, + SP: Deref, + LCS: LnDlcChannelSigner, + > ChannelMessageHandler for SubChannelManager where W::Target: Wallet, - M::Target: LNChannelManager, + M::Target: LNChannelManager, + C::Target: LNChainMonitor, S::Target: Storage, B::Target: Blockchain, O::Target: Oracle, T::Target: Time, F::Target: FeeEstimator, + SP::Target: LnDlcSignerProvider, { fn handle_open_channel( &self, @@ -3731,22 +3847,27 @@ where impl< W: Deref, M: Deref, + C: Deref, S: Deref, B: Deref, O: Deref, T: Deref, F: Deref, D: Deref>, - SP: ChannelSigner, - > MessageSendEventsProvider for SubChannelManager + CS: ChannelSigner, + SP: Deref, + LCS: LnDlcChannelSigner, + > MessageSendEventsProvider for SubChannelManager where W::Target: Wallet, - M::Target: LNChannelManager, + M::Target: LNChannelManager, + C::Target: LNChainMonitor, S::Target: Storage, B::Target: Blockchain, O::Target: Oracle, T::Target: Time, F::Target: FeeEstimator, + SP::Target: LnDlcSignerProvider, { fn get_and_clear_pending_msg_events(&self) -> Vec { let mut msg_events = self.ln_channel_manager.get_and_clear_pending_msg_events(); diff --git a/dlc-manager/src/subchannel/mod.rs b/dlc-manager/src/subchannel/mod.rs index 906f6304..19710111 100644 --- a/dlc-manager/src/subchannel/mod.rs +++ b/dlc-manager/src/subchannel/mod.rs @@ -8,7 +8,8 @@ use dlc::channel::sub_channel::SplitTx; use lightning::{ chain::{ chaininterface::{BroadcasterInterface, FeeEstimator}, - keysinterface::{EntropySource, NodeSigner, SignerProvider}, + chainmonitor::ChainMonitor, + keysinterface::{EntropySource, NodeSigner, SignerProvider, WriteableEcdsaChannelSigner}, }, ln::{ chan_utils::CounterpartyCommitmentSecrets, @@ -56,6 +57,8 @@ pub struct SubChannel { pub counter_fund_pk: PublicKey, /// The revocation secrets from the remote party for already revoked split transactions. pub counter_party_secrets: CounterpartyCommitmentSecrets, + /// The id used to derive the keys for the Lightning channel. + pub channel_keys_id: [u8; 32], } impl std::fmt::Debug for SubChannel { @@ -422,11 +425,6 @@ where revoke_and_ack: &RevokeAndACK, ) -> Result<(), APIError>; - /// Gives the ability to access the funding secret key within the provided callback. - fn sign_with_fund_key_cb(&self, channel_lock: &mut ChannelLock, cb: &mut F) - where - F: FnMut(&SecretKey); - /// Force close the channel with given `channel_id` and `counter_party_node_id`. fn force_close_channel( &self, @@ -443,12 +441,15 @@ where channel_value_satoshis: u64, value_to_self_msat: u64, ); +} +/// +pub trait LNChainMonitor { /// Gets the latest commitment transactions and HTLC transactions that can be used to close the /// channel. fn get_latest_holder_commitment_txn( &self, - channel_lock: &ChannelLock, + funding_txo: &lightning::chain::transaction::OutPoint, ) -> Result, Error>; } @@ -508,16 +509,6 @@ where self.revoke_and_ack_commitment(channel_lock, revoke_and_ack) } - fn sign_with_fund_key_cb( - &self, - channel_lock: &mut ChannelLock<::Signer>, - cb: &mut SF, - ) where - SF: FnMut(&SecretKey), - { - self.sign_with_fund_key_callback(channel_lock, cb); - } - fn force_close_channel( &self, channel_id: &[u8; 32], @@ -542,19 +533,6 @@ where ); } - fn get_latest_holder_commitment_txn( - &self, - channel_lock: &ChannelLock<::Signer>, - ) -> Result, Error> { - self.get_latest_holder_commitment_txn(channel_lock) - .map_err(|e| { - Error::InvalidState(format!( - "Could not retrieve latest commitment transactions {:?}", - e - )) - }) - } - fn with_useable_channel_lock( &self, channel_id: &ChannelId, @@ -584,6 +562,32 @@ where } } +impl< + ChannelSigner: WriteableEcdsaChannelSigner, + C: Deref, + T: Deref, + F: Deref, + L: Deref, + P: Deref, + > LNChainMonitor for ChainMonitor +where + C::Target: lightning::chain::Filter, + T::Target: BroadcasterInterface, + F::Target: FeeEstimator, + L::Target: Logger, + P::Target: lightning::chain::chainmonitor::Persist, +{ + fn get_latest_holder_commitment_txn( + &self, + funding_txo: &lightning::chain::transaction::OutPoint, + ) -> Result, Error> { + self.get_latest_holder_commitment_txn(funding_txo) + .map_err(|e| { + Error::InvalidParameters(format!("Could not get channel monitor: {:?}", e)) + }) + } +} + /// Generate a temporary channel id for a DLC channel based on the LN channel id, the update index of the /// split transaction and the index of the DLC channel within the sub channel. pub fn generate_temporary_channel_id( @@ -597,3 +601,42 @@ pub fn generate_temporary_channel_id( data.extend_from_slice(&channel_index.to_be_bytes()); bitcoin::hashes::sha256::Hash::hash(&data).into_inner() } + +/// Trait to be implemented by a signing component providing the ability to sign LN/DLC split +/// transactions. It is required that the signer's keys are identical to the ones used for the +/// original Lightning channel. +pub trait LnDlcChannelSigner { + /// Get the signature for the split transaction using the LN channel holder funding secret key. + fn get_holder_split_tx_signature( + &self, + secp: &secp256k1_zkp::Secp256k1, + split_tx: &Transaction, + original_funding_redeemscript: &Script, + original_channel_value_satoshis: u64, + ) -> Result; + + /// Get an adaptor signature for the split transaction using the LN channel holder funding + /// secret key as the signing key, and the remote party publish public key as adaptor key. + fn get_holder_split_tx_adaptor_signature( + &self, + secp: &secp256k1_zkp::Secp256k1, + split_tx: &Transaction, + original_channel_value_satoshis: u64, + original_funding_redeemscript: &Script, + other_publish_key: &PublicKey, + ) -> Result; +} + +/// Generates `Signer` that are able to sign split transaction for LN/DLC channels. +pub trait LnDlcSignerProvider { + /// Derives the private key material backing a `Signer`. + /// + /// To derive a new `Signer`, the same `channel_keys_id` and `channel_value_satoshis` parameter + /// that were provided to generate the LDK `ChannelSigner` shoud be passed, and the + /// implementation should derive the same key values. + fn derive_ln_dlc_channel_signer( + &self, + channel_value_satoshis: u64, + channel_keys_id: [u8; 32], + ) -> Signer; +} diff --git a/dlc-manager/src/subchannel/ser.rs b/dlc-manager/src/subchannel/ser.rs index f2f687a9..0dece5c0 100644 --- a/dlc-manager/src/subchannel/ser.rs +++ b/dlc-manager/src/subchannel/ser.rs @@ -24,7 +24,8 @@ impl_dlc_writeable!(SubChannel, { (is_offer, writeable), (own_fund_pk, writeable), (counter_fund_pk, writeable), - (counter_party_secrets, writeable) + (counter_party_secrets, writeable), + (channel_keys_id, writeable) }); impl_dlc_writeable_enum!(SubChannelState, diff --git a/dlc-manager/tests/custom_signer.rs b/dlc-manager/tests/custom_signer.rs index e5eab9e4..cc70094e 100644 --- a/dlc-manager/tests/custom_signer.rs +++ b/dlc-manager/tests/custom_signer.rs @@ -1,6 +1,10 @@ use std::sync::{Arc, Mutex}; use bitcoin::{Script, Transaction, TxOut}; +use dlc_manager::{ + error::Error, + subchannel::{LnDlcChannelSigner, LnDlcSignerProvider}, +}; use lightning::{ chain::keysinterface::{ ChannelSigner, EcdsaChannelSigner, EntropySource, ExtraSign, InMemorySigner, KeysManager, @@ -252,6 +256,45 @@ impl Readable for CustomSigner { } } +impl LnDlcChannelSigner for CustomSigner { + fn get_holder_split_tx_signature( + &self, + secp: &Secp256k1, + split_tx: &Transaction, + original_funding_redeemscript: &Script, + original_channel_value_satoshis: u64, + ) -> Result { + dlc::util::get_raw_sig_for_tx_input( + secp, + &split_tx, + 0, + original_funding_redeemscript, + original_channel_value_satoshis, + &self.in_memory_signer.lock().unwrap().funding_key, + ) + .map_err(|e| e.into()) + } + + fn get_holder_split_tx_adaptor_signature( + &self, + secp: &Secp256k1, + split_tx: &Transaction, + original_channel_value_satoshis: u64, + original_funding_redeemscript: &Script, + other_publish_key: &secp256k1_zkp::PublicKey, + ) -> Result { + dlc::channel::get_tx_adaptor_signature( + secp, + &split_tx, + original_channel_value_satoshis, + original_funding_redeemscript, + &self.in_memory_signer.lock().unwrap().funding_key, + other_publish_key, + ) + .map_err(|e| e.into()) + } +} + impl WriteableEcdsaChannelSigner for CustomSigner {} pub struct CustomKeysManager { @@ -325,6 +368,16 @@ impl SignerProvider for CustomKeysManager { } } +impl LnDlcSignerProvider for CustomKeysManager { + fn derive_ln_dlc_channel_signer( + &self, + channel_value_satoshis: u64, + channel_keys_id: [u8; 32], + ) -> CustomSigner { + self.derive_channel_signer(channel_value_satoshis, channel_keys_id) + } +} + impl EntropySource for CustomKeysManager { fn get_secure_random_bytes(&self) -> [u8; 32] { self.keys_manager.get_secure_random_bytes() diff --git a/dlc-manager/tests/ln_dlc_channel_execution_tests.rs b/dlc-manager/tests/ln_dlc_channel_execution_tests.rs index 152b820d..c3418fae 100644 --- a/dlc-manager/tests/ln_dlc_channel_execution_tests.rs +++ b/dlc-manager/tests/ln_dlc_channel_execution_tests.rs @@ -72,7 +72,7 @@ type ChainMonitor = lightning::chain::chainmonitor::ChainMonitor< CustomSigner, Arc>>, Arc>>, - Arc, + Arc>>, Arc, Arc, >; @@ -83,7 +83,7 @@ pub(crate) type ChannelManager = lightning::ln::channelmanager::ChannelManager< Arc, Arc, Arc, - Arc, + Arc>>, Arc< DefaultRouter< Arc>>, @@ -116,6 +116,7 @@ type DlcChannelManager = Manager< type DlcSubChannelManager = SubChannelManager< Arc, Arc>>, Arc, + Arc, Arc, Arc, Arc, @@ -123,6 +124,8 @@ type DlcSubChannelManager = SubChannelManager< Arc, Arc, CustomSigner, + Arc, + CustomSigner, >; struct LnDlcParty { @@ -161,8 +164,8 @@ impl LnDlcParty { } fn process_events(&self) { - self.peer_manager.process_events(); self.channel_manager.process_pending_events(self); + self.peer_manager.process_events(); self.channel_manager.timer_tick_occurred(); self.chain_monitor.process_pending_events(self); } @@ -367,10 +370,29 @@ impl EventHandler for LnDlcParty { .unwrap(); self.blockchain.broadcast_transaction(&spending_tx); } - Event::ChannelClosed { channel_id, .. } => { + Event::ChannelClosed { + channel_id, reason, .. + } => { + let signed_channel = self + .dlc_manager + .get_store() + .get_sub_channel(channel_id) + .unwrap() + .unwrap(); + if let SubChannelState::Signed(s) = signed_channel.state { + let prev_out = s.split_tx.transaction.input[0].previous_output; + let monitor = self + .chain_monitor + .get_monitor(lightning::chain::transaction::OutPoint { + txid: prev_out.txid, + index: prev_out.vout as u16, + }) + .unwrap(); + let a = monitor.get_latest_holder_commitment_txn(&self.logger); + }; if let Err(error) = self .sub_channel_manager - .notify_ln_channel_closed(channel_id) + .notify_ln_channel_closed(channel_id, &reason) { error!( "Error notifying sub channel manager of LN channel closing: {}", @@ -415,7 +437,7 @@ fn create_ln_node( Some(tx_sync.clone()), mock_blockchain.clone(), logger.clone(), - blockchain_provider.clone(), + mock_blockchain.clone(), persister.clone(), )); @@ -447,7 +469,7 @@ fn create_ln_node( }; Arc::new(ChannelManager::new( - blockchain_provider.clone(), + mock_blockchain.clone(), chain_monitor.clone(), mock_blockchain.clone(), router, @@ -498,8 +520,15 @@ fn create_ln_node( .unwrap(), ); - let sub_channel_manager = - Arc::new(SubChannelManager::new(channel_manager.clone(), dlc_manager.clone()).unwrap()); + let sub_channel_manager = Arc::new( + SubChannelManager::new( + channel_manager.clone(), + dlc_manager.clone(), + chain_monitor.clone(), + consistent_keys_manager.clone(), + ) + .unwrap(), + ); let lightning_msg_handler = MessageHandler { chan_handler: sub_channel_manager.clone(), @@ -1318,7 +1347,7 @@ fn ln_dlc_close_offered_force_close() { open_sub_channel(&test_params); - go_to_off_chain_close_state(&test_params, TargetState::OfferSent); + go_to_off_chain_close_state(&test_params, TargetState::OfferSent, false); assert_sub_channel_state!( test_params.alice_node.sub_channel_manager, @@ -1351,7 +1380,7 @@ fn ln_dlc_close_offered_force_close2() { open_sub_channel(&test_params); - go_to_off_chain_close_state(&test_params, TargetState::OfferSent); + go_to_off_chain_close_state(&test_params, TargetState::OfferSent, false); assert_sub_channel_state!( test_params.alice_node.sub_channel_manager, @@ -1384,7 +1413,7 @@ fn ln_dlc_close_offered_force_close3() { open_sub_channel(&test_params); - go_to_off_chain_close_state(&test_params, TargetState::OfferReceived); + go_to_off_chain_close_state(&test_params, TargetState::OfferReceived, false); assert_sub_channel_state!( test_params.alice_node.sub_channel_manager, @@ -1416,7 +1445,7 @@ fn ln_dlc_close_offered_force_close4() { open_sub_channel(&test_params); - go_to_off_chain_close_state(&test_params, TargetState::OfferReceived); + go_to_off_chain_close_state(&test_params, TargetState::OfferReceived, false); assert_sub_channel_state!( test_params.alice_node.sub_channel_manager, @@ -1449,7 +1478,7 @@ fn ln_dlc_close_accepted_force_close() { open_sub_channel(&test_params); - go_to_off_chain_close_state(&test_params, TargetState::Accepted); + go_to_off_chain_close_state(&test_params, TargetState::Accepted, false); assert_sub_channel_state!( test_params.alice_node.sub_channel_manager, @@ -1482,7 +1511,7 @@ fn ln_dlc_close_accepted_force_close2() { open_sub_channel(&test_params); - go_to_off_chain_close_state(&test_params, TargetState::Accepted); + go_to_off_chain_close_state(&test_params, TargetState::Accepted, false); assert_sub_channel_state!( test_params.alice_node.sub_channel_manager, @@ -1515,7 +1544,7 @@ fn ln_dlc_close_confirmed_force_close() { open_sub_channel(&test_params); - go_to_off_chain_close_state(&test_params, TargetState::Confirmed); + go_to_off_chain_close_state(&test_params, TargetState::Confirmed, false); assert_sub_channel_state!( test_params.alice_node.sub_channel_manager, @@ -1548,7 +1577,7 @@ fn ln_dlc_close_confirmed_force_close2() { open_sub_channel(&test_params); - go_to_off_chain_close_state(&test_params, TargetState::Confirmed); + go_to_off_chain_close_state(&test_params, TargetState::Confirmed, false); assert_sub_channel_state!( test_params.alice_node.sub_channel_manager, @@ -1584,7 +1613,7 @@ fn ln_dlc_close_finalized_force_close() { mocks::mock_time::set_time(EVENT_MATURITY as u64); - go_to_off_chain_close_state(&test_params, TargetState::Finalized); + go_to_off_chain_close_state(&test_params, TargetState::Finalized, false); assert_sub_channel_state!( test_params.alice_node.sub_channel_manager, @@ -1604,6 +1633,190 @@ fn ln_dlc_close_finalized_force_close() { force_close_mid_protocol(&mut test_params, false, &commit_tx); } +#[test] +#[ignore] +fn ln_dlc_ldk_auto_close_established_test() { + let mut test_params = test_init(); + + open_sub_channel(&test_params); + + let commit_tx = get_commit_tx_from_node(&test_params.bob_node, &test_params.funding_txo); + + mocks::mock_time::set_time(EVENT_MATURITY as u64); + + ldk_auto_close(&mut test_params, &commit_tx[0]); +} + +#[test] +#[ignore] +fn ln_dlc_ldk_auto_close_offered_test() { + let mut test_params = test_init(); + + go_to_established_target_state(&test_params, TargetState::OfferReceived); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + Offered + ); + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + Offered + ); + + let commit_tx = get_commit_tx_from_node(&test_params.bob_node, &test_params.funding_txo); + ldk_auto_close(&mut test_params, &commit_tx[0]); +} + +#[test] +#[ignore] +fn ln_dlc_ldk_auto_close_accepted_test() { + let mut test_params = test_init(); + + go_to_established_target_state(&test_params, TargetState::Accepted); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + Offered + ); + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + Accepted + ); + + let sub_channel = test_params + .bob_node + .dlc_manager + .get_store() + .get_sub_channel(test_params.channel_id) + .unwrap() + .unwrap(); + let commit_tx = if let SubChannelState::Accepted(a) = &sub_channel.state { + a.commitment_transactions[0].clone() + } else { + unreachable!(); + }; + + ldk_auto_close(&mut test_params, &commit_tx); +} + +//TODO(tibo): Find a way to trigger an ldk auto force close in this state. Changing the fee does +//not work because Alice is awaiting a revoke from Bob and doesn't send the fee update message, +//sending a bad commitment signature also doesn't really fit with the state. +// fn ln_dlc_ldk_auto_close_confirmed_test() { +// } +// #[test] +// #[ignore] +// fn ln_dlc_ldk_auto_close_finalized_test() { +// } + +#[test] +#[ignore] +fn ln_dlc_ldk_auto_close_close_offered_test() { + let mut test_params = test_init(); + + make_ln_payment(&test_params.alice_node, &test_params.bob_node, 900000); + + open_sub_channel(&test_params); + + go_to_off_chain_close_state(&test_params, TargetState::OfferReceived, false); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + CloseOffered + ); + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + CloseOffered + ); + + mocks::mock_time::set_time(EVENT_MATURITY as u64); + + let commit_tx = + get_commit_tx_from_node(&test_params.bob_node, &test_params.funding_txo).remove(0); + + ldk_auto_close(&mut test_params, &commit_tx); +} + +#[test] +#[ignore] +fn ln_dlc_ldk_auto_close_close_accepted_test() { + let mut test_params = test_init(); + + make_ln_payment(&test_params.alice_node, &test_params.bob_node, 900000); + + open_sub_channel(&test_params); + + go_to_off_chain_close_state(&test_params, TargetState::Accepted, false); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + CloseOffered + ); + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + CloseAccepted + ); + + mocks::mock_time::set_time(EVENT_MATURITY as u64); + + let commit_tx = + get_commit_tx_from_node(&test_params.alice_node, &test_params.funding_txo).remove(0); + + ldk_auto_close(&mut test_params, &commit_tx); +} + +//TODO(tibo): find a way to test this state. +// #[test] +// #[ignore] +// fn ln_dlc_ldk_auto_close_close_confirmed_test() { +// } + +#[test] +#[ignore] +/// Force close triggered by the party who offered to force close the channel, after their +/// counter party processed the close confirm message, but before they processed the close +/// finalize message. +fn ln_dlc_ldk_auto_close_close_finalized_test() { + let mut test_params = test_init(); + + make_ln_payment(&test_params.alice_node, &test_params.bob_node, 900000); + + open_sub_channel(&test_params); + + mocks::mock_time::set_time(EVENT_MATURITY as u64); + + go_to_off_chain_close_state(&test_params, TargetState::Finalized, true); + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + CloseConfirmed + ); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id; + OffChainClosed + ); + + let commit_tx = + get_commit_tx_from_node(&test_params.bob_node, &test_params.funding_txo).remove(0); + + ldk_auto_close(&mut test_params, &commit_tx); +} + fn test_init() -> LnDlcTestParams { env_logger::init(); let (_, _, sink_rpc) = init_clients(); @@ -1843,7 +2056,7 @@ fn get_commit_tx_from_node( funding_txo: &lightning::chain::transaction::OutPoint, ) -> Vec { node.chain_monitor - .get_latest_holder_commitment_txn(*funding_txo) + .get_latest_holder_commitment_txn(funding_txo) .expect("to be able to get latest holder commitment transaction") } @@ -3049,9 +3262,17 @@ fn go_to_established_target_state(test_params: &LnDlcTestParams, target_state: T .unwrap(); } -fn go_to_off_chain_close_state(test_params: &LnDlcTestParams, target_state: TargetState) { - let (close_offer, _) = test_params - .alice_node +fn go_to_off_chain_close_state( + test_params: &LnDlcTestParams, + target_state: TargetState, + reverse_closer: bool, +) { + let (closer, closee) = if reverse_closer { + (&test_params.bob_node, &test_params.alice_node) + } else { + (&test_params.alice_node, &test_params.bob_node) + }; + let (close_offer, _) = closer .sub_channel_manager .offer_subchannel_close( &test_params.channel_id, @@ -3063,12 +3284,11 @@ fn go_to_off_chain_close_state(test_params: &LnDlcTestParams, target_state: Targ return; } - test_params - .bob_node + closee .sub_channel_manager .on_sub_channel_message( &SubChannelMessage::CloseOffer(close_offer), - &test_params.alice_node.channel_manager.get_our_node_id(), + &closer.channel_manager.get_our_node_id(), ) .unwrap(); @@ -3076,8 +3296,7 @@ fn go_to_off_chain_close_state(test_params: &LnDlcTestParams, target_state: Targ return; } - let (accept, _) = test_params - .bob_node + let (accept, _) = closee .sub_channel_manager .accept_subchannel_close_offer(&test_params.channel_id) .unwrap(); @@ -3086,12 +3305,11 @@ fn go_to_off_chain_close_state(test_params: &LnDlcTestParams, target_state: Targ return; } - let confirm = test_params - .alice_node + let confirm = closer .sub_channel_manager .on_sub_channel_message( &SubChannelMessage::CloseAccept(accept), - &test_params.bob_node.channel_manager.get_our_node_id(), + &closee.channel_manager.get_our_node_id(), ) .unwrap() .unwrap(); @@ -3100,12 +3318,113 @@ fn go_to_off_chain_close_state(test_params: &LnDlcTestParams, target_state: Targ return; } - test_params - .bob_node + closee .sub_channel_manager - .on_sub_channel_message( - &confirm, - &test_params.alice_node.channel_manager.get_our_node_id(), - ) + .on_sub_channel_message(&confirm, &closer.channel_manager.get_our_node_id()) + .unwrap(); +} + +fn ldk_auto_close(test_params: &mut LnDlcTestParams, commit_tx: &Transaction) { + let sub_channel = test_params + .alice_node + .dlc_manager + .get_store() + .get_sub_channel(test_params.channel_id) + .unwrap() + .unwrap(); + let dlc_channel_id_alice = sub_channel.get_dlc_channel_id(0); + + let sub_channel = test_params + .bob_node + .dlc_manager + .get_store() + .get_sub_channel(sub_channel.channel_id) + .unwrap() .unwrap(); + let dlc_channel_id_bob = sub_channel.get_dlc_channel_id(0); + + let channel_id = sub_channel.channel_id; + + test_params.alice_node.mock_blockchain.set_est_fee(10000); + test_params.alice_node.process_events(); + test_params.bob_node.process_events(); + test_params.alice_node.process_events(); + test_params.bob_node.process_events(); + test_params.alice_node.process_events(); + test_params.bob_node.process_events(); + test_params.alice_node.process_events(); + test_params.bob_node.process_events(); + + test_params.generate_blocks(500); + test_params.bob_node.update_to_chain_tip(); + test_params.bob_node.process_events(); + + assert_sub_channel_state!(test_params.bob_node.sub_channel_manager, &channel_id; OnChainClosed); + + test_params.generate_blocks(3); + + test_params.bob_node.update_to_chain_tip(); + test_params.alice_node.update_to_chain_tip(); + test_params.alice_node.process_events(); + + assert_sub_channel_state!(test_params.alice_node.sub_channel_manager, &channel_id; CounterOnChainClosed); + + test_params.generate_blocks(500); + + test_params.bob_node.update_to_chain_tip(); + test_params.bob_node.process_events(); + test_params.alice_node.update_to_chain_tip(); + test_params.alice_node.process_events(); + + test_params.generate_blocks(2); + + test_params.bob_node.update_to_chain_tip(); + test_params.bob_node.process_events(); + test_params.alice_node.update_to_chain_tip(); + test_params.alice_node.process_events(); + + if let Some(dlc_channel_id_alice) = dlc_channel_id_alice { + assert_channel_state_unlocked!( + test_params.alice_node.dlc_manager, + dlc_channel_id_alice, + CounterClosed + ); + } + if let Some(dlc_channel_id_bob) = dlc_channel_id_bob { + assert_channel_state_unlocked!( + test_params.bob_node.dlc_manager, + dlc_channel_id_bob, + Closed + ); + } + + assert!(test_params + .bob_node + .dlc_manager + .get_chain_monitor() + .lock() + .unwrap() + .is_empty()); + assert!(test_params + .alice_node + .dlc_manager + .get_chain_monitor() + .lock() + .unwrap() + .is_empty()); + + let all_spent = test_params + .electrs + .get_outspends(&commit_tx.txid()) + .unwrap() + .into_iter() + .all(|x| { + if let OutSpendResp::Spent(_) = x { + true + } else { + false + } + }); + + assert!(all_spent); } diff --git a/dlc/src/util.rs b/dlc/src/util.rs index a07ed9eb..f5ec55bb 100644 --- a/dlc/src/util.rs +++ b/dlc/src/util.rs @@ -165,39 +165,47 @@ pub fn sign_multi_sig_input( input_value: u64, input_index: usize, ) -> Result<(), Error> { - let own_sig = get_sig_for_tx_input( + let own_sig = get_raw_sig_for_tx_input( secp, transaction, input_index, script_pubkey, input_value, - EcdsaSighashType::All, sk, )?; - let own_pk = &PublicKey::from_secret_key(secp, sk); - - let other_finalized_sig = finalize_sig(other_sig, EcdsaSighashType::All); + let own_pk = PublicKey::from_secret_key(secp, sk); - transaction.input[input_index].witness = if own_pk < other_pk { - Witness::from_vec(vec![ - Vec::new(), - own_sig, - other_finalized_sig, - script_pubkey.to_bytes(), - ]) - } else { - Witness::from_vec(vec![ - Vec::new(), - other_finalized_sig, - own_sig, - script_pubkey.to_bytes(), - ]) - }; + finalize_multi_sign_input_transaction( + transaction, + vec![(*other_pk, *other_sig), (own_pk, own_sig)], + script_pubkey, + input_index, + ); Ok(()) } +/// Sorts signatures based on the lexicographical order of associated public keys, appends +/// `EcdsaSighashType::All` to each signature, and insert them in the transaction witness for the +/// procided input index, together with the given script pubkey. +pub fn finalize_multi_sign_input_transaction( + transaction: &mut Transaction, + mut signature_pubkey_pairs: Vec<(PublicKey, Signature)>, + script_pubkey: &Script, + input_index: usize, +) { + signature_pubkey_pairs.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); + let mut signatures = signature_pubkey_pairs + .into_iter() + .map(|(_, s)| finalize_sig(&s, EcdsaSighashType::All)) + .collect(); + let mut witness = vec![Vec::new()]; + witness.append(&mut signatures); + witness.push(script_pubkey.to_bytes()); + transaction.input[input_index].witness = Witness::from_vec(witness); +} + /// Transforms a redeem script for a p2sh-p2w* output to a script signature. pub(crate) fn redeem_script_to_script_sig(redeem: &Script) -> Script { match redeem.len() { diff --git a/mocks/src/mock_blockchain.rs b/mocks/src/mock_blockchain.rs index 4a2fb644..70a17421 100644 --- a/mocks/src/mock_blockchain.rs +++ b/mocks/src/mock_blockchain.rs @@ -12,6 +12,7 @@ where inner: T, discard: Mutex, discard_ids: Mutex>, + est_fee: Mutex, } impl MockBlockchain @@ -23,6 +24,7 @@ where inner, discard: Mutex::new(false), discard_ids: Mutex::new(Vec::new()), + est_fee: Mutex::new(500), } } @@ -33,6 +35,10 @@ where pub fn discard_id(&self, txid: Txid) { self.discard_ids.lock().unwrap().push(txid); } + + pub fn set_est_fee(&self, est_fee: u32) { + *self.est_fee.lock().unwrap() = est_fee; + } } impl BroadcasterInterface for MockBlockchain @@ -83,7 +89,7 @@ where &self, _confirmation_target: lightning::chain::chaininterface::ConfirmationTarget, ) -> u32 { - unimplemented!() + *self.est_fee.lock().unwrap() } }