From 293b26dab1bf1c9d7e7c934651c39d2cd2778f80 Mon Sep 17 00:00:00 2001 From: Tibo-lg Date: Wed, 19 Apr 2023 17:03:48 +0900 Subject: [PATCH] Fix channel reestablishment issue --- Cargo.toml | 6 +- dlc-manager/src/channel/party_points.rs | 2 +- dlc-manager/src/channel_updater.rs | 36 +- dlc-manager/src/contract/mod.rs | 2 +- dlc-manager/src/lib.rs | 5 + dlc-manager/src/sub_channel_manager.rs | 674 +++++++++++++++++- dlc-manager/src/subchannel/mod.rs | 76 +- dlc-manager/src/subchannel/ser.rs | 11 +- .../tests/ln_dlc_channel_execution_tests.rs | 287 ++++++-- dlc-manager/tests/test_utils.rs | 11 +- dlc-messages/src/sub_channel.rs | 42 +- dlc-sled-storage-provider/Cargo.toml | 8 +- dlc-sled-storage-provider/src/lib.rs | 55 +- dlc-sled-storage-provider/test_files/Accepted | Bin 3430 -> 6332 bytes .../test_files/AcceptedChannel | Bin 908 -> 908 bytes .../test_files/AcceptedSubChannel | Bin 3109 -> 3125 bytes dlc-sled-storage-provider/test_files/Closed | Bin 1108 -> 1108 bytes .../test_files/Confirmed | Bin 5983 -> 11993 bytes .../test_files/Confirmed1 | Bin 5983 -> 10069 bytes dlc-sled-storage-provider/test_files/Offered | Bin 1107 -> 7146 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 7023 -> 10959 bytes dlc-sled-storage-provider/test_files/Signed | Bin 5873 -> 9366 bytes dlc-sled-storage-provider/test_files/Signed1 | Bin 5873 -> 9959 bytes .../test_files/SignedChannelEstablished | Bin 3663 -> 3663 bytes .../test_files/SignedChannelSettled | Bin 3465 -> 3465 bytes .../test_files/SignedSubChannel | Bin 3335 -> 3351 bytes .../test_files/sub_channel_actions.json | 202 ++++++ dlc/src/channel/sub_channel.rs | 2 +- dlc/src/lib.rs | 4 +- mocks/src/memory_storage_provider.rs | 26 +- 33 files changed, 1356 insertions(+), 93 deletions(-) create mode 100644 dlc-sled-storage-provider/test_files/sub_channel_actions.json diff --git a/Cargo.toml b/Cargo.toml index 8d19f570..519f085f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,6 @@ members = [ ] [patch.crates-io] -lightning = { git = "https://github.com/p2pderivatives/rust-lightning/", rev = "dff302382d04700f23d79fb3275c1798d775fef6" } -lightning-net-tokio = { git = "https://github.com/p2pderivatives/rust-lightning/", rev = "dff302382d04700f23d79fb3275c1798d775fef6" } -lightning-persister = { git = "https://github.com/p2pderivatives/rust-lightning/", rev = "dff302382d04700f23d79fb3275c1798d775fef6" } +lightning = { git = "https://github.com/p2pderivatives/rust-lightning/", rev = "6df5761" } +lightning-net-tokio = { git = "https://github.com/p2pderivatives/rust-lightning/", rev = "6df5761" } +lightning-persister = { git = "https://github.com/p2pderivatives/rust-lightning/", rev = "6df5761" } diff --git a/dlc-manager/src/channel/party_points.rs b/dlc-manager/src/channel/party_points.rs index ac508e6d..0b11262c 100644 --- a/dlc-manager/src/channel/party_points.rs +++ b/dlc-manager/src/channel/party_points.rs @@ -9,7 +9,7 @@ use secp256k1_zkp::{All, PublicKey, Secp256k1, Signing, Verification}; /// Base points used by a party of a DLC channel to derive public and private /// values necessary for state update throughout the lifetime of the channel. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), diff --git a/dlc-manager/src/channel_updater.rs b/dlc-manager/src/channel_updater.rs index 8d74f6b3..c10b9862 100644 --- a/dlc-manager/src/channel_updater.rs +++ b/dlc-manager/src/channel_updater.rs @@ -183,6 +183,7 @@ where wallet, blockchain, None, + None, ) } @@ -193,6 +194,12 @@ pub(crate) fn accept_channel_offer_internal( wallet: &W, blockchain: &B, sub_channel_info: Option, + params: Option<( + PartyParams, + Vec, + PartyBasePoints, + PublicKey, + )>, ) -> Result<(AcceptedChannel, AcceptedContract, AcceptChannel), Error> where W::Target: Wallet, @@ -200,16 +207,23 @@ where { assert_eq!(offered_channel.offered_contract_id, offered_contract.id); - let (accept_params, _, funding_inputs) = crate::utils::get_party_params( - secp, - offered_contract.total_collateral - offered_contract.offer_params.collateral, - offered_contract.fee_rate_per_vb, - wallet, - blockchain, - sub_channel_info.is_none(), - )?; - - let per_update_seed = wallet.get_new_secret_key()?; + let (accept_params, funding_inputs, accept_points, per_update_seed) = + if let Some((params, funding_inputs_info, accept_points, per_update_seed_pk)) = params { + let per_update_seed = wallet.get_secret_key_for_pubkey(&per_update_seed_pk)?; + (params, funding_inputs_info, accept_points, per_update_seed) + } else { + let (params, _, funding_input_infos) = crate::utils::get_party_params( + secp, + offered_contract.total_collateral - offered_contract.offer_params.collateral, + offered_contract.fee_rate_per_vb, + wallet, + blockchain, + sub_channel_info.is_none(), + )?; + let accept_points = crate::utils::get_party_base_points(secp, wallet)?; + let per_update_seed = wallet.get_new_secret_key()?; + (params, funding_input_infos, accept_points, per_update_seed) + }; let first_per_update_point = PublicKey::from_secret_key( secp, @@ -220,8 +234,6 @@ where .expect("to have generated a valid secret key."), ); - let accept_points = crate::utils::get_party_base_points(secp, wallet)?; - let accept_revoke_params = accept_points.get_revokable_params( secp, &offered_channel.party_points.revocation_basepoint, diff --git a/dlc-manager/src/contract/mod.rs b/dlc-manager/src/contract/mod.rs index ad54d6cd..7bd620cc 100644 --- a/dlc-manager/src/contract/mod.rs +++ b/dlc-manager/src/contract/mod.rs @@ -123,7 +123,7 @@ impl Contract { } /// Information about a funding input. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), diff --git a/dlc-manager/src/lib.rs b/dlc-manager/src/lib.rs index 28f2e40f..fe6c2240 100644 --- a/dlc-manager/src/lib.rs +++ b/dlc-manager/src/lib.rs @@ -54,6 +54,7 @@ use lightning::ln::msgs::DecodeError; use lightning::util::ser::{Readable, Writeable, Writer}; use secp256k1_zkp::XOnlyPublicKey; use secp256k1_zkp::{PublicKey, SecretKey}; +use sub_channel_manager::Action; use subchannel::SubChannel; /// Type alias for a contract id. @@ -177,6 +178,10 @@ pub trait Storage { fn get_sub_channels(&self) -> Result, Error>; /// Returns all the [`SubChannel`] in the `Offered` state. fn get_offered_sub_channels(&self) -> Result, Error>; + /// Save sub channel actions + fn save_sub_channel_actions(&self, actions: &[Action]) -> Result<(), Error>; + /// Get saved sub channel actions + fn get_sub_channel_actions(&self) -> Result, Error>; } /// Oracle trait provides access to oracle information. diff --git a/dlc-manager/src/sub_channel_manager.rs b/dlc-manager/src/sub_channel_manager.rs index 08dca7ff..a833f1b8 100644 --- a/dlc-manager/src/sub_channel_manager.rs +++ b/dlc-manager/src/sub_channel_manager.rs @@ -1,10 +1,13 @@ //! # Module containing a manager enabling set up and update of DLC channels embedded within //! Lightning Network channels. -use std::ops::Deref; +use std::{ops::Deref, sync::Mutex}; use bitcoin::{OutPoint, PackedLockTime, Script, Sequence}; -use dlc::channel::{get_tx_adaptor_signature, sub_channel::LN_GLUE_TX_WEIGHT}; +use dlc::{ + channel::{get_tx_adaptor_signature, sub_channel::LN_GLUE_TX_WEIGHT}, + PartyParams, +}; use dlc_messages::{ channel::{AcceptChannel, OfferChannel}, oracle_msgs::OracleAnnouncement, @@ -23,9 +26,12 @@ use lightning::{ CounterpartyCommitmentSecrets, }, channelmanager::ChannelDetails, - msgs::RevokeAndACK, + msgs::{ChannelMessageHandler, DecodeError, RevokeAndACK}, }, + util::events::MessageSendEventsProvider, + util::ser::{Readable, Writeable, Writer}, }; +use log::{error, trace}; use secp256k1_zkp::{ecdsa::Signature, PublicKey, SecretKey}; use crate::{ @@ -37,13 +43,14 @@ use crate::{ channel_updater::{ self, FundingInfo, SubChannelSignInfo, SubChannelSignVerifyInfo, SubChannelVerifyInfo, }, - contract::{contract_input::ContractInput, Contract}, + contract::{contract_input::ContractInput, Contract, FundingInputInfo}, error::Error, - manager::{get_channel_in_state, get_contract_in_state, Manager}, + manager::{get_channel_in_state, get_contract_in_state, Manager, CET_NSEQUENCE}, subchannel::{ self, generate_temporary_channel_id, AcceptedSubChannel, CloseAcceptedSubChannel, CloseConfirmedSubChannel, CloseOfferedSubChannel, ClosingSubChannel, LNChannelManager, - OfferedSubChannel, SignedSubChannel, SubChannel, SubChannelState, + LnRollBackInfo, OfferedSubChannel, ReestablishFlag, SignedSubChannel, SubChannel, + SubChannelState, }, Blockchain, ChannelId, ContractId, Oracle, Signer, Storage, Time, Wallet, }; @@ -86,6 +93,47 @@ macro_rules! get_sub_channel_in_state { pub(crate) use get_sub_channel_in_state; +/// An [`Action`] to be taken by the sub-channel manager after a disconnection with a peer occured. +#[derive(Clone, PartialEq, Debug)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] +pub enum Action { + /// The included [`SubChannelOffer`] message should be re-submitted to the peer with included + /// public key. + ResendOffer((SubChannelOffer, PublicKey)), + /// The sub channel with specified `channel_id` should be re-accepted. + ReAccept { + /// The id of the sub channel. + channel_id: ChannelId, + /// The parameters originally generated by the local node. + party_params: PartyParams, + /// The inputs originally selected by the local node. + funding_inputs_info: Vec, + /// The base points originally generated by the local node. + accept_points: PartyBasePoints, + /// The public key of the per update seed originally generated by the local node. + per_update_seed_pk: PublicKey, + }, + /// The given sub channel should be moved to the signed state as the revoke and ack must have + /// been given by the peer during the reestablishment of the LN channel. + ForceSign(ChannelId), +} + +impl_dlc_writeable_enum!(Action, + (0, ResendOffer), + (2, ForceSign); + (1, ReAccept, { + (channel_id, writeable), + (party_params, { cb_writeable, dlc_messages::ser_impls::party_params::write, dlc_messages::ser_impls::party_params::read }), + (funding_inputs_info, vec), + (accept_points, writeable), + (per_update_seed_pk, writeable) + });; +); + /// Structure enabling management of DLC channels embedded within Lightning Network channels. pub struct SubChannelManager< W: Deref, @@ -107,6 +155,7 @@ pub struct SubChannelManager< { ln_channel_manager: M, dlc_channel_manager: D, + actions: Mutex>, } impl< @@ -129,11 +178,13 @@ where F::Target: FeeEstimator, { /// Creates a new [`SubChannelManager`]. - pub fn new(ln_channel_manager: M, dlc_channel_manager: D) -> Self { - Self { + pub fn new(ln_channel_manager: M, dlc_channel_manager: D) -> Result { + let actions = dlc_channel_manager.get_store().get_sub_channel_actions()?; + Ok(Self { ln_channel_manager, dlc_channel_manager, - } + actions: Mutex::new(actions), + }) } /// Get a reference to the [`Manager`] held by the instance. @@ -344,6 +395,19 @@ where pub fn accept_sub_channel( &self, channel_id: &ChannelId, + ) -> Result<(PublicKey, SubChannelAccept), Error> { + self.accept_sub_channel_internal(channel_id, None) + } + + fn accept_sub_channel_internal( + &self, + channel_id: &ChannelId, + params: Option<( + PartyParams, + Vec, + PartyBasePoints, + PublicKey, + )>, ) -> Result<(PublicKey, SubChannelAccept), Error> { let (mut offered_sub_channel, state) = get_sub_channel_in_state!( self.dlc_channel_manager, @@ -525,6 +589,7 @@ where self.dlc_channel_manager.get_wallet(), self.dlc_channel_manager.get_blockchain(), Some(sub_channel_info), + params, )?; let ln_glue_signature = dlc::util::get_raw_sig_for_tx_input( @@ -579,6 +644,10 @@ where accept_split_adaptor_signature: split_tx_adaptor_signature, split_tx, ln_glue_transaction: ln_glue_tx, + ln_rollback: LnRollBackInfo { + channel_value_satoshis: channel_details.channel_value_satoshis, + value_to_self_msat: channel_details.value_to_self_msat, + }, }; offered_sub_channel.state = SubChannelState::Accepted(accepted_sub_channel); @@ -980,6 +1049,54 @@ where Ok(Reject { channel_id }) } + /// Marks the channel as finalized when a disconnection happens while the peer is waiting for + /// the RAA from the remote node (the RAA must have been given by the remote node during the + /// reestablishment protocol). + fn mark_channel_finalized(&self, channel_id: ChannelId) -> Result<(), Error> { + let (mut signed_sub_channel, state) = get_sub_channel_in_state!( + self.dlc_channel_manager, + channel_id, + Confirmed, + None:: + )?; + + let dlc_channel_id = + signed_sub_channel + .get_dlc_channel_id(0) + .ok_or(Error::InvalidState( + "Could not get dlc channel id".to_string(), + ))?; + let channel = get_channel_in_state!( + self.dlc_channel_manager, + &dlc_channel_id, + Signed, + None:: + )?; + let contract = get_contract_in_state!( + self.dlc_channel_manager, + &channel + .get_contract_id() + .ok_or_else(|| Error::InvalidState( + "No contract id in on_sub_channel_finalize".to_string() + ))?, + Signed, + None:: + )?; + + self.dlc_channel_manager.get_store().upsert_channel( + Channel::Signed(channel), + Some(Contract::Confirmed(contract)), + )?; + + signed_sub_channel.state = SubChannelState::Signed(state); + + self.dlc_channel_manager + .get_store() + .upsert_sub_channel(&signed_sub_channel)?; + + Ok(()) + } + fn on_subchannel_offer( &self, sub_channel_offer: &SubChannelOffer, @@ -1128,6 +1245,11 @@ where )) })?; + let ln_rollback = LnRollBackInfo { + channel_value_satoshis: channel_details.channel_value_satoshis, + value_to_self_msat: channel_details.value_to_self_msat, + }; + let offer_revoke_params = offered_sub_channel.own_base_points.get_revokable_params( self.dlc_channel_manager.get_secp(), &sub_channel_accept.revocation_basepoint, @@ -1367,6 +1489,7 @@ where split_tx, counter_glue_signature: sub_channel_accept.ln_glue_signature, ln_glue_transaction: ln_glue_tx, + ln_rollback, }; offered_sub_channel.counter_base_points = Some(accept_points); @@ -1520,6 +1643,7 @@ where split_tx: state.split_tx.clone(), counter_glue_signature: sub_channel_confirm.ln_glue_signature, ln_glue_transaction: state.ln_glue_transaction, + ln_rollback: state.ln_rollback, }; let msg = SubChannelFinalize { @@ -1944,6 +2068,91 @@ where Ok(()) } + /// Process pending actions, potentially generating messages that should be sent to the + /// adequate peer. + pub fn process_actions(&self) -> Vec<(SubChannelMessage, PublicKey)> { + let mut actions = self.actions.lock().unwrap(); + let mut retain = Vec::new(); + let mut msgs = Vec::new(); + + for action in actions.drain(..) { + match action { + Action::ResendOffer((o, p)) => msgs.push((SubChannelMessage::Offer(o), p)), + Action::ReAccept { + channel_id, + party_params, + funding_inputs_info, + accept_points, + per_update_seed_pk, + } => { + if let Some(details) = self.ln_channel_manager.get_channel_details(&channel_id) + { + if details.is_usable { + if let Ok((p, msg)) = self.accept_sub_channel_internal( + &channel_id, + Some(( + party_params.clone(), + funding_inputs_info.clone(), + accept_points.clone(), + per_update_seed_pk, + )), + ) { + msgs.push((SubChannelMessage::Accept(msg), p)); + } else { + error!( + "Could not re-accept sub channel {:?}, keeping the action", + channel_id + ); + retain.push(Action::ReAccept { + channel_id, + party_params, + funding_inputs_info, + accept_points, + per_update_seed_pk, + }); + } + } else { + trace!( + "Channel {:?} not yet useable, keeping the re-accept action", + channel_id + ); + retain.push(Action::ReAccept { + channel_id, + party_params, + funding_inputs_info, + accept_points, + per_update_seed_pk, + }); + } + } else { + error!( + "Could not get channel details for id: {:?}, giving up re-accepting", + channel_id + ); + }; + } + Action::ForceSign(id) => { + if let Some(details) = self.ln_channel_manager.get_channel_details(&id) { + if details.is_usable { + if let Err(e) = self.mark_channel_finalized(id) { + error!("Unexpected error {} making channel {:?} as finalized, keeping the action to retry.", e, id); + retain.push(Action::ForceSign(id)); + } + } else { + retain.push(Action::ForceSign(id)); + } + } else { + error!("Could not get channel details for id: {:?}", id); + }; + } + }; + } + + actions.append(&mut retain); + + msgs + } + /// Updtates the view of the blockchain processing transactions and acting upon them if /// necessary. pub fn check_for_watched_tx(&self) -> Result<(), Error> { @@ -2195,6 +2404,205 @@ where Ok(()) } + /// Called when a reestablish message is received by the local node. + fn on_channel_reestablish( + &self, + peer_id: &PublicKey, + channel_id: [u8; 32], + peer_state: Option, + ) -> Result<(), Error> { + if let Some(mut channel) = self + .dlc_channel_manager + .get_store() + .get_sub_channel(channel_id)? + { + if &channel.counter_party != peer_id { + return Err(Error::InvalidParameters(format!( + "Channel {:?} is not established with peer {}", + channel_id, peer_id + ))); + } + let updated_state = match &channel.state { + SubChannelState::Offered(o) if channel.is_offer => { + if peer_state.is_none() { + let dlc_channel_id = channel.get_dlc_channel_id(0).ok_or_else(|| { + Error::InvalidState("Could not get dlc channel id".to_string()) + })?; + let dlc_channel = get_channel_in_state!( + self.dlc_channel_manager, + &dlc_channel_id, + Offered, + None:: + )?; + let contract = get_contract_in_state!( + self.dlc_channel_manager, + &dlc_channel.offered_contract_id, + Offered, + None:: + )?; + let offer_msg = SubChannelOffer { + channel_id, + revocation_basepoint: channel.own_base_points.revocation_basepoint, + publish_basepoint: channel.own_base_points.publish_basepoint, + own_basepoint: channel.own_base_points.own_basepoint, + next_per_split_point: o.per_split_point, + contract_info: (&contract).into(), + channel_revocation_basepoint: dlc_channel + .party_points + .revocation_basepoint, + channel_publish_basepoint: dlc_channel.party_points.publish_basepoint, + channel_own_basepoint: dlc_channel.party_points.own_basepoint, + channel_first_per_update_point: dlc_channel.per_update_point, + payout_spk: contract.offer_params.payout_script_pubkey.clone(), + payout_serial_id: contract.offer_params.payout_serial_id, + offer_collateral: contract.offer_params.collateral, + cet_locktime: contract.cet_locktime, + refund_locktime: contract.refund_locktime, + cet_nsequence: crate::manager::CET_NSEQUENCE, + fee_rate_per_vbyte: contract.fee_rate_per_vb, + }; + self.actions + .lock() + .unwrap() + .push(Action::ResendOffer((offer_msg, contract.counter_party))); + } + None + } + SubChannelState::Accepted(a) => { + self.ln_channel_manager.reset_fund_outpoint( + &channel.channel_id, + a.ln_rollback.channel_value_satoshis, + a.ln_rollback.value_to_self_msat, + )?; + let dlc_channel_id = channel.get_dlc_channel_id(0).ok_or_else(|| { + Error::InvalidState("Could not get dlc channel id".to_string()) + })?; + let dlc_channel = get_channel_in_state!( + self.dlc_channel_manager, + &dlc_channel_id, + Accepted, + None:: + )?; + let contract = get_contract_in_state!( + self.dlc_channel_manager, + &dlc_channel.accepted_contract_id, + Accepted, + None:: + )?; + + let offered_channel = OfferedChannel { + offered_contract_id: contract.offered_contract.id, + temporary_channel_id: dlc_channel.temporary_channel_id, + party_points: dlc_channel.offer_base_points, + per_update_point: dlc_channel.offer_per_update_point, + offer_per_update_seed: None, + is_offer_party: false, + counter_party: dlc_channel.counter_party, + cet_nsequence: CET_NSEQUENCE, + }; + self.dlc_channel_manager + .get_store() + .upsert_sub_channel(&channel)?; + self.dlc_channel_manager.get_store().upsert_channel( + Channel::Offered(offered_channel), + Some(Contract::Offered(contract.offered_contract)), + )?; + let party_params = contract.accept_params.clone(); + let funding_inputs_info = contract.funding_inputs; + let accept_points = dlc_channel.accept_base_points.clone(); + let per_update_seed_pk = dlc_channel.accept_per_update_seed; + self.actions.lock().unwrap().push(Action::ReAccept { + channel_id, + party_params, + funding_inputs_info, + accept_points, + per_update_seed_pk, + }); + Some(SubChannelState::Offered(OfferedSubChannel { + per_split_point: a.offer_per_split_point, + })) + } + SubChannelState::Confirmed(a) => { + if let Some(counter_state) = peer_state { + if counter_state == ReestablishFlag::Accepted as u8 { + self.ln_channel_manager.reset_fund_outpoint( + &channel.channel_id, + a.ln_rollback.channel_value_satoshis, + a.ln_rollback.value_to_self_msat, + )?; + let dlc_channel_id = + channel.get_dlc_channel_id(0).ok_or_else(|| { + Error::InvalidState("Could not get dlc channel id".to_string()) + })?; + let dlc_channel = get_channel_in_state!( + self.dlc_channel_manager, + &dlc_channel_id, + Signed, + None:: + )?; + let contract = get_contract_in_state!( + self.dlc_channel_manager, + &dlc_channel + .get_contract_id() + .expect("Signed contract should have a contract id"), + Signed, + None:: + )?; + + let offered_channel = OfferedChannel { + offered_contract_id: contract.accepted_contract.offered_contract.id, + temporary_channel_id: dlc_channel.temporary_channel_id, + party_points: dlc_channel.own_points, + per_update_point: dlc_channel.own_per_update_point, + offer_per_update_seed: channel.per_split_seed, + is_offer_party: false, + counter_party: dlc_channel.counter_party, + // TODO(tibo): use value from original offer + cet_nsequence: CET_NSEQUENCE, + }; + self.dlc_channel_manager.get_store().upsert_channel( + Channel::Offered(offered_channel), + Some(Contract::Offered( + contract.accepted_contract.offered_contract, + )), + )?; + Some(SubChannelState::Offered(OfferedSubChannel { + per_split_point: a.own_per_split_point, + })) + } else { + self.actions + .lock() + .unwrap() + .push(Action::ForceSign(channel_id)); + None + } + } else { + //TODO(tibo): log + close channel? + None + } + } + _ => None, + }; + + if let Some(state) = updated_state { + channel.state = state; + self.dlc_channel_manager + .get_store() + .upsert_sub_channel(&channel)?; + } + } + + if let Err(e) = self + .dlc_channel_manager + .get_store() + .save_sub_channel_actions(&self.actions.lock().unwrap()) + { + error!("Could not save sub channel manager actions: {}", e); + } + + Ok(()) + } + fn on_sub_channel_reject(&self, reject: &Reject, peer_id: &PublicKey) -> Result<(), Error> { let sub_channel = self .dlc_channel_manager @@ -2238,6 +2646,254 @@ where } } +impl< + W: Deref, + M: Deref, + S: Deref, + B: Deref, + O: Deref, + T: Deref, + F: Deref, + D: Deref>, + > ChannelMessageHandler for SubChannelManager +where + W::Target: Wallet, + M::Target: LNChannelManager, + S::Target: Storage, + B::Target: Blockchain, + O::Target: Oracle, + T::Target: Time, + F::Target: FeeEstimator, +{ + fn handle_open_channel( + &self, + their_node_id: &PublicKey, + their_features: lightning::ln::features::InitFeatures, + msg: &lightning::ln::msgs::OpenChannel, + ) { + self.ln_channel_manager + .handle_open_channel(their_node_id, their_features, msg) + } + + fn handle_accept_channel( + &self, + their_node_id: &PublicKey, + their_features: lightning::ln::features::InitFeatures, + msg: &lightning::ln::msgs::AcceptChannel, + ) { + self.ln_channel_manager + .handle_accept_channel(their_node_id, their_features, msg) + } + + fn handle_funding_created( + &self, + their_node_id: &PublicKey, + msg: &lightning::ln::msgs::FundingCreated, + ) { + self.ln_channel_manager + .handle_funding_created(their_node_id, msg) + } + + fn handle_funding_signed( + &self, + their_node_id: &PublicKey, + msg: &lightning::ln::msgs::FundingSigned, + ) { + self.ln_channel_manager + .handle_funding_signed(their_node_id, msg) + } + + fn handle_channel_ready( + &self, + their_node_id: &PublicKey, + msg: &lightning::ln::msgs::ChannelReady, + ) { + self.ln_channel_manager + .handle_channel_ready(their_node_id, msg) + } + + fn handle_shutdown( + &self, + their_node_id: &PublicKey, + their_features: &lightning::ln::features::InitFeatures, + msg: &lightning::ln::msgs::Shutdown, + ) { + self.ln_channel_manager + .handle_shutdown(their_node_id, their_features, msg) + } + + fn handle_closing_signed( + &self, + their_node_id: &PublicKey, + msg: &lightning::ln::msgs::ClosingSigned, + ) { + self.ln_channel_manager + .handle_closing_signed(their_node_id, msg) + } + + fn handle_update_add_htlc( + &self, + their_node_id: &PublicKey, + msg: &lightning::ln::msgs::UpdateAddHTLC, + ) { + self.ln_channel_manager + .handle_update_add_htlc(their_node_id, msg) + } + + fn handle_update_fulfill_htlc( + &self, + their_node_id: &PublicKey, + msg: &lightning::ln::msgs::UpdateFulfillHTLC, + ) { + self.ln_channel_manager + .handle_update_fulfill_htlc(their_node_id, msg) + } + + fn handle_update_fail_htlc( + &self, + their_node_id: &PublicKey, + msg: &lightning::ln::msgs::UpdateFailHTLC, + ) { + self.ln_channel_manager + .handle_update_fail_htlc(their_node_id, msg) + } + + fn handle_update_fail_malformed_htlc( + &self, + their_node_id: &PublicKey, + msg: &lightning::ln::msgs::UpdateFailMalformedHTLC, + ) { + self.ln_channel_manager + .handle_update_fail_malformed_htlc(their_node_id, msg) + } + + fn handle_commitment_signed( + &self, + their_node_id: &PublicKey, + msg: &lightning::ln::msgs::CommitmentSigned, + ) { + self.ln_channel_manager + .handle_commitment_signed(their_node_id, msg) + } + + fn handle_revoke_and_ack(&self, their_node_id: &PublicKey, msg: &RevokeAndACK) { + self.ln_channel_manager + .handle_revoke_and_ack(their_node_id, msg) + } + + fn handle_update_fee(&self, their_node_id: &PublicKey, msg: &lightning::ln::msgs::UpdateFee) { + self.ln_channel_manager + .handle_update_fee(their_node_id, msg) + } + + fn handle_announcement_signatures( + &self, + their_node_id: &PublicKey, + msg: &lightning::ln::msgs::AnnouncementSignatures, + ) { + self.ln_channel_manager + .handle_announcement_signatures(their_node_id, msg) + } + + fn peer_disconnected(&self, their_node_id: &PublicKey, no_connection_possible: bool) { + self.ln_channel_manager + .peer_disconnected(their_node_id, no_connection_possible) + } + + fn peer_connected( + &self, + their_node_id: &PublicKey, + msg: &lightning::ln::msgs::Init, + ) -> Result<(), ()> { + self.ln_channel_manager.peer_connected(their_node_id, msg) + } + + fn handle_channel_reestablish( + &self, + their_node_id: &PublicKey, + msg: &lightning::ln::msgs::ChannelReestablish, + ) { + if let Err(e) = + self.on_channel_reestablish(their_node_id, msg.channel_id, msg.sub_channel_state) + { + error!( + "Unexpected error {} processing reestablish for channel {:?}.", + e, msg.channel_id + ); + } + self.ln_channel_manager + .handle_channel_reestablish(their_node_id, msg) + } + + fn handle_channel_update( + &self, + their_node_id: &PublicKey, + msg: &lightning::ln::msgs::ChannelUpdate, + ) { + self.ln_channel_manager + .handle_channel_update(their_node_id, msg) + } + + fn handle_error(&self, their_node_id: &PublicKey, msg: &lightning::ln::msgs::ErrorMessage) { + self.ln_channel_manager.handle_error(their_node_id, msg) + } + + fn provided_node_features(&self) -> lightning::ln::features::NodeFeatures { + self.ln_channel_manager.provided_node_features() + } + + fn provided_init_features( + &self, + their_node_id: &PublicKey, + ) -> lightning::ln::features::InitFeatures { + self.ln_channel_manager + .provided_init_features(their_node_id) + } +} + +impl< + W: Deref, + M: Deref, + S: Deref, + B: Deref, + O: Deref, + T: Deref, + F: Deref, + D: Deref>, + > MessageSendEventsProvider for SubChannelManager +where + W::Target: Wallet, + M::Target: LNChannelManager, + S::Target: Storage, + B::Target: Blockchain, + O::Target: Oracle, + T::Target: Time, + F::Target: FeeEstimator, +{ + fn get_and_clear_pending_msg_events(&self) -> Vec { + let mut msg_events = self.ln_channel_manager.get_and_clear_pending_msg_events(); + + for event in msg_events.iter_mut() { + if let lightning::util::events::MessageSendEvent::SendChannelReestablish { + msg, .. + } = event + { + match self.dlc_channel_manager.get_store().get_sub_channel(msg.channel_id) { + Err(e) => error!("Unexpected error {} trying to retrieve sub channel {:?} during sending of reestablish.", e, msg.channel_id), + Ok(None) => trace!("No sub channel with id {:?} to reestablish", msg.channel_id), + Ok(Some(c)) => { + let flag = c.get_reestablish_flag(); + trace!("Inserting reestablish flag {:?} in reestablish for channel {:?}", flag, msg.channel_id); + msg.sub_channel_state = flag; + } + } + } + } + + msg_events + } +} + fn validate_and_get_ln_values_per_party( channel_details: &ChannelDetails, own_collateral: u64, diff --git a/dlc-manager/src/subchannel/mod.rs b/dlc-manager/src/subchannel/mod.rs index 66821002..d3d4fe3b 100644 --- a/dlc-manager/src/subchannel/mod.rs +++ b/dlc-manager/src/subchannel/mod.rs @@ -13,7 +13,7 @@ use lightning::{ ln::{ chan_utils::CounterpartyCommitmentSecrets, channelmanager::{ChannelDetails, ChannelManager}, - msgs::{CommitmentSigned, RevokeAndACK}, + msgs::{ChannelMessageHandler, CommitmentSigned, RevokeAndACK}, }, util::logger::Logger, }; @@ -23,7 +23,7 @@ use crate::{channel::party_points::PartyBasePoints, error::Error, ChannelId, Con pub mod ser; -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq)] /// Contains information about a DLC channel embedded within a Lightning Network Channel. pub struct SubChannel { /// The index for the channel. @@ -97,9 +97,21 @@ impl SubChannel { _ => None, } } + + /// Return the flag associated with the state of the sub channel, or `None` if the state is not + /// relevant for reestablishment. + pub(crate) fn get_reestablish_flag(&self) -> Option { + match self.state { + SubChannelState::Offered(_) => Some(ReestablishFlag::Offered as u8), + SubChannelState::Accepted(_) => Some(ReestablishFlag::Accepted as u8), + SubChannelState::Confirmed(_) => Some(ReestablishFlag::Confirmed as u8), + SubChannelState::Signed(_) => Some(ReestablishFlag::Signed as u8), + _ => None, + } + } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] /// Represents the state of a [`SubChannel`]. pub enum SubChannelState { /// The sub channel was offered (sent or received). @@ -130,14 +142,24 @@ pub enum SubChannelState { Rejected, } -#[derive(Debug, Clone)] +/// Flags associated with states that must be communicated to the remote node during +/// reestablishment. +#[repr(u8)] +pub(crate) enum ReestablishFlag { + Offered = 1, + Accepted = 2, + Confirmed = 3, + Signed = 4, +} + +#[derive(Debug, Clone, PartialEq, Eq)] /// Information about an offer to set up a sub channel. pub struct OfferedSubChannel { /// The current per update point of the local party. pub per_split_point: PublicKey, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] /// Information about a sub channel that is in the accepted state. pub struct AcceptedSubChannel { /// The current per split point of the offer party. @@ -150,6 +172,17 @@ pub struct AcceptedSubChannel { pub split_tx: SplitTx, /// Glue transaction that bridges the split transaction to the Lightning sub channel. pub ln_glue_transaction: Transaction, + /// Information used to facilitate the rollback of a channel split. + pub ln_rollback: LnRollBackInfo, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +/// Holds information used to facilitate the rollback of a channel split. +pub struct LnRollBackInfo { + /// The original value of the channel. + pub channel_value_satoshis: u64, + /// The original `value_to_self_msat` of the LN channel. + pub value_to_self_msat: u64, } impl AcceptedSubChannel { @@ -162,7 +195,7 @@ impl AcceptedSubChannel { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] /// Information about a sub channel whose transactions have been signed. pub struct SignedSubChannel { /// The current per split point of the local party. @@ -179,6 +212,8 @@ pub struct SignedSubChannel { pub ln_glue_transaction: Transaction, /// Signature of the remote party for the glue transaction. pub counter_glue_signature: Signature, + /// Information used to facilitate the rollback of a channel split. + pub ln_rollback: LnRollBackInfo, } impl SignedSubChannel { @@ -191,7 +226,7 @@ impl SignedSubChannel { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] /// Information about an offer to collaboratively close a sub channel. pub struct CloseOfferedSubChannel { /// The signed sub channel for which the offer was made. @@ -202,7 +237,7 @@ pub struct CloseOfferedSubChannel { pub accept_balance: u64, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] /// Information about an offer to collaboratively close a sub channel that was accepted. pub struct CloseAcceptedSubChannel { /// The signed sub channel for which the offer was made. @@ -211,7 +246,7 @@ pub struct CloseAcceptedSubChannel { pub own_balance: u64, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] /// Information about an offer to collaboratively close a sub channel that was confirmed. pub struct CloseConfirmedSubChannel { /// The signed sub channel for which the offer was made. @@ -221,14 +256,14 @@ pub struct CloseConfirmedSubChannel { } /// Information about a sub channel that is in the process of being unilateraly closed. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct ClosingSubChannel { /// The signed sub channel that is being closed. pub signed_sub_channel: SignedSubChannel, } /// Provides the ability to access and update Lightning Network channels. -pub trait LNChannelManager { +pub trait LNChannelManager: ChannelMessageHandler { /// Returns the details of the channel with given `channel_id` if found. fn get_channel_details(&self, channel_id: &ChannelId) -> Option; /// Updates the funding output for the channel and returns the [`CommitmentSigned`] message @@ -270,6 +305,15 @@ pub trait LNChannelManager { channel_id: &[u8; 32], counter_party_node_id: &PublicKey, ) -> Result<(), Error>; + + /// Reset the funding outpoint to the original one and setting the channel values to the given + /// ones. + fn reset_fund_outpoint( + &self, + channel_id: &ChannelId, + channel_value_satoshis: u64, + value_to_self_msat: u64, + ) -> Result<(), Error>; } impl LNChannelManager @@ -353,6 +397,16 @@ where self.force_close_broadcasting_latest_txn(channel_id, counter_party_node_id) .map_err(|e| Error::InvalidParameters(format!("{e:?}"))) } + + fn reset_fund_outpoint( + &self, + channel_id: &ChannelId, + channel_value_satoshis: u64, + value_to_self_msat: u64, + ) -> Result<(), Error> { + self.reset_fund_output(channel_id, channel_value_satoshis, value_to_self_msat) + .map_err(|e| Error::InvalidParameters(format!("{e:?}"))) + } } /// Generate a temporary channel id for a DLC channel based on the LN channel id, the update index of the diff --git a/dlc-manager/src/subchannel/ser.rs b/dlc-manager/src/subchannel/ser.rs index 7bdaa878..631411e9 100644 --- a/dlc-manager/src/subchannel/ser.rs +++ b/dlc-manager/src/subchannel/ser.rs @@ -6,7 +6,8 @@ use lightning::util::ser::{Readable, Writeable, Writer}; use super::{ AcceptedSubChannel, CloseAcceptedSubChannel, CloseConfirmedSubChannel, CloseOfferedSubChannel, - ClosingSubChannel, OfferedSubChannel, SignedSubChannel, SubChannel, SubChannelState, + ClosingSubChannel, LnRollBackInfo, OfferedSubChannel, SignedSubChannel, SubChannel, + SubChannelState, }; impl_dlc_writeable!(SubChannel, { @@ -47,12 +48,15 @@ impl_dlc_writeable!(OfferedSubChannel, { (per_split_point, writeable) }); impl_dlc_writeable_external!(SplitTx, split_tx, {(transaction, writeable), (output_script, writeable)}); +impl_dlc_writeable!(LnRollBackInfo, { (channel_value_satoshis, writeable), (value_to_self_msat, writeable) }); + impl_dlc_writeable!(AcceptedSubChannel, { (offer_per_split_point, writeable), (accept_per_split_point, writeable), (accept_split_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}), (split_tx, {cb_writeable, split_tx::write, split_tx::read}), - (ln_glue_transaction, writeable) + (ln_glue_transaction, writeable), + (ln_rollback, writeable) }); impl_dlc_writeable!(SignedSubChannel, { @@ -62,7 +66,8 @@ impl_dlc_writeable!(SignedSubChannel, { (counter_split_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}), (split_tx, {cb_writeable, split_tx::write, split_tx::read}), (ln_glue_transaction, writeable), - (counter_glue_signature, writeable) + (counter_glue_signature, writeable), + (ln_rollback, writeable) }); impl_dlc_writeable!(CloseOfferedSubChannel, { diff --git a/dlc-manager/tests/ln_dlc_channel_execution_tests.rs b/dlc-manager/tests/ln_dlc_channel_execution_tests.rs index 1e0a7b9f..d0c48fb0 100644 --- a/dlc-manager/tests/ln_dlc_channel_execution_tests.rs +++ b/dlc-manager/tests/ln_dlc_channel_execution_tests.rs @@ -21,7 +21,10 @@ use dlc_manager::{ channel::Channel, contract::Contract, manager::Manager, sub_channel_manager::SubChannelManager, subchannel::SubChannelState, Blockchain, ChannelId, Oracle, Signer, Storage, Utxo, Wallet, }; -use dlc_messages::{ChannelMessage, Message, SubChannelMessage}; +use dlc_messages::{ + sub_channel::{SubChannelAccept, SubChannelOffer}, + ChannelMessage, Message, SubChannelMessage, +}; use electrs_blockchain_provider::{ElectrsBlockchainProvider, OutSpendResp}; use lightning::{ chain::{ @@ -77,7 +80,7 @@ pub(crate) type ChannelManager = lightning::ln::channelmanager::ChannelManager< pub(crate) type PeerManager = lightning::ln::peer_handler::PeerManager< MockSocketDescriptor, - Arc, + Arc, Arc, Arc, Arc, @@ -112,7 +115,7 @@ struct LnDlcParty { logger: Arc, network_graph: NetworkGraph>, chain_height: u64, - sub_channel_manager: DlcSubChannelManager, + sub_channel_manager: Arc, dlc_manager: Arc, blockchain: Arc, mock_blockchain: Arc>>, @@ -138,6 +141,7 @@ enum TestPath { SplitCheat, OfferRejected, CloseRejected, + Reconnect, } impl LnDlcParty { @@ -413,21 +417,6 @@ fn create_ln_node( let mut ephemeral_bytes = [0; 32]; thread_rng().fill_bytes(&mut ephemeral_bytes); - let lightning_msg_handler = MessageHandler { - chan_handler: channel_manager.clone(), - route_handler: Arc::new(IgnoringMessageHandler {}), - onion_message_handler: Arc::new(IgnoringMessageHandler {}), - }; - let peer_manager = PeerManager::new( - lightning_msg_handler, - consistent_keys_manager - .get_node_secret(Recipient::Node) - .unwrap(), - current_time.try_into().unwrap(), - &ephemeral_bytes, - logger.clone(), - Arc::new(IgnoringMessageHandler {}), - ); let network_graph = NetworkGraph::new(blockhash, logger.clone()); @@ -458,7 +447,24 @@ fn create_ln_node( .unwrap(), ); - let sub_channel_manager = SubChannelManager::new(channel_manager.clone(), dlc_manager.clone()); + let sub_channel_manager = + Arc::new(SubChannelManager::new(channel_manager.clone(), dlc_manager.clone()).unwrap()); + + let lightning_msg_handler = MessageHandler { + chan_handler: sub_channel_manager.clone(), + route_handler: Arc::new(IgnoringMessageHandler {}), + onion_message_handler: Arc::new(IgnoringMessageHandler {}), + }; + let peer_manager = PeerManager::new( + lightning_msg_handler, + consistent_keys_manager + .get_node_secret(Recipient::Node) + .unwrap(), + current_time.try_into().unwrap(), + &ephemeral_bytes, + logger.clone(), + Arc::new(IgnoringMessageHandler {}), + ); LnDlcParty { peer_manager: Arc::new(peer_manager), @@ -537,6 +543,12 @@ fn ln_dlc_rejected_close() { ln_dlc_test(TestPath::CloseRejected); } +#[test] +#[ignore] +fn ln_dlc_reconnect() { + ln_dlc_test(TestPath::Reconnect); +} + // #[derive(Debug)] // pub struct TestParams { // pub oracles: Vec, @@ -544,6 +556,7 @@ fn ln_dlc_rejected_close() { // } fn ln_dlc_test(test_path: TestPath) { + env_logger::init(); let (_, _, sink_rpc) = init_clients(); let test_params = get_enum_test_params_custom_collateral(1, 1, None, 60000, 40000); @@ -609,7 +622,7 @@ fn ln_dlc_test(test_path: TestPath) { .peer_manager .new_outbound_connection( bob_node.channel_manager.get_our_node_id(), - alice_descriptor, + alice_descriptor.clone(), None, ) .unwrap(); @@ -756,7 +769,15 @@ fn ln_dlc_test(test_path: TestPath) { return; } - offer_sub_channel(&test_params, &alice_node, &bob_node, &channel_id); + offer_sub_channel( + &test_path, + &test_params, + &alice_node, + &bob_node, + &channel_id, + alice_descriptor.clone(), + bob_descriptor.clone(), + ); if let TestPath::CheatPreSplitCommit = test_path { let revoked_tx = pre_split_commit_tx.unwrap(); @@ -955,7 +976,15 @@ fn ln_dlc_test(test_path: TestPath) { assert_sub_channel_state!(alice_node.sub_channel_manager, &channel_id; OffChainClosed); assert_sub_channel_state!(bob_node.sub_channel_manager, &channel_id; OffChainClosed); - offer_sub_channel(&test_params, &alice_node, &bob_node, &channel_id); + offer_sub_channel( + &test_path, + &test_params, + &alice_node, + &bob_node, + &channel_id, + alice_descriptor, + bob_descriptor, + ); if let TestPath::SplitCheat = test_path { alice_node.dlc_manager.get_store().rollback(); @@ -1285,9 +1314,8 @@ fn ln_cheated_check( fn offer_common( test_params: &TestParams, alice_node: &LnDlcParty, - bob_node: &LnDlcParty, channel_id: &ChannelId, -) { +) -> SubChannelOffer { let oracle_announcements = test_params .oracles .iter() @@ -1312,6 +1340,40 @@ fn offer_common( assert_sub_channel_state!(alice_node.sub_channel_manager, channel_id, Offered); + offer +} + +fn offer_sub_channel( + test_path: &TestPath, + test_params: &TestParams, + alice_node: &LnDlcParty, + bob_node: &LnDlcParty, + channel_id: &ChannelId, + alice_descriptor: MockSocketDescriptor, + bob_descriptor: MockSocketDescriptor, +) { + let offer = offer_common(test_params, alice_node, channel_id); + + if let TestPath::Reconnect = test_path { + reconnect( + alice_node, + bob_node, + alice_descriptor.clone(), + bob_descriptor.clone(), + ); + // Alice should resend the offer message to bob as he has not received it yet. + let mut msgs = alice_node.sub_channel_manager.process_actions(); + assert_eq!(1, msgs.len()); + if let (SubChannelMessage::Offer(o), p) = msgs.pop().unwrap() { + assert_eq!(p, bob_node.channel_manager.get_our_node_id()); + assert_eq!(o, offer); + } else { + panic!("Expected an offer message"); + } + + assert_eq!(0, bob_node.sub_channel_manager.process_actions().len()); + } + bob_node .sub_channel_manager .on_sub_channel_message( @@ -1320,26 +1382,51 @@ fn offer_common( ) .unwrap(); - assert_sub_channel_state!(bob_node.sub_channel_manager, channel_id, Offered); -} + if let TestPath::Reconnect = test_path { + reconnect( + alice_node, + bob_node, + alice_descriptor.clone(), + bob_descriptor.clone(), + ); + assert_eq!(0, alice_node.sub_channel_manager.process_actions().len()); + assert_eq!(0, bob_node.sub_channel_manager.process_actions().len()); + } -fn offer_sub_channel( - test_params: &TestParams, - alice_node: &LnDlcParty, - bob_node: &LnDlcParty, - channel_id: &ChannelId, -) { - offer_common(test_params, alice_node, bob_node, channel_id); + assert_sub_channel_state!(bob_node.sub_channel_manager, channel_id, Offered); - let (_, accept) = bob_node + let (_, mut accept) = bob_node .sub_channel_manager .accept_sub_channel(channel_id) .unwrap(); - assert_sub_channel_state!(bob_node.sub_channel_manager, channel_id, Accepted); + if let TestPath::Reconnect = test_path { + reconnect( + alice_node, + bob_node, + alice_descriptor.clone(), + bob_descriptor.clone(), + ); - bob_node.process_events(); - let confirm = alice_node + assert_sub_channel_state!(alice_node.sub_channel_manager, channel_id, Offered); + assert_sub_channel_state!(bob_node.sub_channel_manager, channel_id, Offered); + + // Bob should re-send the accept message + let mut msgs = bob_node.sub_channel_manager.process_actions(); + assert_eq!(1, msgs.len()); + if let (SubChannelMessage::Accept(a), p) = msgs.pop().unwrap() { + assert_eq!(p, alice_node.channel_manager.get_our_node_id()); + assert_eq_accept(&a, &accept); + accept = a; + } else { + panic!("Expected an accept message"); + } + + assert_eq!(0, alice_node.sub_channel_manager.process_actions().len()); + } + + assert_sub_channel_state!(bob_node.sub_channel_manager, channel_id, Accepted); + let mut confirm = alice_node .sub_channel_manager .on_sub_channel_message( &SubChannelMessage::Accept(accept), @@ -1348,7 +1435,35 @@ fn offer_sub_channel( .unwrap() .unwrap(); - assert_sub_channel_state!(alice_node.sub_channel_manager, channel_id, Confirmed); + if let TestPath::Reconnect = test_path { + reconnect( + alice_node, + bob_node, + alice_descriptor.clone(), + bob_descriptor.clone(), + ); + + assert_sub_channel_state!(alice_node.sub_channel_manager, channel_id, Offered); + assert_sub_channel_state!(bob_node.sub_channel_manager, channel_id, Offered); + + // Bob should re-send the accept message + let mut msgs = bob_node.sub_channel_manager.process_actions(); + assert_eq!(1, msgs.len()); + assert_eq!(0, alice_node.sub_channel_manager.process_actions().len()); + if let (SubChannelMessage::Accept(a), p) = msgs.pop().unwrap() { + assert_eq!(p, alice_node.channel_manager.get_our_node_id()); + confirm = alice_node + .sub_channel_manager + .on_sub_channel_message( + &SubChannelMessage::Accept(a), + &bob_node.channel_manager.get_our_node_id(), + ) + .unwrap() + .unwrap(); + } else { + panic!("Expected an accept message"); + } + } alice_node.process_events(); let finalize = bob_node @@ -1361,14 +1476,74 @@ fn offer_sub_channel( bob_node.process_events(); assert_sub_channel_state!(alice_node.sub_channel_manager, channel_id, Confirmed); + + if let TestPath::Reconnect = test_path { + reconnect( + alice_node, + bob_node, + alice_descriptor.clone(), + bob_descriptor.clone(), + ); + + // For some weird reason uncommenting this triggers a stack overflow... + // assert_sub_channel_state!(alice_node.sub_channel_manager, channel_id, Confirmed); + // assert_sub_channel_state!(bob_node.sub_channel_manager, channel_id, Signed); + + assert_eq!(0, alice_node.sub_channel_manager.process_actions().len()); + assert_eq!(0, bob_node.sub_channel_manager.process_actions().len()); + } else { + alice_node + .sub_channel_manager + .on_sub_channel_message(&finalize, &bob_node.channel_manager.get_our_node_id()) + .unwrap(); + } + + assert_sub_channel_state!(alice_node.sub_channel_manager, channel_id, Signed); + + alice_node.process_events(); +} + +fn reconnect( + alice_node: &LnDlcParty, + bob_node: &LnDlcParty, + alice_descriptor: MockSocketDescriptor, + mut bob_descriptor: MockSocketDescriptor, +) { alice_node - .sub_channel_manager - .on_sub_channel_message(&finalize, &bob_node.channel_manager.get_our_node_id()) + .peer_manager + .socket_disconnected(&alice_descriptor); + + bob_node.peer_manager.socket_disconnected(&bob_descriptor); + + let initial_send = alice_node + .peer_manager + .new_outbound_connection( + bob_node.channel_manager.get_our_node_id(), + alice_descriptor, + None, + ) .unwrap(); - assert_sub_channel_state!(alice_node.sub_channel_manager, channel_id, Signed); + bob_node + .peer_manager + .new_inbound_connection(bob_descriptor.clone(), None) + .unwrap(); + + bob_node + .peer_manager + .read_event(&mut bob_descriptor, &initial_send) + .unwrap(); + bob_node.peer_manager.process_events(); + alice_node.peer_manager.process_events(); + bob_node.peer_manager.process_events(); + bob_node.peer_manager.process_events(); + alice_node.peer_manager.process_events(); + bob_node.peer_manager.process_events(); alice_node.process_events(); + bob_node.process_events(); + alice_node.process_events(); + bob_node.process_events(); } fn reject_offer( @@ -1377,7 +1552,17 @@ fn reject_offer( bob_node: &LnDlcParty, channel_id: &ChannelId, ) { - offer_common(test_params, alice_node, bob_node, channel_id); + 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(); + + assert_sub_channel_state!(bob_node.sub_channel_manager, channel_id, Offered); let reject = bob_node .sub_channel_manager @@ -1396,3 +1581,21 @@ fn reject_offer( assert_sub_channel_state!(alice_node.sub_channel_manager, channel_id; Rejected); } + +fn assert_eq_accept(a: &SubChannelAccept, b: &SubChannelAccept) { + assert_eq_fields!( + a, + b, + channel_id, + revocation_basepoint, + publish_basepoint, + own_basepoint, + first_per_split_point, + channel_revocation_basepoint, + channel_publish_basepoint, + channel_own_basepoint, + first_per_update_point, + payout_spk, + payout_serial_id + ); +} diff --git a/dlc-manager/tests/test_utils.rs b/dlc-manager/tests/test_utils.rs index 69e05ff5..e5b2c8ae 100644 --- a/dlc-manager/tests/test_utils.rs +++ b/dlc-manager/tests/test_utils.rs @@ -47,10 +47,8 @@ pub const ROUNDING_MOD: u64 = 1; macro_rules! receive_loop { ($receive:expr, $manager:expr, $send:expr, $expect_err:expr, $sync_send:expr, $rcv_callback: expr, $msg_callback: expr) => { thread::spawn(move || loop { - let m; match $receive.recv() { Ok(Some(msg)) => { - m = format!("{:?}", msg).split_at(6).0.to_string(); let res = $manager.lock().unwrap().on_dlc_message( &msg, "0218845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166" @@ -264,6 +262,15 @@ macro_rules! assert_sub_channel_state { }}; } +#[macro_export] +macro_rules! assert_eq_fields { + ($a: expr, $b: expr, $($field: ident),*) => { + $( + assert_eq!($a.$field, $b.$field); + )* + }; +} + pub fn enum_outcomes() -> Vec { vec![ "a".to_owned(), diff --git a/dlc-messages/src/sub_channel.rs b/dlc-messages/src/sub_channel.rs index f0c872e6..ca29dfe9 100644 --- a/dlc-messages/src/sub_channel.rs +++ b/dlc-messages/src/sub_channel.rs @@ -82,7 +82,12 @@ impl_dlc_writeable!( ); /// A message to accept an offer to establish a DLC channel within an existing Lightning channel. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] pub struct SubChannelAccept { /// The id of the Lightning channel the message relates to. pub channel_id: [u8; 32], @@ -154,6 +159,11 @@ impl_dlc_writeable!( /// A message to confirm the establishment of a DLC channel within an existing Lightning channel. #[derive(Clone, Debug)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] pub struct SubChannelConfirm { /// The id of the Lightning channel the message relates to. pub channel_id: [u8; 32], @@ -193,6 +203,11 @@ impl_dlc_writeable!(SubChannelConfirm, { /// A message to finalize the establishment of a DLC channel within an existing Lightning channel. #[derive(Clone, Debug)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] pub struct SubChannelFinalize { /// The id of the Lightning channel the message relates to. pub channel_id: [u8; 32], @@ -212,6 +227,11 @@ impl_dlc_writeable!(SubChannelFinalize, { /// A message to offer the collaborative (off-chain) closing of a DLC channel embedded within a /// Lightning channel. #[derive(Clone, Debug)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] pub struct SubChannelCloseOffer { /// The id of the Lightning channel the message relates to. pub channel_id: [u8; 32], @@ -227,6 +247,11 @@ impl_dlc_writeable!(SubChannelCloseOffer, { /// A message to accept the collaborative closing of a DLC channel embedded within a Lightning /// channel. #[derive(Clone, Debug)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] pub struct SubChannelCloseAccept { /// The id of the Lightning channel the message relates to. pub channel_id: [u8; 32], @@ -246,6 +271,11 @@ impl_dlc_writeable!(SubChannelCloseAccept, { /// A message to confirm the collaborative closing of a DLC channel embedded within a Lightning /// channel. #[derive(Clone, Debug)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] pub struct SubChannelCloseConfirm { /// The id of the Lightning channel the message relates to. pub channel_id: [u8; 32], @@ -274,6 +304,11 @@ impl_dlc_writeable!(SubChannelCloseConfirm, { /// A message to finalize the collaborative closing of a DLC channel embedded within a Lightning /// channel. #[derive(Clone, Debug)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] pub struct SubChannelCloseFinalize { /// The id of the Lightning channel the message relates to. pub channel_id: [u8; 32], @@ -295,6 +330,11 @@ impl_dlc_writeable!(SubChannelCloseFinalize, { /// A message to reject an offer to collaboratively close a DLC channel embedded within a Lightning /// channel. #[derive(Clone, Debug)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] pub struct Reject { /// The id of the Lightning channel the message relates to. pub channel_id: [u8; 32], diff --git a/dlc-sled-storage-provider/Cargo.toml b/dlc-sled-storage-provider/Cargo.toml index 5f45a8ef..8d6037f7 100644 --- a/dlc-sled-storage-provider/Cargo.toml +++ b/dlc-sled-storage-provider/Cargo.toml @@ -9,12 +9,16 @@ repository = "https://github.com/p2pderivatives/rust-dlc/tree/master/dlc-sled-st version = "0.1.0" [features] -wallet = ["bitcoin", "secp256k1-zkp", "simple-wallet", "lightning"] +wallet = ["bitcoin", "secp256k1-zkp", "simple-wallet"] [dependencies] bitcoin = {version = "0.29", optional = true} dlc-manager = {path = "../dlc-manager"} -lightning = {version = "0.0.113", optional = true} +lightning = {version = "0.0.113"} secp256k1-zkp = {version = "0.7", optional = true} simple-wallet = {path = "../simple-wallet", optional = true} sled = "0.34" + +[dev-dependencies] +serde = "1.0" +serde_json = "1.0" diff --git a/dlc-sled-storage-provider/src/lib.rs b/dlc-sled-storage-provider/src/lib.rs index 6cda91dd..694a14b0 100644 --- a/dlc-sled-storage-provider/src/lib.rs +++ b/dlc-sled-storage-provider/src/lib.rs @@ -32,7 +32,6 @@ use dlc_manager::subchannel::{SubChannel, SubChannelState}; #[cfg(feature = "wallet")] use dlc_manager::Utxo; use dlc_manager::{error::Error, ContractId, Storage}; -#[cfg(feature = "wallet")] use lightning::util::ser::{Readable, Writeable}; #[cfg(feature = "wallet")] use secp256k1_zkp::{PublicKey, SecretKey}; @@ -54,6 +53,7 @@ const KEY_PAIR_TREE: u8 = 6; const SUB_CHANNEL_TREE: u8 = 7; #[cfg(feature = "wallet")] const ADDRESS_TREE: u8 = 8; +const ACTION_KEY: u8 = 1; /// Implementation of Storage interface using the sled DB backend. pub struct SledStorageProvider { @@ -467,6 +467,43 @@ impl Storage for SledStorageProvider { None, ) } + + fn save_sub_channel_actions( + &self, + actions: &[dlc_manager::sub_channel_manager::Action], + ) -> Result<(), Error> { + let mut buf = Vec::new(); + + for action in actions { + action.write(&mut buf)?; + } + + self.db + .insert([ACTION_KEY], buf) + .map_err(to_storage_error)?; + Ok(()) + } + + fn get_sub_channel_actions( + &self, + ) -> Result, Error> { + let buf = match self.db.get([ACTION_KEY]).map_err(to_storage_error)? { + Some(buf) => buf, + None => return Ok(Vec::new()), + }; + + let len = buf.len(); + + let mut res = Vec::new(); + let mut cursor = Cursor::new(buf); + + while (cursor.position() as usize) < len - 1 { + let action = Readable::read(&mut cursor).map_err(to_storage_error)?; + res.push(action); + } + + Ok(res) + } } #[cfg(feature = "wallet")] @@ -1100,4 +1137,20 @@ mod tests { assert_eq!(4, offered_sub_channels.len()); } ); + + sled_test!( + save_actions_roundtip_test, + |storage: SledStorageProvider| { + let actions: Vec<_> = + serde_json::from_str(include_str!("../test_files/sub_channel_actions.json")) + .unwrap(); + storage + .save_sub_channel_actions(&actions) + .expect("Error saving sub channel actions"); + let recovered = storage + .get_sub_channel_actions() + .expect("Error getting sub channel actions"); + assert_eq!(actions, recovered); + } + ); } diff --git a/dlc-sled-storage-provider/test_files/Accepted b/dlc-sled-storage-provider/test_files/Accepted index 69a46fdee3632d70deb8ad95a697043e240a3597..a187af9d7d8ed73b8fb385b1645ce7ee5ab89a41 100644 GIT binary patch literal 6332 zcmb_=c|4SD)c?$2FtRlmOEQ*1$i7CD>|~87%aBBqrPP${m3<9avL(C1*tcYPEGZ(9 zMr4=lTL|Nw;hD+jSI_V9zMtp*WA1Ct`F^kOT=#YEbDcTaGgCT#oo?qVS*zj!V2o!9 zrCdnqdNqU8(1`Y{X9!~e5C{U00RHLP^^losdJ_R;_z{?lTM2gq;m5dZzirn*vV*_v zp@6%VUmwKpAi@X&6k1-P>x5a-22{62#zm#cCDA*&6^+VVxQiYwe4p@HM9sdYIyhjS zNp4i1-3@h8KsUOk`I&PsC8NBt$njyihILfJ(q*;vo~ipMg`ewLl~agk<>gp0ql$+g zPqD!qqPF|Ewt)x$M8LhEkhK5ps1;<0!)eNHsi~8VbO`;8=k?;ZU@=Qlqxar{*oV;} ztJAAKUu8=@dOp<_oe0Ua87$#EJ-mpyl=Ccp5y}Js8We+Ggu@JOol0&K*5}+9(1BDV zrjk)Xq_Ub2Bbd=-AxwNdeMeo|BWC?q z-xeNy#hby77SJi3yUhPrb&PuHnX#FW$K8TLvOC!;`N*DpE;g~^K|d}(6W^=soa+JA zUf%$tpsmpXbdv#lk*2Xnf-T(5`+K_8YpTNSKFDF*&Qsh%xZV=JWoxs$vk+1v+gq-G zZ!!=xVBw#(MQ>G6Q`VxfcdI|x^pWEzfC<3Bw}bhz1M9*1e3=`j z84EB^km{pg*G4WJL1a?m{ZrVw`tP@AnOd4Mo;R#t_qVMjy}R(@XsM~YRSob+
T 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 literal 3430 zcmZ2Ib5lv!lXraEzIer3oBJJjZ|bvgI$vf%_3jvkC9$0I7#JCu7?>Gw0k8~ftXBhE z0V{~b#;WW9q?kZ#AV3xa3o!o&Q4m#(U=aiX=QF`rT#S0DoZH>4mqfoVZocz;<$=!F z;5;@~DXDFVjPk-l^)zy=I{9NyP#{CRizui}PcfdtAqKGf8Kl14MQ$c;b z2AaDnX1lpec440NxBJ*EmN%`B`a|*$FPs=AcVYA28%lo}6B)QToD*mB?%m@0E%Zr+ z$LYGN2`bG?e(##}=xw2pBG3IxS3~j^sVBcGKe?H2-nGQr32!P_*y$EYPmN>BS$+AZ zex2BSu^0EIp2&?q;1^yvHg4YJJ;hktYG&hcePWA|=JV`5lO$)X zS^3DgmH*ZB($<>!I-2{UcV9j`T~}bORjc3p>;EJc?meP)$8+P~E1S-4FWBZ#%d74n zWwczZdy2Q*<`vyXu!Q~G8~)zl5(36NM{#0Fv3_xCGB{?rSVB^ZOQ4a=$P^*YAp0}q zv(&QIuo+2bZi$7mwldE7pXs;byL{eX6~XBYVhke8`&{SyusbB|n6zActK!Ovywb{7 zxlW+?g(2RtoHKz^15rkL}WVL-0LMj&2i#adTo1@@wy7f#;#|*x*OKc ziv4O--`0K5f*^n5}jH->BmhMU~lwg+}fg9W)?9|F^`L=8%$j4%cF zz$u!M@x+WdC+c?}NOJB?PMh#s>w#+F!9@vG;^{>%x9$GFdG2+v3IG28|Ifs5=ruQp z2XwH*w{sgimi<}#S4i~iyYlv)2~C;Ie^jL8K*<~^J!jggDL&G_&T+pKKO(Y=t)q5z zMk15DfeVv@%$2ouqNPbu=jJ%o&Jz zT1l-|@o!N%{~m(f%)kJ2^nXy8FnkUPdzo1)Sqc?r1jg<9gcEKCOcE{Ojo%E9^vg`= zd}J_*t(L91-e48ag%Gb3?f)j;y_m+_=y>bf)*llC?A%{kf2{YowyG{>S+C>s>BkiK zLiIkz90U0_{9oKYj;uW)JJ{9EhlMp2gdQnjXFlTm^z~HZaFG0J&Lx~PWRl|+YR|pD z$6~|RW9{d4kAA$J9hvl{i}Az$xdjKji>}Kpe*4btgMK0BV~00ieKH>QtWd8nn6Z5d z)X5+U=#wJWBBiYNp7tOvEOr@|-#EYslYqtQmA0wLc_1A43)-L<)~qI}8WY4?@Ys}5?jXr(0m`_kpa294FKZD-sInwqnZ z-<6YdHQuv?Z?PXABvyI)*?u&2e)MI2`o(AYAeLdq!8}L z>)H87`olgx^@F{Rn>W4mRP(&P-0FqVO8&RTYv1i=RAicbFp>SZ^^{4XTc>(I(+a#N zmM_2btf0kK&nrn%jc07RCV}0Ih*d^V*kW@sGb4jEBdoxMYGPnO2*BlFLdXIzF$P9> znGM&<2v-QJ4G?OOEI{alOS8ds!s;KOBOw4t!=8~xJR(-6oTm##?Z+Plftf31STztp_2zDgW+JLI0~O0e zRcauG%(*X|VYLpnf_80L9~;PPz_I{aE&EED6)dg9pm61|ScuKkCqi72rsus^n5S%! zWv}??>=Gs9kj*ub;Wih@6gH4A7~eczJ&WUlv%2?1uE}eP=SN?2(^=fJKRoA2TJQ_* zyk$P%U<4JOj1Exaag_4wx%VfKW+GF=wWZ)90*8qQeB!~j1EsMw8pyDaLHG_hIpDC+ zK|m8+@KDdf12>j~f*M!N*x(flF0ZI)p*kmC3xEHnu7${{f=ZD@AW#Sdse#*x1P1pY OMS^WrMS|^}pmhLdyboId diff --git a/dlc-sled-storage-provider/test_files/AcceptedChannel b/dlc-sled-storage-provider/test_files/AcceptedChannel index 8e8bdcc5f27f8fb6fd842da60392de4d3db1b2a1..069a3df88bb394bd0338ddc35297ed01cbe4a438 100644 GIT binary patch delta 904 zcmV;319$w42aE@QQ1U8l<^jvEF7W_twM=?}K0Z7+mWz~7yS};2n|OON0=i-6)7O5- zS!zvNRK_6F$)O}xK04l2I9z-`(#&E8bOU+A$ZR$?clLTf{)sZ|ci3*PA{rwu&ZQ>j zXg)Z6G@Jr%+06e42+ksD?udC}aOYX0*7GV*9F%ZpA$tpdEm;*20w6g&tN8@q{_5E% z_!co@l`NMtIEzW+g*|cN$$vr=(*kmo2L}?E%epnQH0c(*KDH6gzRO|K{j%%kxVxBj zMZyAci;cz=c1YaTbPN&LK9!g}Pxnd!T$g`|_AJ2!3q`{NR?3GFgN-Te?U20`ZqmN8 zq`3*l0R)DjE}*!?;)t$h(SK7EJ2;)8Xh$;w00000{{R2~0dgb-1^@s6 z03rY&jq_)%Y~9U9FiqDFQ$Ni5chF;>i**xh*~_Vaz=xA*X|D^vU;yAD0?&+w!0ub8 z&~U^M{Q5g;1qMOLg!AYML5n6Z-X?zKgREp@cBvGT<5DrjwQDx-tJcwWkwGKpjpKlb zt#+vtw1A5@x3my6y%yTS3B0{@`KgQdh^=<16g;ZRzBh-u!@0}owqL$)E!ZX`ScqqK zsT7HSPY*D-4o;?H(G$Ss8oevL-~rl*t#+vt-zPl#w|Ap^rc%P~eUu_fj#CXw{u*)8&vEYSp3blI5J zi(BiQB06ZS8@G4QKY@A0`t;ZJ_C&P951pcaw&wd;=P7^Sp%CnPc_{E7ZlbwD(W#3D zlUU4F2e&dIwp$YvhlwPDPr zsObXGWF>(7KYDqmbbgFvqQri@sC+O`LVVKkOT`)E zhQjp~68f}j73u}w+m)d%Yfo)J)p91qm{@z<>$#Nx delta 904 zcmV;319$w42aE@Q!mUv`FBM8>t91U_3>@m=w6L2<6Q6O^^ljNMDZnx>18K@jV_9jR zZ9TyiLgbfFdrh{N*n<<75BtuhIwDUtYOBzDbx zi#(l_lTicI6Ou-51OFux?!-^*X*CBrLB@1M3kWXwwtOFd38J$&0^f5bQY_vg$x%S~ zg03dcsm1Xjew{#t46h9+@~j>xE&@x-T*bTVWPh?2=nx`#n1>3P&*0bOf3!5tUF1jL zDM$jJhm^{DO!J&~n>Do~zIBtS2EZ6Y^IZ}|GDA!m|mVb z<;-&b@#BAg^lgTU>baMDZvzqmDv@AC2~l3r$c&8hFujqfnrNtvvD~p<#C4o-Ltg>_ z0004<8>R8&BJRmg-BDC-#KP7HIBW&9&70dfN!?tN+b8J)00000{{R2~0qP?K1^@s6 z03rY&crEEEqO9 ztAo%odAUb(l@1Hy)uKO)zUczQg6^cWx~yblcBvE|XBn4zl8K~(0{$GO(-88B;E(W# zt#+vtpV6&cOXa%cC7i|f1D5IePV0rKh^=<16hLe}*qD_fBi3eqZYzoGcPgi%_lRe9 zsT93`La6BDQI%0U&Q<#SwrOUrPNA!at#+vt-5fbzE>|HHec+%`<0rM;>3pXNh^=<1 z6j=+)2uSVfe`j965>Qz$D e^{hki>#z8}1Oga@SAq61#E%@83F0u5c>@#29=(77 diff --git a/dlc-sled-storage-provider/test_files/AcceptedSubChannel b/dlc-sled-storage-provider/test_files/AcceptedSubChannel index 762a3c0aedc640f32a4848585ba2019409992a64..68e24c5c419d87f9d9ce10a255e3a03e7d138189 100644 GIT binary patch literal 3125 zcmWH#XIixXWz+<-@5WQA|IKDGS^jO(r9a7@TeEAcCeFIFpV^Do_`K9EV=MOi2jl;2 zpY`WgV8Qp}-H)Fu3ty{>_clDw!0;ao7@1X0sJ{(7FJ>j`f;`GOKxojrpl`l9l#DBkd zk^i#&@lHdP3DJUkI@3$y!w(hxYB{mXiD{CsP~Zl>&-)VFQq~q_Hye7j?PT2aSZgEa zQ^yah)4tySv|~~KOE$|3lQkW8oKso&bM?(7$uDjlH2xm-f$6(F+v}>zMSC_KeK_AI zsJDcHFK+(j4>5P{_XP(oT7IHE!6oQ#P2LLmX}^AYyo%Y9QYUvo=X`i4f0Eac6efY^ zp>ZbJE0`D<7#M>H1Q}2b1Tg(FWdyU77!=+$)a;2+c5FPu{4QAOL5pab_#*a#!#+)| z7M85(uA8b;JBU^aHu8ZY)4FnHnX^-xY>l7z%IU08xWKD;V0-MRZ(QfME(p!%THe1V zCAn;+$i>4KXRtB%-iy3Z@Lgegah!KkT2jYapoDVq!aC{scONfE(#g_~=3(?$tiuhK zIC$jsy!OZ=%oXfS1yUN0e~-SrpV*NO)KR!Lf8zazCYPO`8HAiOsLsFapb!Yw(RcZ* z&4licY|7Vt?&yX1ncQoUr~*qwOfP*HWmI5cxu5rTdBC>glYfS->c~h}Wcqco-|DT) zi}p)L+N^heU)=d&ab2d<^2IwQwZ<@B>W=HoSOX4f*3DXTmh*pU`*4co#3zQC4`zK7 zo68=aeC>pSpN~l3Ha~DsC;&r_(H;@he=kNx+Zb_}h5g^utRpew>*Pm;#vE;V$4=O5QLKm7c-q%!A1{q|}D zU64v3U}WZbKHq$jaNhl`u2*L>UVXCs+rMWrQN;{-%_ol^H=OrOjd_QJfNk=0{S2nw z_aE#RX68(Md|~SMhzot65Fd0&&B)n;qS%BhXZg60_dPTFbp!=vxW+PjGX zy%82{wi6kdlZ2O6*qr}lE$z7Z#QTqXbqF(3)?3Q%a2NK zE^%^J=GbgCw{erwZu1YfQZKu(f2d?nWU6+0Rhr)W?9|oIw|1FCbnabz;*R2L19m^_ zlCn_NrkP@3&wv8JJxGzMa^vB3;_By>#V1-hFbnMd#dl$D#Lv0=H>$k+>DTq~!+AyK zJsq<&u4T`Dv(U+d!6K3CZb+qZ{N>*l^iIVVyd8F*->@ypg>`ah6_q?z&WGg0uKrl#%Q>p1P7cm8ZPjlKw*rA3rcXnIYT8}0A2qI A^Z)<= literal 3109 zcmezQIpEsCTp>FyzS%du*nQ@6E1p+p{HfcR#?I6orn$$CS=!yhpLOH1gCD!Sg!+A> zx1P$~x-r$Kv9Ci!wjo|(vjzjhe=uNVp10}3j;BG{-obw=BjmCOFxjz32hYO4Z#*YMGy}c_tlG(3!pY zMxveU=R0<%=c^mtYMrued++4ysehZencoYq^}bPktJ`>CrFYS4kJ*c-RTWKQ3X49x zH}TqWhN|L}O%D`zZ@=xu9qch(YLniw2(K;rPP;CuDaT%)yz#2hZ4LFynGc$eJH|fB zO4BlapCHhjp=E1z)qvGy>r!*hC4W9gl-<#vYiXG6!?>e4oX53v%d7C*Tf%RwZ1yC1 ze!uR>#K6G7_>(}80o6bN(=Ss-FiVL+;l!;6nNE}bE$D2!uE-xO)Y$AM^3toxZQHJE zf>t}LkM^c^5Umt!|q za@k6eh~L{b_iyj6uQj(9%niF>|9ANsosP9Y35SXwuf*abRw%A{x&7OssMrejDUZPt z(KWj7x>-MOK5^vX$)n8X?%!q~o7a&J)bU@%Y|`bb2CI;%=NEB*Q40N88D>sd9w<9B6k=gUnsW&D< z84I6GlzX{B`^;tY2a!c7KkGOxCAV)dH=O$*V+}Z{A1lWAzuMy3Tqj?Z8G)~Px*#_RbzuS1I`ZTdg!%afx;T><+<-ZwCD%1hPQ_!+(Xv+w>d&v~8GPMmLQ zcUGDHc;&8KD~qTllbPG*2X|z)6h>Pa zO#2lW+=CRE)P2gP{bPE{+gHf7CfInNPuypNYfHLk@LitLaZOp5n@^ExX8+Fl*Oysv zc`xb8f3Dn|YCcVEi@m3a3zOFM_$|{Etb^7u5L83Z8fXyXrXX1Yw>(T{R2?iNQ0dWd nK@ATWZ!}zBA%RMdh6`$Vz<8tK0t*RLdNf>6!vn?}Zs7s|8N>qv diff --git a/dlc-sled-storage-provider/test_files/Closed b/dlc-sled-storage-provider/test_files/Closed index 4c59dd2ef14703d8404803a48dce5129fc92f851..e4cb913931d0f39325838099f276e363f7ea3b47 100644 GIT binary patch delta 1084 zcmV-C1jGB(2-FCF0Rf>PGeGYzxZil|f?^wYM(!)1V_MPGx5ogneJmb4m6!kuHQ?G3Q^%+1nYB|mm;QL5F$n9{|LOq|jSJ3B$ZAPsTL|1ZUF*lkpq@HOdQUAvZ#uZQj zrLDS`EW{hsZCQUChc=<&>TEk}QRYG=f!--cGGowz8f=+=;uWO5&BI6%T&%Ti&*q<< z&?kX%1pUk3Q)rQ^EhPK;5eA-~x|m!`THk}=>0G+#$?E9hN2!m1s~lem@+)Ph(|W{Ahu;npxQ8=uxj?o_U4FVtteSY z&u#!giIsNSet%|!rFsj_+ z#rJK@1WiP*2Q;%&NcSlGRsaeCFaaV*^cQVHzq>X2`=-8kKI&j0`b0092~|Nj9%BLxNk0000M02FF} z@-#co@-<~1(`w&-cw-jhfW;dG07o!H0w4?s=}WVpfA%pb-AZfrCP4!Kb16>{EyK>?~GiN;nGoKYB zQBt?Uf)mOJp8-cwAp(drtzgqn1oUj&>+2q?I%1<+8G{b)1+vNR|D+DR3 Cq6Mx1 delta 1084 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 literal 5983 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 J{QrEW{|k5=8u0)C diff --git a/dlc-sled-storage-provider/test_files/Offered b/dlc-sled-storage-provider/test_files/Offered index 82a4b544dbecbf85a85f414220e1dc80757f8f95..2bb59765d3182442056cd717e9e49340aa2eebe5 100644 GIT binary patch literal 7146 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 O7Pjy6#{dA6yBjY!)Ra2_ diff --git a/dlc-sled-storage-provider/test_files/OfferedChannel b/dlc-sled-storage-provider/test_files/OfferedChannel index 5edb4eb625269167f7af3508996d545bdaa35cd0..6cff97d9b3f3169ca1d7571efc8e630720f91eae 100644 GIT binary patch delta 205 zcmV;;05bpU0qX&OJkt0fnPO3s_KtBfw!fjfhr7O-a%mqKENKu;Qlq{XEO7R)me1WQ zqx3ffI*C|Gi*LY=bwcUh#ahK!z2d-x1I7HG`_u5Ekku~*{wIVb0F4gFQ8&!eK-|`X zqRyVj$pQssUJWcB(^DEc9B=zy*gjGvpOm~EG%y4L5}}Gk&9uV<-TDJA0ScPpgt#o{#68aD5*6UNB#bjeOJQ{g_T zYf43#v47|d+06(65y7Z4Xs*Qo18jErSXDq%+hgC4{2REef|KaUpYEB`>}_sq7o`OpBuTE#$nL_u=4-AeE^Bw*wWYtA{eLR9l?? zS8M&H%OQ7e>^<06$ouM^hQbdPyUmrq&o7#nx0w0Kjk`xyuE8qTlt`cLhOT^n-ko7>Nwf5lVG^lvzxnIdQZ*Rv3gsdTx0 zxutS literal 2424 zcmdnNzOhxZi79N~(sZZNe0Duu^_(}F8a4MG#EKlM$`X0Tyzr8P>gk+qAr1BuPk3ua z%-uIXZ28w4p*j8GlV-$BeO}7I@E;5qm@a-_v5|GIV3*nR^>*>9=_&pu8-p|ar@H)~ z;&(eR^SUYn6fiPhs4mxco6#ee)YrGh$#%`Qzlv3R?Q+w8)SYeCk#f&cXO>ve)jr*t z<@w^P4)yD|&V14qExO5tS&Mbm0ys`z`qLnaBIS|MYwu^-=eN{n8Z;51%}YFlF{n4-GMKywT#^7*&TF9x&c$xWGaJl^zWj)bN1uM#BXb5~%cOxS)myj5iuCu#iBdhg-M+0O=Cb AbN~PV diff --git a/dlc-sled-storage-provider/test_files/OfferedSubChannel1 b/dlc-sled-storage-provider/test_files/OfferedSubChannel1 index 5f82593d685aa7090fa2d01f0016b2e1e8b165ba..daf5c2461a2f4ee06d1735a65cdd3dd5d90fb5e9 100644 GIT binary patch literal 2424 zcmbO@puUxs#{k?i}(m59fhW}u|z%=2yh2xa2j)Y66CcM-PO!w|z0n^w2{A?Q?INo2gzRv9BRFvv* zZ42v_TEprhjb|$>&;IM^yy&l~(7n9QsQFeGv%pv85Sf^hCpC^OY7e{nw&bZx{env! z`{t-#+#_kKarzk}lZN2@Pxg@~XKg#-dOKLR)|PTYTPC)_A>-SU#jY1Si4 z;Xsz@sfXIdqHoL2{8JcnFRQSFWy12T`IFtb9hk3p=YOAil-X@^;?$do&R(ERYG0QXJZ A2><{9 literal 2424 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 T1e4I4SWYAGzjfR?IV}Dc=@0%# diff --git a/dlc-sled-storage-provider/test_files/Signed b/dlc-sled-storage-provider/test_files/Signed index 67f8ceebd22a2ba684933084d1629e9d49adbe6a..83b649e3a3658e1ef0cee0a090fce354627101e6 100644 GIT binary patch literal 9366 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