From a88dbd193e1320a0512ad631847561b8d9ae223a Mon Sep 17 00:00:00 2001 From: Tibo-lg Date: Thu, 20 Jul 2023 15:52:18 +0900 Subject: [PATCH] Allow force closing in any state --- Cargo.toml | 6 +- dlc-manager/src/channel/mod.rs | 88 ++- dlc-manager/src/channel/ser.rs | 22 +- dlc-manager/src/channel/signed_channel.rs | 19 +- dlc-manager/src/channel_updater.rs | 54 +- dlc-manager/src/manager.rs | 130 ++-- dlc-manager/src/sub_channel_manager.rs | 568 +++++++++++++++--- dlc-manager/src/subchannel/mod.rs | 22 + dlc-manager/src/subchannel/ser.rs | 12 +- dlc-manager/tests/channel_execution_tests.rs | 24 +- dlc-manager/tests/console_logger.rs | 1 + .../tests/ln_dlc_channel_execution_tests.rs | 486 ++++++++++++++- dlc-manager/tests/test_utils.rs | 12 +- dlc-sled-storage-provider/src/lib.rs | 33 +- dlc-sled-storage-provider/test_files/Accepted | Bin 8551 -> 3430 bytes .../test_files/AcceptedChannel | Bin 908 -> 908 bytes .../test_files/AcceptedSubChannel | Bin 2997 -> 3343 bytes dlc-sled-storage-provider/test_files/Closed | Bin 1109 -> 1109 bytes .../test_files/Confirmed | Bin 12140 -> 5983 bytes .../test_files/Confirmed1 | Bin 10069 -> 5983 bytes dlc-sled-storage-provider/test_files/Offered | Bin 2706 -> 1107 bytes .../test_files/OfferedChannel | Bin 235 -> 235 bytes .../test_files/OfferedSubChannel | Bin 2424 -> 2424 bytes .../test_files/OfferedSubChannel1 | Bin 2424 -> 2424 bytes .../test_files/PreClosed | Bin 10960 -> 6873 bytes dlc-sled-storage-provider/test_files/Signed | Bin 9367 -> 5873 bytes dlc-sled-storage-provider/test_files/Signed1 | Bin 9959 -> 5873 bytes .../test_files/SignedChannelEstablished | Bin 3663 -> 3663 bytes .../test_files/SignedChannelSettled | Bin 3465 -> 3681 bytes .../test_files/SignedSubChannel | Bin 3385 -> 3385 bytes 30 files changed, 1227 insertions(+), 250 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6e17ce69..7664efa3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,6 @@ members = [ ] [patch.crates-io] -lightning = { git = "https://github.com/p2pderivatives/rust-lightning/", rev = "1e5b2a42" } -lightning-net-tokio = { git = "https://github.com/p2pderivatives/rust-lightning/", rev = "1e5b2a42" } -lightning-persister = { git = "https://github.com/p2pderivatives/rust-lightning/", rev = "1e5b2a42" } +lightning = { git = "https://github.com/p2pderivatives/rust-lightning/", rev = "b190b7e6" } +lightning-net-tokio = { git = "https://github.com/p2pderivatives/rust-lightning/", rev = "b190b7e6" } +lightning-persister = { git = "https://github.com/p2pderivatives/rust-lightning/", rev = "b190b7e6" } 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 f9a4dd46..bfef79a3 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::{ @@ -1071,7 +1071,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 { @@ -1093,16 +1093,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 @@ -1114,7 +1113,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, @@ -1143,15 +1142,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, @@ -1159,19 +1160,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(()) @@ -1966,7 +1961,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"); @@ -1978,21 +1973,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, + }) } } }; @@ -2000,7 +2008,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 { @@ -2018,11 +2026,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, @@ -2163,9 +2170,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. @@ -2173,8 +2183,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 { @@ -2194,22 +2203,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 @@ -2322,12 +2337,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())) - } } } @@ -2379,13 +2388,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, @@ -2405,8 +2414,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(()) } @@ -2417,8 +2425,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( @@ -2430,9 +2438,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 d51c1874..ced8388d 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::{ @@ -585,6 +585,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( @@ -669,6 +673,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); @@ -692,80 +697,218 @@ 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; - 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; + 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, + &sub_channel.counter_fund_pk, 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, - &signed.counter_fund_pk, - 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(()) } @@ -873,8 +1016,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 @@ -895,6 +1052,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, @@ -998,10 +1231,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) @@ -1015,6 +1247,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; @@ -1039,9 +1274,8 @@ where ln_own_balance_msats, )?; - Ok(commitment_signed) - }, - )?; + Ok((commitment_signed, commitment_transactions)) + })?; let close_accept = SubChannelCloseAccept { channel_id: *channel_id, @@ -1054,6 +1288,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); @@ -2047,24 +2295,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, @@ -2072,6 +2305,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); @@ -2195,13 +2429,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) }, )?; @@ -2246,6 +2488,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(), @@ -2270,13 +2531,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(()) }, )?; @@ -2498,21 +2767,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); @@ -3145,6 +3465,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 7e686fd0..ec7d4d24 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, @@ -722,6 +725,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); @@ -756,6 +764,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 779ef465a80b312df05945bb329715f4ffe91ae1..3ad8a07613c0041e1f593547f6a105b0a8842dc0 100644 GIT binary patch literal 3430 zcmbPbp)-His((30n%%1|?>=(vd3i+OQrDsy!HDw8KIwWB21Z6E24)6a04&2A>(u~P zzzSlqu_`+NDJBpb2$03V0?hwG6hsvxSOh`9`Ajet7o)K42d-~dQ&R7*i%xpMuYKtm zcgBatw`C2VCl<^Nxx4cy=Z(CBQInoWtT9@X@F`Amq3I(-n~RZgC;qCse_Sg+^H6x@ zhK!o6l2zB#*z;LVgwJiBdSdO?cX9He9ar+J{%^VT_lDA6#zY3L&wQ-G&knweYW^wx zmY3D|Rf5z9Zi=-a}?knAU z<&sjq>r`K*OAd_FR;}61^8U@lUXw#b4n=E=_Agu==iBAW>BHvoZvD4ae1)@bh%oA1 z{iUkFyC~++&rdH`*KJ(dwf$tL@T3hH9b0C8++<_8NHcHyvdr14Wu3o0w=P%QcI40w zw?C(kgnCN9bZLE(^;{)xb@#uUS`Q_4lsnGVZTMp^d9ue;@#$@;)Qfiv{^%7L%{b?- zaMfxq%c+B%87b+J5Sw6iG z=r|#2OO|O#pna9FZO>7r0}>9*GOK0ZePQ@mopT|1*0rS~%O(}y__vwMmP39H+w^6z ztjDWw_MSGT(34GTdAKi6+0P)xAo5`LZq{jOU*-v|Pk%Rmr-{d< zu)>{_`d6eRg-vPz$p<(WPT!ll{$(WNznf|MyC^@4K^iMIPR z9L1I`=h!SRj%cLpB2g$Fh_YX1|2KgG9Ms9|B9l5;Z81 zGQt$x1E*+4#oaA8uYFFV`F;G<~5r?;S|gZIp;zdM^|&Wuu?&s2OPI*y~-mq{VJe9n^| z1rNSS@Av0d2q`Rn`6+96Z@x-!caxFt)$ngRjEc;;OTXQBV>p#=w=Q+Z>U*=EE$3Lk zepR+NI7pKBgz7Uj{X1YcGcW)h{T~!23;}Y?XB=I7tf1nIz_?wXaKg=iNunjZ@tfh1 zewpczhWAx6 zIr2mV1eRQ^dARM7^n}y-elr~zPw{ch{%Lk3XvOY!vpfHMd>8yZ95RUDrlhLs&apBS1lb6lOmZv^rPEOr@|-#EYslYqsl0qf(d-#L#61$H=JoNersvfi&u zVG?IxUEGgn{@()|mxDtB9IIctoYd^VfY@A{yEQXUNJkL>93IE zyqHNr-G+J6_I?$YLvy$6i(RwG<9N1|SxmNb>Z>?|mMRsO7+|cfxxvhLe0!qt_19)E z4s}F6ET87VR?(Zd;M@6qYp%yeSVLo#5f-bAps>Z}CT2zkX+~I}fjtT$5CU*Hm=Ll6 zOpJjMUS`9!GQt(YY6FBCBnuEa;nHkyov```=tu|v(r_k1CtR8pu9F3-fd!@<*;5Fe zaA_X6PH>$CRLu(0iDV~|KiOf@oESR6DFkFcl1^Z43e&&^V{l{Wgd2*a6Uj~vm`+{{ zop34aI{9Ea`7v~I!lbb46o5$!V(5f>1iMZlm`-5~o!l@f>^enY(xMnTd0b*Ol1bl?9=8}_OgC3pHt|GhdXVKtx1>1VInP5T!80h!H2R9ywi zz=VoDunR#7nfYEg!)hID1#OWED;vmbN(>5zUWcsqOi9YVuy4ww+)Z_7^W?Ll0`%?WkE`&9;HT zzwR^pE^{BS<3NQcqk}TUb{wVrdhY!Rq?yQcz$YFYOF$E`wF(-pEd`e#)U(h*KoeZD z;INQE_zu{+)U&X`D;8WJ;jr+)4M^ogMGJrb#%rNECn)N1)zqZNA#$p~7Kfyp$V6F~ W8MvKDU~msoB-ol!B-ok_S_c3=PXfFE literal 8551 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 diff --git a/dlc-sled-storage-provider/test_files/AcceptedChannel b/dlc-sled-storage-provider/test_files/AcceptedChannel index d15fa1b66d5d181f301fa4fbc6f5228c8dad90c5..edb67032ebaca24576af48a2f210be34cc4a73ca 100644 GIT binary patch delta 904 zcmV;319$w42aE@Q$N&=3xb4_+AC*<_;lYV^QxbnVuJOmlEp45P2ENZQ1L9ByUQfbl zT@w{zbkIf;_8oYXkVwYueW~ z=McoxJ$VA6@DQ_<0Dz0*a~kr{cNt+}feGICpj+3(AROXr}C!UE}tJ94CkoF`DEXbJ>YpTOYr=U`|K%3PkQkdGglh2sJM z0005l0ZAKE1V4yi&PBCrY{3!UFThnawuxO4`BC=GV^y_EH4!n1AqrJdcL{cigPe&%X_b`mfNwXX}mU;yAD15BOg23pvT zyE#01KAiJn694|DdWlxr`wc4wCRZ4?TdZVbcBvGeA-vaJC;Am(@kLW-g-P9|sM@lK zt#+vtl<9nl3w7W;owRuT|KVVeb^d^ch^=<16!$@wQOfu}jOZI~Vbw#DA}rpBt#+vtga*omb28Vy3C8SQtlp|LeN(NEoQ;0q=Zd*B>KU;RVyHW z>04DH`V}|YfLJ3Kb??~0WE5xzkEnW2{%EY6RdnDIq*ntQ+m5Vv5loY~xCLl9+mY|a zJgS@i?#{uaoB9%@__2VmNr^e1&}r-9)%8@!-LauKB delta 904 zcmV;319$w42aE@QZw--=wsZnTwXlf&&=A!DS;$$*;kJ+rMkleq6xLxt0^fEO_8h)B zd`%KSm7*!O4Joz%6s>(eNElsslE5+5q5^d{D3r`{nB6s7&cYL*e9Io+K^2JOJvNu) z{LJfhH5~%d{)!zx=G-MD`q|fzc)5;D#uz*#S5#|Ri*l=fN2wnt0yjYYSa$lgRy1|v zuNsol6c%+dTV!_brf00000{{R2~0dgb-1^@s6 z03rY&BY!Y6wPPGB#8dRz3C8Ltb!U;yAD0tL-I|1@_< z0UuYOu-5&k0j7gRK^;FiI)>|lr(X2h=B#96cBvGuG=1SkDgssBB=|A?iuwZJq@ePM zt#+vt9|Z4dh6tt;f#Y;ifLS>l%ym9+h^=<16nyJ)XaKZ}^IKj+LJ#Ow2~$KlIEZI< zsT41NI7)6FY^u)y|E#tHAmr2asRNdXt#+vt`xwaz8Xo&rnSW~2_>)5=z z$Atq`UtCrLpTNc899~dVB6@5RVDp38km_<;a=f*WqwOUEAwsMh{$fQ3;k@cl`-tX$ z$P*gLQr7#ktWPU((8MhU5MFmCa}WrnuenEE&J@ zwV)ku8n|)1+e_(=EY6PX(odV0?{=c1-cL5pkt%&7Aq)^j>kW8lVnVrcdE;;7>=#{u e{#KYXN*o-Y@B$cwSAq61#E%@83F0u5c>@z4r?4de diff --git a/dlc-sled-storage-provider/test_files/AcceptedSubChannel b/dlc-sled-storage-provider/test_files/AcceptedSubChannel index a7c755bfe30cf50e96e4ec2be0b01e112994a9f6..8d603c3aeb6cb6a6b0d87a38230004fd542c2f76 100644 GIT binary patch literal 3343 zcmeYlvsJjX`Fi&L2Mb$g`p!I>Iw|kd72o$KW_-?irL^L4Ak)ofN~@&oF}|j^vPrvW~a~HHF{$*{qYgmBR8Ixu1QHQTPd=fUm@{Ge`<+^ z-nyIYK~gMkLi?t5tOZJ}eZKci1oz$*HZfIEyQU|J9lh{kC0N3@vF`!*m5r&V!+rim z^v&P(aqdgij&z`oq}CH@TpM1`;%4tSVCyn%ll_xN$G|%3X1*2t=diiJX2te@|0O+^ zNXfIbgC!FA|K~6NyRN`#OM+r`)o1G@xO2rsNBze`=i{IFr?qDM>$`viG)ScR#G$9@y zuO~3ddI&t4 zRBI<2`Jzah5o``~#ruNqlP8K#))CvCd+lLp!Y)zgK94tv+gC;uWPOw0$fQs#zGB01 zUm5>0*`*rS+`q;{gLi|>uzI_wfv`h zE-bOFnV=`C@p9n_r>QQA%xA9m-1w>}{Hryhu(;dsa#7$hrQ^7vU}HGvEkX z=Q>wYfsuLs#p&_;L*DN%cenf&{4aES^^ZvmTptYiyzhUBi;xp_0EIjdFf!lOUHM0; zpFjWjbiuA7v0$D$pX?_pFDFI4RQY5+ar(pg%scaH8)iMbX=5vW=Gzl-gN4O6|C#bH zytmiP*LYQq-^_i?nvd142~4kU^^vJeID0fRt!JU_{k5+m@_(Mxb8FxCUxSf}zgt%* z^Ro0|NpaTYC+fLB{$E@)_452=+v1qx{8RqzTFu;GbYASP_j|8S-|uHvayRwuI9Tf= z@Fyg7 z-0CpC5>+y#!|}?@lKsxMxBLqk82*C+BXiDnw`fm}{~H8F+oO6f?Ra)NUv5oC<0Z}2 ze^hLoYU*w=RVqt`S$~%e=1w=)Oe}j-S-iRY{gdRDZ!=zH?>7<@tYl(fU|>`w;2@wH z2w?hU$_QpDF(^zG_RWkrxrs5}z|p>X5yS2uvRmYzrgjjm6l~-J zMdq65ivhR0Pw_BTwLA~7%V&9P8spaJ<8mbDn`g;B{;dbrq$HQE6ghG8bmx&2(}WF5 zLDzO(+?fBL@lHs`TA+k^d8|?Gp603ryLT+rdb~YhVvxWQu*AOuNi($N)t)o#I3&hV zw7Z#aS$1ScI#7qU^8SJeX`PIXVKd%#FrSL6%~ehW>*(N!3)u4a>)~kC_QU2I)=J*v z6MP1i&=7Dbo{(>s9-nudp;&BV0>iSf=#GqZMdqW^eSa1gC~SP?`88itGK273cGa*OjH&DeA}n8SD$pPf&ShidJu%GrL_SLMM$ zp#TgyMtek1%YRn9(YWn7w}jslql1dL33QoMS< zZrr=v$121*=Jyot&y43%f4o?g))}y-Nbw=lc4nK0y!>p9OBVY7a#rY>-`>%wbF5;o zux7*gYy0Qe^vBL)%9OR)m(R0`<5rQ`$zwk*W!*aRG$tc_QC?LR=Uu1Y-+36BZR&z| zt#7~9zb#Jw;Uu<$9pa0vxO$JpY~3dj^p2%t#d4+-!M2Y-9lGtc<+xJ*s@vhL%Ee*k z`?_;#^7Hp3-`&o2fw|&l_OyvlZ-0&DHF;ucXL+TpEnD|QdG6Od$)!6&zqlBIJp&3N z_aH@PA=`;dD)VdpP5LOUTq*n7Y*KU5{*$5i1E#Q?nY}MKOk9yUVCxn&)eB#$v~`oB ze(>a^c+aZQ_%&6@Rjt7^`OR&YdqL|M2%19B5U`u@5s*xTPX$D7R2}v(8V#4xZ~=wD UXgU~82czi#dm0!G7sBBJ087Ke_W%F@ diff --git a/dlc-sled-storage-provider/test_files/Closed b/dlc-sled-storage-provider/test_files/Closed index 5a8e38c0cadc5ce3857e56969539f504b0b34fb7..da91d06d156e51203bed5450dc0b5b7f6edbf097 100644 GIT binary patch delta 1094 zcmV-M1iAaw2-OIG0RiqW!NT%3^^&o<_Et|vAnn%zG3@5C*JR81v(Ja=mIMF_VK?tf zld4c;=&v}sHmcx~BGuT3W$`Af#MMsEf3mizA$If)c>2tO@=E| zzJB6cVqYb@F{?o65WO;^B%%%Dj<+})_4epJ&YV{{ODU;;z|HNIm%VMUr!_(tuR7)8 zds+{l5B9v(xR@J5^0dH*tL^@XN#uY}m4yt-PsM{Jv3&y#X`}CrU%RR;n#nTI1njm+ z%f_<{meM7u5L*T)UH|jX)R-c7@+k?~w{MNPkB46WnlwtleKDJOycae$d#D)r_;)`; zoqoC)X@e+#izITV7?OfCEesk+$!zRA{Q0F}l^O>6hvXzDTv+y}br}U)X2VPOd12Bz zn=?F>pclB#ZR~vFp%@swnlz#(&vTg)N?n=U2gbbAecP@%8R; zyTLHG%Jg|bYSsEM0Prp#zj<2`sK^Hw(ar_jc?!#K%$&l3=fj#EVZSJf&&K8`+0NW4 z4vQ8|ej8ktsP?RqJlUJj_OK9mR{i2sywSVW7LqLLBlz&v4W>{|zWU2cpV4yCj-sIdjJXnF##|EFaa?EFaacXpGD)2vMdm$ll*i5 z;n9vE8Fj%a^U5qG$M|pquSJA3YF713|62PIZL$$Z0jD1bTdX>jE~_*KiB@D8;TclPm-w M2mk;8005`hz~e0s$^ZZW delta 1094 zcmV-M1iAaw2-OIG0Rblb7s(#pZb zkJao5_j&6_yr4Vp1UzK=b2H%GQ1O0-;sPq-vL&}iBciy^S690A(kmmRfn3**|#yMS-CVDE2lw>!gwXiGL( zEoRnlPJ>9d+Aq=w<%%OV0*p8C{fQ(LHREJ4Qrmn*)jOiems#QIiwQFERq#-u+*w-$ zg9tq?)7v~~Cp+8s?z=Q$S3!5vd)Qg z5^Iw0{)oQk672d_>Bu7X)#|c`lpz?Ou$Mu@k*V9OP!%#v`T7zlu*~vT!V{AZchXyx z9^cb{cQq@-)bx>o%HQ&Vyx1oLk_$7pDE+&-F&I1ph(3DS?fC2F_#On(^We-)2V8gE zf0*o8CnO*;Vq+X+cfwuC(Nm0R1R$xhFqRdyy;$y77TkcPsOHywg3tNFaa?EF##|EF##|EF#$0FF#$3C0Wbmp000010ol<< z(niP-B~113fyY6Fj_2~IJk;-f(LQ8?hZ^B9!~g&Q0092~|Nj9%BLxNk0000M02GOT zF4YGb9W9{PxPeZdz}{FgwgBS<07x)J0wDnBuR!kwosoHK&es8*RknGoQJl1@PT)zt zV!OrTGKVLDY)QfMx_1VFgzF%xulytPzF({(B<1Z2?CxL;@gEiOjP3 znbqql5;GE+c{`gP@_NTg5@AdD0j-^X9`USg0w5D}1iOUf9>;I~Z@TRjAI=ZANs;o0 zMaghN>QXvA$fn3Yq=bu*6PpI%cM5|z`1sNN*SKnPhj^feCb~%J00~i14 zlHzoU-vIie&Sm?N0nOhey_5*Fj^Ww27-Fe^5>l?O3%_6|F)Gz%-1f9DS9?A*K311a zSbam18T&avhQjo#h2Y%Z;{nUh;$D)9{wkE}0{&&H*7*WemSEmgIlPm-w M2><{8|NmGgKzqj#ApigX diff --git a/dlc-sled-storage-provider/test_files/Confirmed b/dlc-sled-storage-provider/test_files/Confirmed index 88f2f3f429590b28741a67cf14b7b9e38901b5c8..949add465c82e6285e603e7b5048d707c8f27224 100644 GIT binary patch literal 5983 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 literal 10069 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 Hp^?NVG8|dD6FN44%bu$AH0Z z%VGwrwgOasWmLTCLxp2P7pdtSFa#h5wHx-%D;NHEbvAZI*$>SEU{#tofa2zpm5n*JADyLpmVwc$cUtEUCHnRvwzZ~q_uqsZX}Kb9^k}i(^~$vt z!i&yl^goegfC5G)-R5?m+({YVo0hn6_fB}QsV%1a(uVHow_8@#AJ&-h$&)#9*=4^z zqdet#y*_)h%9|F2Tz?o-9aA7Fmvnb!@M|U^X1iPF%U*A4H(iu#`}vgrPCLKLpB|rT z`o6|l?GCElaj`@x`Qux+%~2^wrJjAx9;rfiz~O(HaWR?g(=v%@R>8OmNh-MMyIYp;dTtWbc$(QH>jB@^9o^SfP5;SWcb}W7J#TiZ zN>2F$+3eL{nQW>$!QNroufX6Qq{!qk@u$)!?|Y?cUj!t>K1_V^)~3@hyVj*Br22Zj z*bVLPip*+R3olLQXI;PVhqbEG?!=j9Qyri4?5*nmvt;UlpmhucO(AFqER1o| zj5uXbB}Ubuh6ju{8ZNMqK&3~+1vNZiywPxhg#;=+8ZM~e0ppE^3oIm1>ERYG0Jy{5 AF8}}l literal 2424 zcmd;I+LPb#+Hvp9bl-hSn^e9BFi-t9>(0}Cj}Ch~ChxeB!_=m^k0H0_&gUQPacOrq zy_8g~zs2nQwmVbNAT{Di{m0V`4FAD^f%(q-f(PGp3LTRTZqL{^D|L@`(AHnYlR6*& z`JrGKzszS50~9ba8^m^_ltXujY9g z<>mKS-bxAe-Cj45_ee@$Sc$W&$xAKG#XF8&XEKSNdv0e^y6g4rS}q-VUrk;cigc$} zPB#8}$|yoR;S4t;^MV!f@oc`05=qO8HY$5xRQKBFC#tn(w~dkC@kc^_ee;<;Sj}%2 zXpFZ_`q@^i1L(X8S08=>I&njt|E$L4e91wrp^FeHWk;L5d zKLwjMfBGrl6QI`m{Jo#EtPH0?MSQ=7Asl*&7~x<)$Gi<})-oT)94Y)}Q~n z6TFX{Sf#<0_kfYvPu}wIgoS7CZk_b~Rzk+5$M?#scJRKvw&KD=BiHoVfS8fz`*u7R2dkIBC;fEieawyv_*Sr}JfG8kWNF5t2KLpTe_NFl zChgwI{c7{|S=SA&gT2GFUxC3rNRc`8{BxnL%{%$+qq3}&lH7jWI91dz%j*u;u|KMc zt6z!>C^9e7{oAI`Ej&BJs$13g!tuAu=UZ5Eoaf%DZptQRl31-1w2pzG8iLlq!Wbvb zh*JhtVpJV!c))n0;Q|W@RC+XAP{RYp8x0p&NTAZA;er|-Fy3gmz(N9*9&X_R0FjN% A{r~^~ literal 2424 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 Qm<$6m@ZUOa9Um6|2Sv4j`v3p{ literal 10960 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 P1uzKkzZGwtM~nXhZ+H{o diff --git a/dlc-sled-storage-provider/test_files/Signed b/dlc-sled-storage-provider/test_files/Signed index 04e9b3c49e967ffc75004e295b708ae25a169758..49bf73f61c5711fb1a25ff9800825529023c2b53 100644 GIT binary patch literal 5873 zcmb_gcRbbq_rLdjGrBg{p4Z3@Sy5!~5s7Q>5Hd58SynK2VYkndOb<%ao8QNi7cfV;AM^6mDV7m4 zd(gi=;&lH7-0uSS=#5zNLMjiKI-ikY;x*3MUgjghGK+hiAItFkErawro{0oj
^ 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 QXUg_gd7uvF^JTz)0n08%Gynhq diff --git a/dlc-sled-storage-provider/test_files/SignedChannelEstablished b/dlc-sled-storage-provider/test_files/SignedChannelEstablished index dcaa01f1bd7bef3cbd9dbdb9920a57d3eee75eee..1e46dcae44aaf30689412ae5207a56b8aa9f6e13 100644 GIT binary patch literal 3663 zcmey!Tv=I@JN2D__r0w%JME}gB_B-J@Gyy5oJ|z>EL_m#3|zGf7lqr7(_1JNm()d{A_he zl@!^TF0;35gnhVV@hCCm$$DK5kbK_R>GmRz+tzAvs82e4JpMJe! z6#p^hR`AxwVAi#t!>2x7z4RQ<;e*mC=T)ty$5lTSS33*!EQrcxV3^={U24Upv_l{+ z_iHf4z_9$r0p@pMPak+}ED~MpS9z-HMrcbh$Df~JpC)&`u3`9SR^lYXH0>zk&!^5S zC;3=*?h$|S`2rJD;tXDSU?49Ky_P<8P6o4Fchu3%0^P5bgk7}NBz`{>RXe+{Y-;Df z)1`|<4u9C|#>9N-j_Xgy?Z1MHk8w0{pGh^8y(M!*=tE+on)c~~`jax4KTdA3GEiN3 z=3pAL@mrxwn%BD?U4L}?T-%ad&a;;`wJu>gyX(QF3=6%-3%+f>zkRK5orlZE<%}7; z3f4?)!se18*`UDM@=;yxM}5J{#A^Ln&-vW;ZcFxdwi5_=rhY7}3nV}NVQ`O5g>0V7 zs~I~_0#CcU*KzU^@3pWJ9-8wto1IFW+sg-UKb#I+4HivVM)x zId1>yC;5VyzL=hvHzls^d1vt4<0jU=~A3krjdLnROS(BMOBV)+Xe-D+KlDQj8-t~rDk6e5A`!Wvx3Rb7*GuM4u zw6j-jvA>t>tkNo(Amcrk8~5ChS@|OQs>-Ien-hwrr#<)K# zl`TOEnHiotbAxQdQ4lzk_JJLaBWX9N!u@rh&p!0C73<6+Vi}$cuEzJYZ6FHrc+q+p#} zz$ZOr72m0CC!?0nSLl%IReKU5CvAFT(g|7D72WR`6`7A+uRj-`yy1Jr&y=fwuM}mj zDOntUe@QEc?Us@q`uA*CL9D8Ok+lDl<4l&m!iS3uw@KYO&D_1I_E^M=A94qGwc32X z#H3&##y;csBZ0}U?y=g*&ftZ(swYaHU9>kV{wGAc5)^KjkU7pZyWx`tL{ z@BSsClD(nP5@shPoUU%zV^Lx_3lww=?m>zKOIk&O#YfOO24?NTBND&#YNll{omef? zGN0c+>X+6oJHF^0l}1OK*XsTNTZfNe#3zX)H>wUPFp!v|;er$xNX*f2K?)2c=4iMe b1qKpxG+dAZ1Bp2rE=Yla#2lvK!pHysNcH;z literal 3663 zcmXqh_W7QQ$q}9h(KDy>Nf!p%tq{5Yw_}2rd2zr&;gfIgFiEt8H-0lb(l0Ze^O3EVD^Fd`dlfxq}O_jzl#fr}}LGsfx_#RGDYb#SKslBi!?9QG)4*v7~9(T`t|L$uj zqwV)A1@d2W_wCrfZNinh$=Xg~Kg6|b>-=rR6g8(W?$?BR7DQz;F!)Sr4(jCZTLt2B zzdi(_7#Nn{IKa%Qu&gMne}Qk>`TP)8zK>t$3H>f^WV_>LqWmIYx!5{=)xFf%`K0rrv3BF-+w~sg5y=Do7ra$#=V!=X2yK7K$_1Y-Eu){fmUAP z+igO-!~|zMtz%YV$vENh)!MM9HnDzs*}QMM+kdZA%~p+am?cu2(edCGQ;v=PIeX8j zOU)8oHBT+Ptt*v(e$+iQ>FK2SBCclNXUCXl{;TBHzpVOOrut)`!i)olrY>!0yt8g` z+R5*)^q1ZG!EX6QQP=MUbgPzx{PRh%wgbtxdU!irW||{W zr+n?~@2QUZ4T7)wZ(AI5kzd2j%*gqzqrG9D=W&jP*POCx&lmWvjcPG_xmC2rDe-cz zmb4=zyuc(dF#R32XPHh+{siK~0+Th?tASyPnCqF8M3b(b)D!bBF3w@)5o6sPA186y zF1C1Ib%#M1)0_$Sev#PuusMj9SSmVY>55+kvnM9XaJS z;>(T*ES>jv{>#;zONHaoFFp9Zt6-(Od*RFSpcvuMJDIJB~#M&x$xY)##X)7)TMrzyJULGZv|^vVk~C3<@t~S{4X2X}+0w z#Nd3g#oIZ4_Boypcb=(Yx?pKqy!mwddhY!RAf*g!tjZ1y3_#sL05*w{iKyHLQcqM$ z0x4v6DD4Bg3`g2!c<#&%atu%!TRG973isEiQklR9TrQ zd-C}_{<`DAQ)QuhU)p+}75{7!GhsCe-N+m<3(Mn&cT<-CCRkF+Nm1({sG_Wc86 zf~?rd(l`%`&>Ekqldj8!K&;AVS``&vaOW^Ju3u zE9LLc^g?%jzT%wUpBi#F_uc}94TF1-BEiBzkziRIw2px({NUU7!Ou2mU#UEkU9l^- zPjaEs{f(|OUsN(Zz9PRfVLI44d;}vtNhG;Zbx47M#2gJ5q`*L8j)n_TU?4F^!v!fY ZkeH+4f)p4?%+YW`3JfIXFbx+*1^`)S=kWjl diff --git a/dlc-sled-storage-provider/test_files/SignedChannelSettled b/dlc-sled-storage-provider/test_files/SignedChannelSettled index 092c6099dd33948eca4ad73e178426232c676986..c7bb33be85f5dc5c7cd692fb81de324d9fe21929 100644 GIT binary patch literal 3681 zcmZ>9+S|DRgrdMl4uEU{APHhUuHVzBZEn7 zwQSAx2CH~3gm|53|2OgO#Wdw9?OZKq;;&gWze)b*IselVYq7kpPgT>>3`|3!zcWMix)5o z?BSls#Az;PySFyyb;{lU3`hU{JQ?zP;k)DyVxe={7CJbm1RrJ)V-Qh@eRR+!dbfRp z5~p0rp+L90Tb?=BB}9D4;MutmB>z%rq3Uyk@M=>fr;k$~pSV}3l*!r=<8}U{Xu%4` z8%9ERowQ3CKcqLznXAdOx<1^eyR1#=P}TJE-5-uE_JMg8NM|!J#N}_=|Fv(*dypvi z>q8L9HE8Ge=ua?Rk4k!o2S+v zt!~Qnn)`$9^dF8hUH_UiY!iMK)OcufJ(H2skpT5r*9m%R>~Dm{gpbV%vo7HN@GDpG zkFwuC$<23}%^4$3-Cvt2@z|FAgjN6ZCxKr%{a%M$ykMDRVlV!9_a0`MEjw97mz;gP zIPi?mu8*l}UwAPU_KEr1Orz$6 z#n&pHO%wG21=h_UHzcZD+O6f8AI>>h8GZe~-2Cd?MK&9n)*qP(l4rHp8hWUHYiBn1 zai`@LDmfP)hurAS`4Q}0a%2nR;pNA6tDG<8oMqxHxz~)PV$OlUhbxlH_dnXawWa*k)pxeHp6tAu4mRgM5HRU_$FhQWN(>5f%`}7!%oBU~rhBz`?0CW3DrP8sq2Q`$ zZD5DlU0w0Nxi^=C^#PUs()y6Q(qW(10jE1HT4`5aOqug(9?yZheD1$EZ@u{b+zX_R zd3ltJ?R4Q+FEvvOVkCALZ}OfkebZ0a?#zA9vi4b0O?FIGnakX#tURf=jbHxWt(GMx z0y&&)V*ToHFb9< za(AwYpC2@}{LM1f1si4@E?vy{XHm3e=b57Hp4bFcD`!$|0{OcYd; zp0-ElK+uc&>n5AlCTRWiU8j6MVnbp%W8|GXk>KzKW)Tn#id_Z2 zA4mhCv;|IBAcf2gs;q1v!-0`}CUDBB6@4*}zSx}LSfKV$Lqy$EZhdLe{<`Z~b=w{a zA9E<}11rUm@EM*vbAwC*N@FWznA{Cqm=tp48bUbOdA!o~zbdHh+v3*7$5vUmO>5qB zy$8Ww=3O343Tl%}*qxe9qy3X+NX!>o;@1^DJG8};FKR(TSM+C-&PhPu<;fc6Gzj_jwu+tD?f|{}`M}P_D3?{nN7V+qSC1e5OKM z55BWkW8V3S@ux16LjCHNZT2T4jl!-!HO;#}ZAR1@V;9+H^HU@kpZ>dfc;gmEMdpP) z=jEcS{%5{C_aS`x-o7gyX%~gBABrz?G#6dnuJ{O;F@eB6NReQ1p-8ZF4qC^+RDQ0C zLErd6%ckmrRW?R~$r6(ydIJ}>Wo&0JES{J>m+$@V*xq};zIr|qbx_rB`l831rTlK* zx1+25crMrOR7iLYO)vk!&ca8)N)~)-Kyst%u!qoSxQvF&X#T*S21dhWG+baIFv818 GMg{=d?+SDqE{jc<4zV!^zxl^WJn7h=ibo<%l zj|@=2^l|r-7dtByym@(*%7V+AtlHMC%-g0a@u)cLg2Ih=Jtj=tx@zaovv0INn4t5j zbb8}lGvOTz9?v-I_tcTvTjhvJBw&U*7I0y=t=Ck-aKGJe>S(^CX@>ohQN|#vn3x!jDrz)6Gpb zznQIIRdky*cFzpy%&xE>H+b(^WsKv4+HZSBVFPjd`vp_nVfk9St(*85CMH4`x z+^-LTC0S#=8kjT81e!V~1V(nAb6$CaZz*e*^MsyaKQWH4xpQkc__lI0Z}9j3{UhS{ zzHdiYc(F!ZTp=J^dnNn+moIbkk2E!_^<8GFO#Hli*!0cnS0M7t&^Z5Q$ik{QhdSq6 z+OR4!3mje$0vMQE9%#+&i;H#u3BUrAVfl>%tV}=w#+)b1=6>^4Q@T5~)I@r|Zr4jc zN$phTV5iUP-|o>Vn&c1C#qb{pm~_2kSwS2n28H=MSAO_A$-GN2y}EDO^uDP|hxoee zxI#PD>Xj_jH2M20_vUhtN|4fuXupDbA#QZ1n#Zj zAZ1MN)-c_;X_GId^`M(`e!#Yg2mbZPtdY)s+avD8c;I*bdZvoPH6r^QI(L{R*4z(q zn6i(1X>z0byoD{EYQcvu+G$8~ET~(xd-a0UZ#zTYv96uQ(Np{B>S49XGekNziFdBP zBUO9oGV8aGZ$DouZrwOjYcKm`=NZS$pIXsM(zbC>7S?A1@TuUJ1? z^UQhfQ(b4fd7HN_uTEDEZDacQVEZ-4=aC#PYuXP8XH7a{x@GIWna5VWIkkUTj9q7% zA=AoPZ7sz@af}?Dl~wLfB`r7&M4$MqowLHBr+z)lyO4Lwin_Y*rHEe-nf&cwFu(Zo zV^d9YKc`K$o0!>gt}|#tk%(+6N8gS!P10f~5C6Pbx9oHB(>31;#bemrl9ps}e^d+O z&wg5bc}`=%ni=u`3to%)@t0UCzY(%XFS_>8^U#G{P&k5O6-0ru3MA70{{uxb6H&Pj z6v;%TERaIx231xzkX^tiw$1&QdD?(OyO&?H&M9nxWZbF36IxE6KdrS&+%K|NT$ACs zGdD;nj%4pp+6OiXD2=U%0qJCL4^kwUtrQ8CVnOQ|n2vi*i(C2o@a+{ne2jbkcQ0pA zHGZyr@yHAN4LfpNMfl#m`gTx9@O1R<7yBi`U))GNapdq}73(SQ(gJpM73Y76Iu8xk z|6pg~BN*{XBFT-aLkbKe=4iMe1qKpxG+dAZ1Bp2rE=Yla#2gJ5q`*L8j)n_TU?4Gv JShz4U008IRuMq$M diff --git a/dlc-sled-storage-provider/test_files/SignedSubChannel b/dlc-sled-storage-provider/test_files/SignedSubChannel index 2c707e78bac5cc3aad9950b788c00e53fae059d5..827da6ef7294c47dff6cd699e71b93d6793b0157 100644 GIT binary patch literal 3385 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