diff --git a/Cargo.toml b/Cargo.toml index 7c28419a..f5fdec70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,6 @@ members = [ ] [patch.crates-io] -lightning = { git = "https://github.com/p2pderivatives/rust-lightning/", rev = "74690beb" } -lightning-net-tokio = { git = "https://github.com/p2pderivatives/rust-lightning/", rev = "74690beb" } -lightning-persister = { git = "https://github.com/p2pderivatives/rust-lightning/", rev = "74690beb" } +lightning = { git = "https://github.com/p2pderivatives/rust-lightning/", rev = "40a46270" } +lightning-net-tokio = { git = "https://github.com/p2pderivatives/rust-lightning/", rev = "40a46270" } +lightning-persister = { git = "https://github.com/p2pderivatives/rust-lightning/", rev = "40a46270" } diff --git a/dlc-manager/src/channel/mod.rs b/dlc-manager/src/channel/mod.rs index 8e8d0b70..4cb7a275 100644 --- a/dlc-manager/src/channel/mod.rs +++ b/dlc-manager/src/channel/mod.rs @@ -1,6 +1,6 @@ //! # Module containing structures and methods for working with DLC channels. -use bitcoin::hashes::Hash; +use bitcoin::{hashes::Hash, Transaction, Txid}; use dlc_messages::channel::{AcceptChannel, SignChannel}; use secp256k1_zkp::PublicKey; @@ -27,6 +27,24 @@ pub enum Channel { Accepted(AcceptedChannel), /// A channel whose fund outputs have been signed by the offer party. Signed(SignedChannel), + /// A [`Channel`] is in `Closing` state when the local party + /// has broadcast a buffer transaction and is waiting to finalize the + /// closing of a the channel by broadcasting a CET. + Closing(ClosingChannel), + /// A [`Channel`] is in `Closed` state when it was force closed by + /// the local party. + Closed(ClosedChannel), + /// A [`Channel`] is in `CounterClosed` state when it was force + /// closed by the counter party. + CounterClosed(ClosedChannel), + /// A [`Channel`] is in `ClosedPublished` state when the local + /// party broadcast a punishment transaction in response to the counter + /// party broadcasting a settle or buffer transaction for a revoked channel + /// state. + ClosedPunished(ClosedPunishedChannel), + /// A [`SignedChannel`] is in `CollaborativelyClosed` state when it was + /// collaboratively closed. + CollaborativelyClosed(ClosedChannel), /// A channel that failed when validating an /// [`dlc_messages::channel::AcceptChannel`] message. FailedAccept(FailedAccept), @@ -43,8 +61,13 @@ impl std::fmt::Debug for Channel { Channel::Signed(_) => "signed", Channel::FailedAccept(_) => "failed accept", Channel::FailedSign(_) => "failed sign", + Channel::Closing(_) => "closing", + Channel::Closed(_) => "closed", + Channel::CounterClosed(_) => "counter closed", + Channel::ClosedPunished(_) => "closed punished", + Channel::CollaborativelyClosed(_) => "collaboratively closed", }; - f.debug_struct("Contract").field("state", &state).finish() + f.debug_struct("Channel").field("state", &state).finish() } } @@ -57,6 +80,11 @@ impl Channel { Channel::Signed(s) => s.counter_party, Channel::FailedAccept(f) => f.counter_party, Channel::FailedSign(f) => f.counter_party, + Channel::Closing(c) => c.counter_party, + Channel::Closed(c) | Channel::CounterClosed(c) | Channel::CollaborativelyClosed(c) => { + c.counter_party + } + Channel::ClosedPunished(c) => c.counter_party, } } } @@ -91,6 +119,52 @@ pub struct FailedSign { pub sign_message: SignChannel, } +#[derive(Clone)] +/// A channel is closing when its buffer transaction was broadcast or detected on chain. +pub struct ClosingChannel { + /// The [`secp256k1_zkp::PublicKey`] of the counter party. + pub counter_party: PublicKey, + /// The temporary [`crate::ChannelId`] of the channel. + pub temporary_channel_id: ChannelId, + /// The [`crate::ChannelId`] for the channel. + pub channel_id: ChannelId, + /// The previous state the channel was before being closed, if that state was the `Signed` one, + /// otherwise is `None`. + pub rollback_state: Option, + /// The buffer transaction that was broadcast. + pub buffer_transaction: Transaction, + /// The [`crate::ContractId`] of the contract that was used to close + /// the channel. + pub contract_id: ContractId, + /// Whether the local party initiated the closing of the channel. + pub is_initiator: bool, +} + +#[derive(Clone)] +/// A channel is closed when its buffer transaction has been spent. +pub struct ClosedChannel { + /// The [`secp256k1_zkp::PublicKey`] of the counter party. + pub counter_party: PublicKey, + /// The temporary [`crate::ChannelId`] of the channel. + pub temporary_channel_id: ChannelId, + /// The [`crate::ChannelId`] for the channel. + pub channel_id: ChannelId, +} + +#[derive(Clone)] +/// A channel is closed punished when the counter party broadcast a revoked transaction triggering +/// the broadcast of a punishment transaction by the local party. +pub struct ClosedPunishedChannel { + /// The [`secp256k1_zkp::PublicKey`] of the counter party. + pub counter_party: PublicKey, + /// The temporary [`crate::ChannelId`] of the channel. + pub temporary_channel_id: ChannelId, + /// The [`crate::ChannelId`] for the channel. + pub channel_id: ChannelId, + /// The transaction id of the punishment transaction that was broadcast. + pub punish_txid: Txid, +} + impl Channel { /// Returns the temporary [`crate::ChannelId`] for the channel. pub fn get_temporary_id(&self) -> ChannelId { @@ -99,6 +173,10 @@ impl Channel { Channel::Accepted(a) => a.temporary_channel_id, Channel::Signed(s) => s.temporary_channel_id, Channel::FailedAccept(f) => f.temporary_channel_id, + Channel::Closed(c) | Channel::CounterClosed(c) | Channel::CollaborativelyClosed(c) => { + c.temporary_channel_id + } + Channel::ClosedPunished(c) => c.temporary_channel_id, _ => unimplemented!(), } } @@ -111,6 +189,11 @@ impl Channel { Channel::Signed(s) => s.channel_id, Channel::FailedAccept(f) => f.temporary_channel_id, Channel::FailedSign(f) => f.channel_id, + Channel::Closing(c) => c.channel_id, + Channel::Closed(c) | Channel::CounterClosed(c) | Channel::CollaborativelyClosed(c) => { + c.channel_id + } + Channel::ClosedPunished(c) => c.channel_id, } } @@ -122,6 +205,7 @@ impl Channel { Channel::Signed(s) => s.get_contract_id(), Channel::FailedAccept(_) => None, Channel::FailedSign(_) => None, + _ => None, } } } diff --git a/dlc-manager/src/channel/ser.rs b/dlc-manager/src/channel/ser.rs index c3efabc9..feb706bd 100644 --- a/dlc-manager/src/channel/ser.rs +++ b/dlc-manager/src/channel/ser.rs @@ -3,7 +3,7 @@ use super::accepted_channel::AcceptedChannel; use super::offered_channel::OfferedChannel; use super::party_points::PartyBasePoints; use super::signed_channel::{SignedChannel, SignedChannelState}; -use super::{FailedAccept, FailedSign}; +use super::{ClosedChannel, ClosedPunishedChannel, ClosingChannel, FailedAccept, FailedSign}; use dlc_messages::ser_impls::{ read_ecdsa_adaptor_signature, read_string, write_ecdsa_adaptor_signature, write_string, @@ -60,12 +60,24 @@ impl_dlc_writeable_enum!( (6, RenewOffered, {(offered_contract_id, writeable), (counter_payout, writeable), (is_offer, writeable), (offer_next_per_update_point, writeable), (timeout, writeable)}), (7, RenewAccepted, {(contract_id, writeable), (offer_per_update_point, writeable), (accept_per_update_point, writeable), (buffer_transaction, writeable), (buffer_script_pubkey, writeable), (timeout, writeable), (own_payout, writeable)}), (8, RenewConfirmed, {(contract_id, writeable), (offer_per_update_point, writeable), (accept_per_update_point, writeable), (buffer_transaction, writeable), (buffer_script_pubkey, writeable), (offer_buffer_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}), (timeout, writeable), (own_payout, writeable), (total_collateral, writeable)}), - (15, RenewFinalized, {(contract_id, writeable), (offer_per_update_point, writeable), (accept_per_update_point, writeable), (buffer_transaction, writeable), (buffer_script_pubkey, writeable), (offer_buffer_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}), (accept_buffer_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}), (timeout, writeable), (own_payout, writeable), (total_collateral, writeable)}), - (9, Closing, {(buffer_transaction, writeable), (contract_id, writeable), (is_initiator, writeable)}), - (10, ClosedPunished, { (punishment_txid, writeable) }), + (9, RenewFinalized, {(contract_id, writeable), (offer_per_update_point, writeable), (accept_per_update_point, writeable), (buffer_transaction, writeable), (buffer_script_pubkey, writeable), (offer_buffer_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}), (accept_buffer_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}), (timeout, writeable), (own_payout, writeable), (total_collateral, writeable)}), + (10, Closing, {(buffer_transaction, writeable), (contract_id, writeable), (is_initiator, writeable)}), (11, CollaborativeCloseOffered, { (counter_payout, writeable), (offer_signature, writeable), (close_tx, writeable), (timeout, writeable) }) - ;;(12, Closed), (13, CounterClosed), (14, CollaborativelyClosed) + ;; ); impl_dlc_writeable!(FailedAccept, {(temporary_channel_id, writeable), (error_message, {cb_writeable, write_string, read_string}), (accept_message, writeable), (counter_party, writeable)}); impl_dlc_writeable!(FailedSign, {(channel_id, writeable), (error_message, {cb_writeable, write_string, read_string}), (sign_message, writeable), (counter_party, writeable)}); + +impl_dlc_writeable!(ClosingChannel, { + (channel_id, writeable), + (counter_party, writeable), + (temporary_channel_id, writeable), + (rollback_state, option), + (buffer_transaction, writeable), + (contract_id, writeable), + (is_initiator, writeable) + +}); +impl_dlc_writeable!(ClosedChannel, {(channel_id, writeable), (counter_party, writeable), (temporary_channel_id, writeable)}); +impl_dlc_writeable!(ClosedPunishedChannel, {(channel_id, writeable), (counter_party, writeable), (temporary_channel_id, writeable), (punish_txid, writeable)}); diff --git a/dlc-manager/src/channel/signed_channel.rs b/dlc-manager/src/channel/signed_channel.rs index 1146ca99..0951c080 100644 --- a/dlc-manager/src/channel/signed_channel.rs +++ b/dlc-manager/src/channel/signed_channel.rs @@ -2,7 +2,7 @@ //! transaction inputs. This module contains the model for a signed channel, //! the possible states in which it can be as well as methods to work with it. -use bitcoin::{Script, Transaction, Txid}; +use bitcoin::{Script, Transaction}; use dlc::PartyParams; use lightning::ln::chan_utils::CounterpartyCommitmentSecrets; use secp256k1_zkp::{ecdsa::Signature, EcdsaAdaptorSignature, PublicKey}; @@ -300,20 +300,6 @@ typed_enum!( /// Whether the local party initiated the closing of the channel. is_initiator: bool, }, - /// A [`SignedChannel`] is in `Closed` state when it was force closed by - /// the local party. - Closed, - /// A [`SignedChannel`] is in `CounterClosed` state when it was force - /// closed by the counter party. - CounterClosed, - /// A [`SignedChannel`] is in `ClosedPublished` state when the local - /// party broadcast a punishment transaction in response to the counter - /// party broadcasting a settle or buffer transaction for a revoked channel - /// state. - ClosedPunished { - /// The transaction id of the punishment transaction that was broadcast. - punishment_txid: Txid, - }, /// A [`SignedChannel`] is in `CollaborativeCloseOffered` state when the local party /// has sent a [`dlc_messages::channel::CollaborativeCloseOffer`] message. CollaborativeCloseOffered { @@ -327,9 +313,6 @@ typed_enum!( /// unresponsive and the channel will be forced closed. timeout: u64, }, - /// A [`SignedChannel`] is in `CollaborativelyClosed` state when it was - /// collaboratively closed. - CollaborativelyClosed, }, /// Enum automatically generated associating a number to each signed channel /// state. diff --git a/dlc-manager/src/channel_updater.rs b/dlc-manager/src/channel_updater.rs index c2c5bec7..5b8088f6 100644 --- a/dlc-manager/src/channel_updater.rs +++ b/dlc-manager/src/channel_updater.rs @@ -9,6 +9,7 @@ use crate::{ offered_channel::OfferedChannel, party_points::PartyBasePoints, signed_channel::{SignedChannel, SignedChannelState}, + Channel, ClosedChannel, }, contract::{ accepted_contract::AcceptedContract, contract_info::ContractInfo, @@ -2265,9 +2266,9 @@ where /// closing transaction and returning it. pub fn accept_collaborative_close_offer( secp: &Secp256k1, - signed_channel: &mut SignedChannel, + signed_channel: &SignedChannel, signer: &S, -) -> Result +) -> Result<(Transaction, Channel), Error> where S::Target: Signer, { @@ -2295,8 +2296,12 @@ where )?; // TODO(tibo): should only transition to close after confirmation. - signed_channel.state = SignedChannelState::CollaborativelyClosed; - Ok(close_tx) + let channel = Channel::CollaborativelyClosed(ClosedChannel { + counter_party: signed_channel.counter_party, + temporary_channel_id: signed_channel.temporary_channel_id, + channel_id: signed_channel.channel_id, + }); + Ok((close_tx, channel)) } fn get_settle_tx_and_adaptor_sig( @@ -2528,13 +2533,14 @@ where /// Extract the CET and computes the signature for it, and marks the channel as closed. pub fn finalize_unilateral_close_settled_channel( secp: &Secp256k1, - signed_channel: &mut SignedChannel, + signed_channel: &SignedChannel, confirmed_contract: &SignedContract, contract_info: &ContractInfo, attestations: &[(usize, OracleAttestation)], adaptor_info: &AdaptorInfo, signer: &S, -) -> Result + is_initiator: bool, +) -> Result<(Transaction, Channel), Error> where S::Target: Signer, { @@ -2618,10 +2624,18 @@ where &adaptor_sigs[range_info.adaptor_index], &oracle_sigs, )?; + let closed_channel = ClosedChannel { + counter_party: signed_channel.counter_party, + temporary_channel_id: signed_channel.temporary_channel_id, + channel_id: signed_channel.channel_id, + }; + let channel = if is_initiator { + Channel::Closed(closed_channel) + } else { + Channel::CounterClosed(closed_channel) + }; - signed_channel.state = SignedChannelState::Closed; - - Ok(cet) + Ok((cet, channel)) } /// Sign the settlement transaction and update the state of the channel. @@ -2630,7 +2644,7 @@ pub fn close_settled_channel( signed_channel: &mut SignedChannel, signer: &S, is_initiator: bool, -) -> Result +) -> Result<(Transaction, Channel), Error> where S::Target: Signer, { @@ -2639,11 +2653,11 @@ where pub(crate) fn close_settled_channel_internal( secp: &Secp256k1, - signed_channel: &mut SignedChannel, + signed_channel: &SignedChannel, signer: &S, sub_channel: Option<(SubChannel, &ClosingSubChannel)>, is_initiator: bool, -) -> Result +) -> Result<(Transaction, Channel), Error> where S::Target: Signer, { @@ -2744,10 +2758,18 @@ where )?; } - signed_channel.state = if is_initiator { - SignedChannelState::Closed + let channel = if is_initiator { + Channel::Closed(ClosedChannel { + counter_party: signed_channel.counter_party, + temporary_channel_id: signed_channel.temporary_channel_id, + channel_id: signed_channel.channel_id, + }) } else { - SignedChannelState::CounterClosed + Channel::CounterClosed(ClosedChannel { + counter_party: signed_channel.counter_party, + temporary_channel_id: signed_channel.temporary_channel_id, + channel_id: signed_channel.channel_id, + }) }; - Ok(settle_tx) + Ok((settle_tx, channel)) } diff --git a/dlc-manager/src/manager.rs b/dlc-manager/src/manager.rs index 393d08f7..c390f1ec 100644 --- a/dlc-manager/src/manager.rs +++ b/dlc-manager/src/manager.rs @@ -4,7 +4,7 @@ use super::{Blockchain, Oracle, Storage, Time, Wallet}; use crate::chain_monitor::{ChainMonitor, ChannelInfo, RevokedTxType, TxType}; use crate::channel::offered_channel::OfferedChannel; use crate::channel::signed_channel::{SignedChannel, SignedChannelState, SignedChannelStateType}; -use crate::channel::Channel; +use crate::channel::{Channel, ClosedChannel, ClosedPunishedChannel}; use crate::channel_updater::verify_signed_channel; use crate::channel_updater::{self, get_signed_channel_state}; use crate::contract::{ @@ -1070,7 +1070,7 @@ where /// Accept an offer to collaboratively close the channel. The close transaction /// will be broadcast and the state of the channel updated. pub fn accept_collaborative_close(&self, channel_id: &ChannelId) -> Result<(), Error> { - let mut signed_channel = + let signed_channel = get_channel_in_state!(self, channel_id, Signed, None as Option)?; let closed_contract = if let Some(SignedChannelState::Established { @@ -1092,16 +1092,15 @@ where None }; - let close_tx = crate::channel_updater::accept_collaborative_close_offer( + let (close_tx, closed_channel) = crate::channel_updater::accept_collaborative_close_offer( &self.secp, - &mut signed_channel, + &signed_channel, &self.wallet, )?; self.blockchain.send_transaction(&close_tx)?; - self.store - .upsert_channel(Channel::Signed(signed_channel), None)?; + self.store.upsert_channel(closed_channel, None)?; if let Some(closed_contract) = closed_contract { self.store @@ -1113,7 +1112,7 @@ where fn try_finalize_closing_established_channel( &self, - mut signed_channel: SignedChannel, + signed_channel: SignedChannel, ) -> Result<(), Error> { let (buffer_tx, contract_id, &is_initiator) = get_signed_channel_state!( signed_channel, @@ -1137,15 +1136,17 @@ where Error::InvalidState("Could not get information to close contract".to_string()) })?; - let signed_cet = channel_updater::finalize_unilateral_close_settled_channel( - &self.secp, - &mut signed_channel, - &confirmed_contract, - contract_info, - &attestations, - adaptor_info, - &self.wallet, - )?; + let (signed_cet, closed_channel) = + channel_updater::finalize_unilateral_close_settled_channel( + &self.secp, + &signed_channel, + &confirmed_contract, + contract_info, + &attestations, + adaptor_info, + &self.wallet, + is_initiator, + )?; let closed_contract = self.close_contract( &confirmed_contract, @@ -1153,19 +1154,13 @@ where attestations.iter().map(|x| &x.1).cloned().collect(), )?; - signed_channel.state = if is_initiator { - SignedChannelState::Closed - } else { - SignedChannelState::CounterClosed - }; - self.chain_monitor .lock() .unwrap() .cleanup_channel(signed_channel.channel_id); self.store - .upsert_channel(Channel::Signed(signed_channel), Some(closed_contract))?; + .upsert_channel(closed_channel, Some(closed_contract))?; } Ok(()) @@ -1960,7 +1955,7 @@ where _ => false, }; - let contract = if is_buffer_tx { + if is_buffer_tx { let contract_id = signed_channel .get_contract_id() .expect("to have a contract id"); @@ -1972,21 +1967,34 @@ where std::mem::swap(&mut signed_channel.state, &mut state); signed_channel.roll_back_state = Some(state); - None + self.store + .upsert_channel(Channel::Signed(signed_channel), None)?; } else { let contract_id = signed_channel.get_contract_id(); - signed_channel.state = { + let closed_channel = { match &signed_channel.state { SignedChannelState::Closing { is_initiator, .. } => { if *is_initiator { - SignedChannelState::Closed + Channel::Closed(ClosedChannel { + counter_party: signed_channel.counter_party, + temporary_channel_id: signed_channel.temporary_channel_id, + channel_id: signed_channel.channel_id, + }) } else { - SignedChannelState::CounterClosed + Channel::CounterClosed(ClosedChannel { + counter_party: signed_channel.counter_party, + temporary_channel_id: signed_channel.temporary_channel_id, + channel_id: signed_channel.channel_id, + }) } } _ => { error!("Saw spending of buffer transaction without being in closing state"); - SignedChannelState::Closed + Channel::Closed(ClosedChannel { + counter_party: signed_channel.counter_party, + temporary_channel_id: signed_channel.temporary_channel_id, + channel_id: signed_channel.channel_id, + }) } } }; @@ -1994,7 +2002,7 @@ where .lock() .unwrap() .cleanup_channel(signed_channel.channel_id); - if let Some(contract_id) = contract_id { + let contract = if let Some(contract_id) = contract_id { let contract_opt = self.store.get_contract(&contract_id)?; if let Some(contract) = contract_opt { match contract { @@ -2012,11 +2020,10 @@ where } } else { None - } + }; + self.store.upsert_channel(closed_channel, contract)?; }; - self.store - .upsert_channel(Channel::Signed(signed_channel), contract)?; !is_buffer_tx } else if let TxType::Revoked { update_idx, @@ -2157,9 +2164,12 @@ where self.blockchain.send_transaction(&signed_tx)?; - signed_channel.state = SignedChannelState::ClosedPunished { - punishment_txid: signed_tx.txid(), - }; + let closed_channel = Channel::ClosedPunished(ClosedPunishedChannel { + counter_party: signed_channel.counter_party, + temporary_channel_id: signed_channel.temporary_channel_id, + channel_id: signed_channel.channel_id, + punish_txid: signed_tx.txid(), + }); //TODO(tibo): should probably make sure the tx is confirmed somewhere before //stop watching the cheating tx. @@ -2167,8 +2177,7 @@ where .lock() .unwrap() .cleanup_channel(signed_channel.channel_id); - self.store - .upsert_channel(Channel::Signed(signed_channel), None)?; + self.store.upsert_channel(closed_channel, None)?; true } else if let TxType::CollaborativeClose = channel_info.tx_type { if let Some(SignedChannelState::Established { @@ -2188,22 +2197,28 @@ where self.store .update_contract(&Contract::Closed(closed_contract))?; } - signed_channel.state = SignedChannelState::CollaborativelyClosed; + let closed_channel = Channel::CollaborativelyClosed(ClosedChannel { + counter_party: signed_channel.counter_party, + temporary_channel_id: signed_channel.temporary_channel_id, + channel_id: signed_channel.channel_id, + }); self.chain_monitor .lock() .unwrap() .cleanup_channel(signed_channel.channel_id); - self.store - .upsert_channel(Channel::Signed(signed_channel), None)?; + self.store.upsert_channel(closed_channel, None)?; true } else if let TxType::SettleTx = channel_info.tx_type { - signed_channel.state = SignedChannelState::CounterClosed; + let closed_channel = Channel::CounterClosed(ClosedChannel { + counter_party: signed_channel.counter_party, + temporary_channel_id: signed_channel.temporary_channel_id, + channel_id: signed_channel.channel_id, + }); self.chain_monitor .lock() .unwrap() .cleanup_channel(signed_channel.channel_id); - self.store - .upsert_channel(Channel::Signed(signed_channel), None)?; + self.store.upsert_channel(closed_channel, None)?; true } else { false @@ -2316,12 +2331,6 @@ where SignedChannelState::Closing { .. } => Err(Error::InvalidState( "Channel is already closing.".to_string(), )), - SignedChannelState::Closed - | SignedChannelState::CounterClosed - | SignedChannelState::CollaborativelyClosed - | SignedChannelState::ClosedPunished { .. } => { - Err(Error::InvalidState("Channel already closed.".to_string())) - } } } @@ -2373,13 +2382,13 @@ where /// Unilaterally close a channel that has been settled. fn close_settled_channel( &self, - mut signed_channel: SignedChannel, + signed_channel: SignedChannel, sub_channel: Option<(SubChannel, &ClosingSubChannel)>, is_initiator: bool, ) -> Result<(), Error> { - let settle_tx = crate::channel_updater::close_settled_channel_internal( + let (settle_tx, closed_channel) = crate::channel_updater::close_settled_channel_internal( &self.secp, - &mut signed_channel, + &signed_channel, &self.wallet, sub_channel, is_initiator, @@ -2399,8 +2408,7 @@ where .unwrap() .cleanup_channel(signed_channel.channel_id); - self.store - .upsert_channel(Channel::Signed(signed_channel), None)?; + self.store.upsert_channel(closed_channel, None)?; Ok(()) } @@ -2411,8 +2419,8 @@ where &self, channel_id: ChannelId, own_balance: u64, - ) -> Result<(SignedChannel, Option), Error> { - let mut channel = get_channel_in_state!(self, &channel_id, Signed, None::)?; + ) -> Result<(Channel, Option), Error> { + let channel = get_channel_in_state!(self, &channel_id, Signed, None::)?; let contract = if let Some(contract_id) = channel.get_contract_id() { Some(Contract::Closed(self.get_collaboratively_closed_contract( @@ -2424,9 +2432,13 @@ where None }; - channel.state = SignedChannelState::CollaborativelyClosed; + let closed_channel = Channel::CollaborativelyClosed(ClosedChannel { + counter_party: channel.counter_party, + temporary_channel_id: channel.temporary_channel_id, + channel_id, + }); - Ok((channel, contract)) + Ok((closed_channel, contract)) } fn get_collaboratively_closed_contract( diff --git a/dlc-manager/src/sub_channel_manager.rs b/dlc-manager/src/sub_channel_manager.rs index 128fe87c..8ffe5858 100644 --- a/dlc-manager/src/sub_channel_manager.rs +++ b/dlc-manager/src/sub_channel_manager.rs @@ -3,7 +3,7 @@ use std::{marker::PhantomData, ops::Deref, sync::Mutex}; -use bitcoin::{OutPoint, PackedLockTime, Script, Sequence}; +use bitcoin::{OutPoint, PackedLockTime, Script, Sequence, Transaction}; use dlc::{ channel::{get_tx_adaptor_signature, sub_channel::LN_GLUE_TX_WEIGHT}, PartyParams, @@ -38,12 +38,12 @@ use crate::{ chain_monitor::{ChannelInfo, RevokedTxType, TxType}, channel::{ generate_temporary_contract_id, offered_channel::OfferedChannel, - party_points::PartyBasePoints, Channel, + party_points::PartyBasePoints, Channel, ClosedChannel, }, channel_updater::{ self, FundingInfo, SubChannelSignInfo, SubChannelSignVerifyInfo, SubChannelVerifyInfo, }, - contract::{contract_input::ContractInput, Contract, FundingInputInfo}, + contract::{contract_input::ContractInput, ClosedContract, Contract, FundingInputInfo}, error::Error, manager::{get_channel_in_state, get_contract_in_state, Manager, CET_NSEQUENCE}, subchannel::{ @@ -583,6 +583,10 @@ where glue_tx_output_value, ); + let commitment_transactions = self + .ln_channel_manager + .get_latest_holder_commitment_txn(channel_lock); + let commitment_signed = self .ln_channel_manager .get_updated_funding_outpoint_commitment_signed( @@ -667,6 +671,7 @@ where split_tx, ln_glue_transaction: ln_glue_tx, ln_rollback: (&channel_details).into(), + commitment_transactions, }; offered_sub_channel.state = SubChannelState::Accepted(accepted_sub_channel); @@ -690,84 +695,222 @@ where /// Start force closing the sub channel with given [`ChannelId`]. pub fn force_close_sub_channel(&self, channel_id: &ChannelId) -> Result<(), Error> { - let (mut signed, state) = get_sub_channel_in_state!( - self.dlc_channel_manager, - *channel_id, - Signed, - None:: + let mut sub_channel = self + .dlc_channel_manager + .get_store() + .get_sub_channel(*channel_id)? + .ok_or(Error::InvalidParameters(format!( + "Unknown sub channel {:?}", + channel_id + )))?; + + match sub_channel.state { + SubChannelState::Offered(_) => self.force_close_offered_channel(sub_channel)?, + SubChannelState::Accepted(ref a) => { + let commitment_transactions = a.commitment_transactions.clone(); + + self.force_close_with_saved_commitment(sub_channel, &commitment_transactions)?; + } + SubChannelState::Signed(_) | SubChannelState::Finalized(_) => { + self.force_close_signed_channel(sub_channel, None)?; + } + SubChannelState::Confirmed(ref c) => { + let commitment_transactions = c.commitment_transactions.clone(); + self.force_close_with_saved_commitment(sub_channel, &commitment_transactions)?; + } + SubChannelState::CloseOffered(c) => { + sub_channel.state = SubChannelState::Signed(c.signed_subchannel); + self.force_close_signed_channel(sub_channel, None)?; + } + SubChannelState::CloseAccepted(c) => { + self.ln_channel_manager.with_channel_lock_no_check( + channel_id, + &sub_channel.counter_party, + |channel_lock| { + self.ln_channel_manager.set_funding_outpoint( + channel_lock, + &lightning::chain::transaction::OutPoint { + txid: c.signed_subchannel.ln_glue_transaction.txid(), + index: 0, + }, + c.ln_rollback.channel_value_satoshis, + c.ln_rollback.value_to_self_msat, + ); + Ok(()) + }, + )?; + sub_channel.state = SubChannelState::Signed(c.signed_subchannel); + self.force_close_signed_channel(sub_channel, Some(c.commitment_transactions))?; + } + SubChannelState::CloseConfirmed(_) => { + self.force_close_offered_channel(sub_channel)?; + } + SubChannelState::OnChainClosed + | SubChannelState::CounterOnChainClosed + | SubChannelState::OffChainClosed + | SubChannelState::ClosedPunished(_) + | SubChannelState::Rejected + | SubChannelState::Closing(_) => { + error!( + "Tried to force close channel with {:?} state", + sub_channel.state + ); + return Err(Error::InvalidParameters( + "Channel is not in a state to be force closed".to_string(), + )); + } + }; + + Ok(()) + } + + fn force_close_offered_channel(&self, mut sub_channel: SubChannel) -> Result<(), Error> { + let (closed_channel, closed_contract) = self.get_closed_dlc_channel_and_contract( + sub_channel + .get_dlc_channel_id(0) + .expect("to have a channel id in offered state"), + false, )?; - let counter_party = signed.counter_party; - let channel_details = self - .ln_channel_manager - .get_channel_details(channel_id) - .unwrap(); - 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(&signed.own_base_points.publish_basepoint)?; + let dlc_channel_id = sub_channel.get_dlc_channel_id(0); + sub_channel.state = SubChannelState::OnChainClosed; + self.ln_channel_manager + .force_close_channel(&sub_channel.channel_id, &sub_channel.counter_party)?; + self.dlc_channel_manager + .get_store() + .upsert_sub_channel(&sub_channel)?; + self.dlc_channel_manager + .get_store() + .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); + if let Some(dlc_channel_id) = dlc_channel_id { + chain_monitor.cleanup_channel(dlc_channel_id); + } + self.dlc_channel_manager + .get_store() + .persist_chain_monitor(&chain_monitor) + } - let publish_sk = derive_private_key( - self.dlc_channel_manager.get_secp(), - &state.own_per_split_point, - &publish_base_secret, - ); + fn force_close_with_saved_commitment( + &self, + mut sub_channel: SubChannel, + commitment_transactions: &Vec, + ) -> Result<(), Error> { + for tx in commitment_transactions { + self.dlc_channel_manager + .get_blockchain() + .send_transaction(tx)?; + } - let counter_split_signature = state - .counter_split_adaptor_signature - .decrypt(&publish_sk) - .map_err(|e| APIError::ExternalError { err: e.to_string() })?; + let dlc_channel_id = sub_channel + .get_dlc_channel_id(0) + .expect("to have a channel id in offered state"); + let (closed_channel, closed_contract) = + self.get_closed_dlc_channel_and_contract(dlc_channel_id, false)?; + sub_channel.state = SubChannelState::OnChainClosed; + self.dlc_channel_manager + .get_store() + .upsert_channel(closed_channel, Some(closed_contract))?; + 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) + } - let mut split_tx = state.split_tx.transaction.clone(); + fn force_close_signed_channel( + &self, + mut sub_channel: SubChannel, + commitment_transactions: Option>, + ) -> Result<(), Error> { + 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; + let channel_details = self + .ln_channel_manager + .get_channel_details(&channel_id) + .unwrap(); + 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 mut own_sig = None; + let publish_sk = derive_private_key( + self.dlc_channel_manager.get_secp(), + &state.own_per_split_point, + &publish_base_secret, + ); - 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( + 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 own_sig = None; + + 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(), - &split_tx, - 0, - &signed.original_funding_redeemscript, - signed.fund_value_satoshis, + &mut split_tx, + &counter_split_signature, + &channel_details.counter_funding_pubkey, fund_sk, + &sub_channel.original_funding_redeemscript, + sub_channel.fund_value_satoshis, + 0, ) - .unwrap(), - ); - dlc::util::sign_multi_sig_input( - self.dlc_channel_manager.get_secp(), - &mut split_tx, - &counter_split_signature, - &channel_details.counter_funding_pubkey, - fund_sk, - &signed.original_funding_redeemscript, - signed.fund_value_satoshis, - 0, - ) - .unwrap(); - }); - self.dlc_channel_manager - .get_blockchain() - .send_transaction(&split_tx)?; + .unwrap(); + }); + self.dlc_channel_manager + .get_blockchain() + .send_transaction(&split_tx)?; - let closing_sub_channel = ClosingSubChannel { - signed_sub_channel: state, - is_initiator: true, - }; + let closing_sub_channel = ClosingSubChannel { + signed_sub_channel: state, + is_initiator: true, + commitment_transactions, + }; - signed.state = SubChannelState::Closing(closing_sub_channel); + sub_channel.state = SubChannelState::Closing(closing_sub_channel); - self.dlc_channel_manager - .get_store() - .upsert_sub_channel(&signed)?; + self.dlc_channel_manager + .get_store() + .upsert_sub_channel(&sub_channel)?; - Ok(()) - }, - )?; + Ok(()) + }, + )?; + } else { + unreachable!("Should not call this method if not in Signed state"); + } Ok(()) } @@ -875,8 +1018,22 @@ where error!("Error force closing DLC subchannel {}", e); } - self.ln_channel_manager - .force_close_channel(channel_id, &counter_party)?; + if let Some(commitment_transactions) = &state.commitment_transactions { + for tx in commitment_transactions { + if let Err(e) = self + .dlc_channel_manager + .get_blockchain() + .send_transaction(tx) + { + error!("Could not broadcast transaction {}: {}", tx.txid(), e); + } + } + } else if let Err(e) = self + .ln_channel_manager + .force_close_channel(channel_id, &counter_party) + { + error!("Error force closing LN side of channel: {}", e); + }; closing.state = if state.is_initiator { SubChannelState::OnChainClosed @@ -897,6 +1054,82 @@ where Ok(()) } + /// Notify that LDK has decided to close the channel with given id. + pub fn notify_ln_channel_closed(&self, channel_id: ChannelId) -> Result<(), Error> { + let mut sub_channel = self + .dlc_channel_manager + .get_store() + .get_sub_channel(channel_id)? + .ok_or(Error::InvalidParameters(format!( + "No channel with id {:?} found", + channel_id + )))?; + + 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) + } + }; + + if let Some(channel) = updated_channel { + self.dlc_channel_manager + .get_store() + .upsert_channel(channel, updated_contract)?; + } + + self.dlc_channel_manager + .get_store() + .upsert_sub_channel(&sub_channel)?; + + self.dlc_channel_manager + .get_store() + .persist_chain_monitor(&chain_monitor)?; + + Ok(()) + } + /// Generates an offer to collaboratively close a sub channel off chain, updating its state. pub fn offer_subchannel_close( &self, @@ -1000,10 +1233,9 @@ where .get_channel_details(channel_id) .ok_or_else(|| Error::InvalidParameters(format!("Unknown channel {channel_id:?}")))?; - let commitment_signed = self.ln_channel_manager.with_useable_channel_lock( - channel_id, - &sub_channel.counter_party, - |channel_lock| { + let (commitment_signed, commitment_transactions) = self + .ln_channel_manager + .with_useable_channel_lock(channel_id, &sub_channel.counter_party, |channel_lock| { let dlc_channel_id = sub_channel .get_dlc_channel_id(0) @@ -1017,6 +1249,9 @@ where Signed, None:: )?; + let commitment_transactions = self + .ln_channel_manager + .get_latest_holder_commitment_txn(channel_lock); let total_collateral = dlc_channel.own_params.collateral + dlc_channel.counter_params.collateral; @@ -1041,9 +1276,8 @@ where ln_own_balance_msats, )?; - Ok(commitment_signed) - }, - )?; + Ok((commitment_signed, commitment_transactions)) + })?; let close_accept = SubChannelCloseAccept { channel_id: *channel_id, @@ -1056,6 +1290,7 @@ where own_balance: state.accept_balance, counter_balance: state.offer_balance, ln_rollback: (&channel_details).into(), + commitment_transactions, }; sub_channel.state = SubChannelState::CloseAccepted(close_accepted_subchannel); @@ -1397,9 +1632,15 @@ where glue_tx_output_value, ); - let (split_tx_adaptor_signature, commitment_signed, revoke_and_ack) = self - .ln_channel_manager - .with_useable_channel_lock(channel_id, counter_party, |channel_lock| { + let ( + split_tx_adaptor_signature, + commitment_signed, + revoke_and_ack, + commitment_transactions, + ) = self.ln_channel_manager.with_useable_channel_lock( + 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| { @@ -1418,6 +1659,10 @@ where let split_tx_adaptor_signature = split_tx_adaptor_signature.unwrap(); + let commitment_transactions = self + .ln_channel_manager + .get_latest_holder_commitment_txn(channel_lock); + let commitment_signed = self .ln_channel_manager .get_updated_funding_outpoint_commitment_signed( @@ -1439,8 +1684,10 @@ where split_tx_adaptor_signature, commitment_signed, revoke_and_ack, + commitment_transactions, )) - })?; + }, + )?; let accept_channel = AcceptChannel { temporary_channel_id: offered_channel.temporary_channel_id, @@ -1539,6 +1786,7 @@ where prev_commitment_secret: SecretKey::from_slice(&revoke_and_ack.per_commitment_secret) .expect("a valid secret key"), next_per_commitment_point: revoke_and_ack.next_per_commitment_point, + commitment_transactions, }; offered_sub_channel.counter_base_points = Some(accept_points); @@ -2043,24 +2291,9 @@ where next_per_commitment_point: raa.next_per_commitment_point, }; - self.dlc_channel_manager - .get_chain_monitor() - .lock() - .unwrap() - .add_tx( - state.signed_subchannel.split_tx.transaction.txid(), - ChannelInfo { - channel_id: sub_channel.channel_id, - tx_type: TxType::Revoked { - update_idx: sub_channel.update_idx, - own_adaptor_signature: state - .signed_subchannel - .own_split_adaptor_signature, - is_offer: sub_channel.is_offer, - revoked_tx_type: RevokedTxType::Split, - }, - }, - ); + let commitment_transactions = self + .ln_channel_manager + .get_latest_holder_commitment_txn(channel_lock); let updated_channel = CloseConfirmedSubChannel { signed_subchannel: state.signed_subchannel, @@ -2068,6 +2301,7 @@ where counter_balance: state.accept_balance, ln_rollback: (&channel_details).into(), check_ln_secret: true, + commitment_transactions, }; sub_channel.state = SubChannelState::CloseConfirmed(updated_channel); @@ -2191,13 +2425,21 @@ where sub_channel.state = SubChannelState::OffChainClosed; + let mut chain_monitor = + self.dlc_channel_manager.get_chain_monitor().lock().unwrap(); + chain_monitor.cleanup_channel(dlc_channel_id); + self.dlc_channel_manager .get_store() - .upsert_channel(Channel::Signed(dlc_channel), contract)?; + .upsert_channel(dlc_channel, contract)?; self.dlc_channel_manager .get_store() .upsert_sub_channel(&sub_channel)?; + + self.dlc_channel_manager + .get_store() + .persist_chain_monitor(&chain_monitor)?; Ok(finalize) }, )?; @@ -2242,6 +2484,25 @@ where .dlc_channel_manager .get_closed_sub_dlc_channel(dlc_channel_id, state.own_balance)?; + self.dlc_channel_manager + .get_chain_monitor() + .lock() + .unwrap() + .add_tx( + state.signed_subchannel.split_tx.transaction.txid(), + ChannelInfo { + channel_id: sub_channel.channel_id, + tx_type: TxType::Revoked { + update_idx: sub_channel.update_idx, + own_adaptor_signature: state + .signed_subchannel + .own_split_adaptor_signature, + is_offer: sub_channel.is_offer, + revoked_tx_type: RevokedTxType::Split, + }, + }, + ); + if state.check_ln_secret { match ( finalize.commit_revocation_secret.as_ref(), @@ -2266,13 +2527,21 @@ where sub_channel.state = SubChannelState::OffChainClosed; + let mut chain_monitor = + self.dlc_channel_manager.get_chain_monitor().lock().unwrap(); + chain_monitor.cleanup_channel(dlc_channel_id); + self.dlc_channel_manager .get_store() - .upsert_channel(Channel::Signed(dlc_channel), contract)?; + .upsert_channel(dlc_channel, contract)?; self.dlc_channel_manager .get_store() .upsert_sub_channel(&sub_channel)?; + + self.dlc_channel_manager + .get_store() + .persist_chain_monitor(&chain_monitor)?; Ok(()) }, )?; @@ -2494,21 +2763,72 @@ where // case of reorg, though if the counter party has sent the // tx to close the channel it is unlikely that the tx will // not be part of a future block. - let state = match &sub_channel.state { - SubChannelState::Signed(s) => s, + let (state, commitment_transactions) = match &sub_channel.state { + SubChannelState::Signed(s) => (s, None), SubChannelState::Closing(_) => { log::info!("Spotted closing split transaction on chain"); continue; } + SubChannelState::CloseOffered(s) => (&s.signed_subchannel, None), + SubChannelState::CloseAccepted(s) => { + if let Err(e) = self.ln_channel_manager.with_channel_lock_no_check( + &sub_channel.channel_id, + &sub_channel.counter_party, + |channel_lock| { + self.ln_channel_manager.set_funding_outpoint( + channel_lock, + &lightning::chain::transaction::OutPoint { + txid: s.signed_subchannel.split_tx.transaction.txid(), + index: 0, + }, + s.ln_rollback.channel_value_satoshis, + s.ln_rollback.value_to_self_msat, + ); + Ok(()) + }, + ) { + log::error!("Could not reset funding outpoint: {:?}", e); + } + ( + &s.signed_subchannel, + Some(s.commitment_transactions.clone()), + ) + } + SubChannelState::CloseConfirmed(s) => { + if let Err(e) = self.ln_channel_manager.with_channel_lock_no_check( + &sub_channel.channel_id, + &sub_channel.counter_party, + |channel_lock| { + self.ln_channel_manager.set_funding_outpoint( + channel_lock, + &lightning::chain::transaction::OutPoint { + txid: s.signed_subchannel.split_tx.transaction.txid(), + index: 0, + }, + s.ln_rollback.channel_value_satoshis, + s.ln_rollback.value_to_self_msat, + ); + Ok(()) + }, + ) { + log::error!("Could not reset funding outpoint: {:?}", e); + } + ( + &s.signed_subchannel, + Some(s.commitment_transactions.clone()), + ) + } _ => { - log::error!("Unexpected channel state"); + log::error!("Unexpected channel state {:?}", sub_channel.state); continue; } }; + log::info!("Spotted split transaction, marking sub channel as closing"); let closing_sub_channel = ClosingSubChannel { signed_sub_channel: state.clone(), is_initiator: false, + commitment_transactions, }; chain_monitor.remove_tx(&tx.txid()); sub_channel.state = SubChannelState::Closing(closing_sub_channel); @@ -3141,6 +3461,54 @@ where Ok(()) } + + fn get_closed_dlc_channel_and_contract( + &self, + channel_id: [u8; 32], + counter_closed: bool, + ) -> Result<(Channel, Contract), Error> { + let channel = self + .dlc_channel_manager + .get_store() + .get_channel(&channel_id)? + .ok_or(Error::InvalidParameters(format!( + "No such channel {:?}", + channel_id + )))?; + let closed_channel_data = ClosedChannel { + counter_party: channel.get_counter_party_id(), + temporary_channel_id: channel.get_temporary_id(), + channel_id: channel.get_id(), + }; + let closed_channel = if counter_closed { + Channel::CounterClosed(closed_channel_data) + } else { + Channel::Closed(closed_channel_data) + }; + let contract_id = channel + .get_contract_id() + .ok_or(Error::InvalidParameters(format!( + "Channel {:?} does not have a contract associated", + channel_id + )))?; + let contract = self + .dlc_channel_manager + .get_store() + .get_contract(&contract_id)? + .ok_or(Error::InvalidParameters(format!( + "No such contract {:?}", + contract_id + )))?; + let closed_contract = Contract::Closed(ClosedContract { + attestations: None, + signed_cet: None, + contract_id, + temporary_contract_id: contract.get_id(), + counter_party_id: contract.get_counter_party_id(), + pnl: 0, + }); + Ok((closed_channel, closed_contract)) + } } impl< diff --git a/dlc-manager/src/subchannel/mod.rs b/dlc-manager/src/subchannel/mod.rs index 647ec268..4f3207c6 100644 --- a/dlc-manager/src/subchannel/mod.rs +++ b/dlc-manager/src/subchannel/mod.rs @@ -190,6 +190,8 @@ pub struct AcceptedSubChannel { pub ln_glue_transaction: Transaction, /// Information used to facilitate the rollback of a channel split. pub ln_rollback: LnRollBackInfo, + /// Commitment transactions to broadcast in order to force close the channel + pub commitment_transactions: Vec, } #[derive(Clone, Debug, PartialEq, Eq)] @@ -247,6 +249,8 @@ pub struct ConfirmedSubChannel { pub next_per_commitment_point: PublicKey, /// Information used to facilitate the rollback of a channel split. pub ln_rollback: LnRollBackInfo, + /// Commitment transactions to broadcast in order to force close the channel + pub commitment_transactions: Vec, } impl ConfirmedSubChannel { @@ -314,6 +318,8 @@ pub struct CloseAcceptedSubChannel { pub counter_balance: u64, /// Rollback information about the split channel pub ln_rollback: LnRollBackInfo, + /// Commitment transactions to force close the channel on the previous state. + pub commitment_transactions: Vec, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -329,6 +335,8 @@ pub struct CloseConfirmedSubChannel { pub ln_rollback: LnRollBackInfo, /// Whether to check for LN secret (to deal with reestblishments) pub check_ln_secret: bool, + /// Commitment transactions to force close the channel on the previous state. + pub commitment_transactions: Vec, } /// Information about a sub channel that is in the process of being unilateraly closed. @@ -338,6 +346,9 @@ pub struct ClosingSubChannel { pub signed_sub_channel: SignedSubChannel, /// Whether the local party initiated the closing. pub is_initiator: bool, + /// Commitment transactions to use to close the lightning side of the split channel for cases + /// where LDK is already using a different funding output. + pub commitment_transactions: Option>, } /// Provides the ability to access and update Lightning Network channels. @@ -414,6 +425,10 @@ where channel_value_satoshis: u64, value_to_self_msat: u64, ); + + /// 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) -> Vec; } impl @@ -506,6 +521,13 @@ where ); } + fn get_latest_holder_commitment_txn( + &self, + channel_lock: &ChannelLock<::Signer>, + ) -> Vec { + self.get_latest_holder_commitment_txn(channel_lock) + } + fn with_useable_channel_lock( &self, channel_id: &ChannelId, diff --git a/dlc-manager/src/subchannel/ser.rs b/dlc-manager/src/subchannel/ser.rs index 99519d71..e22e65ad 100644 --- a/dlc-manager/src/subchannel/ser.rs +++ b/dlc-manager/src/subchannel/ser.rs @@ -56,7 +56,8 @@ impl_dlc_writeable!(AcceptedSubChannel, { (accept_per_split_point, writeable), (split_tx, {cb_writeable, split_tx::write, split_tx::read}), (ln_glue_transaction, writeable), - (ln_rollback, writeable) + (ln_rollback, writeable), + (commitment_transactions, vec) }); impl_dlc_writeable!(ConfirmedSubChannel, { @@ -68,7 +69,8 @@ impl_dlc_writeable!(ConfirmedSubChannel, { (counter_glue_signature, writeable), (ln_rollback, writeable), (prev_commitment_secret, writeable), - (next_per_commitment_point, writeable) + (next_per_commitment_point, writeable), + (commitment_transactions, vec) }); impl_dlc_writeable!(SignedSubChannel, { @@ -89,8 +91,8 @@ impl_dlc_writeable!(CloseOfferedSubChannel, { (is_offer, writeable) }); -impl_dlc_writeable!(CloseAcceptedSubChannel, { (signed_subchannel, writeable), (own_balance, writeable), (counter_balance, writeable), (ln_rollback, writeable) }); +impl_dlc_writeable!(CloseAcceptedSubChannel, { (signed_subchannel, writeable), (own_balance, writeable), (counter_balance, writeable), (ln_rollback, writeable), (commitment_transactions, vec) }); -impl_dlc_writeable!(CloseConfirmedSubChannel, { (signed_subchannel, writeable), (own_balance, writeable), (counter_balance, writeable), (ln_rollback, writeable), (check_ln_secret, writeable) }); +impl_dlc_writeable!(CloseConfirmedSubChannel, { (signed_subchannel, writeable), (own_balance, writeable), (counter_balance, writeable), (ln_rollback, writeable), (check_ln_secret, writeable), (commitment_transactions, vec) }); -impl_dlc_writeable!(ClosingSubChannel, { (signed_sub_channel, writeable), (is_initiator, writeable) }); +impl_dlc_writeable!(ClosingSubChannel, { (signed_sub_channel, writeable), (is_initiator, writeable), (commitment_transactions, {option_cb, dlc_messages::ser_impls::write_vec, dlc_messages::ser_impls::read_vec}) }); diff --git a/dlc-manager/tests/channel_execution_tests.rs b/dlc-manager/tests/channel_execution_tests.rs index 4e1e6a11..539c7120 100644 --- a/dlc-manager/tests/channel_execution_tests.rs +++ b/dlc-manager/tests/channel_execution_tests.rs @@ -820,7 +820,7 @@ fn close_established_channel( .periodic_check() .expect("to be able to do the periodic check"); - assert_channel_state!(first, channel_id, Signed, Closed); + assert_channel_state!(first, channel_id, Closed); assert_contract_state!(first, contract_id, PreClosed); @@ -832,7 +832,7 @@ fn close_established_channel( .periodic_check() .expect("to be able to do the periodic check"); - assert_channel_state!(second, channel_id, Signed, CounterClosed); + assert_channel_state!(second, channel_id, CounterClosed); assert_contract_state!(second, contract_id, PreClosed); generate_blocks(5); @@ -875,7 +875,7 @@ fn cheat_punish( .periodic_check() .expect("the check to succeed"); - assert_channel_state!(second, channel_id, Signed, ClosedPunished); + assert_channel_state!(second, channel_id, ClosedPunished); } fn settle_channel( @@ -1227,7 +1227,7 @@ fn collaborative_close( .accept_collaborative_close(&channel_id) .expect("to be able to accept a collaborative close"); - assert_channel_state!(second, channel_id, Signed, CollaborativelyClosed); + assert_channel_state!(second, channel_id, CollaborativelyClosed); assert_contract_state!(second, contract_id, Closed); generate_blocks(2); @@ -1238,7 +1238,7 @@ fn collaborative_close( .periodic_check() .expect("the check to succeed"); - assert_channel_state!(first, channel_id, Signed, CollaborativelyClosed); + assert_channel_state!(first, channel_id, CollaborativelyClosed); assert_contract_state!(first, contract_id, Closed); } @@ -1279,7 +1279,7 @@ fn renew_timeout( .periodic_check() .expect("not to error"); - assert_channel_state!(first, channel_id, Signed, Closed); + assert_channel_state!(first, channel_id, Closed); } else { let (renew_accept, _) = second .lock() @@ -1306,7 +1306,7 @@ fn renew_timeout( .periodic_check() .expect("not to error"); - assert_channel_state!(second, channel_id, Signed, Closed); + assert_channel_state!(second, channel_id, Closed); } else if let TestPath::RenewConfirmTimeout = path { // Process Confirm second_receive.recv().expect("Error synchronizing"); @@ -1319,7 +1319,7 @@ fn renew_timeout( .periodic_check() .expect("not to error"); - assert_channel_state!(first, channel_id, Signed, Closed); + assert_channel_state!(first, channel_id, Closed); } else if let TestPath::RenewFinalizeTimeout = path { //Process confirm second_receive.recv().expect("Error synchronizing"); @@ -1340,7 +1340,7 @@ fn renew_timeout( .periodic_check() .expect("not to error"); - assert_channel_state!(second, channel_id, Signed, Closed); + assert_channel_state!(second, channel_id, Closed); } } } @@ -1407,6 +1407,12 @@ fn settle_timeout( .periodic_check() .expect("not to error"); + second + .lock() + .unwrap() + .get_store() + .get_channel(&channel_id) + .unwrap(); assert_channel_state!(second, channel_id, Signed, Closing); } else if let TestPath::SettleConfirmTimeout = path { // Process Confirm diff --git a/dlc-manager/tests/console_logger.rs b/dlc-manager/tests/console_logger.rs index 52b185b3..eb622ab7 100644 --- a/dlc-manager/tests/console_logger.rs +++ b/dlc-manager/tests/console_logger.rs @@ -1,6 +1,7 @@ use chrono::Utc; use lightning::util::logger::{Logger, Record}; +#[derive(Debug)] pub(crate) struct ConsoleLogger { pub name: String, } diff --git a/dlc-manager/tests/ln_dlc_channel_execution_tests.rs b/dlc-manager/tests/ln_dlc_channel_execution_tests.rs index 46ad01ed..6e3148eb 100644 --- a/dlc-manager/tests/ln_dlc_channel_execution_tests.rs +++ b/dlc-manager/tests/ln_dlc_channel_execution_tests.rs @@ -23,8 +23,12 @@ use bitcoincore_rpc::RpcApi; use console_logger::ConsoleLogger; use custom_signer::{CustomKeysManager, CustomSigner}; use dlc_manager::{ - channel::Channel, contract::Contract, manager::Manager, sub_channel_manager::SubChannelManager, - subchannel::SubChannelState, Blockchain, ChannelId, Oracle, Signer, Storage, Utxo, Wallet, + channel::Channel, + contract::Contract, + manager::Manager, + sub_channel_manager::SubChannelManager, + subchannel::{SubChannel, SubChannelState}, + Blockchain, ChannelId, Oracle, Signer, Storage, Utxo, Wallet, }; use dlc_messages::{ sub_channel::{SubChannelAccept, SubChannelOffer}, @@ -54,6 +58,7 @@ use lightning::{ }; use lightning_persister::FilesystemPersister; use lightning_transaction_sync::EsploraSyncClient; +use log::error; use mocks::{ memory_storage_provider::MemoryStorage, mock_blockchain::MockBlockchain, @@ -162,6 +167,21 @@ enum TestPath { Reconnect, ReconnectReOfferAfterClose, DisconnectedForceClose, + OfferedForceClose, + OfferedForceClose2, + AcceptedForceClose, + AcceptedForceClose2, + ConfirmedForceClose, + ConfirmedForceClose2, + FinalizedForceClose, + FinalizedForceClose2, + CloseOfferedForceClose, + CloseOfferedForceClose2, + CloseAcceptedForceClose, + CloseAcceptedForceClose2, + CloseConfirmedForceClose, + CloseConfirmedForceClose2, + CloseFinalizedForceClose, } impl LnDlcParty { @@ -179,6 +199,7 @@ impl LnDlcParty { fn process_events(&self) { self.peer_manager.process_events(); self.channel_manager.process_pending_events(self); + self.channel_manager.timer_tick_occurred(); self.chain_monitor.process_pending_events(self); } } @@ -351,6 +372,17 @@ impl EventHandler for LnDlcParty { .unwrap(); self.blockchain.broadcast_transaction(&spending_tx); } + Event::ChannelClosed { channel_id, .. } => { + if let Err(error) = self + .sub_channel_manager + .notify_ln_channel_closed(channel_id) + { + error!( + "Error notifying sub channel manager of LN channel closing: {}", + error + ); + } + } _ => { //Ignore } @@ -589,6 +621,96 @@ fn ln_dlc_disconnected_force_close() { ln_dlc_test(TestPath::DisconnectedForceClose); } +#[test] +#[ignore] +fn ln_dlc_offered_force_close() { + ln_dlc_test(TestPath::OfferedForceClose); +} + +#[test] +#[ignore] +fn ln_dlc_offered_force_close2() { + ln_dlc_test(TestPath::OfferedForceClose2); +} + +#[test] +#[ignore] +fn ln_dlc_accepted_force_close() { + ln_dlc_test(TestPath::AcceptedForceClose); +} + +#[test] +#[ignore] +fn ln_dlc_accepted_force_close2() { + ln_dlc_test(TestPath::AcceptedForceClose2); +} + +#[test] +#[ignore] +fn ln_dlc_confirmed_force_close() { + ln_dlc_test(TestPath::ConfirmedForceClose); +} + +#[test] +#[ignore] +fn ln_dlc_confirmed_force_close2() { + ln_dlc_test(TestPath::ConfirmedForceClose2); +} + +#[test] +#[ignore] +fn ln_dlc_finalized_force_close() { + ln_dlc_test(TestPath::FinalizedForceClose); +} + +#[test] +#[ignore] +fn ln_dlc_finalized_force_close2() { + ln_dlc_test(TestPath::FinalizedForceClose2); +} + +#[test] +#[ignore] +fn ln_dlc_close_offered_force_close() { + ln_dlc_test(TestPath::CloseOfferedForceClose); +} + +#[test] +#[ignore] +fn ln_dlc_close_offered_force_close2() { + ln_dlc_test(TestPath::CloseOfferedForceClose2); +} + +#[test] +#[ignore] +fn ln_dlc_close_accepted_force_close() { + ln_dlc_test(TestPath::CloseAcceptedForceClose); +} + +#[test] +#[ignore] +fn ln_dlc_close_accepted_force_close2() { + ln_dlc_test(TestPath::CloseAcceptedForceClose2); +} + +#[test] +#[ignore] +fn ln_dlc_close_confirmed_force_close() { + ln_dlc_test(TestPath::CloseConfirmedForceClose); +} + +#[test] +#[ignore] +fn ln_dlc_close_confirmed_force_close2() { + ln_dlc_test(TestPath::CloseConfirmedForceClose2); +} + +#[test] +#[ignore] +fn ln_dlc_close_finalized_force_close() { + ln_dlc_test(TestPath::CloseFinalizedForceClose); +} + // #[derive(Debug)] // pub struct TestParams { // pub oracles: Vec, @@ -788,14 +910,11 @@ fn ln_dlc_test(test_path: TestPath) { let get_commit_tx_from_node = |node: &LnDlcParty| { let mut res = node .persister - .read_channelmonitors( - alice_node.keys_manager.clone(), - alice_node.keys_manager.clone(), - ) + .read_channelmonitors(node.keys_manager.clone(), node.keys_manager.clone()) .unwrap(); assert!(res.len() == 1); let (_, channel_monitor) = res.remove(0); - channel_monitor.get_latest_holder_commitment_txn(&alice_node.logger) + channel_monitor.get_latest_holder_commitment_txn(&node.logger) }; let pre_split_commit_tx = if let TestPath::CheatPreSplitCommit = test_path { @@ -1097,6 +1216,51 @@ fn ln_dlc_test(test_path: TestPath) { return; } + if let TestPath::OfferedForceClose + | TestPath::OfferedForceClose2 + | TestPath::AcceptedForceClose + | TestPath::AcceptedForceClose2 + | TestPath::ConfirmedForceClose + | TestPath::ConfirmedForceClose2 + | TestPath::FinalizedForceClose + | TestPath::FinalizedForceClose2 = test_path + { + force_close_establish_tests( + &test_path, + &test_params, + &mut alice_node, + &mut bob_node, + channel_id, + alice_descriptor, + bob_descriptor, + electrs.clone(), + &get_commit_tx_from_node, + &generate_blocks, + ); + return; + } + + if let TestPath::CloseOfferedForceClose + | TestPath::CloseOfferedForceClose2 + | TestPath::CloseAcceptedForceClose + | TestPath::CloseAcceptedForceClose2 + | TestPath::CloseConfirmedForceClose + | TestPath::CloseConfirmedForceClose2 + | TestPath::CloseFinalizedForceClose = test_path + { + force_close_off_chain_close_tests( + &test_path, + &test_params, + &mut alice_node, + &mut bob_node, + channel_id, + electrs.clone(), + &get_commit_tx_from_node, + &generate_blocks, + ); + return; + } + let commit_tx = get_commit_tx_from_node(&alice_node).remove(0); if let TestPath::CheatPostSplitCommit = test_path { @@ -1171,8 +1335,8 @@ fn ln_dlc_test(test_path: TestPath) { generate_blocks(1); bob_node.update_to_chain_tip(); - assert_channel_state_unlocked!(alice_node.dlc_manager, dlc_channel_id, Signed, Closed); - assert_channel_state_unlocked!(bob_node.dlc_manager, dlc_channel_id, Signed, CounterClosed); + assert_channel_state_unlocked!(alice_node.dlc_manager, dlc_channel_id, Closed); + assert_channel_state_unlocked!(bob_node.dlc_manager, dlc_channel_id, CounterClosed); assert_contract_state_unlocked!(alice_node.dlc_manager, contract_id, PreClosed); assert_contract_state_unlocked!(bob_node.dlc_manager, contract_id, PreClosed); @@ -1188,8 +1352,8 @@ fn ln_dlc_test(test_path: TestPath) { assert_contract_state_unlocked!(alice_node.dlc_manager, contract_id, Closed); assert_contract_state_unlocked!(bob_node.dlc_manager, contract_id, Closed); } else { - assert_channel_state_unlocked!(alice_node.dlc_manager, dlc_channel_id, Signed, Closed); - assert_channel_state_unlocked!(bob_node.dlc_manager, dlc_channel_id, Signed, CounterClosed); + assert_channel_state_unlocked!(alice_node.dlc_manager, dlc_channel_id, Closed); + assert_channel_state_unlocked!(bob_node.dlc_manager, dlc_channel_id, CounterClosed); } generate_blocks(500); @@ -1957,16 +2121,302 @@ fn off_chain_close_finalize( assert_channel_state_unlocked!( alice_node.dlc_manager, dlc_channel_id, - Signed, - CollaborativelyClosed - ); - assert_channel_state_unlocked!( - bob_node.dlc_manager, - dlc_channel_id, - Signed, CollaborativelyClosed ); + assert_channel_state_unlocked!(bob_node.dlc_manager, dlc_channel_id, CollaborativelyClosed); assert_sub_channel_state!(alice_node.sub_channel_manager, &channel_id; OffChainClosed); assert_sub_channel_state!(bob_node.sub_channel_manager, &channel_id; OffChainClosed); } + +fn force_close_establish_tests( + test_path: &TestPath, + test_params: &TestParams, + alice_node: &mut LnDlcParty, + bob_node: &mut LnDlcParty, + channel_id: ChannelId, + alice_descriptor: MockSocketDescriptor, + bob_descriptor: MockSocketDescriptor, + electrs: Arc, + get_commit_tx_from_node: &F, + generate_blocks: &G, +) where + F: Fn(&LnDlcParty) -> Vec, + G: Fn(u64), +{ + off_chain_close_offer( + test_path, + test_params, + alice_node, + bob_node, + channel_id, + alice_descriptor.clone(), + bob_descriptor.clone(), + ); + off_chain_close_finalize( + test_path, + alice_node, + bob_node, + channel_id, + alice_descriptor.clone(), + bob_descriptor.clone(), + test_params, + ); + + let offer = offer_common(test_params, alice_node, &channel_id); + bob_node + .sub_channel_manager + .on_sub_channel_message( + &SubChannelMessage::Offer(offer), + &alice_node.channel_manager.get_our_node_id(), + ) + .unwrap(); + if let TestPath::AcceptedForceClose + | TestPath::AcceptedForceClose2 + | TestPath::ConfirmedForceClose + | TestPath::ConfirmedForceClose2 + | TestPath::FinalizedForceClose + | TestPath::FinalizedForceClose2 = test_path + { + let (_, accept) = bob_node + .sub_channel_manager + .accept_sub_channel(&channel_id) + .unwrap(); + if let TestPath::ConfirmedForceClose + | TestPath::ConfirmedForceClose2 + | TestPath::FinalizedForceClose + | TestPath::FinalizedForceClose2 = test_path + { + let confirm = alice_node + .sub_channel_manager + .on_sub_channel_message( + &SubChannelMessage::Accept(accept), + &bob_node.channel_manager.get_our_node_id(), + ) + .unwrap() + .unwrap(); + if let TestPath::FinalizedForceClose | TestPath::FinalizedForceClose2 = test_path { + bob_node + .sub_channel_manager + .on_sub_channel_message(&confirm, &alice_node.channel_manager.get_our_node_id()) + .unwrap(); + } + } + } + + let (closer, closee) = if let TestPath::OfferedForceClose + | TestPath::AcceptedForceClose + | TestPath::ConfirmedForceClose + | TestPath::FinalizedForceClose = test_path + { + (alice_node, bob_node) + } else { + (bob_node, alice_node) + }; + + let sub_channel = closer + .dlc_manager + .get_store() + .get_sub_channel(channel_id) + .unwrap() + .unwrap(); + + let commit_tx = if let TestPath::OfferedForceClose + | TestPath::OfferedForceClose2 + | TestPath::AcceptedForceClose = test_path + { + get_commit_tx_from_node(&closer).remove(0) + } else if let TestPath::AcceptedForceClose2 | TestPath::ConfirmedForceClose2 = test_path { + if let SubChannelState::Accepted(a) = &sub_channel.state { + a.commitment_transactions[0].clone() + } else { + unreachable!(); + } + } else if let TestPath::ConfirmedForceClose | TestPath::FinalizedForceClose = test_path { + if let SubChannelState::Confirmed(c) = &sub_channel.state { + c.commitment_transactions[0].clone() + } else { + unreachable!(); + } + } else { + get_commit_tx_from_node(&closer).remove(0) + }; + + force_close_common( + sub_channel, + closer, + closee, + &commit_tx, + &electrs, + generate_blocks, + ); +} + +fn force_close_off_chain_close_tests( + test_path: &TestPath, + test_params: &TestParams, + alice_node: &mut LnDlcParty, + bob_node: &mut LnDlcParty, + channel_id: ChannelId, + electrs: Arc, + get_commit_tx_from_node: &F, + generate_blocks: &G, +) where + F: Fn(&LnDlcParty) -> Vec, + G: Fn(u64), +{ + let sub_channel = alice_node + .dlc_manager + .get_store() + .get_sub_channel(channel_id) + .unwrap() + .unwrap(); + + let dlc_channel_id = sub_channel.get_dlc_channel_id(0).unwrap(); + assert_channel_contract_state!(alice_node.dlc_manager, dlc_channel_id, Confirmed); + + let (close_offer, _) = alice_node + .sub_channel_manager + .offer_subchannel_close(&channel_id, test_params.contract_input.accept_collateral) + .unwrap(); + + if let TestPath::CloseAcceptedForceClose + | TestPath::CloseAcceptedForceClose2 + | TestPath::CloseConfirmedForceClose + | TestPath::CloseConfirmedForceClose2 + | TestPath::CloseFinalizedForceClose = test_path + { + bob_node + .sub_channel_manager + .on_sub_channel_message( + &SubChannelMessage::CloseOffer(close_offer), + &alice_node.channel_manager.get_our_node_id(), + ) + .unwrap(); + let (accept, _) = bob_node + .sub_channel_manager + .accept_subchannel_close_offer(&channel_id) + .unwrap(); + if let TestPath::CloseConfirmedForceClose + | TestPath::CloseConfirmedForceClose2 + | TestPath::CloseFinalizedForceClose = test_path + { + let confirm = alice_node + .sub_channel_manager + .on_sub_channel_message( + &SubChannelMessage::CloseAccept(accept), + &bob_node.channel_manager.get_our_node_id(), + ) + .unwrap() + .unwrap(); + if let TestPath::CloseFinalizedForceClose = test_path { + bob_node + .sub_channel_manager + .on_sub_channel_message(&confirm, &alice_node.channel_manager.get_our_node_id()) + .unwrap(); + } + } + } + + let (closer, closee) = if let TestPath::CloseOfferedForceClose + | TestPath::CloseAcceptedForceClose + | TestPath::CloseConfirmedForceClose + | TestPath::CloseFinalizedForceClose = test_path + { + (alice_node, bob_node) + } else { + (bob_node, alice_node) + }; + + let commit_tx = get_commit_tx_from_node(&closer).remove(0); + + force_close_common( + sub_channel, + closer, + closee, + &commit_tx, + &electrs, + generate_blocks, + ); +} + +fn force_close_common( + sub_channel: SubChannel, + closer: &mut LnDlcParty, + closee: &mut LnDlcParty, + commit_tx: &Transaction, + electrs: &Arc, + generate_blocks: &F, +) where + F: Fn(u64), +{ + let dlc_channel_id_closer = sub_channel.get_dlc_channel_id(0).unwrap(); + + let sub_channel = closee + .dlc_manager + .get_store() + .get_sub_channel(sub_channel.channel_id) + .unwrap() + .unwrap(); + let dlc_channel_id_closee = sub_channel.get_dlc_channel_id(0); + + let channel_id = sub_channel.channel_id; + + closer + .sub_channel_manager + .force_close_sub_channel(&channel_id) + .expect("To be able to force close offered channel"); + + generate_blocks(500); + closer.update_to_chain_tip(); + closer.process_events(); + + assert_sub_channel_state!(closer.sub_channel_manager, &channel_id; OnChainClosed); + + generate_blocks(3); + + closer.update_to_chain_tip(); + closee.update_to_chain_tip(); + closee.process_events(); + + assert_sub_channel_state!(closee.sub_channel_manager, &channel_id; CounterOnChainClosed); + + generate_blocks(500); + + closer.update_to_chain_tip(); + closer.process_events(); + closee.update_to_chain_tip(); + closee.process_events(); + + assert_channel_state_unlocked!(closer.dlc_manager, dlc_channel_id_closer, Closed); + if let Some(dlc_channel_id_closee) = dlc_channel_id_closee { + assert_channel_state_unlocked!(closee.dlc_manager, dlc_channel_id_closee, CounterClosed); + } + + assert!(closer + .dlc_manager + .get_chain_monitor() + .lock() + .unwrap() + .is_empty()); + assert!(closee + .dlc_manager + .get_chain_monitor() + .lock() + .unwrap() + .is_empty()); + + let all_spent = 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-manager/tests/test_utils.rs b/dlc-manager/tests/test_utils.rs index e5b2c8ae..89fc48c5 100644 --- a/dlc-manager/tests/test_utils.rs +++ b/dlc-manager/tests/test_utils.rs @@ -192,7 +192,7 @@ macro_rules! assert_channel_state_unlocked { let res = $d .get_store() .get_channel(&$id) - .expect("Could not retrieve contract"); + .expect("Could not retrieve channel"); if let Some(Channel::$p(c)) = res { $(if let dlc_manager::channel::signed_channel::SignedChannelState::$s { .. } = c.state { } else { @@ -203,15 +203,7 @@ macro_rules! assert_channel_state_unlocked { write_channel!(channel, $p); } } else { - let state = match res { - Some(Channel::Offered(_)) => "offered", - Some(Channel::Accepted(_)) => "accepted", - Some(Channel::Signed(_)) => "signed", - Some(Channel::FailedAccept(_)) => "failed accept", - Some(Channel::FailedSign(_)) => "failed sign", - None => "none", - }; - panic!("Unexpected channel state {}", state); + panic!("Could not find requested channel"); } }}; } diff --git a/dlc-sled-storage-provider/src/lib.rs b/dlc-sled-storage-provider/src/lib.rs index 90e82cf5..570a4757 100644 --- a/dlc-sled-storage-provider/src/lib.rs +++ b/dlc-sled-storage-provider/src/lib.rs @@ -20,7 +20,9 @@ use dlc_manager::chain_monitor::ChainMonitor; use dlc_manager::channel::accepted_channel::AcceptedChannel; use dlc_manager::channel::offered_channel::OfferedChannel; use dlc_manager::channel::signed_channel::{SignedChannel, SignedChannelStateType}; -use dlc_manager::channel::{Channel, FailedAccept, FailedSign}; +use dlc_manager::channel::{ + Channel, ClosedChannel, ClosedPunishedChannel, ClosingChannel, FailedAccept, FailedSign, +}; use dlc_manager::contract::accepted_contract::AcceptedContract; use dlc_manager::contract::offered_contract::OfferedContract; use dlc_manager::contract::ser::Serializable; @@ -122,6 +124,11 @@ convertible_enum!( Offered = 1, Accepted, Signed; SignedChannelPrefix, state, + Closing, + Closed, + CounterClosed, + ClosedPunished, + CollaborativelyClosed, FailedAccept, FailedSign,; }, @@ -137,11 +144,7 @@ convertible_enum!( SettledConfirmed, Settled, Closing, - Closed, - CounterClosed, - ClosedPunished, CollaborativeCloseOffered, - CollaborativelyClosed, RenewAccepted, RenewOffered, RenewConfirmed, @@ -708,6 +711,11 @@ fn serialize_channel(channel: &Channel) -> Result, ::std::io::Error> { Channel::Signed(s) => s.serialize(), Channel::FailedAccept(f) => f.serialize(), Channel::FailedSign(f) => f.serialize(), + Channel::Closing(c) => c.serialize(), + Channel::Closed(c) | Channel::CounterClosed(c) | Channel::CollaborativelyClosed(c) => { + c.serialize() + } + Channel::ClosedPunished(c) => c.serialize(), }; let mut serialized = serialized?; let mut res = Vec::with_capacity(serialized.len() + 1); @@ -742,6 +750,21 @@ fn deserialize_channel(buff: &sled::IVec) -> Result { ChannelPrefix::FailedSign => { Channel::FailedSign(FailedSign::deserialize(&mut cursor).map_err(to_storage_error)?) } + ChannelPrefix::Closing => { + Channel::Closing(ClosingChannel::deserialize(&mut cursor).map_err(to_storage_error)?) + } + ChannelPrefix::Closed => { + Channel::Closed(ClosedChannel::deserialize(&mut cursor).map_err(to_storage_error)?) + } + ChannelPrefix::CollaborativelyClosed => Channel::CollaborativelyClosed( + ClosedChannel::deserialize(&mut cursor).map_err(to_storage_error)?, + ), + ChannelPrefix::CounterClosed => Channel::CounterClosed( + ClosedChannel::deserialize(&mut cursor).map_err(to_storage_error)?, + ), + ChannelPrefix::ClosedPunished => Channel::ClosedPunished( + ClosedPunishedChannel::deserialize(&mut cursor).map_err(to_storage_error)?, + ), }; Ok(channel) } diff --git a/dlc-sled-storage-provider/test_files/Accepted b/dlc-sled-storage-provider/test_files/Accepted index 779ef465..3ad8a076 100644 Binary files a/dlc-sled-storage-provider/test_files/Accepted and b/dlc-sled-storage-provider/test_files/Accepted differ diff --git a/dlc-sled-storage-provider/test_files/AcceptedChannel b/dlc-sled-storage-provider/test_files/AcceptedChannel index d15fa1b6..edb67032 100644 Binary files a/dlc-sled-storage-provider/test_files/AcceptedChannel and b/dlc-sled-storage-provider/test_files/AcceptedChannel differ diff --git a/dlc-sled-storage-provider/test_files/AcceptedSubChannel b/dlc-sled-storage-provider/test_files/AcceptedSubChannel index a7c755bf..8d603c3a 100644 Binary files a/dlc-sled-storage-provider/test_files/AcceptedSubChannel and b/dlc-sled-storage-provider/test_files/AcceptedSubChannel differ diff --git a/dlc-sled-storage-provider/test_files/Closed b/dlc-sled-storage-provider/test_files/Closed index 5a8e38c0..da91d06d 100644 Binary files a/dlc-sled-storage-provider/test_files/Closed and b/dlc-sled-storage-provider/test_files/Closed differ diff --git a/dlc-sled-storage-provider/test_files/Confirmed b/dlc-sled-storage-provider/test_files/Confirmed index 88f2f3f4..949add46 100644 Binary files a/dlc-sled-storage-provider/test_files/Confirmed and b/dlc-sled-storage-provider/test_files/Confirmed differ diff --git a/dlc-sled-storage-provider/test_files/Confirmed1 b/dlc-sled-storage-provider/test_files/Confirmed1 index 9b58812d..06571026 100644 Binary files a/dlc-sled-storage-provider/test_files/Confirmed1 and b/dlc-sled-storage-provider/test_files/Confirmed1 differ diff --git a/dlc-sled-storage-provider/test_files/Offered b/dlc-sled-storage-provider/test_files/Offered index bceafe9f..cffc99f5 100644 Binary files a/dlc-sled-storage-provider/test_files/Offered and b/dlc-sled-storage-provider/test_files/Offered differ diff --git a/dlc-sled-storage-provider/test_files/OfferedChannel b/dlc-sled-storage-provider/test_files/OfferedChannel index eed80072..e4c1deaa 100644 Binary files a/dlc-sled-storage-provider/test_files/OfferedChannel and b/dlc-sled-storage-provider/test_files/OfferedChannel differ diff --git a/dlc-sled-storage-provider/test_files/OfferedSubChannel b/dlc-sled-storage-provider/test_files/OfferedSubChannel index e1194009..568cff30 100644 Binary files a/dlc-sled-storage-provider/test_files/OfferedSubChannel and b/dlc-sled-storage-provider/test_files/OfferedSubChannel differ diff --git a/dlc-sled-storage-provider/test_files/OfferedSubChannel1 b/dlc-sled-storage-provider/test_files/OfferedSubChannel1 index 9e58df87..1a6f9d3b 100644 Binary files a/dlc-sled-storage-provider/test_files/OfferedSubChannel1 and b/dlc-sled-storage-provider/test_files/OfferedSubChannel1 differ diff --git a/dlc-sled-storage-provider/test_files/PreClosed b/dlc-sled-storage-provider/test_files/PreClosed index 69aa2e93..db676190 100644 Binary files a/dlc-sled-storage-provider/test_files/PreClosed and b/dlc-sled-storage-provider/test_files/PreClosed differ diff --git a/dlc-sled-storage-provider/test_files/Signed b/dlc-sled-storage-provider/test_files/Signed index 04e9b3c4..49bf73f6 100644 Binary files a/dlc-sled-storage-provider/test_files/Signed and b/dlc-sled-storage-provider/test_files/Signed differ diff --git a/dlc-sled-storage-provider/test_files/Signed1 b/dlc-sled-storage-provider/test_files/Signed1 index 2e4001d6..1e9e735d 100644 Binary files a/dlc-sled-storage-provider/test_files/Signed1 and b/dlc-sled-storage-provider/test_files/Signed1 differ diff --git a/dlc-sled-storage-provider/test_files/SignedChannelEstablished b/dlc-sled-storage-provider/test_files/SignedChannelEstablished index dcaa01f1..1e46dcae 100644 Binary files a/dlc-sled-storage-provider/test_files/SignedChannelEstablished and b/dlc-sled-storage-provider/test_files/SignedChannelEstablished differ diff --git a/dlc-sled-storage-provider/test_files/SignedChannelSettled b/dlc-sled-storage-provider/test_files/SignedChannelSettled index 092c6099..c7bb33be 100644 Binary files a/dlc-sled-storage-provider/test_files/SignedChannelSettled and b/dlc-sled-storage-provider/test_files/SignedChannelSettled differ diff --git a/dlc-sled-storage-provider/test_files/SignedSubChannel b/dlc-sled-storage-provider/test_files/SignedSubChannel index 2c707e78..827da6ef 100644 Binary files a/dlc-sled-storage-provider/test_files/SignedSubChannel and b/dlc-sled-storage-provider/test_files/SignedSubChannel differ diff --git a/dlc/src/channel/mod.rs b/dlc/src/channel/mod.rs index 31261ca0..d97d2580 100644 --- a/dlc/src/channel/mod.rs +++ b/dlc/src/channel/mod.rs @@ -211,7 +211,7 @@ pub fn create_settle_transaction( )?) / (output.len() as u64); - for mut o in &mut output { + for o in &mut output { o.value += remaining_fee; }