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..b7fdf69a 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,256 @@ 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() { + match event { + lightning::util::events::MessageSendEvent::SendChannelReestablish { + msg, .. + } => { + 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..00f531f5 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 69a46fde..a187af9d 100644 Binary files a/dlc-sled-storage-provider/test_files/Accepted and b/dlc-sled-storage-provider/test_files/Accepted differ diff --git a/dlc-sled-storage-provider/test_files/AcceptedChannel b/dlc-sled-storage-provider/test_files/AcceptedChannel index 8e8bdcc5..069a3df8 100644 Binary files a/dlc-sled-storage-provider/test_files/AcceptedChannel and b/dlc-sled-storage-provider/test_files/AcceptedChannel differ diff --git a/dlc-sled-storage-provider/test_files/AcceptedSubChannel b/dlc-sled-storage-provider/test_files/AcceptedSubChannel index 762a3c0a..68e24c5c 100644 Binary files a/dlc-sled-storage-provider/test_files/AcceptedSubChannel and b/dlc-sled-storage-provider/test_files/AcceptedSubChannel differ diff --git a/dlc-sled-storage-provider/test_files/Closed b/dlc-sled-storage-provider/test_files/Closed index 4c59dd2e..e4cb9139 100644 Binary files a/dlc-sled-storage-provider/test_files/Closed and b/dlc-sled-storage-provider/test_files/Closed differ diff --git a/dlc-sled-storage-provider/test_files/Confirmed b/dlc-sled-storage-provider/test_files/Confirmed index ba0ad56e..b2cc3067 100644 Binary files a/dlc-sled-storage-provider/test_files/Confirmed and b/dlc-sled-storage-provider/test_files/Confirmed differ diff --git a/dlc-sled-storage-provider/test_files/Confirmed1 b/dlc-sled-storage-provider/test_files/Confirmed1 index cfd9d2bc..e99d1ec1 100644 Binary files a/dlc-sled-storage-provider/test_files/Confirmed1 and b/dlc-sled-storage-provider/test_files/Confirmed1 differ diff --git a/dlc-sled-storage-provider/test_files/Offered b/dlc-sled-storage-provider/test_files/Offered index 82a4b544..2bb59765 100644 Binary files a/dlc-sled-storage-provider/test_files/Offered and b/dlc-sled-storage-provider/test_files/Offered differ diff --git a/dlc-sled-storage-provider/test_files/OfferedChannel b/dlc-sled-storage-provider/test_files/OfferedChannel index 5edb4eb6..6cff97d9 100644 Binary files a/dlc-sled-storage-provider/test_files/OfferedChannel and b/dlc-sled-storage-provider/test_files/OfferedChannel differ diff --git a/dlc-sled-storage-provider/test_files/OfferedSubChannel b/dlc-sled-storage-provider/test_files/OfferedSubChannel index 3340a20e..754601d8 100644 Binary files a/dlc-sled-storage-provider/test_files/OfferedSubChannel and b/dlc-sled-storage-provider/test_files/OfferedSubChannel differ diff --git a/dlc-sled-storage-provider/test_files/OfferedSubChannel1 b/dlc-sled-storage-provider/test_files/OfferedSubChannel1 index 5f82593d..daf5c246 100644 Binary files a/dlc-sled-storage-provider/test_files/OfferedSubChannel1 and b/dlc-sled-storage-provider/test_files/OfferedSubChannel1 differ diff --git a/dlc-sled-storage-provider/test_files/PreClosed b/dlc-sled-storage-provider/test_files/PreClosed index 8a37c3ff..6020d5d2 100644 Binary files a/dlc-sled-storage-provider/test_files/PreClosed and b/dlc-sled-storage-provider/test_files/PreClosed differ diff --git a/dlc-sled-storage-provider/test_files/Signed b/dlc-sled-storage-provider/test_files/Signed index 67f8ceeb..83b649e3 100644 Binary files a/dlc-sled-storage-provider/test_files/Signed and b/dlc-sled-storage-provider/test_files/Signed differ diff --git a/dlc-sled-storage-provider/test_files/Signed1 b/dlc-sled-storage-provider/test_files/Signed1 index 159cf42f..294407a4 100644 Binary files a/dlc-sled-storage-provider/test_files/Signed1 and b/dlc-sled-storage-provider/test_files/Signed1 differ diff --git a/dlc-sled-storage-provider/test_files/SignedChannelEstablished b/dlc-sled-storage-provider/test_files/SignedChannelEstablished index 88099cdb..47a60657 100644 Binary files a/dlc-sled-storage-provider/test_files/SignedChannelEstablished and b/dlc-sled-storage-provider/test_files/SignedChannelEstablished differ diff --git a/dlc-sled-storage-provider/test_files/SignedChannelSettled b/dlc-sled-storage-provider/test_files/SignedChannelSettled index c15f3019..953a4313 100644 Binary files a/dlc-sled-storage-provider/test_files/SignedChannelSettled and b/dlc-sled-storage-provider/test_files/SignedChannelSettled differ diff --git a/dlc-sled-storage-provider/test_files/SignedSubChannel b/dlc-sled-storage-provider/test_files/SignedSubChannel index 86800faa..954c7766 100644 Binary files a/dlc-sled-storage-provider/test_files/SignedSubChannel and b/dlc-sled-storage-provider/test_files/SignedSubChannel differ diff --git a/dlc-sled-storage-provider/test_files/sub_channel_actions.json b/dlc-sled-storage-provider/test_files/sub_channel_actions.json new file mode 100644 index 00000000..accc4eee --- /dev/null +++ b/dlc-sled-storage-provider/test_files/sub_channel_actions.json @@ -0,0 +1,202 @@ +[{ + "resendOffer": [ + { + "channelId": [ + 220, + 219, + 19, + 222, + 231, + 7, + 165, + 201, + 58, + 199, + 75, + 27, + 150, + 172, + 19, + 7, + 251, + 149, + 37, + 84, + 49, + 168, + 232, + 161, + 50, + 106, + 80, + 63, + 8, + 159, + 228, + 4 + ], + "revocationBasepoint": "033d3572b4c8ec379b050b0a704049537fe0f184fca525b18440f6a854c771ce39", + "publishBasepoint": "02a09e88a32df051e2ce553497714f7843e2f96ddb9abf4f22a865c259eb8eed24", + "ownBasepoint": "020d6e4d24b63d954979e3135e25a60cff901c8505f7e9b7350619136e2c529892", + "nextPerSplitPoint": "02d086a049d035cfe950dc3068eb5d6303e50cbaa7b75fc8b885ea375438f19161", + "contractInfo": { + "singleContractInfo": { + "totalCollateral": 100000, + "contractInfo": { + "contractDescriptor": { + "enumeratedContractDescriptor": { + "payouts": [ + { + "outcome": "a", + "offerPayout": 100000 + }, + { + "outcome": "b", + "offerPayout": 0 + }, + { + "outcome": "c", + "offerPayout": 100000 + }, + { + "outcome": "d", + "offerPayout": 0 + } + ] + } + }, + "oracleInfo": { + "single": { + "oracleAnnouncement": { + "announcementSignature": "9a4b096056033507268e388ffc562d75f70dd984f93aab619524cfbb8f9970b7a1b6e6232e4fedbd84ffb53b6b2ee04d123c3d254980dda50fcd7b212b677fec", + "oraclePublicKey": "6e93d8781ef74005de5c93c4019e2b17d225e8bc300d8d72651efe9cf6fad9b3", + "oracleEvent": { + "oracleNonces": [ + "92531091608b7eb2c073105ea52f5ba3a2088273714a40fcdb8548fdb29a6bba" + ], + "eventMaturityEpoch": 1623133104, + "eventDescriptor": { + "enumEvent": { + "outcomes": [ + "a", + "b", + "c", + "d" + ] + } + }, + "eventId": "Test" + } + } + } + } + } + } + }, + "channelRevocationBasepoint": "034a3f5279d5d45ac9d6398c273573f07491301137e9006bf646ef08fe5696e855", + "channelPublishBasepoint": "03055e74b628802ead1ced794574e6713e27d58ec22d37f3c2a199e67528b590c6", + "channelOwnBasepoint": "03c59a9fec201e68d166b80c6856ac3aeb4fa83f8e6f86c8b47b329d01077dcd76", + "channelFirstPerUpdatePoint": "0229736a13e2b973b20c809dcbc859beee3f5615cff254065366fab826f75c406f", + "payoutSpk": "001443c30eeb21dda1b1b22e619f65dff23db092c615", + "payoutSerialId": 7757339960515235026, + "offerCollateral": 60000, + "cetLocktime": 0, + "refundLocktime": 1623737904, + "cetNsequence": 288, + "feeRatePerVbyte": 1 + }, + "03ca51aacfd5873f44cf128bf4533df97109e9149df7dee10909068a647dceb31e" + ] +}, +{ + "reAccept": { + "channel_id": [ + 220, + 219, + 19, + 222, + 231, + 7, + 165, + 201, + 58, + 199, + 75, + 27, + 150, + 172, + 19, + 7, + 251, + 149, + 37, + 84, + 49, + 168, + 232, + 161, + 50, + 106, + 80, + 63, + 8, + 159, + 228, + 4 + ], + "party_params": { + "fundPubkey": "02f71cf58bb7cde41a13995ea495f09525d7754ff7b5ac0a546612e7836f3a69c5", + "changeScriptPubkey": "0014c78cc7cd3a16cd657a3f3078c398a3bc0869f74e", + "changeSerialId": 6125834209858196150, + "payoutScriptPubkey": "001486d04be0e79f8da95d83a956ec12351370cd32b3", + "payoutSerialId": 7002708850577740307, + "inputs": [], + "inputAmount": 0, + "collateral": 40000 + }, + "funding_inputs_info": [], + "accept_points": { + "ownBasepoint": "0271376656257acc24551c36eaa927836ef2fa2f973e73bf2db8a9a332174612f6", + "revocationBasepoint": "02167b74d7a709d32bba2d8480ec5da11e301085b52c3abb238ccb49d1f2b7e561", + "publishBasepoint": "02b78e11bb04344d4ccb5a186d0106003f8aeef0ab8264d92261765d8bc60b1385" + }, + "per_update_seed_pk": "0292a6b044e642f9b989a2d4551619452dbd88720baf6eff727b12afcfca1be2b2" + } +}, +{ + "forceSign": [ + 106, + 124, + 90, + 126, + 179, + 19, + 244, + 159, + 215, + 178, + 144, + 77, + 53, + 20, + 198, + 26, + 111, + 112, + 29, + 244, + 184, + 54, + 8, + 182, + 9, + 61, + 151, + 97, + 157, + 74, + 94, + 168 + ] +} +] diff --git a/dlc/src/channel/sub_channel.rs b/dlc/src/channel/sub_channel.rs index b46ed149..bb0d59ab 100644 --- a/dlc/src/channel/sub_channel.rs +++ b/dlc/src/channel/sub_channel.rs @@ -56,7 +56,7 @@ pub fn dlc_channel_and_split_fee(fee_rate_per_vb: u64) -> Result { )?) } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] /// Structure containing a split transaction and its associated output script. pub struct SplitTx { /// The actual Bitcoin transaction representation. diff --git a/dlc/src/lib.rs b/dlc/src/lib.rs index 1ce35af1..eaf25bb2 100644 --- a/dlc/src/lib.rs +++ b/dlc/src/lib.rs @@ -147,7 +147,7 @@ impl DlcTransactions { } /// Contains info about a utxo used for funding a DLC contract -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), @@ -225,7 +225,7 @@ impl fmt::Display for Error { /// Contains the parameters required for creating DLC transactions for a single /// party. Specifically these are the common fields between Offer and Accept /// messages. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), diff --git a/mocks/src/memory_storage_provider.rs b/mocks/src/memory_storage_provider.rs index 0163c3f0..7375123a 100644 --- a/mocks/src/memory_storage_provider.rs +++ b/mocks/src/memory_storage_provider.rs @@ -8,6 +8,7 @@ use dlc_manager::channel::{ use dlc_manager::contract::{ offered_contract::OfferedContract, signed_contract::SignedContract, Contract, PreClosedContract, }; +use dlc_manager::sub_channel_manager::Action; use dlc_manager::subchannel::{SubChannel, SubChannelState}; use dlc_manager::Storage; use dlc_manager::{error::Error as DaemonError, ChannelId, ContractId, Utxo}; @@ -26,6 +27,7 @@ pub struct MemoryStorage { addresses: RwLock>, utxos: RwLock>, key_pairs: RwLock>, + actions: RwLock>, } impl MemoryStorage { @@ -40,6 +42,7 @@ impl MemoryStorage { addresses: RwLock::new(HashMap::new()), utxos: RwLock::new(HashMap::new()), key_pairs: RwLock::new(HashMap::new()), + actions: RwLock::new(Vec::new()), } } @@ -284,8 +287,13 @@ impl Storage for MemoryStorage { &self, channel_id: dlc_manager::ChannelId, ) -> Result, DaemonError> { - let map = self.sub_channels.read().expect("could not get read lock"); - Ok(map.get(&channel_id).cloned()) + let res = self + .sub_channels + .read() + .expect("could not get read lock") + .get(&channel_id) + .cloned(); + Ok(res) } fn get_sub_channels(&self) -> Result, DaemonError> { @@ -311,6 +319,20 @@ impl Storage for MemoryStorage { Ok(res) } + + fn save_sub_channel_actions(&self, actions: &[Action]) -> Result<(), DaemonError> { + let mut vec = self.actions.write().expect("Could not get write lock"); + vec.append(&mut actions.to_vec()); + Ok(()) + } + + fn get_sub_channel_actions(&self) -> Result, DaemonError> { + Ok(self + .actions + .read() + .expect("Could not get read lock") + .clone()) + } } impl WalletStorage for MemoryStorage {