diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index c1b963df..9901268e 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -35,7 +35,7 @@ jobs: key: test-cache-${{ github.run_id }}-${{ github.run_number }} - uses: actions/checkout@v2 - id: set-matrix - run: cargo test --no-run && echo "::set-output name=matrix::$(scripts/get_test_list.sh execution manager channel_execution)" + run: cargo test --no-run && echo "::set-output name=matrix::$(scripts/get_test_list.sh manager channel_execution ln_dlc)" integration_tests: name: integration-tests needs: integration_tests_prepare @@ -58,6 +58,6 @@ jobs: - name: Wait for electrs to be ready run: ./scripts/wait_for_electrs.sh - name: Run test - run: RUST_BACKTRACE=1 ${{ matrix.tests }} --ignored + run: RUST_MIN_STACK=104857600 RUST_BACKTRACE=1 ${{ matrix.tests }} --ignored --exact - name: Stop bitcoin node run: ./scripts/stop_node.sh diff --git a/Cargo.toml b/Cargo.toml index 745810ad..a5198501 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,4 @@ [workspace] - members = [ "bitcoin-test-utils", "bitcoin-rpc-provider", @@ -14,3 +13,9 @@ members = [ "dlc-sled-storage-provider", "electrs-blockchain-provider", ] +resolver = "2" + +[patch.crates-io] +lightning = { git = "https://github.com/p2pderivatives/rust-lightning/", rev = "a57281b" } +lightning-net-tokio = { git = "https://github.com/p2pderivatives/rust-lightning/", rev = "a57281b" } +lightning-persister = { git = "https://github.com/p2pderivatives/rust-lightning/", rev = "a57281b" } diff --git a/bitcoin-rpc-provider/Cargo.toml b/bitcoin-rpc-provider/Cargo.toml index 24a72356..c2214c58 100644 --- a/bitcoin-rpc-provider/Cargo.toml +++ b/bitcoin-rpc-provider/Cargo.toml @@ -9,7 +9,7 @@ bitcoin = {version = "0.29.2"} bitcoincore-rpc = {version = "0.16.0"} bitcoincore-rpc-json = {version = "0.16.0"} dlc-manager = {path = "../dlc-manager"} -lightning = {version = "0.0.113"} +lightning = {version = "0.0.116"} log = "0.4.14" -rust-bitcoin-coin-selection = {version = "0.1.0", git = "https://github.com/p2pderivatives/rust-bitcoin-coin-selection", features = ["rand"]} +rust-bitcoin-coin-selection = { rev = "23a6bf85", git = "https://github.com/p2pderivatives/rust-bitcoin-coin-selection", features = ["rand"]} simple-wallet = {path = "../simple-wallet"} diff --git a/bitcoin-rpc-provider/src/lib.rs b/bitcoin-rpc-provider/src/lib.rs index 94fabd12..13e7c9ca 100644 --- a/bitcoin-rpc-provider/src/lib.rs +++ b/bitcoin-rpc-provider/src/lib.rs @@ -25,18 +25,11 @@ use rust_bitcoin_coin_selection::select_coins; /// The minimum feerate we are allowed to send, as specify by LDK. const MIN_FEERATE: u32 = 253; -#[derive(Clone, Eq, Hash, PartialEq)] -pub enum Target { - Background, - Normal, - HighPriority, -} - pub struct BitcoinCoreProvider { client: Arc>, // Used to implement the FeeEstimator interface, heavily inspired by // https://github.com/lightningdevkit/ldk-sample/blob/main/src/bitcoind_client.rs#L26 - fees: Arc>, + fees: Arc>, } #[derive(Debug)] @@ -107,10 +100,10 @@ impl BitcoinCoreProvider { pub fn new_from_rpc_client(rpc_client: Client) -> Self { let client = Arc::new(Mutex::new(rpc_client)); - let mut fees: HashMap = HashMap::new(); - fees.insert(Target::Background, AtomicU32::new(MIN_FEERATE)); - fees.insert(Target::Normal, AtomicU32::new(2000)); - fees.insert(Target::HighPriority, AtomicU32::new(5000)); + let mut fees: HashMap = HashMap::new(); + fees.insert(ConfirmationTarget::Background, AtomicU32::new(MIN_FEERATE)); + fees.insert(ConfirmationTarget::Normal, AtomicU32::new(2000)); + fees.insert(ConfirmationTarget::HighPriority, AtomicU32::new(5000)); let fees = Arc::new(fees); poll_for_fee_estimates(client.clone(), fees.clone()); BitcoinCoreProvider { client, fees } @@ -372,35 +365,22 @@ impl Blockchain for BitcoinCoreProvider { } impl FeeEstimator for BitcoinCoreProvider { - fn get_est_sat_per_1000_weight( - &self, - confirmation_target: lightning::chain::chaininterface::ConfirmationTarget, - ) -> u32 { - match confirmation_target { - ConfirmationTarget::Background => self - .fees - .get(&Target::Background) - .unwrap() - .load(Ordering::Acquire), - ConfirmationTarget::Normal => self - .fees - .get(&Target::Normal) - .unwrap() - .load(Ordering::Acquire), - ConfirmationTarget::HighPriority => self - .fees - .get(&Target::HighPriority) - .unwrap() - .load(Ordering::Acquire), - } + fn get_est_sat_per_1000_weight(&self, confirmation_target: ConfirmationTarget) -> u32 { + self.fees + .get(&confirmation_target) + .unwrap() + .load(Ordering::Acquire) } } -fn poll_for_fee_estimates(client: Arc>, fees: Arc>) { +fn poll_for_fee_estimates( + client: Arc>, + fees: Arc>, +) { std::thread::spawn(move || loop { match query_fee_estimate(&client, 144, EstimateMode::Economical) { Ok(fee_rate) => { - fees.get(&Target::Background) + fees.get(&ConfirmationTarget::Background) .unwrap() .store(fee_rate, Ordering::Release); } @@ -410,7 +390,7 @@ fn poll_for_fee_estimates(client: Arc>, fees: Arc { - fees.get(&Target::Normal) + fees.get(&ConfirmationTarget::Normal) .unwrap() .store(fee_rate, Ordering::Release); } @@ -420,7 +400,7 @@ fn poll_for_fee_estimates(client: Arc>, fees: Arc { - fees.get(&Target::HighPriority) + fees.get(&ConfirmationTarget::HighPriority) .unwrap() .store(fee_rate, Ordering::Release); } diff --git a/bitcoin-test-utils/src/lib.rs b/bitcoin-test-utils/src/lib.rs index d79cfaf1..165a54d2 100644 --- a/bitcoin-test-utils/src/lib.rs +++ b/bitcoin-test-utils/src/lib.rs @@ -42,8 +42,7 @@ pub fn from_hex(hex: &str, target: &mut [u8]) -> Result { /// Transforms an hex string to a Vec. /// Panics if the string is not valid hex. pub fn str_to_hex(hex_str: &str) -> Vec { - let mut hex = Vec::::new(); - hex.resize(hex_str.len() / 2, 0); + let mut hex = vec![0; hex_str.len() / 2]; from_hex(hex_str, &mut hex).unwrap(); hex } diff --git a/dlc-manager/Cargo.toml b/dlc-manager/Cargo.toml index 3bbd99d4..08343a38 100644 --- a/dlc-manager/Cargo.toml +++ b/dlc-manager/Cargo.toml @@ -19,22 +19,26 @@ bitcoin = {version = "0.29.2"} dlc = {version = "0.4.0", path = "../dlc"} dlc-messages = {version = "0.4.0", path = "../dlc-messages"} dlc-trie = {version = "0.4.0", path = "../dlc-trie"} -lightning = {version = "0.0.113"} +lightning = {version = "0.0.116"} log = "0.4.14" rand_chacha = {version = "0.3.1", optional = true} secp256k1-zkp = {version = "0.7.0", features = ["bitcoin_hashes", "rand", "rand-std"]} serde = {version = "1.0", optional = true} [dev-dependencies] +bitcoin-bech32 = "0.12.1" bitcoin-rpc-provider = {path = "../bitcoin-rpc-provider"} bitcoin-test-utils = {path = "../bitcoin-test-utils"} bitcoincore-rpc = {version = "0.16.0"} bitcoincore-rpc-json = {version = "0.16.0"} criterion = "0.4.0" +chrono = "0.4" dlc-manager = {path = ".", features = ["use-serde"]} dlc-messages = {path = "../dlc-messages", features = ["serde"]} electrs-blockchain-provider = {path = "../electrs-blockchain-provider"} env_logger = "0.9.1" +lightning-persister = {version = "0.0.116"} +lightning-transaction-sync = {version = "0.0.116", features=["esplora-blocking"]} mocks = {path = "../mocks"} secp256k1-zkp = {version = "0.7.0", features = ["bitcoin_hashes", "rand", "rand-std", "global-context", "use-serde"]} serde = "1.0" diff --git a/dlc-manager/src/chain_monitor.rs b/dlc-manager/src/chain_monitor.rs index 7942ec14..eb238312 100644 --- a/dlc-manager/src/chain_monitor.rs +++ b/dlc-manager/src/chain_monitor.rs @@ -1,11 +1,11 @@ -//! +//! This module includes the [`ChainMonitor`] struct that helps watching the blockchain for +//! transactions of interest in the context of DLC. use std::collections::HashMap; -use bitcoin::{Block, BlockHash, Transaction, Txid}; +use bitcoin::{Block, OutPoint, Transaction, Txid}; use dlc_messages::ser_impls::{ - read_ecdsa_adaptor_signature, read_hash_map, read_vec, write_ecdsa_adaptor_signature, - write_hash_map, write_vec, + read_ecdsa_adaptor_signature, read_hash_map, write_ecdsa_adaptor_signature, write_hash_map, }; use lightning::ln::msgs::DecodeError; use lightning::util::ser::{Readable, Writeable, Writer}; @@ -13,20 +13,18 @@ use secp256k1_zkp::EcdsaAdaptorSignature; use crate::ChannelId; -const NB_SAVED_BLOCK_HASHES: usize = 6; - /// A `ChainMonitor` keeps a list of transaction ids to watch for in the blockchain, /// and some associated information used to apply an action when the id is seen. #[derive(Debug, PartialEq, Eq)] pub struct ChainMonitor { - watched_tx: HashMap, + pub(crate) watched_tx: HashMap, + pub(crate) watched_txo: HashMap, pub(crate) last_height: u64, - pub(crate) last_block_hashes: Vec, } -impl_dlc_writeable!(ChainMonitor, { (watched_tx, { cb_writeable, write_hash_map, read_hash_map}), (last_height, writeable), (last_block_hashes, { cb_writeable, write_vec, read_vec}) }); +impl_dlc_writeable!(ChainMonitor, { (watched_tx, { cb_writeable, write_hash_map, read_hash_map}), (watched_txo, { cb_writeable, write_hash_map, read_hash_map}), (last_height, writeable) }); -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub(crate) struct ChannelInfo { pub channel_id: ChannelId, pub tx_type: TxType, @@ -34,7 +32,7 @@ pub(crate) struct ChannelInfo { impl_dlc_writeable!(ChannelInfo, { (channel_id, writeable), (tx_type, writeable) }); -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub(crate) enum TxType { Revoked { update_idx: u64, @@ -42,8 +40,11 @@ pub(crate) enum TxType { is_offer: bool, revoked_tx_type: RevokedTxType, }, - Current, + BufferTx, CollaborativeClose, + SplitTx, + SettleTx, + Cet, } impl_dlc_writeable_enum!(TxType,; @@ -53,66 +54,173 @@ impl_dlc_writeable_enum!(TxType,; (is_offer, writeable), (revoked_tx_type, writeable) });; - (1, Current), (2, CollaborativeClose) + (1, BufferTx), (2, CollaborativeClose), (3, SplitTx), (4, SettleTx), (5, Cet) ); -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, Copy)] pub(crate) enum RevokedTxType { Buffer, Settle, + Split, } -impl_dlc_writeable_enum!(RevokedTxType,;;;(0, Buffer), (1, Settle)); +impl_dlc_writeable_enum!(RevokedTxType,;;;(0, Buffer), (1, Settle), (2, Split)); impl ChainMonitor { /// Returns a new [`ChainMonitor`] with fields properly initialized. pub fn new(init_height: u64) -> Self { ChainMonitor { watched_tx: HashMap::new(), + watched_txo: HashMap::new(), last_height: init_height, - last_block_hashes: Vec::with_capacity(NB_SAVED_BLOCK_HASHES), } } + /// Returns true if the monitor doesn't contain any transaction to be watched. + pub fn is_empty(&self) -> bool { + self.watched_tx.is_empty() + } + pub(crate) fn add_tx(&mut self, txid: Txid, channel_info: ChannelInfo) { - self.watched_tx.insert(txid, channel_info); + log::debug!("Watching transaction {txid}: {channel_info:?}"); + self.watched_tx.insert(txid, WatchState::new(channel_info)); + + // When we watch a buffer transaction we also want to watch + // the buffer transaction _output_ so that we can detect when + // a CET spends it without having to watch every possible CET + if channel_info.tx_type == TxType::BufferTx { + let outpoint = OutPoint { + txid, + // We can safely assume that the buffer transaction + // only has one output + vout: 0, + }; + self.add_txo( + outpoint, + ChannelInfo { + channel_id: channel_info.channel_id, + tx_type: TxType::Cet, + }, + ); + } + } + + fn add_txo(&mut self, outpoint: OutPoint, channel_info: ChannelInfo) { + log::debug!("Watching transaction output {outpoint}: {channel_info:?}"); + self.watched_txo + .insert(outpoint, WatchState::new(channel_info)); + } + + pub(crate) fn cleanup_channel(&mut self, channel_id: ChannelId) { + log::debug!("Cleaning up data related to channel {channel_id:?}"); + + self.watched_tx + .retain(|_, state| state.channel_id() != channel_id); + + self.watched_txo + .retain(|_, state| state.channel_id() != channel_id); } pub(crate) fn remove_tx(&mut self, txid: &Txid) { + log::debug!("Stopped watching transaction {txid}"); self.watched_tx.remove(txid); } - pub(crate) fn process_block( - &self, - block: &Block, - height: u64, - ) -> Vec<(Transaction, ChannelInfo)> { - let mut res = Vec::new(); - + /// Check if any watched transactions are part of the block, confirming them if so. + /// + /// # Panics + /// + /// Panics if the new block's height is not exactly one more than the last processed height. + pub(crate) fn process_block(&mut self, block: &Block, height: u64) { assert_eq!(self.last_height + 1, height); - for tx in &block.txdata { - let txid = tx.txid(); - if self.watched_tx.contains_key(&txid) { - let channel_info = self - .watched_tx - .get(&txid) - .expect("to be able to retrieve the channel info"); - res.push((tx.clone(), channel_info.clone())); + for tx in block.txdata.iter() { + if let Some(state) = self.watched_tx.get_mut(&tx.txid()) { + state.confirm(tx.clone()); + } + + for txin in tx.input.iter() { + if let Some(state) = self.watched_txo.get_mut(&txin.previous_output) { + state.confirm(tx.clone()) + } } } - res + self.last_height += 1; } - /// To be safe this is a separate function from process block to make sure updates are - /// saved before we update the state. It is better to re-process a block than not - /// process it at all. - pub(crate) fn increment_height(&mut self, last_block_hash: &BlockHash) { - self.last_height += 1; - self.last_block_hashes.push(*last_block_hash); - if self.last_block_hashes.len() > NB_SAVED_BLOCK_HASHES { - self.last_block_hashes.remove(0); + /// All the currently watched transactions which have been confirmed. + pub(crate) fn confirmed_txs(&self) -> Vec<(Transaction, ChannelInfo)> { + (self.watched_tx.values()) + .chain(self.watched_txo.values()) + .filter_map(|state| match state { + WatchState::Registered { .. } => None, + WatchState::Confirmed { + channel_info, + transaction, + } => Some((transaction.clone(), *channel_info)), + }) + .collect() + } +} + +/// The state of a watched transaction or transaction output. +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) enum WatchState { + /// It has been registered but we are not aware of any + /// confirmations. + Registered { channel_info: ChannelInfo }, + /// It has received at least one confirmation. + Confirmed { + channel_info: ChannelInfo, + transaction: Transaction, + }, +} + +impl_dlc_writeable_enum!( + WatchState,; + (0, Registered, {(channel_info, writeable)}), + (1, Confirmed, {(channel_info, writeable), (transaction, writeable)});; +); + +impl WatchState { + fn new(channel_info: ChannelInfo) -> Self { + Self::Registered { channel_info } + } + + fn confirm(&mut self, transaction: Transaction) { + match self { + WatchState::Registered { ref channel_info } => { + log::info!( + "Transaction {} confirmed: {channel_info:?}", + transaction.txid() + ); + + *self = WatchState::Confirmed { + channel_info: *channel_info, + transaction, + } + } + WatchState::Confirmed { + channel_info, + transaction, + } => { + log::error!( + "Transaction {} already confirmed: {channel_info:?}", + transaction.txid() + ); + } } } + + fn channel_info(&self) -> ChannelInfo { + match self { + WatchState::Registered { channel_info } + | WatchState::Confirmed { channel_info, .. } => *channel_info, + } + } + + fn channel_id(&self) -> ChannelId { + self.channel_info().channel_id + } } diff --git a/dlc-manager/src/channel/mod.rs b/dlc-manager/src/channel/mod.rs index 20abe0de..d05155d5 100644 --- a/dlc-manager/src/channel/mod.rs +++ b/dlc-manager/src/channel/mod.rs @@ -1,9 +1,10 @@ //! # Module containing structures and methods for working with DLC channels. +use bitcoin::{hashes::Hash, Transaction, Txid}; use dlc_messages::channel::{AcceptChannel, SignChannel}; use secp256k1_zkp::PublicKey; -use crate::ChannelId; +use crate::{ChannelId, ContractId}; use self::{ accepted_channel::AcceptedChannel, offered_channel::OfferedChannel, @@ -26,6 +27,24 @@ pub enum Channel { Accepted(AcceptedChannel), /// A channel whose fund outputs have been signed by the offer party. Signed(SignedChannel), + /// A [`Channel`] is in `Closing` state when the local party + /// has broadcast a buffer transaction and is waiting to finalize the + /// closing of the channel by broadcasting a CET. + Closing(ClosingChannel), + /// A [`Channel`] is in `Closed` state when it was force closed by + /// the local party. + Closed(ClosedChannel), + /// A [`Channel`] is in `CounterClosed` state when it was force + /// closed by the counter party. + CounterClosed(ClosedChannel), + /// A [`Channel`] is in `ClosedPunished` state when the local + /// party broadcast a punishment transaction in response to the counter + /// party broadcasting a settle or buffer transaction for a revoked channel + /// state. + ClosedPunished(ClosedPunishedChannel), + /// A [`SignedChannel`] is in `CollaborativelyClosed` state when it was + /// collaboratively closed. + CollaborativelyClosed(ClosedChannel), /// A channel that failed when validating an /// [`dlc_messages::channel::AcceptChannel`] message. FailedAccept(FailedAccept), @@ -42,8 +61,13 @@ impl std::fmt::Debug for Channel { Channel::Signed(_) => "signed", Channel::FailedAccept(_) => "failed accept", Channel::FailedSign(_) => "failed sign", + Channel::Closing(_) => "closing", + Channel::Closed(_) => "closed", + Channel::CounterClosed(_) => "counter closed", + Channel::ClosedPunished(_) => "closed punished", + Channel::CollaborativelyClosed(_) => "collaboratively closed", }; - f.debug_struct("Contract").field("state", &state).finish() + f.debug_struct("Channel").field("state", &state).finish() } } @@ -56,6 +80,11 @@ impl Channel { Channel::Signed(s) => s.counter_party, Channel::FailedAccept(f) => f.counter_party, Channel::FailedSign(f) => f.counter_party, + Channel::Closing(c) => c.counter_party, + Channel::Closed(c) | Channel::CounterClosed(c) | Channel::CollaborativelyClosed(c) => { + c.counter_party + } + Channel::ClosedPunished(c) => c.counter_party, } } } @@ -90,6 +119,52 @@ pub struct FailedSign { pub sign_message: SignChannel, } +#[derive(Clone)] +/// A channel is closing when its buffer transaction was broadcast or detected on chain. +pub struct ClosingChannel { + /// The [`secp256k1_zkp::PublicKey`] of the counter party. + pub counter_party: PublicKey, + /// The temporary [`crate::ChannelId`] of the channel. + pub temporary_channel_id: ChannelId, + /// The [`crate::ChannelId`] for the channel. + pub channel_id: ChannelId, + /// The previous state the channel was before being closed, if that state was the `Signed` one, + /// otherwise is `None`. + pub rollback_state: Option, + /// The buffer transaction that was broadcast. + pub buffer_transaction: Transaction, + /// The [`crate::ContractId`] of the contract that was used to close + /// the channel. + pub contract_id: ContractId, + /// Whether the local party initiated the closing of the channel. + pub is_closer: bool, +} + +#[derive(Clone)] +/// A channel is closed when its buffer transaction has been spent. +pub struct ClosedChannel { + /// The [`secp256k1_zkp::PublicKey`] of the counter party. + pub counter_party: PublicKey, + /// The temporary [`crate::ChannelId`] of the channel. + pub temporary_channel_id: ChannelId, + /// The [`crate::ChannelId`] for the channel. + pub channel_id: ChannelId, +} + +#[derive(Clone)] +/// A channel is closed punished when the counter party broadcast a revoked transaction triggering +/// the broadcast of a punishment transaction by the local party. +pub struct ClosedPunishedChannel { + /// The [`secp256k1_zkp::PublicKey`] of the counter party. + pub counter_party: PublicKey, + /// The temporary [`crate::ChannelId`] of the channel. + pub temporary_channel_id: ChannelId, + /// The [`crate::ChannelId`] for the channel. + pub channel_id: ChannelId, + /// The transaction id of the punishment transaction that was broadcast. + pub punish_txid: Txid, +} + impl Channel { /// Returns the temporary [`crate::ChannelId`] for the channel. pub fn get_temporary_id(&self) -> ChannelId { @@ -98,6 +173,10 @@ impl Channel { Channel::Accepted(a) => a.temporary_channel_id, Channel::Signed(s) => s.temporary_channel_id, Channel::FailedAccept(f) => f.temporary_channel_id, + Channel::Closed(c) | Channel::CounterClosed(c) | Channel::CollaborativelyClosed(c) => { + c.temporary_channel_id + } + Channel::ClosedPunished(c) => c.temporary_channel_id, _ => unimplemented!(), } } @@ -110,6 +189,34 @@ impl Channel { Channel::Signed(s) => s.channel_id, Channel::FailedAccept(f) => f.temporary_channel_id, Channel::FailedSign(f) => f.channel_id, + Channel::Closing(c) => c.channel_id, + Channel::Closed(c) | Channel::CounterClosed(c) | Channel::CollaborativelyClosed(c) => { + c.channel_id + } + Channel::ClosedPunished(c) => c.channel_id, } } + + /// Returns the contract id associated with the channel if in a state where a contract is set. + pub fn get_contract_id(&self) -> Option { + match self { + Channel::Offered(o) => Some(o.offered_contract_id), + Channel::Accepted(a) => Some(a.accepted_contract_id), + Channel::Signed(s) => s.get_contract_id(), + Channel::FailedAccept(_) => None, + Channel::FailedSign(_) => None, + _ => None, + } + } +} + +/// Generate a temporary contract id for a DLC based on the channel id and the update index of the DLC channel. +pub fn generate_temporary_contract_id( + channel_id: ChannelId, + channel_update_idx: u64, +) -> ContractId { + let mut data = Vec::with_capacity(65); + data.extend_from_slice(&channel_id); + data.extend_from_slice(&channel_update_idx.to_be_bytes()); + bitcoin::hashes::sha256::Hash::hash(&data).into_inner() } 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/ser.rs b/dlc-manager/src/channel/ser.rs index 2d8a60bd..d6ad81a3 100644 --- a/dlc-manager/src/channel/ser.rs +++ b/dlc-manager/src/channel/ser.rs @@ -3,7 +3,7 @@ use super::accepted_channel::AcceptedChannel; use super::offered_channel::OfferedChannel; use super::party_points::PartyBasePoints; use super::signed_channel::{SignedChannel, SignedChannelState}; -use super::{FailedAccept, FailedSign}; +use super::{ClosedChannel, ClosedPunishedChannel, ClosingChannel, FailedAccept, FailedSign}; use dlc_messages::ser_impls::{ read_ecdsa_adaptor_signature, read_string, write_ecdsa_adaptor_signature, write_string, @@ -45,25 +45,39 @@ impl_dlc_writeable!(SignedChannel, { (roll_back_state, option), (own_per_update_seed, writeable), (counter_party_commitment_secrets, writeable), - (fee_rate_per_vb, writeable) + (fee_rate_per_vb, writeable), + (sub_channel_id, option) }); impl_dlc_writeable_enum!( SignedChannelState,; - (0, Established, {(signed_contract_id, writeable), (own_buffer_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}), (counter_buffer_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}), (buffer_transaction, writeable), (is_offer, writeable)}), + (0, Established, {(signed_contract_id, writeable), (own_buffer_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}), (counter_buffer_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}), (buffer_transaction, writeable), (is_offer, writeable), (total_collateral, writeable)}), (1, SettledOffered, {(counter_payout, writeable), (next_per_update_point, writeable), (timeout, writeable)}), - (2, SettledReceived, {(own_payout, writeable), (counter_next_per_update_point, writeable)}), - (3, SettledAccepted, {(counter_next_per_update_point, writeable), (own_next_per_update_point, writeable), (settle_tx, writeable), (own_settle_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}), (timeout, writeable), (own_payout, writeable)}), - (4, SettledConfirmed, {(settle_tx, writeable), (counter_settle_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}), (own_settle_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}), (counter_next_per_update_point, writeable), (own_next_per_update_point, writeable), (timeout, writeable), (own_payout, writeable) }), - (5, Settled, {(settle_tx, writeable), (counter_settle_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}), (own_settle_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature})}), + (2, SettledReceived, {(own_payout, writeable), (counter_next_per_update_point, writeable), (counter_payout, writeable)}), + (3, SettledAccepted, {(counter_next_per_update_point, writeable), (own_next_per_update_point, writeable), (settle_tx, writeable), (own_settle_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}), (timeout, writeable), (own_payout, writeable), (counter_payout, writeable)}), + (4, SettledConfirmed, {(settle_tx, writeable), (counter_settle_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}), (own_settle_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}), (counter_next_per_update_point, writeable), (own_next_per_update_point, writeable), (timeout, writeable), (own_payout, writeable), (counter_payout, writeable) }), + (5, Settled, {(settle_tx, writeable), (counter_settle_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}), (own_settle_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}), (own_payout, writeable), (counter_payout, writeable)}), (6, RenewOffered, {(offered_contract_id, writeable), (counter_payout, writeable), (is_offer, writeable), (offer_next_per_update_point, writeable), (timeout, writeable)}), - (7, RenewAccepted, {(contract_id, writeable), (offer_per_update_point, writeable), (accept_per_update_point, writeable), (buffer_transaction, writeable), (buffer_script_pubkey, writeable), (accept_buffer_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}), (timeout, writeable), (own_payout, writeable)}), - (8, RenewConfirmed, {(contract_id, writeable), (offer_per_update_point, writeable), (accept_per_update_point, writeable), (buffer_transaction, writeable), (buffer_script_pubkey, writeable), (offer_buffer_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}), (accept_buffer_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}), (timeout, writeable), (own_payout, writeable)}), - (9, Closing, {(buffer_transaction, writeable), (signed_cet, writeable), (contract_id, writeable), (attestations, vec)}), - (10, ClosedPunished, { (punishment_txid, writeable) }), + (7, RenewAccepted, {(contract_id, writeable), (offer_per_update_point, writeable), (accept_per_update_point, writeable), (buffer_transaction, writeable), (buffer_script_pubkey, writeable), (timeout, writeable), (own_payout, writeable)}), + (8, RenewConfirmed, {(contract_id, writeable), (offer_per_update_point, writeable), (accept_per_update_point, writeable), (buffer_transaction, writeable), (buffer_script_pubkey, writeable), (offer_buffer_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}), (timeout, writeable), (own_payout, writeable), (total_collateral, writeable)}), + (10, RenewFinalized, {(contract_id, writeable), (prev_offer_per_update_point, writeable), (buffer_transaction, writeable), (buffer_script_pubkey, writeable), (offer_buffer_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}), (accept_buffer_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}), (timeout, writeable), (own_payout, writeable), (total_collateral, writeable)}), + (9, Closing, {(buffer_transaction, writeable), (contract_id, writeable), (is_initiator, writeable)}), (11, CollaborativeCloseOffered, { (counter_payout, writeable), (offer_signature, writeable), (close_tx, writeable), (timeout, writeable) }) - ;;(12, Closed), (13, CounterClosed), (14, CollaborativelyClosed) + ;; ); impl_dlc_writeable!(FailedAccept, {(temporary_channel_id, writeable), (error_message, {cb_writeable, write_string, read_string}), (accept_message, writeable), (counter_party, writeable)}); impl_dlc_writeable!(FailedSign, {(channel_id, writeable), (error_message, {cb_writeable, write_string, read_string}), (sign_message, writeable), (counter_party, writeable)}); + +impl_dlc_writeable!(ClosingChannel, { + (channel_id, writeable), + (counter_party, writeable), + (temporary_channel_id, writeable), + (rollback_state, option), + (buffer_transaction, writeable), + (contract_id, writeable), + (is_closer, writeable) + +}); +impl_dlc_writeable!(ClosedChannel, {(channel_id, writeable), (counter_party, writeable), (temporary_channel_id, writeable)}); +impl_dlc_writeable!(ClosedPunishedChannel, {(channel_id, writeable), (counter_party, writeable), (temporary_channel_id, writeable), (punish_txid, writeable)}); diff --git a/dlc-manager/src/channel/signed_channel.rs b/dlc-manager/src/channel/signed_channel.rs index eeee9819..e6b8c7dc 100644 --- a/dlc-manager/src/channel/signed_channel.rs +++ b/dlc-manager/src/channel/signed_channel.rs @@ -2,9 +2,8 @@ //! transaction inputs. This module contains the model for a signed channel, //! the possible states in which it can be as well as methods to work with it. -use bitcoin::{Script, Transaction, Txid}; +use bitcoin::{Script, Transaction}; use dlc::PartyParams; -use dlc_messages::oracle_msgs::OracleAttestation; use lightning::ln::chan_utils::CounterpartyCommitmentSecrets; use secp256k1_zkp::{ecdsa::Signature, EcdsaAdaptorSignature, PublicKey}; @@ -108,6 +107,8 @@ typed_enum!( /// Whether the local party is the one that initiated the latest channel /// state change. is_offer: bool, + /// The total amount of collateral in the channel + total_collateral: u64, }, /// A [`SignedChannel`] is in `SettledOffered` state when the local party /// has sent a [`dlc_messages::channel::SettleOffer`] message. @@ -126,6 +127,8 @@ typed_enum!( SettledReceived { /// The payout that was proposed to the local party to settle the channel. own_payout: u64, + /// The payout that was proposed to the counter party. + counter_payout: u64, /// The per update point to be used by the counter party for the setup /// of the next channel state. counter_next_per_update_point: PublicKey, @@ -149,6 +152,8 @@ typed_enum!( timeout: u64, /// The payout to the local party after settling the channel. own_payout: u64, + /// The payout that was proposed to the counter party. + counter_payout: u64, }, /// A [`SignedChannel`] is in `SettledConfirmed` state when the local party /// has sent a [`dlc_messages::channel::SettleConfirm`] message. @@ -172,6 +177,8 @@ typed_enum!( timeout: u64, /// The payout to the local party after settling the channel. own_payout: u64, + /// The payout that was proposed to the counter party. + counter_payout: u64, }, /// A [`SignedChannel`] is in `Settled` state when the local party /// has all the necessary information to close the channel with the last @@ -185,6 +192,10 @@ typed_enum!( /// The adaptor signature for the settle transaction generated by the /// local party. own_settle_adaptor_signature: EcdsaAdaptorSignature, + /// The amount the local party holds in the channel. + own_payout: u64, + /// The amount the counter party holds in the channel. + counter_payout: u64, }, /// A [`SignedChannel`] is in `RenewOffered` state when the local party /// has sent or received a [`dlc_messages::channel::RenewOffer`] message. @@ -217,9 +228,6 @@ typed_enum!( buffer_transaction: Transaction, /// The buffer transaction script pubkey. buffer_script_pubkey: Script, - /// The adaptor signature for the buffer transaction generated by - /// the accept party. - accept_buffer_adaptor_signature: EcdsaAdaptorSignature, /// The UNIX epoch at which the counter party will be considered /// unresponsive and the channel will be forced closed. timeout: u64, @@ -244,6 +252,28 @@ typed_enum!( /// The adaptor signature for the buffer transaction generated by /// the offer party. offer_buffer_adaptor_signature: EcdsaAdaptorSignature, + /// The UNIX epoch at which the counter party will be considered + /// unresponsive and the channel will be forced closed. + timeout: u64, + /// The payout to the local party attributed for closing the previous state. + own_payout: u64, + /// The total amount of collateral in the channel. + total_collateral: u64, + }, + /// Finalize the renewal of the contract within a DLC channel. + RenewFinalized { + /// The [`crate::ContractId`] of the offered contract. + contract_id: ContractId, + /// The previous per update point that was used by the offer party for the previous + /// state of the channel. + prev_offer_per_update_point: PublicKey, + /// The buffer transaction. + buffer_transaction: Transaction, + /// The buffer transaction script pubkey. + buffer_script_pubkey: Script, + /// The adaptor signature for the buffer transaction generated by + /// the offer party. + offer_buffer_adaptor_signature: EcdsaAdaptorSignature, /// The adaptor signature for the buffer transaction generated by /// the accept party. accept_buffer_adaptor_signature: EcdsaAdaptorSignature, @@ -252,6 +282,8 @@ typed_enum!( timeout: u64, /// The payout to the local party attributed for closing the previous state. own_payout: u64, + /// The total amount of collateral in the channel. + total_collateral: u64, }, /// A [`SignedChannel`] is in `Closing` state when the local party /// has broadcast a buffer transaction and is waiting to finalize the @@ -259,27 +291,11 @@ typed_enum!( Closing { /// The buffer transaction that was broadcast. buffer_transaction: Transaction, - /// The signed CET to be broadcast when the lock time has passed. - signed_cet: Transaction, /// The [`crate::ContractId`] of the contract that was used to close /// the channel. contract_id: ContractId, - /// The attestations used to decrypt the CET adaptor signature. - attestations: Vec, - }, - /// A [`SignedChannel`] is in `Closed` state when it was force closed by - /// the local party. - Closed, - /// A [`SignedChannel`] is in `CounterClosed` state when it was force - /// closed by the counter party. - CounterClosed, - /// A [`SignedChannel`] is in `ClosedPublished` state when the local - /// party broadcast a punishment transaction in response to the counter - /// party broadcasting a settle or buffer transaction for a revoked channel - /// state. - ClosedPunished { - /// The transaction id of the punishment transaction that was broadcast. - punishment_txid: Txid, + /// Whether the local party initiated the closing of the channel. + is_initiator: bool, }, /// A [`SignedChannel`] is in `CollaborativeCloseOffered` state when the local party /// has sent a [`dlc_messages::channel::CollaborativeCloseOffer`] message. @@ -294,9 +310,6 @@ typed_enum!( /// unresponsive and the channel will be forced closed. timeout: u64, }, - /// A [`SignedChannel`] is in `CollaborativelyClosed` state when it was - /// collaboratively closed. - CollaborativelyClosed, }, /// Enum automatically generated associating a number to each signed channel /// state. @@ -307,7 +320,13 @@ impl SignedChannel { /// Returns the contract id associated with the channel if in a state where /// a contract is established or under establishment. pub fn get_contract_id(&self) -> Option { - match &self.state { + self.state.get_contract_id() + } +} + +impl SignedChannelState { + fn get_contract_id(&self) -> Option { + match &self { SignedChannelState::Established { signed_contract_id, .. } => Some(*signed_contract_id), @@ -317,6 +336,7 @@ impl SignedChannel { } => Some(*offered_contract_id), SignedChannelState::RenewAccepted { contract_id, .. } => Some(*contract_id), SignedChannelState::RenewConfirmed { contract_id, .. } => Some(*contract_id), + SignedChannelState::RenewFinalized { contract_id, .. } => Some(*contract_id), SignedChannelState::Closing { contract_id, .. } => Some(*contract_id), _ => None, } @@ -364,4 +384,13 @@ pub struct SignedChannel { pub counter_party_commitment_secrets: CounterpartyCommitmentSecrets, /// The current fee rate to be used to create transactions. pub fee_rate_per_vb: u64, + /// Whether this channel is embedded within a Lightning Network channel. + pub sub_channel_id: Option, +} + +impl SignedChannel { + /// Return whether this channel is part of an LN/DLC sub channel. + pub fn is_sub_channel(&self) -> bool { + self.sub_channel_id.is_some() + } } diff --git a/dlc-manager/src/channel/utils.rs b/dlc-manager/src/channel/utils.rs index db8eea66..ce9539df 100644 --- a/dlc-manager/src/channel/utils.rs +++ b/dlc-manager/src/channel/utils.rs @@ -35,3 +35,14 @@ pub(crate) fn derive_bitcoin_public_revocation_key( key, }) } + +/// Generate a temporary contract id for a DLC based on the channel id and the update index of the DLC channel. +pub fn generate_temporary_contract_id( + channel_id: ChannelId, + channel_update_idx: u64, +) -> ContractId { + let mut data = Vec::with_capacity(65); + data.extend_from_slice(&channel_id); + data.extend_from_slice(&channel_update_idx.to_be_bytes()); + bitcoin::hashes::sha256::Hash::hash(&data).into_inner() +} diff --git a/dlc-manager/src/channel_updater.rs b/dlc-manager/src/channel_updater.rs index dfd9cf74..d8c33cd3 100644 --- a/dlc-manager/src/channel_updater.rs +++ b/dlc-manager/src/channel_updater.rs @@ -1,13 +1,15 @@ //! # This module contains static functions to update the state of a DLC channel. -use std::ops::Deref; +use std::{ops::Deref, sync::Mutex}; use crate::{ + chain_monitor::{ChainMonitor, ChannelInfo, TxType}, channel::{ accepted_channel::AcceptedChannel, offered_channel::OfferedChannel, party_points::PartyBasePoints, signed_channel::{SignedChannel, SignedChannelState}, + Channel, ClosedChannel, }, contract::{ accepted_contract::AcceptedContract, contract_info::ContractInfo, @@ -19,10 +21,10 @@ use crate::{ verify_signed_contract_internal, }, error::Error, - utils::get_new_temporary_id, - Blockchain, Signer, Time, Wallet, + subchannel::{ClosingSubChannel, SubChannel}, + Blockchain, ChannelId, ContractId, Signer, Time, Wallet, }; -use bitcoin::{OutPoint, Script, Sequence, Transaction, TxIn, Witness}; +use bitcoin::{OutPoint, Script, Sequence, Transaction}; use dlc::{ channel::{get_tx_adaptor_signature, verify_tx_adaptor_signature, DlcChannelTransactions}, PartyParams, @@ -30,7 +32,8 @@ use dlc::{ use dlc_messages::{ channel::{ AcceptChannel, CollaborativeCloseOffer, Reject, RenewAccept, RenewConfirm, RenewFinalize, - RenewOffer, SettleAccept, SettleConfirm, SettleFinalize, SettleOffer, SignChannel, + RenewOffer, RenewRevoke, SettleAccept, SettleConfirm, SettleFinalize, SettleOffer, + SignChannel, }, oracle_msgs::{OracleAnnouncement, OracleAttestation}, FundingSignatures, @@ -44,19 +47,19 @@ const INITIAL_UPDATE_NUMBER: u64 = (1 << 48) - 1; macro_rules! get_signed_channel_state { ($signed_channel: ident, $state: ident, ref $field: ident) => {{ - match $signed_channel.state { + match &$signed_channel.state { SignedChannelState::$state{ref $field, ..} => Ok($field), _ => Err(Error::InvalidState(format!("Expected state {} got {:?}", stringify!($state), $signed_channel.state))), } }}; ($signed_channel: ident, $state: ident, $field: ident) => {{ - match $signed_channel.state { + match &$signed_channel.state { SignedChannelState::$state{$field, ..} => Ok($field), _ => Err(Error::InvalidState(format!("Expected state {} got {:?}", stringify!($state), $signed_channel.state))), } }}; ($signed_channel: ident, $state: ident, $($field: ident),* $(|$($ref_field: ident),*)?) => {{ - match $signed_channel.state { + match &$signed_channel.state { SignedChannelState::$state{$($field,)* $($(ref $ref_field,)*)? ..} => Ok(($($field,)* $($($ref_field,)*)?)), _ => Err(Error::InvalidState(format!("Expected state {} got {:?}", stringify!($state), $signed_channel.state))), } @@ -64,6 +67,34 @@ macro_rules! get_signed_channel_state { } pub(crate) use get_signed_channel_state; +/// Information about the funding input of a sub channel. +pub struct FundingInfo { + /// The funding transaction for the sub channel. + pub funding_tx: Transaction, + /// The script pubkey of the funding output. + pub funding_script_pubkey: Script, + /// The value of the funding output. + pub funding_input_value: u64, +} + +pub(crate) struct SubChannelSignVerifyInfo { + pub funding_info: FundingInfo, + pub own_adaptor_sk: SecretKey, + pub counter_adaptor_pk: PublicKey, + pub sub_channel_id: ChannelId, +} + +pub(crate) struct SubChannelSignInfo { + pub funding_info: FundingInfo, + pub own_adaptor_sk: SecretKey, +} + +pub(crate) struct SubChannelVerifyInfo { + pub funding_info: FundingInfo, + pub counter_adaptor_pk: PublicKey, + pub sub_channel_id: ChannelId, +} + /// Creates an [`OfferedChannel`] and an associated [`OfferedContract`] using /// the given parameter. pub fn offer_channel( @@ -76,6 +107,8 @@ pub fn offer_channel( wallet: &W, blockchain: &B, time: &T, + temporary_channel_id: ContractId, + is_sub_channel: bool, ) -> Result<(OfferedChannel, OfferedContract), Error> where W::Target: Wallet, @@ -88,9 +121,13 @@ where contract.fee_rate, wallet, blockchain, + !is_sub_channel, )?; let party_points = crate::utils::get_party_base_points(secp, wallet)?; + let temporary_contract_id = + crate::channel::generate_temporary_contract_id(temporary_channel_id, INITIAL_UPDATE_NUMBER); + let offered_contract = OfferedContract::new( contract, oracle_announcements.to_vec(), @@ -99,10 +136,9 @@ where counter_party, refund_delay, time.unix_time_now() as u32, + temporary_contract_id, ); - let temporary_channel_id = get_new_temporary_id(); - let per_update_seed = wallet.get_new_secret_key()?; let first_per_update_point = PublicKey::from_secret_key( @@ -142,19 +178,54 @@ where W::Target: Wallet, B::Target: Blockchain, { - assert_eq!(offered_channel.offered_contract_id, offered_contract.id); - - let total_collateral = offered_contract.total_collateral; - - let (accept_params, _, funding_inputs) = crate::utils::get_party_params( + accept_channel_offer_internal( secp, - total_collateral - offered_contract.offer_params.collateral, - offered_contract.fee_rate_per_vb, + offered_channel, + offered_contract, wallet, blockchain, - )?; + None, + None, + ) +} - let per_update_seed = wallet.get_new_secret_key()?; +pub(crate) fn accept_channel_offer_internal( + secp: &Secp256k1, + offered_channel: &OfferedChannel, + offered_contract: &OfferedContract, + wallet: &W, + blockchain: &B, + sub_channel_info: Option, + params: Option<( + PartyParams, + Vec, + PartyBasePoints, + PublicKey, + )>, +) -> Result<(AcceptedChannel, AcceptedContract, AcceptChannel), Error> +where + W::Target: Wallet, + B::Target: Blockchain, +{ + assert_eq!(offered_channel.offered_contract_id, offered_contract.id); + + 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, @@ -165,8 +236,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, @@ -181,23 +250,69 @@ where &offered_channel.per_update_point, ); - let DlcChannelTransactions { - buffer_transaction, - buffer_script_pubkey, - dlc_transactions, - } = dlc::channel::create_channel_transactions( - &offered_contract.offer_params, - &accept_params, - &offer_revoke_params, - &accept_revoke_params, - &offered_contract.contract_info[0].get_payouts(total_collateral)?, - offered_contract.refund_locktime, - offered_contract.fee_rate_per_vb, - 0, - offered_contract.cet_locktime, - offered_contract.fund_output_serial_id, - Sequence(offered_channel.cet_nsequence), - )?; + let ( + DlcChannelTransactions { + buffer_transaction, + dlc_transactions, + buffer_script_pubkey, + }, + own_buffer_adaptor_sk, + buffer_input_value, + buffer_input_spk, + funding_vout, + ) = if let Some(sub_channel_info) = sub_channel_info { + let SubChannelSignInfo { + funding_info, + own_adaptor_sk, + } = sub_channel_info; + let txs = dlc::channel::create_renewal_channel_transactions( + &offered_contract.offer_params, + &accept_params, + &offer_revoke_params, + &accept_revoke_params, + &funding_info.funding_tx, + &funding_info.funding_script_pubkey, + &offered_contract.contract_info[0].get_payouts(total_collateral)?, + offered_contract.refund_locktime, + offered_contract.fee_rate_per_vb, + offered_contract.cet_locktime, + Sequence(crate::manager::CET_NSEQUENCE), + Some(1), + Some(Sequence(crate::manager::CET_NSEQUENCE)), + )?; + ( + txs, + own_adaptor_sk, + funding_info.funding_input_value, + funding_info.funding_script_pubkey, + 1, + ) + } else { + let txs = dlc::channel::create_channel_transactions( + &offered_contract.offer_params, + &accept_params, + &offer_revoke_params, + &accept_revoke_params, + &offered_contract.contract_info[0].get_payouts(total_collateral)?, + offered_contract.refund_locktime, + offered_contract.fee_rate_per_vb, + 0, + offered_contract.cet_locktime, + offered_contract.fund_output_serial_id, + Sequence(offered_channel.cet_nsequence), + )?; + let accept_fund_sk = wallet.get_secret_key_for_pubkey(&accept_params.fund_pubkey)?; + let funding_output_value = txs.dlc_transactions.get_fund_output().value; + let funding_vout = txs.dlc_transactions.get_fund_output_index(); + let funding_spk = txs.dlc_transactions.funding_script_pubkey.clone(); + ( + txs, + accept_fund_sk, + funding_output_value, + funding_spk, + funding_vout, + ) + }; let own_base_secret_key = wallet.get_secret_key_for_pubkey(&accept_points.own_basepoint)?; @@ -205,18 +320,16 @@ where let channel_id = crate::utils::compute_id( dlc_transactions.fund.txid(), - dlc_transactions.get_fund_output_index() as u16, + funding_vout as u16, &offered_channel.temporary_channel_id, ); - let own_fund_sk = wallet.get_secret_key_for_pubkey(&accept_params.fund_pubkey)?; - let buffer_adaptor_signature = get_tx_adaptor_signature( secp, &buffer_transaction, - dlc_transactions.get_fund_output().value, - &dlc_transactions.funding_script_pubkey, - &own_fund_sk, + buffer_input_value, + &buffer_input_spk, + &own_buffer_adaptor_sk, &offer_revoke_params.publish_pk.inner, )?; @@ -242,8 +355,8 @@ where temporary_channel_id: offered_channel.temporary_channel_id, channel_id, accept_per_update_seed: PublicKey::from_secret_key(secp, &per_update_seed), - accept_buffer_adaptor_signature: buffer_adaptor_signature, counter_party: offered_contract.counter_party, + accept_buffer_adaptor_signature: buffer_adaptor_signature, }; let accept_channel = accepted_channel.get_accept_channel_msg( @@ -266,6 +379,32 @@ pub fn verify_and_sign_accepted_channel( accept_channel: &AcceptChannel, cet_nsequence: u32, signer: &S, + chain_monitor: &Mutex, +) -> Result<(SignedChannel, SignedContract, SignChannel), Error> +where + S::Target: Signer, +{ + verify_and_sign_accepted_channel_internal( + secp, + offered_channel, + offered_contract, + accept_channel, + cet_nsequence, + signer, + None, + chain_monitor, + ) +} + +pub(crate) fn verify_and_sign_accepted_channel_internal( + secp: &Secp256k1, + offered_channel: &OfferedChannel, + offered_contract: &OfferedContract, + accept_channel: &AcceptChannel, + cet_nsequence: u32, + signer: &S, + sub_channel_info: Option, + chain_monitor: &Mutex, ) -> Result<(SignedChannel, SignedContract, SignChannel), Error> where S::Target: Signer, @@ -299,14 +438,12 @@ where &offer_own_base_secret, ); - let offer_fund_sk = - signer.get_secret_key_for_pubkey(&offered_contract.offer_params.fund_pubkey)?; - let offer_revoke_params = offered_channel.party_points.get_revokable_params( secp, &accept_points.revocation_basepoint, &offered_channel.per_update_point, ); + let accept_revoke_params = accept_points.get_revokable_params( secp, &offered_channel.party_points.revocation_basepoint, @@ -315,27 +452,88 @@ where let total_collateral = offered_contract.total_collateral; - let DlcChannelTransactions { - buffer_transaction, - dlc_transactions, - buffer_script_pubkey, - } = dlc::channel::create_channel_transactions( - &offered_contract.offer_params, - &accept_params, - &offer_revoke_params, - &accept_revoke_params, - &offered_contract.contract_info[0].get_payouts(total_collateral)?, - offered_contract.refund_locktime, - offered_contract.fee_rate_per_vb, - 0, - offered_contract.cet_locktime, - offered_contract.fund_output_serial_id, - Sequence(cet_nsequence), - )?; + let ( + DlcChannelTransactions { + buffer_transaction, + dlc_transactions, + buffer_script_pubkey, + }, + own_buffer_adaptor_sk, + counter_buffer_adaptor_pk, + buffer_input_value, + buffer_input_spk, + is_sub_channel, + sub_channel_id, + ) = if let Some(sub_channel_info) = sub_channel_info { + let SubChannelSignVerifyInfo { + funding_info, + own_adaptor_sk, + counter_adaptor_pk, + sub_channel_id, + } = sub_channel_info; + let txs = dlc::channel::create_renewal_channel_transactions( + &offered_contract.offer_params, + &accept_params, + &offer_revoke_params, + &accept_revoke_params, + &funding_info.funding_tx, + &funding_info.funding_script_pubkey, + &offered_contract.contract_info[0].get_payouts(total_collateral)?, + offered_contract.refund_locktime, + offered_contract.fee_rate_per_vb, + offered_contract.cet_locktime, + Sequence(crate::manager::CET_NSEQUENCE), + Some(1), + Some(Sequence(crate::manager::CET_NSEQUENCE)), + )?; + ( + txs, + own_adaptor_sk, + counter_adaptor_pk, + funding_info.funding_input_value, + funding_info.funding_script_pubkey, + true, + Some(sub_channel_id), + ) + } else { + let txs = dlc::channel::create_channel_transactions( + &offered_contract.offer_params, + &accept_params, + &offer_revoke_params, + &accept_revoke_params, + &offered_contract.contract_info[0].get_payouts(total_collateral)?, + offered_contract.refund_locktime, + offered_contract.fee_rate_per_vb, + 0, + offered_contract.cet_locktime, + offered_contract.fund_output_serial_id, + Sequence(cet_nsequence), + )?; + let offer_fund_sk = + signer.get_secret_key_for_pubkey(&offered_contract.offer_params.fund_pubkey)?; + let counter_fund_pk = accept_params.fund_pubkey; + let funding_output_value = txs.dlc_transactions.get_fund_output().value; + let funding_spk = txs.dlc_transactions.funding_script_pubkey.clone(); + ( + txs, + offer_fund_sk, + counter_fund_pk, + funding_output_value, + funding_spk, + false, + None, + ) + }; + + let fund_output_index = if is_sub_channel { + 1 + } else { + dlc_transactions.get_fund_output_index() + }; let channel_id = crate::utils::compute_id( dlc_transactions.fund.txid(), - dlc_transactions.get_fund_output_index() as u16, + fund_output_index as u16, &offered_channel.temporary_channel_id, ); @@ -364,9 +562,9 @@ where verify_tx_adaptor_signature( secp, &buffer_transaction, - dlc_transactions.get_fund_output().value, + buffer_input_value, &dlc_transactions.funding_script_pubkey, - &signed_contract.accepted_contract.accept_params.fund_pubkey, + &counter_buffer_adaptor_pk, &offer_revoke_params.publish_pk.inner, &accept_channel.buffer_adaptor_signature, )?; @@ -374,12 +572,20 @@ where let own_buffer_adaptor_signature = get_tx_adaptor_signature( secp, &buffer_transaction, - dlc_transactions.get_fund_output().value, - &dlc_transactions.funding_script_pubkey, - &offer_fund_sk, + buffer_input_value, + &buffer_input_spk, + &own_buffer_adaptor_sk, &accept_revoke_params.publish_pk.inner, )?; + chain_monitor.lock().unwrap().add_tx( + buffer_transaction.txid(), + ChannelInfo { + channel_id, + tx_type: TxType::BufferTx, + }, + ); + let signed_channel = SignedChannel { counter_party: signed_contract .accepted_contract @@ -395,14 +601,15 @@ where counter_buffer_adaptor_signature: accept_channel.buffer_adaptor_signature, buffer_transaction, is_offer: true, + total_collateral, }, update_idx: INITIAL_UPDATE_NUMBER, channel_id, temporary_channel_id: offered_channel.temporary_channel_id, roll_back_state: None, fund_tx: dlc_transactions.fund.clone(), - fund_script_pubkey: dlc_transactions.funding_script_pubkey.clone(), - fund_output_index: dlc_transactions.get_fund_output_index(), + fund_script_pubkey: dlc_transactions.funding_script_pubkey, + fund_output_index, own_params: offered_contract.offer_params.clone(), own_per_update_point: offered_channel.per_update_point, own_per_update_seed: offered_channel @@ -413,6 +620,7 @@ where .accepted_contract .offered_contract .fee_rate_per_vb, + sub_channel_id, }; let sign_channel = SignChannel { @@ -435,6 +643,30 @@ pub fn verify_signed_channel( accepted_contract: &AcceptedContract, sign_channel: &SignChannel, signer: &S, + chain_monitor: &Mutex, +) -> Result<(SignedChannel, SignedContract), Error> +where + S::Target: Signer, +{ + verify_signed_channel_internal( + secp, + accepted_channel, + accepted_contract, + sign_channel, + signer, + None, + chain_monitor, + ) +} + +pub(crate) fn verify_signed_channel_internal( + secp: &Secp256k1, + accepted_channel: &AcceptedChannel, + accepted_contract: &AcceptedContract, + sign_channel: &SignChannel, + signer: &S, + sub_channel_info: Option, + chain_monitor: &Mutex, ) -> Result<(SignedChannel, SignedContract), Error> where S::Target: Signer, @@ -446,12 +678,40 @@ where let counter_own_pk = accepted_channel .offer_base_points .get_own_pk(secp, &accepted_channel.offer_per_update_point); + + let ( + buffer_input_spk, + buffer_input_value, + counter_buffer_adaptor_key, + is_sub_channel, + sub_channel_id, + ) = if let Some(sub_channel_info) = sub_channel_info { + ( + sub_channel_info.funding_info.funding_script_pubkey.clone(), + sub_channel_info.funding_info.funding_input_value, + sub_channel_info.counter_adaptor_pk, + true, + Some(sub_channel_info.sub_channel_id), + ) + } else { + ( + accepted_contract + .dlc_transactions + .funding_script_pubkey + .clone(), + accepted_contract.dlc_transactions.get_fund_output().value, + accepted_contract.offered_contract.offer_params.fund_pubkey, + false, + None, + ) + }; + verify_tx_adaptor_signature( secp, &accepted_channel.buffer_transaction, - accepted_contract.dlc_transactions.get_fund_output().value, - &accepted_contract.dlc_transactions.funding_script_pubkey, - &accepted_contract.offered_contract.offer_params.fund_pubkey, + buffer_input_value, + &buffer_input_spk, + &counter_buffer_adaptor_key, &own_publish_pk, &sign_channel.buffer_adaptor_signature, )?; @@ -471,6 +731,20 @@ where Some(accepted_channel.channel_id), )?; + let fund_output_index = if is_sub_channel { + 1 + } else { + accepted_contract.dlc_transactions.get_fund_output_index() + }; + + chain_monitor.lock().unwrap().add_tx( + accepted_channel.buffer_transaction.txid(), + ChannelInfo { + channel_id: accepted_channel.channel_id, + tx_type: TxType::BufferTx, + }, + ); + let signed_channel = SignedChannel { counter_party: signed_contract .accepted_contract @@ -482,7 +756,7 @@ where counter_points: accepted_channel.offer_base_points.clone(), counter_per_update_point: accepted_channel.offer_per_update_point, counter_params: accepted_contract.offered_contract.offer_params.clone(), - fund_output_index: accepted_contract.dlc_transactions.get_fund_output_index(), + fund_output_index, own_params: accepted_contract.accept_params.clone(), own_per_update_point: accepted_channel.accept_per_update_point, state: SignedChannelState::Established { @@ -491,6 +765,7 @@ where counter_buffer_adaptor_signature: sign_channel.buffer_adaptor_signature, buffer_transaction: accepted_channel.buffer_transaction.clone(), is_offer: false, + total_collateral: accepted_contract.offered_contract.total_collateral, }, update_idx: INITIAL_UPDATE_NUMBER, fund_tx, @@ -505,6 +780,7 @@ where .accepted_contract .offered_contract .fee_rate_per_vb, + sub_channel_id, }; Ok((signed_channel, signed_contract)) @@ -575,9 +851,19 @@ pub fn on_settle_offer( )); } + let total_collateral = + signed_channel.own_params.collateral + signed_channel.counter_params.collateral; + + if settle_offer.counter_payout > total_collateral { + return Err(Error::InvalidState( + "Proposed settle offer payout greater than total collateral".to_string(), + )); + } + let mut new_state = SignedChannelState::SettledReceived { own_payout: settle_offer.counter_payout, counter_next_per_update_point: settle_offer.next_per_update_point, + counter_payout: total_collateral - settle_offer.counter_payout, }; std::mem::swap(&mut signed_channel.state, &mut new_state); @@ -597,22 +883,53 @@ pub fn settle_channel_accept( peer_timeout: u64, signer: &S, time: &T, + chain_monitor: &Mutex, ) -> Result where S::Target: Signer, T::Target: Time, { - let (own_payout, counter_next_per_update_point) = if let SignedChannelState::SettledReceived { - own_payout, - counter_next_per_update_point, - } = channel.state - { - (own_payout, counter_next_per_update_point) - } else { - return Err(Error::InvalidState( - "Signed channel was not in SettledReceived state as expected.".to_string(), - )); - }; + settle_channel_accept_internal( + secp, + channel, + csv_timelock, + lock_time, + peer_timeout, + signer, + time, + None, + chain_monitor, + ) +} + +pub(crate) fn settle_channel_accept_internal( + secp: &Secp256k1, + channel: &mut SignedChannel, + csv_timelock: u32, + lock_time: u32, + peer_timeout: u64, + signer: &S, + time: &T, + own_settle_adaptor_sk: Option, + chain_monitor: &Mutex, +) -> Result +where + S::Target: Signer, + T::Target: Time, +{ + let (own_payout, counter_next_per_update_point, counter_payout) = + if let SignedChannelState::SettledReceived { + own_payout, + counter_next_per_update_point, + counter_payout, + } = channel.state + { + (own_payout, counter_next_per_update_point, counter_payout) + } else { + return Err(Error::InvalidState( + "Signed channel was not in SettledReceived state as expected.".to_string(), + )); + }; let per_update_seed_pk = channel.own_per_update_seed; let per_update_seed = signer.get_secret_key_for_pubkey(&per_update_seed_pk)?; @@ -630,19 +947,27 @@ where let final_offer_payout = total_collateral - own_payout + fee_remainder / 2; let final_accept_payout = own_payout + fee_remainder / 2; - let fund_tx = &channel.fund_tx; let fund_vout = channel.fund_output_index; let funding_script_pubkey = &channel.fund_script_pubkey; - let own_fund_sk = signer.get_secret_key_for_pubkey(&channel.own_params.fund_pubkey)?; + let own_adaptor_sk = if let Some(own_settle_adaptor_sk) = own_settle_adaptor_sk { + own_settle_adaptor_sk + } else { + signer.get_secret_key_for_pubkey(&channel.own_params.fund_pubkey)? + }; + + let settle_input_outpoint = OutPoint { + txid: channel.fund_tx.txid(), + vout: channel.fund_output_index as u32, + }; let (settle_tx, settle_adaptor_signature) = get_settle_tx_and_adaptor_sig( secp, &own_next_per_update_point, - fund_tx, - fund_vout, + &settle_input_outpoint, + channel.fund_tx.output[fund_vout].value, funding_script_pubkey, - &own_fund_sk, + &own_adaptor_sk, &channel.counter_points, &channel.own_points, &counter_next_per_update_point, @@ -654,6 +979,14 @@ where channel.fee_rate_per_vb, )?; + chain_monitor.lock().unwrap().add_tx( + settle_tx.txid(), + ChannelInfo { + channel_id: channel.channel_id, + tx_type: TxType::SettleTx, + }, + ); + channel.state = SignedChannelState::SettledAccepted { counter_next_per_update_point, own_next_per_update_point, @@ -661,6 +994,7 @@ where own_settle_adaptor_signature: settle_adaptor_signature, timeout: time.unix_time_now() + peer_timeout, own_payout, + counter_payout, }; let msg = SettleAccept { @@ -685,6 +1019,39 @@ pub fn settle_channel_confirm( peer_timeout: u64, signer: &S, time: &T, + chain_monitor: &Mutex, +) -> Result +where + T::Target: Time, + S::Target: Signer, +{ + settle_channel_confirm_internal( + secp, + channel, + settle_channel_accept, + csv_timelock, + lock_time, + peer_timeout, + signer, + time, + None, + None, + chain_monitor, + ) +} + +pub(crate) fn settle_channel_confirm_internal( + secp: &Secp256k1, + channel: &mut SignedChannel, + settle_channel_accept: &SettleAccept, + csv_timelock: u32, + lock_time: u32, + peer_timeout: u64, + signer: &S, + time: &T, + own_settle_adaptor_sk: Option, + counter_settle_adaptor_pk: Option, + chain_monitor: &Mutex, ) -> Result where T::Target: Time, @@ -709,19 +1076,28 @@ where let final_offer_payout = total_collateral - counter_payout + fee_remainder / 2; let final_accept_payout = counter_payout + fee_remainder / 2; - let fund_tx = &channel.fund_tx; - let fund_vout = channel.fund_output_index; + let settle_input_outpoint = OutPoint { + txid: channel.fund_tx.txid(), + vout: channel.fund_output_index as u32, + }; let funding_script_pubkey = &channel.fund_script_pubkey; - let own_fund_sk = signer.get_secret_key_for_pubkey(&channel.own_params.fund_pubkey)?; + let own_settle_adaptor_sk = if let Some(own_settle_adaptor_sk) = own_settle_adaptor_sk { + own_settle_adaptor_sk + } else { + signer.get_secret_key_for_pubkey(&channel.own_params.fund_pubkey)? + }; + + let counter_settle_adaptor_pk = + counter_settle_adaptor_pk.unwrap_or(channel.counter_params.fund_pubkey); let (settle_tx, settle_adaptor_signature) = get_settle_tx_and_adaptor_sig( secp, &next_per_update_point, - fund_tx, - fund_vout, + &settle_input_outpoint, + channel.fund_tx.output[channel.fund_output_index].value, funding_script_pubkey, - &own_fund_sk, + &own_settle_adaptor_sk, &channel.own_points, &channel.counter_points, &settle_channel_accept.next_per_update_point, @@ -731,11 +1107,19 @@ where lock_time, Some(( &settle_channel_accept.settle_adaptor_signature, - channel.counter_params.fund_pubkey, + counter_settle_adaptor_pk, )), channel.fee_rate_per_vb, )?; + chain_monitor.lock().unwrap().add_tx( + settle_tx.txid(), + ChannelInfo { + channel_id: channel.channel_id, + tx_type: TxType::SettleTx, + }, + ); + let per_update_seed_pk = channel.own_per_update_seed; let per_update_seed = signer.get_secret_key_for_pubkey(&per_update_seed_pk)?; @@ -751,7 +1135,8 @@ where counter_next_per_update_point: settle_channel_accept.next_per_update_point, own_settle_adaptor_signature: settle_adaptor_signature, timeout: time.unix_time_now() + peer_timeout, - own_payout: total_collateral - counter_payout, + own_payout: final_offer_payout, + counter_payout: final_accept_payout, }; channel.state = state; @@ -775,6 +1160,19 @@ pub fn settle_channel_finalize( settle_channel_confirm: &SettleConfirm, signer: &S, ) -> Result +where + S::Target: Signer, +{ + settle_channel_finalize_internal(secp, channel, settle_channel_confirm, signer, None) +} + +pub(crate) fn settle_channel_finalize_internal( + secp: &Secp256k1, + channel: &mut SignedChannel, + settle_channel_confirm: &SettleConfirm, + signer: &S, + counter_settle_adaptor_pk: Option, +) -> Result where S::Target: Signer, { @@ -783,18 +1181,24 @@ where counter_next_per_update_point, settle_tx, own_settle_adaptor_signature, + own_payout, + counter_payout, ) = match &channel.state { SignedChannelState::SettledAccepted { counter_next_per_update_point, own_next_per_update_point, settle_tx, own_settle_adaptor_signature, + own_payout, + counter_payout, .. } => ( own_next_per_update_point, counter_next_per_update_point, settle_tx, own_settle_adaptor_signature, + *own_payout, + *counter_payout, ), _ => { return Err(Error::InvalidState( @@ -812,12 +1216,15 @@ where own_next_per_update_point, ); + let counter_settle_adaptor_pk = + counter_settle_adaptor_pk.unwrap_or(channel.counter_params.fund_pubkey); + verify_tx_adaptor_signature( secp, settle_tx, channel.fund_tx.output[channel.fund_output_index].value, &channel.fund_script_pubkey, - &channel.counter_params.fund_pubkey, + &counter_settle_adaptor_pk, &accept_revoke_params.publish_pk.inner, &settle_channel_confirm.settle_adaptor_signature, )?; @@ -849,6 +1256,8 @@ where settle_tx: settle_tx.clone(), counter_settle_adaptor_signature: settle_channel_confirm.settle_adaptor_signature, own_settle_adaptor_signature: *own_settle_adaptor_signature, + own_payout, + counter_payout, }; channel.own_per_update_point = *own_next_per_update_point; @@ -880,6 +1289,8 @@ pub fn settle_channel_on_finalize( counter_next_per_update_point, own_next_per_update_point, own_settle_adaptor_signature, + own_payout, + counter_payout, ) = match &channel.state { SignedChannelState::SettledConfirmed { settle_tx, @@ -887,6 +1298,8 @@ pub fn settle_channel_on_finalize( counter_next_per_update_point, own_next_per_update_point, own_settle_adaptor_signature, + own_payout, + counter_payout, .. } => ( settle_tx.clone(), @@ -894,6 +1307,8 @@ pub fn settle_channel_on_finalize( *counter_next_per_update_point, *own_next_per_update_point, *own_settle_adaptor_signature, + *own_payout, + *counter_payout, ), _ => { return Err(Error::InvalidState( @@ -924,6 +1339,8 @@ pub fn settle_channel_on_finalize( settle_tx, counter_settle_adaptor_signature, own_settle_adaptor_signature, + own_payout, + counter_payout, }; channel.roll_back_state = None; @@ -967,6 +1384,46 @@ where S::Target: Signer, T::Target: Time, { + // Validity checks. + match &signed_channel.state { + SignedChannelState::Established { + total_collateral, .. + } => { + if *total_collateral + != contract_input.accept_collateral + contract_input.offer_collateral + { + return Err(Error::InvalidParameters( + "Sum of collaterals in contract must equal total collateral in channel." + .to_string(), + )); + } + } + SignedChannelState::Settled { + own_payout, + counter_payout, + .. + } => { + if contract_input.offer_collateral != *own_payout + || contract_input.accept_collateral != *counter_payout + { + return Err(Error::InvalidParameters( + "Contract collateral not equal to each party's balance in the channel" + .to_string(), + )); + } + } + s => { + return Err(Error::InvalidState(format!( + "Can only renewed established or closed channels, not {s}." + ))); + } + }; + + let temporary_contract_id: ContractId = crate::channel::generate_temporary_contract_id( + signed_channel.channel_id, + signed_channel.update_idx, + ); + let mut offered_contract = OfferedContract::new( contract_input, oracle_announcements, @@ -975,6 +1432,7 @@ where &signed_channel.counter_party, refund_delay, time.unix_time_now() as u32, + temporary_contract_id, ); offered_contract.fund_output_serial_id = 0; @@ -1004,7 +1462,6 @@ where let msg = RenewOffer { channel_id: signed_channel.channel_id, - temporary_contract_id: offered_contract.id, counter_payout, next_per_update_point, contract_info: (&offered_contract).into(), @@ -1032,8 +1489,13 @@ pub fn on_renew_offer( )); } - let offered_contract = OfferedContract { - id: renew_offer.temporary_contract_id, + let temporary_contract_id = crate::channel::generate_temporary_contract_id( + signed_channel.channel_id, + signed_channel.update_idx, + ); + + let offered_contract = OfferedContract { + id: temporary_contract_id, is_offer_party: false, contract_info: crate::conversion_utils::get_contract_info_and_announcements( &renew_offer.contract_info, @@ -1077,6 +1539,30 @@ pub fn accept_channel_renewal( signer: &S, time: &T, ) -> Result<(AcceptedContract, RenewAccept), Error> +where + S::Target: Signer, + T::Target: Time, +{ + accept_channel_renewal_internal( + secp, + signed_channel, + offered_contract, + cet_nsequence, + peer_timeout, + signer, + time, + ) +} + +pub(crate) fn accept_channel_renewal_internal( + secp: &Secp256k1, + signed_channel: &mut SignedChannel, + offered_contract: &OfferedContract, + cet_nsequence: u32, + peer_timeout: u64, + signer: &S, + time: &T, +) -> Result<(AcceptedContract, RenewAccept), Error> where S::Target: Signer, T::Target: Time, @@ -1094,7 +1580,6 @@ where } }; - let own_fund_sk = signer.get_secret_key_for_pubkey(&signed_channel.own_params.fund_pubkey)?; let own_base_secret_key = signer.get_secret_key_for_pubkey(&signed_channel.own_points.own_basepoint)?; let per_update_seed = signer.get_secret_key_for_pubkey(&signed_channel.own_per_update_seed)?; @@ -1120,6 +1605,12 @@ where &accept_per_update_point, ); + let (fund_vout, buffer_nsequence) = if signed_channel.is_sub_channel() { + (Some(1), Some(Sequence(crate::manager::CET_NSEQUENCE))) + } else { + (None, None) + }; + let DlcChannelTransactions { buffer_transaction, buffer_script_pubkey, @@ -1136,15 +1627,8 @@ where offered_contract.fee_rate_per_vb, 0, Sequence(cet_nsequence), - )?; - - let buffer_adaptor_signature = get_tx_adaptor_signature( - secp, - &buffer_transaction, - dlc_transactions.get_fund_output().value, - &dlc_transactions.funding_script_pubkey, - &own_fund_sk, - &offer_revoke_params.publish_pk.inner, + fund_vout, + buffer_nsequence, )?; let own_secret_key = derive_private_key(secp, &accept_per_update_point, &own_base_secret_key); @@ -1166,7 +1650,6 @@ where accept_per_update_point, buffer_transaction, buffer_script_pubkey, - accept_buffer_adaptor_signature: buffer_adaptor_signature, timeout: time.unix_time_now() + peer_timeout, own_payout, }; @@ -1176,7 +1659,6 @@ where let renew_accept = RenewAccept { channel_id: signed_channel.channel_id, next_per_update_point: accept_per_update_point, - buffer_adaptor_signature, cet_adaptor_signatures: (&adaptor_sigs as &[_]).into(), refund_signature: accepted_contract.accept_refund_signature, }; @@ -1198,6 +1680,34 @@ pub fn verify_renew_accept_and_confirm( signer: &S, time: &T, ) -> Result<(SignedContract, RenewConfirm), Error> +where + S::Target: Signer, + T::Target: Time, +{ + verify_renew_accept_and_confirm_internal( + secp, + renew_accept, + signed_channel, + offered_contract, + cet_nsequence, + peer_timeout, + signer, + time, + None, + ) +} + +pub(crate) fn verify_renew_accept_and_confirm_internal( + secp: &Secp256k1, + renew_accept: &RenewAccept, + signed_channel: &mut SignedChannel, + offered_contract: &OfferedContract, + cet_nsequence: u32, + peer_timeout: u64, + signer: &S, + time: &T, + own_buffer_adaptor_sk: Option, +) -> Result<(SignedContract, RenewConfirm), Error> where S::Target: Signer, T::Target: Time, @@ -1207,20 +1717,13 @@ where let own_base_secret_key = signer.get_secret_key_for_pubkey(&signed_channel.own_points.own_basepoint)?; - let per_update_seed = signer.get_secret_key_for_pubkey(&signed_channel.own_per_update_seed)?; - - let prev_per_update_secret = SecretKey::from_slice(&build_commitment_secret( - per_update_seed.as_ref(), - signed_channel.update_idx, - ))?; - let offer_per_update_point = get_signed_channel_state!(signed_channel, RenewOffered, offer_next_per_update_point)?; let offer_revoke_params = signed_channel.own_points.get_revokable_params( secp, &signed_channel.counter_points.revocation_basepoint, - &offer_per_update_point, + offer_per_update_point, ); let accept_revoke_params = signed_channel.counter_points.get_revokable_params( secp, @@ -1232,6 +1735,11 @@ where let own_payout = total_collateral - get_signed_channel_state!(signed_channel, RenewOffered, counter_payout)?; + let (fund_vout, buffer_nsequence) = if signed_channel.is_sub_channel() { + (Some(1), Some(Sequence(crate::manager::CET_NSEQUENCE))) + } else { + (None, None) + }; let DlcChannelTransactions { buffer_transaction, @@ -1249,9 +1757,11 @@ where offered_contract.fee_rate_per_vb, 0, Sequence(cet_nsequence), + fund_vout, + buffer_nsequence, )?; - let offer_own_sk = derive_private_key(secp, &offer_per_update_point, &own_base_secret_key); + let offer_own_sk = derive_private_key(secp, offer_per_update_point, &own_base_secret_key); let cet_adaptor_signatures: Vec<_> = (&renew_accept.cet_adaptor_signatures).into(); let (signed_contract, cet_adaptor_signatures) = verify_accepted_and_sign_contract_internal( @@ -1270,42 +1780,38 @@ where Some(signed_channel.channel_id), )?; - verify_tx_adaptor_signature( - secp, - &buffer_transaction, - dlc_transactions.get_fund_output().value, - &dlc_transactions.funding_script_pubkey, - &signed_contract.accepted_contract.accept_params.fund_pubkey, - &offer_revoke_params.publish_pk.inner, - &renew_accept.buffer_adaptor_signature, - )?; + let buffer_input_value = if signed_channel.is_sub_channel() { + signed_channel.fund_tx.output[1].value + } else { + signed_channel.fund_tx.output[signed_channel.fund_output_index].value + }; + let own_buffer_adaptor_sk = own_buffer_adaptor_sk.as_ref().unwrap_or(&own_fund_sk); let own_buffer_adaptor_signature = get_tx_adaptor_signature( secp, &buffer_transaction, - dlc_transactions.get_fund_output().value, + buffer_input_value, &dlc_transactions.funding_script_pubkey, - &own_fund_sk, + own_buffer_adaptor_sk, &accept_revoke_params.publish_pk.inner, )?; let state = SignedChannelState::RenewConfirmed { contract_id: signed_contract.accepted_contract.get_contract_id(), - offer_per_update_point, + offer_per_update_point: *offer_per_update_point, accept_per_update_point: renew_accept.next_per_update_point, buffer_transaction, buffer_script_pubkey, offer_buffer_adaptor_signature: own_buffer_adaptor_signature, - accept_buffer_adaptor_signature: renew_accept.buffer_adaptor_signature, timeout: time.unix_time_now() + peer_timeout, own_payout, + total_collateral: offered_contract.total_collateral, }; signed_channel.state = state; let renew_confirm = RenewConfirm { channel_id: signed_channel.channel_id, - per_update_secret: prev_per_update_secret, buffer_adaptor_signature: own_buffer_adaptor_signature, cet_adaptor_signatures: (&cet_adaptor_signatures as &[_]).into(), refund_signature: signed_contract.offer_refund_signature, @@ -1318,20 +1824,54 @@ where /// [`RenewAccept`] message, verifying the message and updating the state of the /// channel and associated contract the same time. Expects the channel to be in /// [`SignedChannelState::RenewAccepted`] state. -pub fn verify_renew_confirm_and_finalize( +pub fn verify_renew_confirm_and_finalize( secp: &Secp256k1, signed_channel: &mut SignedChannel, accepted_contract: &AcceptedContract, renew_confirm: &RenewConfirm, + peer_timeout: u64, + time: &T, signer: &S, + chain_monitor: &Mutex, ) -> Result<(SignedContract, RenewFinalize), Error> where + T::Target: Time, + S::Target: Signer, +{ + verify_renew_confirm_and_finalize_internal( + secp, + signed_channel, + accepted_contract, + renew_confirm, + peer_timeout, + time, + signer, + None, + None, + chain_monitor, + ) +} + +pub(crate) fn verify_renew_confirm_and_finalize_internal( + secp: &Secp256k1, + signed_channel: &mut SignedChannel, + accepted_contract: &AcceptedContract, + renew_confirm: &RenewConfirm, + peer_timeout: u64, + time: &T, + signer: &S, + counter_buffer_own_pk: Option, + own_buffer_adaptor_sk: Option, + chain_monitor: &Mutex, +) -> Result<(SignedContract, RenewFinalize), Error> +where + T::Target: Time, S::Target: Signer, { let ( - offer_per_update_point, - accept_per_update_point, - accept_buffer_adaptor_signature, + &offer_per_update_point, + &accept_per_update_point, + own_payout, buffer_transaction, buffer_script_pubkey, ) = get_signed_channel_state!( @@ -1339,7 +1879,7 @@ where RenewAccepted, offer_per_update_point, accept_per_update_point, - accept_buffer_adaptor_signature | buffer_transaction, + own_payout | buffer_transaction, buffer_script_pubkey )?; @@ -1350,16 +1890,20 @@ where let counter_own_pk = signed_channel .counter_points .get_own_pk(secp, &offer_per_update_point); + let counter_buffer_own_pk = counter_buffer_own_pk + .as_ref() + .unwrap_or(&accepted_contract.offered_contract.offer_params.fund_pubkey); verify_tx_adaptor_signature( secp, buffer_transaction, - accepted_contract.dlc_transactions.get_fund_output().value, - &accepted_contract.dlc_transactions.funding_script_pubkey, - &accepted_contract.offered_contract.offer_params.fund_pubkey, + signed_channel.fund_tx.output[signed_channel.fund_output_index].value, + &signed_channel.fund_script_pubkey, + counter_buffer_own_pk, &own_publish_pk, &renew_confirm.buffer_adaptor_signature, )?; + let cet_adaptor_signatures: Vec<_> = (&renew_confirm.cet_adaptor_signatures).into(); let (signed_contract, _) = verify_signed_contract_internal( secp, @@ -1376,24 +1920,7 @@ where Some(signed_channel.channel_id), )?; - signed_channel.state = SignedChannelState::Established { - signed_contract_id: signed_contract.accepted_contract.get_contract_id(), - own_buffer_adaptor_signature: accept_buffer_adaptor_signature, - counter_buffer_adaptor_signature: renew_confirm.buffer_adaptor_signature, - buffer_transaction: buffer_transaction.clone(), - is_offer: false, - }; - - signed_channel.update_idx -= 1; - - signed_channel - .counter_party_commitment_secrets - .provide_secret( - signed_channel.update_idx + 1, - *renew_confirm.per_update_secret.as_ref(), - ) - .map_err(|_| Error::InvalidParameters("Provided secret was invalid".to_string()))?; - + let prev_offer_per_update_point = signed_channel.counter_per_update_point; signed_channel.counter_per_update_point = offer_per_update_point; signed_channel.own_per_update_point = accept_per_update_point; @@ -1401,47 +1928,134 @@ where let prev_per_update_secret = SecretKey::from_slice(&build_commitment_secret( per_update_seed.as_ref(), - signed_channel.update_idx + 1, + signed_channel.update_idx, ))?; + let offer_revoke_params = signed_channel.counter_points.get_revokable_params( + secp, + &signed_channel.own_points.revocation_basepoint, + &signed_channel.counter_per_update_point, + ); + + let own_fund_sk = signer.get_secret_key_for_pubkey(&signed_channel.own_params.fund_pubkey)?; + let own_buffer_adaptor_sk = own_buffer_adaptor_sk.as_ref().unwrap_or(&own_fund_sk); + + let buffer_input_value = if signed_channel.is_sub_channel() { + signed_channel.fund_tx.output[1].value + } else { + signed_channel.fund_tx.output[signed_channel.fund_output_index].value + }; + + let buffer_adaptor_signature = get_tx_adaptor_signature( + secp, + buffer_transaction, + buffer_input_value, + &signed_channel.fund_script_pubkey, + own_buffer_adaptor_sk, + &offer_revoke_params.publish_pk.inner, + )?; + let total_collateral = + signed_channel.own_params.collateral + signed_channel.counter_params.collateral; + + chain_monitor.lock().unwrap().add_tx( + buffer_transaction.txid(), + ChannelInfo { + channel_id: signed_channel.channel_id, + tx_type: TxType::BufferTx, + }, + ); + + signed_channel.state = SignedChannelState::RenewFinalized { + contract_id: signed_contract.accepted_contract.get_contract_id(), + prev_offer_per_update_point, + buffer_transaction: buffer_transaction.clone(), + buffer_script_pubkey: buffer_script_pubkey.clone(), + offer_buffer_adaptor_signature: renew_confirm.buffer_adaptor_signature, + accept_buffer_adaptor_signature: buffer_adaptor_signature, + timeout: time.unix_time_now() + peer_timeout, + own_payout: *own_payout, + total_collateral, + }; + let renew_finalize = RenewFinalize { channel_id: signed_channel.channel_id, per_update_secret: prev_per_update_secret, + buffer_adaptor_signature, }; Ok((signed_contract, renew_finalize)) } /// Verify the given [`RenewFinalize`] and update the state of the channel. -pub fn renew_channel_on_finalize( +pub fn renew_channel_on_finalize( + secp: &Secp256k1, signed_channel: &mut SignedChannel, renew_finalize: &RenewFinalize, -) -> Result<(), Error> { + counter_buffer_own_pk: Option, + signer: &S, +) -> Result +where + S::Target: Signer, +{ let ( contract_id, + total_collateral, offer_per_update_point, accept_per_update_point, offer_buffer_adaptor_signature, - accept_buffer_adaptor_signature, buffer_transaction, ) = get_signed_channel_state!( signed_channel, RenewConfirmed, contract_id, + total_collateral, offer_per_update_point, accept_per_update_point, - offer_buffer_adaptor_signature, - accept_buffer_adaptor_signature | buffer_transaction + offer_buffer_adaptor_signature | buffer_transaction + )?; + + let offer_revoke_params = signed_channel.own_points.get_revokable_params( + secp, + &signed_channel.counter_points.revocation_basepoint, + offer_per_update_point, + ); + + let buffer_input_value = if signed_channel.is_sub_channel() { + signed_channel.fund_tx.output[1].value + } else { + signed_channel.fund_tx.output[signed_channel.fund_output_index].value + }; + let counter_buffer_own_pk = counter_buffer_own_pk + .as_ref() + .unwrap_or(&signed_channel.counter_params.fund_pubkey); + + verify_tx_adaptor_signature( + secp, + buffer_transaction, + buffer_input_value, + &signed_channel.fund_script_pubkey, + counter_buffer_own_pk, + &offer_revoke_params.publish_pk.inner, + &renew_finalize.buffer_adaptor_signature, )?; let state = SignedChannelState::Established { - signed_contract_id: contract_id, - counter_buffer_adaptor_signature: accept_buffer_adaptor_signature, - own_buffer_adaptor_signature: offer_buffer_adaptor_signature, + signed_contract_id: *contract_id, + counter_buffer_adaptor_signature: renew_finalize.buffer_adaptor_signature, + own_buffer_adaptor_signature: *offer_buffer_adaptor_signature, buffer_transaction: buffer_transaction.clone(), is_offer: true, + total_collateral: *total_collateral, }; + if PublicKey::from_secret_key(secp, &renew_finalize.per_update_secret) + != signed_channel.counter_per_update_point + { + return Err(Error::InvalidParameters( + "Invalid per update secret in channel renew finalize".to_string(), + )); + } + signed_channel .counter_party_commitment_secrets .provide_secret( @@ -1450,13 +2064,78 @@ pub fn renew_channel_on_finalize( ) .map_err(|_| Error::InvalidParameters("Provided secret was invalid".to_string()))?; - signed_channel.own_per_update_point = offer_per_update_point; - signed_channel.counter_per_update_point = accept_per_update_point; + let per_update_seed = signer.get_secret_key_for_pubkey(&signed_channel.own_per_update_seed)?; + + let prev_per_update_secret = SecretKey::from_slice(&build_commitment_secret( + per_update_seed.as_ref(), + signed_channel.update_idx, + ))?; + + signed_channel.own_per_update_point = *offer_per_update_point; + signed_channel.counter_per_update_point = *accept_per_update_point; signed_channel.state = state; signed_channel.roll_back_state = None; signed_channel.update_idx -= 1; + let msg = RenewRevoke { + channel_id: signed_channel.channel_id, + per_update_secret: prev_per_update_secret, + }; + + Ok(msg) +} + +/// Verify the given [`RenewRevoke`] and update the state of the channel. +pub fn renew_channel_on_revoke( + secp: &Secp256k1, + signed_channel: &mut SignedChannel, + renew_revoke: &RenewRevoke, +) -> Result<(), Error> { + let ( + contract_id, + total_collateral, + prev_offer_per_update_point, + offer_buffer_adaptor_signature, + accept_buffer_adaptor_signature, + buffer_transaction, + ) = get_signed_channel_state!( + signed_channel, + RenewFinalized, + contract_id, + total_collateral, + prev_offer_per_update_point, + offer_buffer_adaptor_signature, + accept_buffer_adaptor_signature | buffer_transaction + )?; + + if PublicKey::from_secret_key(secp, &renew_revoke.per_update_secret) + != *prev_offer_per_update_point + { + return Err(Error::InvalidParameters( + "Invalid per update secret in channel renew revoke".to_string(), + )); + } + + signed_channel + .counter_party_commitment_secrets + .provide_secret( + signed_channel.update_idx, + *renew_revoke.per_update_secret.as_ref(), + ) + .map_err(|_| Error::InvalidParameters("Provided secret was invalid".to_string()))?; + + signed_channel.update_idx -= 1; + + signed_channel.state = SignedChannelState::Established { + signed_contract_id: *contract_id, + counter_buffer_adaptor_signature: *accept_buffer_adaptor_signature, + own_buffer_adaptor_signature: *offer_buffer_adaptor_signature, + buffer_transaction: buffer_transaction.clone(), + is_offer: true, + total_collateral: *total_collateral, + }; + Ok(()) } @@ -1466,7 +2145,7 @@ pub fn renew_channel_on_finalize( pub fn reject_renew_offer(signed_channel: &mut SignedChannel) -> Result { let is_offer = get_signed_channel_state!(signed_channel, RenewOffered, is_offer)?; - if is_offer { + if *is_offer { return Err(Error::InvalidState( "Cannot reject own renew offer.".to_string(), )); @@ -1606,9 +2285,9 @@ where /// closing transaction and returning it. pub fn accept_collaborative_close_offer( secp: &Secp256k1, - signed_channel: &mut SignedChannel, + signed_channel: &SignedChannel, signer: &S, -) -> Result +) -> Result<(Transaction, Channel), Error> where S::Target: Signer, { @@ -1627,7 +2306,7 @@ where dlc::util::sign_multi_sig_input( secp, &mut close_tx, - &offer_signature, + offer_signature, &signed_channel.counter_params.fund_pubkey, &own_fund_sk, &signed_channel.fund_script_pubkey, @@ -1636,17 +2315,21 @@ where )?; // TODO(tibo): should only transition to close after confirmation. - signed_channel.state = SignedChannelState::CollaborativelyClosed; - Ok(close_tx) + let channel = Channel::CollaborativelyClosed(ClosedChannel { + counter_party: signed_channel.counter_party, + temporary_channel_id: signed_channel.temporary_channel_id, + channel_id: signed_channel.channel_id, + }); + Ok((close_tx, channel)) } fn get_settle_tx_and_adaptor_sig( secp: &Secp256k1, own_next_per_update_point: &PublicKey, - fund_tx: &Transaction, - fund_vout: usize, - funding_script_pubkey: &Script, - own_fund_sk: &SecretKey, + settle_input_outpoint: &OutPoint, + settle_input_value: u64, + settle_input_spk: &Script, + own_adaptor_sk: &SecretKey, offer_points: &PartyBasePoints, accept_points: &PartyBasePoints, counter_per_update_point: &PublicKey, @@ -1664,16 +2347,6 @@ fn get_settle_tx_and_adaptor_sig( (counter_per_update_point, own_next_per_update_point) }; - let fund_tx_in = TxIn { - previous_output: bitcoin::OutPoint { - txid: fund_tx.txid(), - vout: fund_vout as u32, - }, - script_sig: Script::new(), - sequence: Sequence::MAX, - witness: Witness::default(), - }; - let offer_revoke_params = offer_points.get_revokable_params( secp, &accept_points.revocation_basepoint, @@ -1687,14 +2360,14 @@ fn get_settle_tx_and_adaptor_sig( ); let settle_tx = dlc::channel::create_settle_transaction( - &fund_tx_in, + settle_input_outpoint, &offer_revoke_params, &accept_revoke_params, offer_payout, accept_payout, csv_timelock, lock_time, - fund_tx.output[fund_vout].value, + settle_input_value, fee_rate_per_vb, )?; @@ -1702,8 +2375,8 @@ fn get_settle_tx_and_adaptor_sig( verify_tx_adaptor_signature( secp, &settle_tx, - fund_tx.output[fund_vout].value, - funding_script_pubkey, + settle_input_value, + settle_input_spk, &fund_pk, &offer_revoke_params.publish_pk.inner, adaptor_sig, @@ -1719,9 +2392,9 @@ fn get_settle_tx_and_adaptor_sig( let settle_adaptor_signature = dlc::channel::get_tx_adaptor_signature( secp, &settle_tx, - fund_tx.output[fund_vout].value, - funding_script_pubkey, - own_fund_sk, + settle_input_value, + settle_input_spk, + own_adaptor_sk, &counter_pk, )?; @@ -1763,23 +2436,15 @@ pub fn on_reject(signed_channel: &mut SignedChannel) -> Result<(), Error> { pub fn initiate_unilateral_close_established_channel( secp: &Secp256k1, signed_channel: &mut SignedChannel, - confirmed_contract: &SignedContract, - contract_info: &ContractInfo, - attestations: &[(usize, OracleAttestation)], - adaptor_info: &AdaptorInfo, + buffer_adaptor_signature: EcdsaAdaptorSignature, + mut buffer_transaction: Transaction, signer: &S, + sub_channel: Option<(SubChannel, &ClosingSubChannel)>, + is_initiator: bool, ) -> Result<(), Error> where S::Target: Signer, { - let (buffer_adaptor_signature, buffer_transaction) = get_signed_channel_state!( - signed_channel, - Established, - counter_buffer_adaptor_signature | buffer_transaction - )?; - - let mut buffer_transaction = buffer_transaction.clone(); - let publish_base_secret = signer.get_secret_key_for_pubkey(&signed_channel.own_points.publish_basepoint)?; @@ -1791,18 +2456,115 @@ where let counter_buffer_signature = buffer_adaptor_signature.decrypt(&publish_sk)?; - let fund_sk = signer.get_secret_key_for_pubkey(&signed_channel.own_params.fund_pubkey)?; + if let Some((sub_channel, closing)) = sub_channel { + let signed_sub_channel = &closing.signed_sub_channel; + let own_base_secret_key = + signer.get_secret_key_for_pubkey(&sub_channel.own_base_points.own_basepoint)?; + let own_secret_key = derive_private_key( + secp, + &signed_sub_channel.own_per_split_point, + &own_base_secret_key, + ); + let sig = dlc::util::get_raw_sig_for_tx_input( + secp, + &buffer_transaction, + 0, + &signed_sub_channel.split_tx.output_script, + signed_sub_channel.split_tx.transaction.output[1].value, + &own_secret_key, + )?; - dlc::util::sign_multi_sig_input( - secp, - &mut buffer_transaction, - &counter_buffer_signature, - &signed_channel.counter_params.fund_pubkey, - &fund_sk, - &signed_channel.fund_script_pubkey, - signed_channel.fund_tx.output[signed_channel.fund_output_index].value, - 0, - )?; + let (own_pk, counter_pk, offer_params, accept_params) = { + let own_revoke_params = sub_channel.own_base_points.get_revokable_params( + secp, + &sub_channel + .counter_base_points + .as_ref() + .expect("to have counter base points") + .revocation_basepoint, + &signed_sub_channel.own_per_split_point, + ); + let counter_revoke_params = sub_channel + .counter_base_points + .as_ref() + .expect("to have counter base points") + .get_revokable_params( + secp, + &sub_channel.own_base_points.revocation_basepoint, + &signed_sub_channel.counter_per_split_point, + ); + if sub_channel.is_offer { + ( + own_revoke_params.own_pk, + counter_revoke_params.own_pk, + own_revoke_params, + counter_revoke_params, + ) + } else { + ( + own_revoke_params.own_pk, + counter_revoke_params.own_pk, + counter_revoke_params, + own_revoke_params, + ) + } + }; + + dlc::channel::satisfy_buffer_descriptor( + &mut buffer_transaction, + &offer_params, + &accept_params, + &own_pk.inner, + &sig, + &counter_pk, + &counter_buffer_signature, + )?; + } else { + let buffer_input_sk = + signer.get_secret_key_for_pubkey(&signed_channel.own_params.fund_pubkey)?; + dlc::util::sign_multi_sig_input( + secp, + &mut buffer_transaction, + &counter_buffer_signature, + &signed_channel.counter_params.fund_pubkey, + &buffer_input_sk, + &signed_channel.fund_script_pubkey, + signed_channel.fund_tx.output[signed_channel.fund_output_index].value, + 0, + )?; + } + + let contract_id = signed_channel.get_contract_id().ok_or_else(|| { + Error::InvalidState( + "Expected to be in a state with an associated contract id but was not.".to_string(), + ) + })?; + + signed_channel.state = SignedChannelState::Closing { + buffer_transaction, + contract_id, + is_initiator, + }; + + Ok(()) +} + +/// Extract the CET and computes the signature for it, and marks the channel as closed. +pub fn finalize_unilateral_close_settled_channel( + secp: &Secp256k1, + signed_channel: &SignedChannel, + confirmed_contract: &SignedContract, + contract_info: &ContractInfo, + attestations: &[(usize, OracleAttestation)], + adaptor_info: &AdaptorInfo, + signer: &S, + is_initiator: bool, +) -> Result<(Transaction, Channel), Error> +where + S::Target: Signer, +{ + let buffer_transaction = + get_signed_channel_state!(signed_channel, Closing, buffer_transaction)?; let (range_info, oracle_sigs) = crate::utils::get_range_info_and_oracle_sigs(contract_info, adaptor_info, attestations)?; @@ -1881,23 +2643,40 @@ where &adaptor_sigs[range_info.adaptor_index], &oracle_sigs, )?; - - signed_channel.state = SignedChannelState::Closing { - buffer_transaction, - signed_cet: cet, - contract_id: confirmed_contract.accepted_contract.get_contract_id(), - attestations: attestations.iter().map(|x| x.1.clone()).collect(), + let closed_channel = ClosedChannel { + counter_party: signed_channel.counter_party, + temporary_channel_id: signed_channel.temporary_channel_id, + channel_id: signed_channel.channel_id, + }; + let channel = if is_initiator { + Channel::Closed(closed_channel) + } else { + Channel::CounterClosed(closed_channel) }; - Ok(()) + Ok((cet, channel)) } /// Sign the settlement transaction and update the state of the channel. -pub fn close_settled_channel( - secp: &Secp256k1, +pub fn close_settled_channel( + secp: &Secp256k1, signed_channel: &mut SignedChannel, signer: &S, -) -> Result + is_initiator: bool, +) -> Result<(Transaction, Channel), Error> +where + S::Target: Signer, +{ + close_settled_channel_internal(secp, signed_channel, signer, None, is_initiator) +} + +pub(crate) fn close_settled_channel_internal( + secp: &Secp256k1, + signed_channel: &SignedChannel, + signer: &S, + sub_channel: Option<(SubChannel, &ClosingSubChannel)>, + is_initiator: bool, +) -> Result<(Transaction, Channel), Error> where S::Target: Signer, { @@ -1920,19 +2699,96 @@ where let counter_settle_signature = counter_settle_adaptor_signature.decrypt(&publish_sk)?; - let fund_sk = signer.get_secret_key_for_pubkey(&signed_channel.own_params.fund_pubkey)?; + if let Some((sub_channel, closing)) = sub_channel { + let signed_sub_channel = &closing.signed_sub_channel; + let own_base_secret_key = + signer.get_secret_key_for_pubkey(&sub_channel.own_base_points.own_basepoint)?; + let own_secret_key = derive_private_key( + secp, + &signed_sub_channel.own_per_split_point, + &own_base_secret_key, + ); + let sig = dlc::util::get_raw_sig_for_tx_input( + secp, + &settle_tx, + 0, + &signed_sub_channel.split_tx.output_script, + signed_sub_channel.split_tx.transaction.output[1].value, + &own_secret_key, + )?; + + let (own_pk, counter_pk, offer_params, accept_params) = { + let own_revoke_params = sub_channel.own_base_points.get_revokable_params( + secp, + &sub_channel + .counter_base_points + .as_ref() + .expect("to have counter base points") + .revocation_basepoint, + &signed_sub_channel.own_per_split_point, + ); + let counter_revoke_params = sub_channel + .counter_base_points + .as_ref() + .expect("to have counter base points") + .get_revokable_params( + secp, + &sub_channel.own_base_points.revocation_basepoint, + &signed_sub_channel.counter_per_split_point, + ); + if sub_channel.is_offer { + ( + own_revoke_params.own_pk, + counter_revoke_params.own_pk, + own_revoke_params, + counter_revoke_params, + ) + } else { + ( + own_revoke_params.own_pk, + counter_revoke_params.own_pk, + counter_revoke_params, + own_revoke_params, + ) + } + }; + + dlc::channel::satisfy_buffer_descriptor( + &mut settle_tx, + &offer_params, + &accept_params, + &own_pk.inner, + &sig, + &counter_pk, + &counter_settle_signature, + )?; + } else { + let fund_sk = signer.get_secret_key_for_pubkey(&signed_channel.own_params.fund_pubkey)?; - dlc::util::sign_multi_sig_input( - secp, - &mut settle_tx, - &counter_settle_signature, - &signed_channel.counter_params.fund_pubkey, - &fund_sk, - &signed_channel.fund_script_pubkey, - signed_channel.fund_tx.output[signed_channel.fund_output_index].value, - 0, - )?; + dlc::util::sign_multi_sig_input( + secp, + &mut settle_tx, + &counter_settle_signature, + &signed_channel.counter_params.fund_pubkey, + &fund_sk, + &signed_channel.fund_script_pubkey, + signed_channel.fund_tx.output[signed_channel.fund_output_index].value, + 0, + )?; + } - signed_channel.state = SignedChannelState::Closed; - Ok(settle_tx) + let channel = if is_initiator { + Channel::Closed(ClosedChannel { + counter_party: signed_channel.counter_party, + temporary_channel_id: signed_channel.temporary_channel_id, + channel_id: signed_channel.channel_id, + }) + } else { + Channel::CounterClosed(ClosedChannel { + counter_party: signed_channel.counter_party, + temporary_channel_id: signed_channel.temporary_channel_id, + channel_id: signed_channel.channel_id, + }) + }; + Ok((settle_tx, channel)) } diff --git a/dlc-manager/src/contract/contract_input.rs b/dlc-manager/src/contract/contract_input.rs index 75f0ee81..ff70c70d 100644 --- a/dlc-manager/src/contract/contract_input.rs +++ b/dlc-manager/src/contract/contract_input.rs @@ -8,7 +8,7 @@ use secp256k1_zkp::XOnlyPublicKey; use serde::{Deserialize, Serialize}; /// Oracle information required for the initial creation of a contract. -#[derive(Debug)] +#[derive(Debug, Clone)] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), @@ -45,7 +45,7 @@ impl OracleInput { } /// Represents the contract specifications. -#[derive(Debug)] +#[derive(Debug, Clone)] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), @@ -58,7 +58,7 @@ pub struct ContractInputInfo { pub oracles: OracleInput, } -#[derive(Debug)] +#[derive(Debug, Clone)] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), 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/contract/offered_contract.rs b/dlc-manager/src/contract/offered_contract.rs index b9e18a31..4deaf1e5 100644 --- a/dlc-manager/src/contract/offered_contract.rs +++ b/dlc-manager/src/contract/offered_contract.rs @@ -4,6 +4,7 @@ use crate::conversion_utils::{ get_contract_info_and_announcements, get_tx_input_infos, BITCOIN_CHAINHASH, PROTOCOL_VERSION, }; use crate::utils::get_new_serial_id; +use crate::ContractId; use super::contract_info::ContractInfo; use super::contract_input::ContractInput; @@ -82,6 +83,7 @@ impl OfferedContract { counter_party: &PublicKey, refund_delay: u32, cet_locktime: u32, + temporary_contract_id: ContractId, ) -> Self { let total_collateral = contract.offer_collateral + contract.accept_collateral; @@ -94,7 +96,7 @@ impl OfferedContract { let contract_info = contract .contract_infos .iter() - .zip(oracle_announcements.into_iter()) + .zip(oracle_announcements) .map(|(x, y)| ContractInfo { contract_descriptor: x.contract_descriptor.clone(), oracle_announcements: y, @@ -102,7 +104,7 @@ impl OfferedContract { }) .collect::>(); OfferedContract { - id: crate::utils::get_new_temporary_id(), + id: temporary_contract_id, is_offer_party: true, contract_info, offer_params: offer_params.clone(), diff --git a/dlc-manager/src/contract_updater.rs b/dlc-manager/src/contract_updater.rs index 055d7337..f4f14d41 100644 --- a/dlc-manager/src/contract_updater.rs +++ b/dlc-manager/src/contract_updater.rs @@ -48,6 +48,7 @@ where contract_input.fee_rate, wallet, blockchain, + true, )?; let offered_contract = OfferedContract::new( @@ -58,6 +59,7 @@ where counter_party, refund_delay, time.unix_time_now() as u32, + crate::utils::get_new_temporary_id(), ); let offer_msg: OfferDlc = (&offered_contract).into(); @@ -85,6 +87,7 @@ where offered_contract.fee_rate_per_vb, wallet, blockchain, + true, )?; let dlc_transactions = dlc::create_dlc_transactions( @@ -422,7 +425,7 @@ where })?; let vout = x.funding_input.prev_tx_vout; let tx_out = tx.output.get(vout as usize).ok_or_else(|| { - Error::InvalidParameters(format!("Previous tx output not found at index {}", vout)) + Error::InvalidParameters(format!("Previous tx output not found at index {vout}")) })?; // pass wallet instead of privkeys @@ -618,7 +621,7 @@ where })?; let vout = funding_input_info.funding_input.prev_tx_vout; let tx_out = tx.output.get(vout as usize).ok_or_else(|| { - Error::InvalidParameters(format!("Previous tx output not found at index {}", vout)) + Error::InvalidParameters(format!("Previous tx output not found at index {vout}")) })?; signer.sign_tx_input(&mut fund_tx, input_index, tx_out, None)?; diff --git a/dlc-manager/src/conversion_utils.rs b/dlc-manager/src/conversion_utils.rs index 04ebfc1f..faf1d3e4 100644 --- a/dlc-manager/src/conversion_utils.rs +++ b/dlc-manager/src/conversion_utils.rs @@ -218,7 +218,7 @@ impl From<&OfferedContract> for SerContractInfo { let mut contract_infos: Vec = offered_contract .contract_info .iter() - .zip(oracle_infos.into_iter()) + .zip(oracle_infos) .map(|(c, o)| ContractInfoInner { contract_descriptor: (&c.contract_descriptor).into(), oracle_info: o, diff --git a/dlc-manager/src/error.rs b/dlc-manager/src/error.rs index 9d2b6176..1e90051c 100644 --- a/dlc-manager/src/error.rs +++ b/dlc-manager/src/error.rs @@ -1,6 +1,8 @@ //! #Error use std::fmt; +use lightning::util::errors::APIError; + /// An error code. #[derive(Debug)] pub enum Error { @@ -14,7 +16,7 @@ pub enum Error { /// An invalid state was encounter, likely to indicate a bug. InvalidState(String), /// An error occurred in the wallet component. - WalletError(Box), + WalletError(Box), /// An error occurred in the blockchain component. BlockchainError(String), /// The storage component encountered an error. @@ -74,6 +76,20 @@ impl From for Error { } } +impl From for APIError { + fn from(value: Error) -> Self { + APIError::ExternalError { + err: value.to_string(), + } + } +} + +impl From for Error { + fn from(value: APIError) -> Self { + Error::InvalidState(format!("{:?}", value)) + } +} + impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { diff --git a/dlc-manager/src/lib.rs b/dlc-manager/src/lib.rs index d6707ec8..fe6c2240 100644 --- a/dlc-manager/src/lib.rs +++ b/dlc-manager/src/lib.rs @@ -25,6 +25,9 @@ extern crate log; extern crate rand_chacha; extern crate secp256k1_zkp; +#[macro_use] +mod utils; + pub mod chain_monitor; pub mod channel; pub mod channel_updater; @@ -34,7 +37,8 @@ mod conversion_utils; pub mod error; pub mod manager; pub mod payout_curve; -mod utils; +pub mod sub_channel_manager; +pub mod subchannel; use bitcoin::{Address, Block, OutPoint, Script, Transaction, TxOut, Txid}; use chain_monitor::ChainMonitor; @@ -50,6 +54,8 @@ 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. pub type ContractId = [u8; 32]; @@ -164,6 +170,18 @@ pub trait Storage { fn persist_chain_monitor(&self, monitor: &ChainMonitor) -> Result<(), Error>; /// Returns the latest [`ChainMonitor`] in the store if any. fn get_chain_monitor(&self) -> Result, Error>; + /// Creates or updates a [`SubChannel`]. + fn upsert_sub_channel(&self, subchannel: &SubChannel) -> Result<(), Error>; + /// Returns the [`SubChannel`] with given `channel_id` if it exists. + fn get_sub_channel(&self, channel_id: ChannelId) -> Result, Error>; + /// Return all the [`SubChannel`] within the store. + 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/manager.rs b/dlc-manager/src/manager.rs index 8ef4bbcd..8cda0681 100644 --- a/dlc-manager/src/manager.rs +++ b/dlc-manager/src/manager.rs @@ -4,9 +4,9 @@ use super::{Blockchain, Oracle, Storage, Time, Wallet}; use crate::chain_monitor::{ChainMonitor, ChannelInfo, RevokedTxType, TxType}; use crate::channel::offered_channel::OfferedChannel; use crate::channel::signed_channel::{SignedChannel, SignedChannelState, SignedChannelStateType}; -use crate::channel::Channel; -use crate::channel_updater::get_signed_channel_state; +use crate::channel::{Channel, ClosedChannel, ClosedPunishedChannel}; use crate::channel_updater::verify_signed_channel; +use crate::channel_updater::{self, get_signed_channel_state}; use crate::contract::{ accepted_contract::AcceptedContract, contract_info::ContractInfo, contract_input::ContractInput, contract_input::OracleInput, offered_contract::OfferedContract, @@ -15,27 +15,33 @@ use crate::contract::{ }; use crate::contract_updater::{accept_contract, verify_accepted_and_sign_contract}; use crate::error::Error; -use crate::Signer; -use crate::{ChannelId, ContractId}; +use crate::sub_channel_manager::get_sub_channel_in_state; +use crate::subchannel::{ClosingSubChannel, SubChannel, SubChannelState}; +use crate::utils::get_object_in_state; +use crate::{ChannelId, ContractId, Signer}; +use bitcoin::consensus::encode::serialize_hex; use bitcoin::Address; use bitcoin::Transaction; use dlc_messages::channel::{ AcceptChannel, CollaborativeCloseOffer, OfferChannel, Reject, RenewAccept, RenewConfirm, - RenewFinalize, RenewOffer, SettleAccept, SettleConfirm, SettleFinalize, SettleOffer, - SignChannel, + RenewFinalize, RenewOffer, RenewRevoke, SettleAccept, SettleConfirm, SettleFinalize, + SettleOffer, SignChannel, }; use dlc_messages::oracle_msgs::{OracleAnnouncement, OracleAttestation}; -use dlc_messages::{AcceptDlc, Message as DlcMessage, OfferDlc, SignDlc}; +use dlc_messages::{ + AcceptDlc, ChannelMessage, Message as DlcMessage, OfferDlc, OnChainMessage, SignDlc, +}; use lightning::chain::chaininterface::FeeEstimator; use lightning::ln::chan_utils::{ build_commitment_secret, derive_private_key, derive_private_revocation_key, }; use log::{error, warn}; -use secp256k1_zkp::XOnlyPublicKey; use secp256k1_zkp::{ecdsa::Signature, All, PublicKey, Secp256k1, SecretKey}; +use secp256k1_zkp::{EcdsaAdaptorSignature, XOnlyPublicKey}; use std::collections::HashMap; use std::ops::Deref; use std::string::ToString; +use std::sync::Mutex; /// The number of confirmations required before moving the the confirmed state. pub const NB_CONFIRMATIONS: u32 = 6; @@ -68,43 +74,13 @@ where blockchain: B, store: S, secp: Secp256k1, - chain_monitor: ChainMonitor, + chain_monitor: Mutex, time: T, fee_estimator: F, } -macro_rules! get_object_in_state { - ($manager: ident, $id: expr, $state: ident, $peer_id: expr, $object_type: ident, $get_call: ident) => {{ - let object = $manager.store.$get_call($id)?; - match object { - Some(c) => { - if let Some(p) = $peer_id as Option { - if c.get_counter_party_id() != p { - return Err(Error::InvalidParameters(format!( - "Peer {:02x?} is not involved with contract {:02x?}.", - $peer_id, $id - ))); - } - } - match c { - $object_type::$state(s) => Ok(s), - _ => Err(Error::InvalidState(format!( - "Invalid state {:?} expected {}.", - c, - stringify!($state), - ))), - } - } - None => Err(Error::InvalidParameters(format!( - "Unknown {} id.", - stringify!($object_type) - ))), - } - }}; -} - macro_rules! get_contract_in_state { - ($manager: ident, $contract_id: expr, $state: ident, $peer_id: expr) => {{ + ($manager: expr, $contract_id: expr, $state: ident, $peer_id: expr) => {{ get_object_in_state!( $manager, $contract_id, @@ -116,8 +92,10 @@ macro_rules! get_contract_in_state { }}; } +pub(crate) use get_contract_in_state; + macro_rules! get_channel_in_state { - ($manager: ident, $channel_id: expr, $state: ident, $peer_id: expr) => {{ + ($manager: expr, $channel_id: expr, $state: ident, $peer_id: expr) => {{ get_object_in_state!( $manager, $channel_id, @@ -129,6 +107,8 @@ macro_rules! get_channel_in_state { }}; } +pub(crate) use get_channel_in_state; + macro_rules! get_signed_channel_rollback_state { ($signed_channel: ident, $state: ident, $($field: ident),*) => {{ match $signed_channel.roll_back_state.as_ref() { @@ -148,7 +128,25 @@ macro_rules! check_for_timed_out_channels { if let SignedChannelState::$state { timeout, .. } = channel.state { let is_timed_out = timeout < $manager.time.unix_time_now(); if is_timed_out { - match $manager.force_close_channel_internal(channel) { + let sub_channel = if channel.is_sub_channel() { + log::info!( + "Skipping force-closure of subchannel {}: not supported", + bitcoin::hashes::hex::ToHex::to_hex(&channel.channel_id[..]) + ); + continue; + + // TODO: Implement subchannel force closing + // let s = get_sub_channel_in_state!( + // $manager, + // channel.channel_id, + // Signed, + // None:: + // )?; + // Some(s) + } else { + None + }; + match $manager.force_close_channel_internal(channel, sub_channel, true) { Err(e) => error!("Error force closing channel {}", e), _ => {} } @@ -176,16 +174,19 @@ where time: T, fee_estimator: F, ) -> Result { - let init_height = blockchain.get_blockchain_height()?; + let chain_monitor = store + .get_chain_monitor()? + .unwrap_or(ChainMonitor::new(blockchain.get_blockchain_height()?)); + Ok(Manager { secp: secp256k1_zkp::Secp256k1::new(), wallet, - blockchain, store, oracles, time, fee_estimator, - chain_monitor: ChainMonitor::new(init_height), + chain_monitor: Mutex::new(chain_monitor), + blockchain, }) } @@ -194,81 +195,114 @@ where &self.store } - #[doc(hidden)] - pub fn get_mut_store(&mut self) -> &mut S { - &mut self.store + pub(crate) fn get_wallet(&self) -> &W { + &self.wallet + } + + pub(crate) fn get_blockchain(&self) -> &B { + &self.blockchain + } + + pub(crate) fn get_time(&self) -> &T { + &self.time + } + + pub(crate) fn get_fee_estimator(&self) -> &F { + &self.fee_estimator + } + + pub(crate) fn get_secp(&self) -> &Secp256k1 { + &self.secp + } + + /// Return the chain monitor used to watch for relevant transactions on chain. + pub fn get_chain_monitor(&self) -> &Mutex { + &self.chain_monitor } /// Function called to pass a DlcMessage to the Manager. pub fn on_dlc_message( - &mut self, + &self, msg: &DlcMessage, counter_party: PublicKey, ) -> Result, Error> { match msg { - DlcMessage::Offer(o) => { - self.on_offer_message(o, counter_party)?; - Ok(None) - } - DlcMessage::Accept(a) => Ok(Some(self.on_accept_message(a, &counter_party)?)), - DlcMessage::Sign(s) => { - self.on_sign_message(s, &counter_party)?; - Ok(None) - } - DlcMessage::OfferChannel(o) => { - self.on_offer_channel(o, counter_party)?; - Ok(None) - } - DlcMessage::AcceptChannel(a) => Ok(Some(DlcMessage::SignChannel( - self.on_accept_channel(a, &counter_party)?, - ))), - DlcMessage::SignChannel(s) => { - self.on_sign_channel(s, &counter_party)?; - Ok(None) - } - DlcMessage::SettleOffer(s) => match self.on_settle_offer(s, &counter_party)? { - Some(msg) => Ok(Some(DlcMessage::Reject(msg))), - None => Ok(None), + DlcMessage::OnChain(on_chain) => match on_chain { + OnChainMessage::Offer(o) => { + self.on_offer_message(o, counter_party)?; + Ok(None) + } + OnChainMessage::Accept(a) => Ok(Some(self.on_accept_message(a, &counter_party)?)), + OnChainMessage::Sign(s) => { + self.on_sign_message(s, &counter_party)?; + Ok(None) + } }, - DlcMessage::SettleAccept(s) => Ok(Some(DlcMessage::SettleConfirm( - self.on_settle_accept(s, &counter_party)?, - ))), - DlcMessage::SettleConfirm(s) => Ok(Some(DlcMessage::SettleFinalize( - self.on_settle_confirm(s, &counter_party)?, - ))), - DlcMessage::SettleFinalize(s) => { - self.on_settle_finalize(s, &counter_party)?; - Ok(None) - } - DlcMessage::RenewOffer(r) => match self.on_renew_offer(r, &counter_party)? { - Some(msg) => Ok(Some(DlcMessage::Reject(msg))), - None => Ok(None), + DlcMessage::Channel(channel) => match channel { + ChannelMessage::Offer(o) => { + self.on_offer_channel(o, counter_party)?; + Ok(None) + } + ChannelMessage::Accept(a) => Ok(Some(DlcMessage::Channel(ChannelMessage::Sign( + self.on_accept_channel(a, &counter_party)?, + )))), + ChannelMessage::Sign(s) => { + self.on_sign_channel(s, &counter_party)?; + Ok(None) + } + ChannelMessage::SettleOffer(s) => match self.on_settle_offer(s, &counter_party)? { + Some(msg) => Ok(Some(DlcMessage::Channel(ChannelMessage::Reject(msg)))), + None => Ok(None), + }, + ChannelMessage::SettleAccept(s) => Ok(Some(DlcMessage::Channel( + ChannelMessage::SettleConfirm(self.on_settle_accept(s, &counter_party)?), + ))), + ChannelMessage::SettleConfirm(s) => Ok(Some(DlcMessage::Channel( + ChannelMessage::SettleFinalize(self.on_settle_confirm(s, &counter_party)?), + ))), + ChannelMessage::SettleFinalize(s) => { + self.on_settle_finalize(s, &counter_party)?; + Ok(None) + } + ChannelMessage::RenewOffer(r) => match self.on_renew_offer(r, &counter_party)? { + Some(msg) => Ok(Some(DlcMessage::Channel(ChannelMessage::Reject(msg)))), + None => Ok(None), + }, + ChannelMessage::RenewAccept(r) => Ok(Some(DlcMessage::Channel( + ChannelMessage::RenewConfirm(self.on_renew_accept(r, &counter_party)?), + ))), + ChannelMessage::RenewConfirm(r) => Ok(Some(DlcMessage::Channel( + ChannelMessage::RenewFinalize(self.on_renew_confirm(r, &counter_party)?), + ))), + ChannelMessage::RenewFinalize(r) => { + let revoke = self.on_renew_finalize(r, &counter_party)?; + Ok(Some(DlcMessage::Channel(ChannelMessage::RenewRevoke( + revoke, + )))) + } + ChannelMessage::CollaborativeCloseOffer(c) => { + self.on_collaborative_close_offer(c, &counter_party)?; + Ok(None) + } + ChannelMessage::Reject(r) => { + self.on_reject(r, &counter_party)?; + Ok(None) + } + ChannelMessage::RenewRevoke(r) => { + self.on_renew_revoke(r, &counter_party)?; + Ok(None) + } }, - DlcMessage::RenewAccept(r) => Ok(Some(DlcMessage::RenewConfirm( - self.on_renew_accept(r, &counter_party)?, - ))), - DlcMessage::RenewConfirm(r) => Ok(Some(DlcMessage::RenewFinalize( - self.on_renew_confirm(r, &counter_party)?, - ))), - DlcMessage::RenewFinalize(r) => { - self.on_renew_finalize(r, &counter_party)?; - Ok(None) - } - DlcMessage::CollaborativeCloseOffer(c) => { - self.on_collaborative_close_offer(c, &counter_party)?; - Ok(None) - } - DlcMessage::Reject(r) => { - self.on_reject(r, &counter_party)?; - Ok(None) - } + DlcMessage::SubChannel(_) => Err(Error::InvalidParameters( + "SubChannel messages not supported".to_string(), + )), } } /// Function called to create a new DLC. The offered contract will be stored /// and an OfferDlc message returned. pub fn send_offer( - &mut self, + &self, contract_input: &ContractInput, counter_party: PublicKey, ) -> Result { @@ -300,7 +334,7 @@ where /// Function to call to accept a DLC for which an offer was received. pub fn accept_contract_offer( - &mut self, + &self, contract_id: &ContractId, ) -> Result<(ContractId, PublicKey, AcceptDlc), Error> { let offered_contract = @@ -328,9 +362,38 @@ where Ok((contract_id, counter_party, accept_msg)) } + /// Function to update the state of the [`ChainMonitor`] with new + /// blocks. + /// + /// Consumers **MUST** call this periodically in order to + /// determine when pending transactions reach confirmation. + pub fn periodic_chain_monitor(&self) -> Result<(), Error> { + let cur_height = self.blockchain.get_blockchain_height()?; + let last_height = self.chain_monitor.lock().unwrap().last_height; + + // TODO(luckysori): We could end up reprocessing a block at + // the same height if there is a reorg. + if cur_height < last_height { + return Err(Error::InvalidState( + "Current height is lower than last height.".to_string(), + )); + } + + for height in last_height + 1..=cur_height { + let block = self.blockchain.get_block_at_height(height)?; + + self.chain_monitor + .lock() + .unwrap() + .process_block(&block, height); + } + + Ok(()) + } + /// Function to call to check the state of the currently executing DLCs and /// update them if possible. - pub fn periodic_check(&mut self) -> Result<(), Error> { + pub fn periodic_check(&self) -> Result<(), Error> { self.check_signed_contracts()?; self.check_confirmed_contracts()?; self.check_preclosed_contracts()?; @@ -340,7 +403,7 @@ where } fn on_offer_message( - &mut self, + &self, offered_message: &OfferDlc, counter_party: PublicKey, ) -> Result<(), Error> { @@ -361,7 +424,7 @@ where } fn on_accept_message( - &mut self, + &self, accept_msg: &AcceptDlc, counter_party: &PublicKey, ) -> Result { @@ -393,14 +456,10 @@ where self.store .update_contract(&Contract::Signed(signed_contract))?; - Ok(DlcMessage::Sign(signed_msg)) + Ok(DlcMessage::OnChain(OnChainMessage::Sign(signed_msg))) } - fn on_sign_message( - &mut self, - sign_message: &SignDlc, - peer_id: &PublicKey, - ) -> Result<(), Error> { + fn on_sign_message(&self, sign_message: &SignDlc, peer_id: &PublicKey) -> Result<(), Error> { let accepted_contract = get_contract_in_state!(self, &sign_message.contract_id, Accepted, Some(*peer_id))?; @@ -439,7 +498,7 @@ where } fn sign_fail_on_error( - &mut self, + &self, accepted_contract: AcceptedContract, sign_message: SignDlc, e: Error, @@ -455,7 +514,7 @@ where } fn accept_fail_on_error( - &mut self, + &self, offered_contract: OfferedContract, accept_message: AcceptDlc, e: Error, @@ -470,7 +529,7 @@ where Err(e) } - fn check_signed_contract(&mut self, contract: &SignedContract) -> Result<(), Error> { + fn check_signed_contract(&self, contract: &SignedContract) -> Result<(), Error> { let confirmations = self.blockchain.get_transaction_confirmations( &contract.accepted_contract.dlc_transactions.fund.txid(), )?; @@ -481,7 +540,7 @@ where Ok(()) } - fn check_signed_contracts(&mut self) -> Result<(), Error> { + fn check_signed_contracts(&self) -> Result<(), Error> { for c in self.store.get_signed_contracts()? { if let Err(e) = self.check_signed_contract(&c) { error!( @@ -495,7 +554,7 @@ where Ok(()) } - fn check_confirmed_contracts(&mut self) -> Result<(), Error> { + fn check_confirmed_contracts(&self) -> Result<(), Error> { for c in self.store.get_confirmed_contracts()? { // Confirmed contracts from channel are processed in channel specific methods. if c.channel_id.is_some() { @@ -549,7 +608,7 @@ where None } - fn check_confirmed_contract(&mut self, contract: &SignedContract) -> Result<(), Error> { + fn check_confirmed_contract(&self, contract: &SignedContract) -> Result<(), Error> { let closable_contract_info = self.get_closable_contract_info(contract); if let Some((contract_info, adaptor_info, attestations)) = closable_contract_info { let cet = crate::contract_updater::get_signed_cet( @@ -585,7 +644,7 @@ where Ok(()) } - fn check_preclosed_contracts(&mut self) -> Result<(), Error> { + fn check_preclosed_contracts(&self) -> Result<(), Error> { for c in self.store.get_preclosed_contracts()? { if let Err(e) = self.check_preclosed_contract(&c) { error!( @@ -599,7 +658,7 @@ where Ok(()) } - fn check_preclosed_contract(&mut self, contract: &PreClosedContract) -> Result<(), Error> { + fn check_preclosed_contract(&self, contract: &PreClosedContract) -> Result<(), Error> { let broadcasted_txid = contract.signed_cet.txid(); let confirmations = self .blockchain @@ -632,7 +691,7 @@ where } fn close_contract( - &mut self, + &self, contract: &SignedContract, signed_cet: Transaction, attestations: Vec, @@ -677,7 +736,7 @@ where Ok(Contract::Closed(closed_contract)) } - fn check_refund(&mut self, contract: &SignedContract) -> Result<(), Error> { + fn check_refund(&self, contract: &SignedContract) -> Result<(), Error> { // TODO(tibo): should check for confirmation of refund before updating state if contract .accepted_contract @@ -718,7 +777,7 @@ where /// Create a new channel offer and return the [`dlc_messages::channel::OfferChannel`] /// message to be sent to the `counter_party`. pub fn offer_channel( - &mut self, + &self, contract_input: &ContractInput, counter_party: PublicKey, ) -> Result { @@ -738,6 +797,8 @@ where &self.wallet, &self.blockchain, &self.time, + crate::utils::get_new_temporary_id(), + false, )?; let msg = offered_channel.get_offer_channel_msg(&offered_contract); @@ -754,7 +815,7 @@ where /// message to be sent, the updated [`crate::ChannelId`] and [`crate::ContractId`], /// as well as the public key of the offering node. pub fn accept_channel( - &mut self, + &self, channel_id: &ChannelId, ) -> Result<(AcceptChannel, ChannelId, ContractId, PublicKey), Error> { let offered_channel = @@ -800,17 +861,17 @@ where } /// Force close the channel with given [`crate::ChannelId`]. - pub fn force_close_channel(&mut self, channel_id: &ChannelId) -> Result<(), Error> { + pub fn force_close_channel(&self, channel_id: &ChannelId) -> Result<(), Error> { let channel = get_channel_in_state!(self, channel_id, Signed, None as Option)?; - self.force_close_channel_internal(channel) + self.force_close_channel_internal(channel, None, true) } /// Offer to settle the balance of a channel so that the counter party gets /// `counter_payout`. Returns the [`dlc_messages::channel::SettleChannelOffer`] /// message to be sent and the public key of the counter party node. pub fn settle_offer( - &mut self, + &self, channel_id: &ChannelId, counter_payout: u64, ) -> Result<(SettleOffer, PublicKey), Error> { @@ -837,13 +898,28 @@ where /// Accept a settlement offer, returning the [`SettleAccept`] message to be /// sent to the node with the returned [`PublicKey`] id. pub fn accept_settle_offer( - &mut self, + &self, channel_id: &ChannelId, ) -> Result<(SettleAccept, PublicKey), Error> { let mut signed_channel = get_channel_in_state!(self, channel_id, Signed, None as Option)?; - let msg = crate::channel_updater::settle_channel_accept( + let own_settle_adaptor_sk = if let Some(sub_channel_id) = + signed_channel.sub_channel_id.as_ref() + { + let (signed_sub_channel, state) = + get_sub_channel_in_state!(self, *sub_channel_id, Signed, None::)?; + let own_base_secret_key = self + .wallet + .get_secret_key_for_pubkey(&signed_sub_channel.own_base_points.own_basepoint)?; + let own_secret_key = + derive_private_key(&self.secp, &state.own_per_split_point, &own_base_secret_key); + Some(own_secret_key) + } else { + None + }; + + let msg = crate::channel_updater::settle_channel_accept_internal( &self.secp, &mut signed_channel, CET_NSEQUENCE, @@ -851,6 +927,8 @@ where PEER_TIMEOUT, &self.wallet, &self.time, + own_settle_adaptor_sk, + &self.chain_monitor, )?; let counter_party = signed_channel.counter_party; @@ -865,7 +943,7 @@ where /// counter party's node to offer the establishment of a new contract in the /// channel. pub fn renew_offer( - &mut self, + &self, channel_id: &ChannelId, counter_payout: u64, contract_input: &ContractInput, @@ -906,7 +984,7 @@ where /// [`RenewAccept`] message to be sent to the peer with the returned /// [`PublicKey`] as node id. pub fn accept_renew_offer( - &mut self, + &self, channel_id: &ChannelId, ) -> Result<(RenewAccept, PublicKey), Error> { let mut signed_channel = @@ -922,7 +1000,7 @@ where None as Option )?; - let (accepted_contract, msg) = crate::channel_updater::accept_channel_renewal( + let (accepted_contract, msg) = crate::channel_updater::accept_channel_renewal_internal( &self.secp, &mut signed_channel, &offered_contract, @@ -945,10 +1023,7 @@ where /// Reject an offer to renew the contract in the channel. Returns the /// [`Reject`] message to be sent to the peer with the returned /// [`PublicKey`] node id. - pub fn reject_renew_offer( - &mut self, - channel_id: &ChannelId, - ) -> Result<(Reject, PublicKey), Error> { + pub fn reject_renew_offer(&self, channel_id: &ChannelId) -> Result<(Reject, PublicKey), Error> { let mut signed_channel = get_channel_in_state!(self, channel_id, Signed, None as Option)?; let offered_contract_id = signed_channel.get_contract_id().ok_or_else(|| { @@ -980,7 +1055,7 @@ where /// channel to inform them that the local party does not wish to accept the /// proposed settle offer. pub fn reject_settle_offer( - &mut self, + &self, channel_id: &ChannelId, ) -> Result<(Reject, PublicKey), Error> { let mut signed_channel = @@ -1001,7 +1076,7 @@ where /// channel will be forced closed after a timeout if the counter party does /// not broadcast the close transaction. pub fn offer_collaborative_close( - &mut self, + &self, channel_id: &ChannelId, counter_payout: u64, ) -> Result { @@ -1016,7 +1091,7 @@ where &self.time, )?; - self.chain_monitor.add_tx( + self.chain_monitor.lock().unwrap().add_tx( close_tx.txid(), ChannelInfo { channel_id: *channel_id, @@ -1026,20 +1101,20 @@ where self.store .upsert_channel(Channel::Signed(signed_channel), None)?; - self.store.persist_chain_monitor(&self.chain_monitor)?; + self.store + .persist_chain_monitor(&self.chain_monitor.lock().unwrap())?; Ok(msg) } /// Accept an offer to collaboratively close the channel. The close transaction /// will be broadcast and the state of the channel updated. - pub fn accept_collaborative_close(&mut self, channel_id: &ChannelId) -> Result<(), Error> { - let mut signed_channel = + pub fn accept_collaborative_close(&self, channel_id: &ChannelId) -> Result<(), Error> { + let signed_channel = get_channel_in_state!(self, channel_id, Signed, None as Option)?; let closed_contract = if let Some(SignedChannelState::Established { signed_contract_id, - is_offer, .. }) = &signed_channel.roll_back_state { @@ -1048,40 +1123,24 @@ where CollaborativeCloseOffered, counter_payout )?; - let contract = - get_contract_in_state!(self, signed_contract_id, Confirmed, None::)?; - let own_collateral = if *is_offer { - contract - .accepted_contract - .offered_contract - .offer_params - .collateral - } else { - contract.accepted_contract.accept_params.collateral - }; - let pnl = own_collateral as i64 - counter_payout as i64; - Some(ClosedContract { - attestations: None, - signed_cet: None, - contract_id: *signed_contract_id, - temporary_contract_id: contract.accepted_contract.offered_contract.id, - counter_party_id: signed_channel.counter_party, - pnl, - }) + Some(self.get_collaboratively_closed_contract( + signed_contract_id, + *counter_payout, + true, + )?) } else { None }; - let close_tx = crate::channel_updater::accept_collaborative_close_offer( + let (close_tx, closed_channel) = crate::channel_updater::accept_collaborative_close_offer( &self.secp, - &mut signed_channel, + &signed_channel, &self.wallet, )?; self.blockchain.send_transaction(&close_tx)?; - self.store - .upsert_channel(Channel::Signed(signed_channel), None)?; + self.store.upsert_channel(closed_channel, None)?; if let Some(closed_contract) = closed_contract { self.store @@ -1092,40 +1151,68 @@ where } fn try_finalize_closing_established_channel( - &mut self, - mut signed_channel: SignedChannel, + &self, + signed_channel: SignedChannel, ) -> Result<(), Error> { - let (buffer_tx, signed_cet, contract_id, attestations) = get_signed_channel_state!( + let (buffer_tx, contract_id, &is_initiator) = get_signed_channel_state!( signed_channel, Closing, buffer_transaction, - signed_cet, contract_id, - attestations + is_initiator )?; if self .blockchain .get_transaction_confirmations(&buffer_tx.txid())? - > CET_NSEQUENCE + >= CET_NSEQUENCE { - let confirmed_contract = - get_contract_in_state!(self, &contract_id, Confirmed, None as Option)?; + log::info!( + "Buffer transaction for contract {} has enough confirmations to spend from it", + serialize_hex(&contract_id) + ); - let closed_contract = - self.close_contract(&confirmed_contract, signed_cet, attestations)?; + let confirmed_contract = + get_contract_in_state!(self, contract_id, Confirmed, None as Option)?; + + let (contract_info, adaptor_info, attestations) = self + .get_closable_contract_info(&confirmed_contract) + .ok_or_else(|| { + Error::InvalidState("Could not get information to close contract".to_string()) + })?; + + let (signed_cet, closed_channel) = + channel_updater::finalize_unilateral_close_settled_channel( + &self.secp, + &signed_channel, + &confirmed_contract, + contract_info, + &attestations, + adaptor_info, + &self.wallet, + is_initiator, + )?; + + let closed_contract = self.close_contract( + &confirmed_contract, + signed_cet, + attestations.iter().map(|x| &x.1).cloned().collect(), + )?; - signed_channel.state = SignedChannelState::Closed; + self.chain_monitor + .lock() + .unwrap() + .cleanup_channel(signed_channel.channel_id); self.store - .upsert_channel(Channel::Signed(signed_channel), Some(closed_contract))?; + .upsert_channel(closed_channel, Some(closed_contract))?; } Ok(()) } fn on_offer_channel( - &mut self, + &self, offer_channel: &OfferChannel, counter_party: PublicKey, ) -> Result<(), Error> { @@ -1158,7 +1245,7 @@ where } fn on_accept_channel( - &mut self, + &self, accept_channel: &AcceptChannel, peer_id: &PublicKey, ) -> Result { @@ -1184,6 +1271,7 @@ where //TODO(tibo): this should be parameterizable. CET_NSEQUENCE, &self.wallet, + &self.chain_monitor, ); match res { @@ -1191,7 +1279,7 @@ where Err(e) => { let channel = crate::channel::FailedAccept { temporary_channel_id: accept_channel.temporary_channel_id, - error_message: format!("Error validating accept channel: {}", e), + error_message: format!("Error validating accept channel: {e}"), accept_message: accept_channel.clone(), counter_party: *peer_id, }; @@ -1210,33 +1298,19 @@ where self.blockchain.get_network()?, ))?; - if let SignedChannelState::Established { - buffer_transaction, .. - } = &signed_channel.state - { - self.chain_monitor.add_tx( - buffer_transaction.txid(), - ChannelInfo { - channel_id: signed_channel.channel_id, - tx_type: TxType::Current, - }, - ); - } else { - unreachable!(); - } - self.store.upsert_channel( Channel::Signed(signed_channel), Some(Contract::Signed(signed_contract)), )?; - self.store.persist_chain_monitor(&self.chain_monitor)?; + self.store + .persist_chain_monitor(&self.chain_monitor.lock().unwrap())?; Ok(sign_channel) } fn on_sign_channel( - &mut self, + &self, sign_channel: &SignChannel, peer_id: &PublicKey, ) -> Result<(), Error> { @@ -1256,6 +1330,7 @@ where &accepted_contract, sign_channel, &self.wallet, + &self.chain_monitor, ); match res { @@ -1263,7 +1338,7 @@ where Err(e) => { let channel = crate::channel::FailedSign { channel_id: sign_channel.channel_id, - error_message: format!("Error validating accept channel: {}", e), + error_message: format!("Error validating accept channel: {e}"), sign_message: sign_channel.clone(), counter_party: *peer_id, }; @@ -1274,34 +1349,20 @@ where } }; - if let SignedChannelState::Established { - buffer_transaction, .. - } = &signed_channel.state - { - self.chain_monitor.add_tx( - buffer_transaction.txid(), - ChannelInfo { - channel_id: signed_channel.channel_id, - tx_type: TxType::Current, - }, - ); - } else { - unreachable!(); - } - self.blockchain.send_transaction(&signed_channel.fund_tx)?; self.store.upsert_channel( Channel::Signed(signed_channel), Some(Contract::Signed(signed_contract)), )?; - self.store.persist_chain_monitor(&self.chain_monitor)?; + self.store + .persist_chain_monitor(&self.chain_monitor.lock().unwrap())?; Ok(()) } fn on_settle_offer( - &mut self, + &self, settle_offer: &SettleOffer, peer_id: &PublicKey, ) -> Result, Error> { @@ -1323,14 +1384,40 @@ where } fn on_settle_accept( - &mut self, + &self, settle_accept: &SettleAccept, peer_id: &PublicKey, ) -> Result { let mut signed_channel = get_channel_in_state!(self, &settle_accept.channel_id, Signed, Some(*peer_id))?; - let msg = crate::channel_updater::settle_channel_confirm( + let (own_settle_adaptor_sk, counter_settle_adaptor_pk) = if let Some(sub_channel_id) = + signed_channel.sub_channel_id + { + let (signed_sub_channel, state) = + get_sub_channel_in_state!(self, sub_channel_id, Signed, None::)?; + let own_base_secret_key = self + .wallet + .get_secret_key_for_pubkey(&signed_sub_channel.own_base_points.own_basepoint)?; + let own_secret_key = + derive_private_key(&self.secp, &state.own_per_split_point, &own_base_secret_key); + let accept_revoke_params = signed_sub_channel + .counter_base_points + .expect("to have counter base points") + .get_revokable_params( + &self.secp, + &signed_sub_channel.own_base_points.revocation_basepoint, + &state.counter_per_split_point, + ); + ( + Some(own_secret_key), + Some(accept_revoke_params.own_pk.inner), + ) + } else { + (None, None) + }; + + let msg = crate::channel_updater::settle_channel_confirm_internal( &self.secp, &mut signed_channel, settle_accept, @@ -1339,6 +1426,9 @@ where PEER_TIMEOUT, &self.wallet, &self.time, + own_settle_adaptor_sk, + counter_settle_adaptor_pk, + &self.chain_monitor, )?; self.store @@ -1348,13 +1438,13 @@ where } fn on_settle_confirm( - &mut self, + &self, settle_confirm: &SettleConfirm, peer_id: &PublicKey, ) -> Result { let mut signed_channel = get_channel_in_state!(self, &settle_confirm.channel_id, Signed, Some(*peer_id))?; - let own_payout = get_signed_channel_state!(signed_channel, SettledAccepted, own_payout)?; + let &own_payout = get_signed_channel_state!(signed_channel, SettledAccepted, own_payout)?; let (prev_buffer_tx, own_buffer_adaptor_signature, is_offer, signed_contract_id) = get_signed_channel_rollback_state!( signed_channel, Established, @@ -1369,14 +1459,32 @@ where let is_offer = *is_offer; let signed_contract_id = *signed_contract_id; - let msg = crate::channel_updater::settle_channel_finalize( + let counter_settle_adaptor_pk = + if let Some(sub_channel_id) = signed_channel.sub_channel_id.as_ref() { + let (signed_sub_channel, state) = + get_sub_channel_in_state!(self, *sub_channel_id, Signed, None::)?; + let accept_revoke_params = signed_sub_channel + .counter_base_points + .expect("to have counter base points") + .get_revokable_params( + &self.secp, + &signed_sub_channel.own_base_points.revocation_basepoint, + &state.counter_per_split_point, + ); + Some(accept_revoke_params.own_pk.inner) + } else { + None + }; + + let msg = crate::channel_updater::settle_channel_finalize_internal( &self.secp, &mut signed_channel, settle_confirm, &self.wallet, + counter_settle_adaptor_pk, )?; - self.chain_monitor.add_tx( + self.chain_monitor.lock().unwrap().add_tx( prev_buffer_txid, ChannelInfo { channel_id: signed_channel.channel_id, @@ -1389,43 +1497,28 @@ where }, ); - let contract = - get_contract_in_state!(self, &signed_contract_id, Confirmed, None::)?; - - let own_collateral = if contract.accepted_contract.offered_contract.is_offer_party { - contract - .accepted_contract - .offered_contract - .offer_params - .collateral - } else { - contract.accepted_contract.accept_params.collateral - }; - - let closed_contract = Contract::Closed(ClosedContract { - attestations: None, - signed_cet: None, - contract_id: signed_contract_id, - temporary_contract_id: contract.accepted_contract.offered_contract.id, - counter_party_id: signed_channel.counter_party, - pnl: (own_collateral as i64) - (own_payout as i64), - }); + let closed_contract = Contract::Closed(self.get_collaboratively_closed_contract( + &signed_contract_id, + own_payout, + true, + )?); self.store .upsert_channel(Channel::Signed(signed_channel), Some(closed_contract))?; - self.store.persist_chain_monitor(&self.chain_monitor)?; + self.store + .persist_chain_monitor(&self.chain_monitor.lock().unwrap())?; Ok(msg) } fn on_settle_finalize( - &mut self, + &self, settle_finalize: &SettleFinalize, peer_id: &PublicKey, ) -> Result<(), Error> { let mut signed_channel = get_channel_in_state!(self, &settle_finalize.channel_id, Signed, Some(*peer_id))?; - let own_payout = get_signed_channel_state!(signed_channel, SettledConfirmed, own_payout)?; + let &own_payout = get_signed_channel_state!(signed_channel, SettledConfirmed, own_payout)?; let (buffer_tx, own_buffer_adaptor_signature, is_offer, signed_contract_id) = get_signed_channel_rollback_state!( signed_channel, Established, @@ -1446,7 +1539,7 @@ where settle_finalize, )?; - self.chain_monitor.add_tx( + self.chain_monitor.lock().unwrap().add_tx( buffer_txid, ChannelInfo { channel_id: signed_channel.channel_id, @@ -1459,37 +1552,21 @@ where }, ); - let contract = - get_contract_in_state!(self, &signed_contract_id, Confirmed, None::)?; - - let own_collateral = if contract.accepted_contract.offered_contract.is_offer_party { - contract - .accepted_contract - .offered_contract - .offer_params - .collateral - } else { - contract.accepted_contract.accept_params.collateral - }; - - let closed_contract = Contract::Closed(ClosedContract { - attestations: None, - signed_cet: None, - contract_id: signed_contract_id, - temporary_contract_id: contract.accepted_contract.offered_contract.id, - counter_party_id: signed_channel.counter_party, - pnl: (own_collateral as i64) - (own_payout as i64), - }); - + let closed_contract = Contract::Closed(self.get_collaboratively_closed_contract( + &signed_contract_id, + own_payout, + true, + )?); self.store .upsert_channel(Channel::Signed(signed_channel), Some(closed_contract))?; - self.store.persist_chain_monitor(&self.chain_monitor)?; + self.store + .persist_chain_monitor(&self.chain_monitor.lock().unwrap())?; Ok(()) } fn on_renew_offer( - &mut self, + &self, renew_offer: &RenewOffer, peer_id: &PublicKey, ) -> Result, Error> { @@ -1516,7 +1593,7 @@ where } fn on_renew_accept( - &mut self, + &self, renew_accept: &RenewAccept, peer_id: &PublicKey, ) -> Result { @@ -1531,16 +1608,34 @@ where let offered_contract = get_contract_in_state!(self, &offered_contract_id, Offered, Some(*peer_id))?; - let (signed_contract, msg) = crate::channel_updater::verify_renew_accept_and_confirm( - &self.secp, - renew_accept, - &mut signed_channel, - &offered_contract, - CET_NSEQUENCE, - PEER_TIMEOUT, - &self.wallet, - &self.time, - )?; + let own_buffer_adaptor_sk = if let Some(sub_channel_id) = + signed_channel.sub_channel_id.as_ref() + { + let (signed_sub_channel, state) = + get_sub_channel_in_state!(self, *sub_channel_id, Signed, None::)?; + let own_base_secret_key = self + .wallet + .get_secret_key_for_pubkey(&signed_sub_channel.own_base_points.own_basepoint)?; + let own_secret_key = + derive_private_key(&self.secp, &state.own_per_split_point, &own_base_secret_key); + + Some(own_secret_key) + } else { + None + }; + + let (signed_contract, msg) = + crate::channel_updater::verify_renew_accept_and_confirm_internal( + &self.secp, + renew_accept, + &mut signed_channel, + &offered_contract, + CET_NSEQUENCE, + PEER_TIMEOUT, + &self.wallet, + &self.time, + own_buffer_adaptor_sk, + )?; // Directly confirmed as we're in a channel the fund tx is already confirmed. self.store.upsert_channel( @@ -1552,12 +1647,13 @@ where } fn on_renew_confirm( - &mut self, + &self, renew_confirm: &RenewConfirm, peer_id: &PublicKey, ) -> Result { let mut signed_channel = get_channel_in_state!(self, &renew_confirm.channel_id, Signed, Some(*peer_id))?; + let own_payout = get_signed_channel_state!(signed_channel, RenewAccepted, own_payout)?; let contract_id = signed_channel.get_contract_id().ok_or_else(|| { Error::InvalidState( "Expected to be in a state with an associated contract id but was not.".to_string(), @@ -1575,27 +1671,11 @@ where signed_contract_id, .. } => { - let contract = - get_contract_in_state!(self, signed_contract_id, Confirmed, None::)?; - let own_collateral = if contract.accepted_contract.offered_contract.is_offer_party { - contract - .accepted_contract - .offered_contract - .offer_params - .collateral - } else { - contract.accepted_contract.accept_params.collateral - }; - let pnl = (contract.accepted_contract.offered_contract.total_collateral as i64) - - (own_collateral as i64); - let closed_contract = Contract::Closed(ClosedContract { - attestations: None, - signed_cet: None, - contract_id: *signed_contract_id, - temporary_contract_id: contract.accepted_contract.offered_contract.id, - counter_party_id: signed_channel.counter_party, - pnl, - }); + let closed_contract = Contract::Closed(self.get_collaboratively_closed_contract( + signed_contract_id, + *own_payout, + true, + )?); ( TxType::Revoked { update_idx: signed_channel.update_idx, @@ -1623,24 +1703,57 @@ where ), s => { return Err(Error::InvalidState(format!( - "Expected rollback state Established or Revoked but found {:?}", - s + "Expected rollback state Established or Revoked but found {s:?}" ))) } }; - let accepted_contract = get_contract_in_state!(self, &contract_id, Accepted, Some(*peer_id))?; - let (signed_contract, msg) = crate::channel_updater::verify_renew_confirm_and_finalize( - &self.secp, - &mut signed_channel, - &accepted_contract, - renew_confirm, - &self.wallet, - )?; + let (counter_buffer_adaptor_pk, own_buffer_adaptor_sk) = if let Some(sub_channel_id) = + signed_channel.sub_channel_id.as_ref() + { + let (signed_sub_channel, state) = + get_sub_channel_in_state!(self, *sub_channel_id, Signed, None::)?; + let accept_revoke_params = signed_sub_channel + .counter_base_points + .expect("to have counter base points") + .get_revokable_params( + &self.secp, + &signed_sub_channel.own_base_points.revocation_basepoint, + &state.counter_per_split_point, + ); + let (signed_sub_channel, state) = + get_sub_channel_in_state!(self, *sub_channel_id, Signed, None::)?; + let own_base_secret_key = self + .wallet + .get_secret_key_for_pubkey(&signed_sub_channel.own_base_points.own_basepoint)?; + let own_secret_key = + derive_private_key(&self.secp, &state.own_per_split_point, &own_base_secret_key); + + ( + Some(accept_revoke_params.own_pk.inner), + Some(own_secret_key), + ) + } else { + (None, None) + }; - self.chain_monitor.add_tx( + let (signed_contract, msg) = + crate::channel_updater::verify_renew_confirm_and_finalize_internal( + &self.secp, + &mut signed_channel, + &accepted_contract, + renew_confirm, + PEER_TIMEOUT, + &self.time, + &self.wallet, + counter_buffer_adaptor_pk, + own_buffer_adaptor_sk, + &self.chain_monitor, + )?; + + self.chain_monitor.lock().unwrap().add_tx( prev_tx_id, ChannelInfo { channel_id: signed_channel.channel_id, @@ -1648,24 +1761,14 @@ where }, ); - let buffer_tx = - get_signed_channel_state!(signed_channel, Established, ref buffer_transaction)?; - - self.chain_monitor.add_tx( - buffer_tx.txid(), - ChannelInfo { - channel_id: signed_channel.channel_id, - tx_type: TxType::Current, - }, - ); - // Directly confirmed as we're in a channel the fund tx is already confirmed. self.store.upsert_channel( Channel::Signed(signed_channel), Some(Contract::Confirmed(signed_contract)), )?; - self.store.persist_chain_monitor(&self.chain_monitor)?; + self.store + .persist_chain_monitor(&self.chain_monitor.lock().unwrap())?; if let Some(closed_contract) = closed_contract { self.store.update_contract(&closed_contract)?; @@ -1675,12 +1778,13 @@ where } fn on_renew_finalize( - &mut self, + &self, renew_finalize: &RenewFinalize, peer_id: &PublicKey, - ) -> Result<(), Error> { + ) -> Result { let mut signed_channel = get_channel_in_state!(self, &renew_finalize.channel_id, Signed, Some(*peer_id))?; + let own_payout = get_signed_channel_state!(signed_channel, RenewConfirmed, own_payout)?; let (tx_type, prev_tx_id, closed_contract) = match signed_channel .roll_back_state @@ -1693,27 +1797,11 @@ where signed_contract_id, .. } => { - let contract = - get_contract_in_state!(self, signed_contract_id, Confirmed, None::)?; - let own_collateral = if contract.accepted_contract.offered_contract.is_offer_party { - contract - .accepted_contract - .offered_contract - .offer_params - .collateral - } else { - contract.accepted_contract.accept_params.collateral - }; - let pnl = (contract.accepted_contract.offered_contract.total_collateral as i64) - - (own_collateral as i64); - let closed_contract = Contract::Closed(ClosedContract { - attestations: None, - signed_cet: None, - contract_id: *signed_contract_id, - temporary_contract_id: contract.accepted_contract.offered_contract.id, - counter_party_id: signed_channel.counter_party, - pnl, - }); + let closed_contract = self.get_collaboratively_closed_contract( + signed_contract_id, + *own_payout, + true, + )?; ( TxType::Revoked { update_idx: signed_channel.update_idx, @@ -1722,7 +1810,7 @@ where revoked_tx_type: RevokedTxType::Buffer, }, buffer_transaction.txid(), - Some(closed_contract), + Some(Contract::Closed(closed_contract)), ) } SignedChannelState::Settled { @@ -1741,15 +1829,37 @@ where ), s => { return Err(Error::InvalidState(format!( - "Expected rollback state of Established or Settled but was {:?}", - s + "Expected rollback state of Established or Settled but was {s:?}" ))) } }; - crate::channel_updater::renew_channel_on_finalize(&mut signed_channel, renew_finalize)?; + let counter_buffer_adaptor_pk = + if let Some(sub_channel_id) = signed_channel.sub_channel_id.as_ref() { + let (signed_sub_channel, state) = + get_sub_channel_in_state!(self, *sub_channel_id, Signed, None::)?; + let accept_revoke_params = signed_sub_channel + .counter_base_points + .expect("to have counter base points") + .get_revokable_params( + &self.secp, + &signed_sub_channel.own_base_points.revocation_basepoint, + &state.counter_per_split_point, + ); + Some(accept_revoke_params.own_pk.inner) + } else { + None + }; + + let msg = crate::channel_updater::renew_channel_on_finalize( + &self.secp, + &mut signed_channel, + renew_finalize, + counter_buffer_adaptor_pk, + &self.wallet, + )?; - self.chain_monitor.add_tx( + self.chain_monitor.lock().unwrap().add_tx( prev_tx_id, ChannelInfo { channel_id: signed_channel.channel_id, @@ -1760,27 +1870,46 @@ where let buffer_tx = get_signed_channel_state!(signed_channel, Established, ref buffer_transaction)?; - self.chain_monitor.add_tx( + self.chain_monitor.lock().unwrap().add_tx( buffer_tx.txid(), ChannelInfo { channel_id: signed_channel.channel_id, - tx_type: TxType::Current, + tx_type: TxType::BufferTx, }, ); self.store .upsert_channel(Channel::Signed(signed_channel), None)?; - self.store.persist_chain_monitor(&self.chain_monitor)?; + self.store + .persist_chain_monitor(&self.chain_monitor.lock().unwrap())?; if let Some(closed_contract) = closed_contract { self.store.update_contract(&closed_contract)?; } - Ok(()) + Ok(msg) + } + + fn on_renew_revoke( + &self, + renew_revoke: &RenewRevoke, + peer_id: &PublicKey, + ) -> Result<(), Error> { + let mut signed_channel = + get_channel_in_state!(self, &renew_revoke.channel_id, Signed, Some(*peer_id))?; + + crate::channel_updater::renew_channel_on_revoke( + &self.secp, + &mut signed_channel, + renew_revoke, + )?; + + self.store + .upsert_channel(Channel::Signed(signed_channel), None) } fn on_collaborative_close_offer( - &mut self, + &self, close_offer: &CollaborativeCloseOffer, peer_id: &PublicKey, ) -> Result<(), Error> { @@ -1800,7 +1929,7 @@ where Ok(()) } - fn on_reject(&mut self, reject: &Reject, counter_party: &PublicKey) -> Result<(), Error> { + fn on_reject(&self, reject: &Reject, counter_party: &PublicKey) -> Result<(), Error> { let mut signed_channel = get_channel_in_state!(self, &reject.channel_id, Signed, Some(*counter_party))?; @@ -1811,7 +1940,7 @@ where Ok(()) } - fn channel_checks(&mut self) -> Result<(), Error> { + fn channel_checks(&self) -> Result<(), Error> { let established_closing_channels = self .store .get_signed_channels(Some(SignedChannelStateType::Closing))?; @@ -1828,10 +1957,11 @@ where self.check_for_watched_tx() } - fn check_for_timed_out_channels(&mut self) -> Result<(), Error> { + fn check_for_timed_out_channels(&self) -> Result<(), Error> { check_for_timed_out_channels!(self, RenewOffered); check_for_timed_out_channels!(self, RenewAccepted); check_for_timed_out_channels!(self, RenewConfirmed); + check_for_timed_out_channels!(self, RenewFinalized); check_for_timed_out_channels!(self, SettledOffered); check_for_timed_out_channels!(self, SettledAccepted); check_for_timed_out_channels!(self, SettledConfirmed); @@ -1839,77 +1969,58 @@ where Ok(()) } - fn check_for_watched_tx(&mut self) -> Result<(), Error> { - let cur_height = self.blockchain.get_blockchain_height()?; - let last_height = self.chain_monitor.last_height; - - if cur_height < last_height { - return Err(Error::InvalidState( - "Current height is lower than last height.".to_string(), - )); - } - - //todo(tibo): check and deal with reorgs. - - for height in last_height + 1..cur_height { - let block = self.blockchain.get_block_at_height(height)?; - - let watch_res = self.chain_monitor.process_block(&block, height); - - for (tx, channel_info) in watch_res { - let mut signed_channel = match get_channel_in_state!( - self, - &channel_info.channel_id, - Signed, - None as Option - ) { - Ok(c) => c, - Err(e) => { - error!( - "Could not retrieve channel {:?}: {}", - channel_info.channel_id, e - ); - continue; - } - }; + pub(crate) fn process_watched_txs( + &self, + watched_txs: Vec<(Transaction, ChannelInfo)>, + ) -> Result<(), Error> { + for (tx, channel_info) in watched_txs { + let mut signed_channel = match get_channel_in_state!( + self, + &channel_info.channel_id, + Signed, + None as Option + ) { + Ok(c) => c, + Err(e) => { + error!( + "Could not retrieve channel {:?}: {}", + channel_info.channel_id, e + ); + continue; + } + }; - if let TxType::Current = channel_info.tx_type { + let persist = match channel_info.tx_type { + TxType::BufferTx => { // TODO(tibo): should only considered closed after some confirmations. // Ideally should save previous state, and maybe restore in // case of reorg, though if the counter party has sent the // tx to close the channel it is unlikely that the tx will // not be part of a future block. - let contract = if let Some(contract_id) = signed_channel.get_contract_id() { - let contract_opt = self.store.get_contract(&contract_id)?; - if let Some(contract) = contract_opt { - match contract { - Contract::Confirmed(c) => { - Some(Contract::PreClosed(PreClosedContract { - signed_contract: c, - attestations: None, - signed_cet: tx.clone(), - })) - } - _ => None, - } - } else { - None - } - } else { - None + + let contract_id = signed_channel + .get_contract_id() + .expect("to have a contract id"); + let mut state = SignedChannelState::Closing { + buffer_transaction: tx.clone(), + is_initiator: false, + contract_id, }; + std::mem::swap(&mut signed_channel.state, &mut state); + + signed_channel.roll_back_state = Some(state); - signed_channel.state = SignedChannelState::CounterClosed; self.store - .upsert_channel(Channel::Signed(signed_channel), contract)?; - continue; - } else if let TxType::Revoked { + .upsert_channel(Channel::Signed(signed_channel), None)?; + + false + } + TxType::Revoked { update_idx, own_adaptor_signature, is_offer, revoked_tx_type, - } = channel_info.tx_type - { + } => { let secret = signed_channel .counter_party_commitment_secrets .get_secret(update_idx) @@ -2026,21 +2137,43 @@ where is_offer, )? } + RevokedTxType::Split => { + dlc::channel::sub_channel::create_and_sign_punish_split_transaction( + &self.secp, + offer_params, + accept_params, + &own_sk, + &counter_sk, + &counter_revocation_sk, + &tx, + &self.wallet.get_new_address()?, + 0, + fee_rate_per_vb, + )? + } }; self.blockchain.send_transaction(&signed_tx)?; - signed_channel.state = SignedChannelState::ClosedPunished { - punishment_txid: signed_tx.txid(), - }; - - self.store - .upsert_channel(Channel::Signed(signed_channel), None)?; - } else if let TxType::CollaborativeClose = channel_info.tx_type { + let closed_channel = Channel::ClosedPunished(ClosedPunishedChannel { + counter_party: signed_channel.counter_party, + temporary_channel_id: signed_channel.temporary_channel_id, + channel_id: signed_channel.channel_id, + punish_txid: signed_tx.txid(), + }); + + //TODO(tibo): should probably make sure the tx is confirmed somewhere before + //stop watching the cheating tx. + self.chain_monitor + .lock() + .unwrap() + .cleanup_channel(signed_channel.channel_id); + self.store.upsert_channel(closed_channel, None)?; + true + } + TxType::CollaborativeClose => { if let Some(SignedChannelState::Established { - signed_contract_id, - is_offer, - .. + signed_contract_id, .. }) = signed_channel.roll_back_state { let counter_payout = get_signed_channel_state!( @@ -2048,55 +2181,170 @@ where CollaborativeCloseOffered, counter_payout )?; - let contract = get_contract_in_state!( - self, + let closed_contract = self.get_collaboratively_closed_contract( &signed_contract_id, - Confirmed, - None:: + *counter_payout, + false, )?; - let own_payout = - contract.accepted_contract.offered_contract.total_collateral - - counter_payout; - let own_collateral = if is_offer { - contract - .accepted_contract - .offered_contract - .offer_params - .collateral - } else { - contract.accepted_contract.accept_params.collateral - }; - let pnl = (own_collateral as i64) - (own_payout as i64); - - let closed_contract = ClosedContract { - attestations: None, - signed_cet: None, - contract_id: signed_contract_id, - temporary_contract_id: contract.accepted_contract.offered_contract.id, - counter_party_id: signed_channel.counter_party, - pnl, - }; self.store .update_contract(&Contract::Closed(closed_contract))?; } - signed_channel.state = SignedChannelState::CollaborativelyClosed; + let closed_channel = Channel::CollaborativelyClosed(ClosedChannel { + counter_party: signed_channel.counter_party, + temporary_channel_id: signed_channel.temporary_channel_id, + channel_id: signed_channel.channel_id, + }); + self.chain_monitor + .lock() + .unwrap() + .cleanup_channel(signed_channel.channel_id); + self.store.upsert_channel(closed_channel, None)?; + true + } + TxType::SettleTx => { + let closed_channel = Channel::CounterClosed(ClosedChannel { + counter_party: signed_channel.counter_party, + temporary_channel_id: signed_channel.temporary_channel_id, + channel_id: signed_channel.channel_id, + }); + self.chain_monitor + .lock() + .unwrap() + .cleanup_channel(signed_channel.channel_id); + self.store.upsert_channel(closed_channel, None)?; + true + } + TxType::Cet => { + let contract_id = signed_channel.get_contract_id(); + let closed_channel = { + match &signed_channel.state { + SignedChannelState::Closing { is_initiator, .. } => { + if *is_initiator { + Channel::Closed(ClosedChannel { + counter_party: signed_channel.counter_party, + temporary_channel_id: signed_channel.temporary_channel_id, + channel_id: signed_channel.channel_id, + }) + } else { + Channel::CounterClosed(ClosedChannel { + counter_party: signed_channel.counter_party, + temporary_channel_id: signed_channel.temporary_channel_id, + channel_id: signed_channel.channel_id, + }) + } + } + _ => { + error!("Saw spending of buffer transaction without being in closing state"); + Channel::Closed(ClosedChannel { + counter_party: signed_channel.counter_party, + temporary_channel_id: signed_channel.temporary_channel_id, + channel_id: signed_channel.channel_id, + }) + } + } + }; + + self.chain_monitor + .lock() + .unwrap() + .cleanup_channel(signed_channel.channel_id); + + let pre_closed_contract = contract_id + .map(|contract_id| { + self.store.get_contract(&contract_id).map(|contract| { + contract.map(|contract| match contract { + Contract::Confirmed(signed_contract) => { + Some(Contract::PreClosed(PreClosedContract { + signed_contract, + attestations: None, + signed_cet: tx.clone(), + })) + } + _ => None, + }) + }) + }) + .transpose()? + .flatten() + .flatten(); + self.store - .upsert_channel(Channel::Signed(signed_channel), None)?; + .upsert_channel(closed_channel, pre_closed_contract)?; + + true } - } + TxType::SplitTx => false, + }; - self.chain_monitor.increment_height(&block.block_hash()); + if persist { + self.store + .persist_chain_monitor(&self.chain_monitor.lock().unwrap())?; + } } + Ok(()) + } + + fn check_for_watched_tx(&self) -> Result<(), Error> { + let confirmed_txs = self.chain_monitor.lock().unwrap().confirmed_txs(); + + self.process_watched_txs(confirmed_txs)?; + + self.get_store() + .persist_chain_monitor(&self.chain_monitor.lock().unwrap())?; Ok(()) } - fn force_close_channel_internal(&mut self, mut channel: SignedChannel) -> Result<(), Error> { - match channel.state { - SignedChannelState::Established { .. } => { - self.initiate_unilateral_close_established_channel(channel) + pub(crate) fn force_close_sub_channel( + &self, + channel_id: &ChannelId, + sub_channel: (SubChannel, &ClosingSubChannel), + ) -> Result<(), Error> { + let channel = get_channel_in_state!(self, channel_id, Signed, None as Option)?; + let is_initiator = sub_channel.1.is_initiator; + self.force_close_channel_internal(channel, Some(sub_channel), is_initiator) + } + + fn force_close_channel_internal( + &self, + mut channel: SignedChannel, + sub_channel: Option<(SubChannel, &ClosingSubChannel)>, + is_initiator: bool, + ) -> Result<(), Error> { + match &channel.state { + SignedChannelState::Established { + counter_buffer_adaptor_signature, + buffer_transaction, + .. + } => { + let counter_buffer_adaptor_signature = *counter_buffer_adaptor_signature; + let buffer_transaction = buffer_transaction.clone(); + self.initiate_unilateral_close_established_channel( + channel, + sub_channel, + is_initiator, + counter_buffer_adaptor_signature, + buffer_transaction, + ) + } + SignedChannelState::RenewFinalized { + buffer_transaction, + offer_buffer_adaptor_signature, + .. + } => { + let offer_buffer_adaptor_signature = *offer_buffer_adaptor_signature; + let buffer_transaction = buffer_transaction.clone(); + self.initiate_unilateral_close_established_channel( + channel, + sub_channel, + is_initiator, + offer_buffer_adaptor_signature, + buffer_transaction, + ) + } + SignedChannelState::Settled { .. } => { + self.close_settled_channel(channel, sub_channel, is_initiator) } - SignedChannelState::Settled { .. } => self.close_settled_channel(channel), SignedChannelState::SettledOffered { .. } | SignedChannelState::SettledReceived { .. } | SignedChannelState::SettledAccepted { .. } @@ -2109,89 +2357,161 @@ where .roll_back_state .take() .expect("to have a rollback state"); - self.force_close_channel_internal(channel) + self.force_close_channel_internal(channel, sub_channel, is_initiator) } SignedChannelState::Closing { .. } => Err(Error::InvalidState( "Channel is already closing.".to_string(), )), - SignedChannelState::Closed - | SignedChannelState::CounterClosed - | SignedChannelState::CollaborativelyClosed - | SignedChannelState::ClosedPunished { .. } => { - Err(Error::InvalidState("Channel already closed.".to_string())) - } } } /// Initiate the unilateral closing of a channel that has been established. fn initiate_unilateral_close_established_channel( - &mut self, + &self, mut signed_channel: SignedChannel, + sub_channel: Option<(SubChannel, &ClosingSubChannel)>, + is_initiator: bool, + buffer_adaptor_signature: EcdsaAdaptorSignature, + buffer_transaction: Transaction, ) -> Result<(), Error> { - let contract_id = signed_channel.get_contract_id().ok_or_else(|| { - Error::InvalidState( - "Expected to be in a state with an associated contract id but was not.".to_string(), - ) - })?; - - let confirmed_contract = - get_contract_in_state!(self, &contract_id, Confirmed, None as Option)?; - - let (contract_info, adaptor_info, attestations) = self - .get_closable_contract_info(&confirmed_contract) - .ok_or_else(|| { - Error::InvalidState("Could not get closable contract info".to_string()) - })?; - crate::channel_updater::initiate_unilateral_close_established_channel( &self.secp, &mut signed_channel, - &confirmed_contract, - contract_info, - &attestations, - adaptor_info, + buffer_adaptor_signature, + buffer_transaction, &self.wallet, + sub_channel, + is_initiator, )?; let buffer_transaction = get_signed_channel_state!(signed_channel, Closing, ref buffer_transaction)?; - self.blockchain.send_transaction(buffer_transaction)?; + if self + .blockchain + .get_transaction_confirmations(&buffer_transaction.txid()) + .unwrap_or(0) + == 0 + { + self.blockchain.send_transaction(buffer_transaction)?; + } - self.chain_monitor.remove_tx(&buffer_transaction.txid()); + self.chain_monitor + .lock() + .unwrap() + .remove_tx(&buffer_transaction.txid()); self.store .upsert_channel(Channel::Signed(signed_channel), None)?; - self.store.persist_chain_monitor(&self.chain_monitor)?; + self.store + .persist_chain_monitor(&self.chain_monitor.lock().unwrap())?; Ok(()) } /// Unilaterally close a channel that has been settled. - fn close_settled_channel(&mut self, mut signed_channel: SignedChannel) -> Result<(), Error> { - let settle_tx = crate::channel_updater::close_settled_channel( + fn close_settled_channel( + &self, + signed_channel: SignedChannel, + sub_channel: Option<(SubChannel, &ClosingSubChannel)>, + is_initiator: bool, + ) -> Result<(), Error> { + let (settle_tx, closed_channel) = crate::channel_updater::close_settled_channel_internal( &self.secp, - &mut signed_channel, + &signed_channel, &self.wallet, + sub_channel, + is_initiator, )?; - self.blockchain.send_transaction(&settle_tx)?; + if self + .blockchain + .get_transaction_confirmations(&settle_tx.txid()) + .unwrap_or(0) + == 0 + { + self.blockchain.send_transaction(&settle_tx)?; + } - self.store - .upsert_channel(Channel::Signed(signed_channel), None)?; + self.chain_monitor + .lock() + .unwrap() + .cleanup_channel(signed_channel.channel_id); + + self.store.upsert_channel(closed_channel, None)?; Ok(()) } + + /// Function used to mark a dlc channel as closed when the parent sub channel was + /// collaboratively closed. + pub(crate) fn get_closed_sub_dlc_channel( + &self, + channel_id: ChannelId, + own_balance: u64, + ) -> Result<(Channel, Option), Error> { + let channel = get_channel_in_state!(self, &channel_id, Signed, None::)?; + + let contract = if let Some(contract_id) = channel.get_contract_id() { + Some(Contract::Closed(self.get_collaboratively_closed_contract( + &contract_id, + own_balance, + true, + )?)) + } else { + None + }; + + let closed_channel = Channel::CollaborativelyClosed(ClosedChannel { + counter_party: channel.counter_party, + temporary_channel_id: channel.temporary_channel_id, + channel_id, + }); + + Ok((closed_channel, contract)) + } + + fn get_collaboratively_closed_contract( + &self, + contract_id: &ContractId, + payout: u64, + is_own_payout: bool, + ) -> Result { + let contract = get_contract_in_state!(self, contract_id, Confirmed, None::)?; + let own_collateral = if contract.accepted_contract.offered_contract.is_offer_party { + contract + .accepted_contract + .offered_contract + .offer_params + .collateral + } else { + contract.accepted_contract.accept_params.collateral + }; + let own_payout = if is_own_payout { + payout + } else { + contract.accepted_contract.offered_contract.total_collateral - payout + }; + let pnl = own_payout as i64 - own_collateral as i64; + Ok(ClosedContract { + attestations: None, + signed_cet: None, + contract_id: *contract_id, + temporary_contract_id: contract.accepted_contract.offered_contract.id, + counter_party_id: contract.accepted_contract.offered_contract.counter_party, + pnl, + }) + } } #[cfg(test)] mod test { - use dlc_messages::Message; + use dlc_messages::{ChannelMessage, Message, OnChainMessage}; use mocks::{ dlc_manager::{manager::Manager, Oracle}, memory_storage_provider::MemoryStorage, - mock_blockchain::MockBlockchain, + mock_blockchain::{MockBlockchain, MockBroadcaster}, mock_oracle_provider::MockOracle, mock_time::MockTime, mock_wallet::MockWallet, @@ -2201,15 +2521,15 @@ mod test { type TestManager = Manager< Rc, - Rc, + Rc>>, Rc, Rc, Rc, - Rc, + Rc>>, >; fn get_manager() -> TestManager { - let blockchain = Rc::new(MockBlockchain {}); + let blockchain = Rc::new(MockBlockchain::new(Rc::new(MockBroadcaster {}))); let store = Rc::new(MemoryStorage::new()); let wallet = Rc::new(MockWallet::new(&blockchain, 100)); @@ -2233,11 +2553,11 @@ mod test { #[test] fn reject_offer_with_existing_contract_id() { - let offer_message = Message::Offer( + let offer_message = Message::OnChain(OnChainMessage::Offer( serde_json::from_str(include_str!("../test_inputs/offer_contract.json")).unwrap(), - ); + )); - let mut manager = get_manager(); + let manager = get_manager(); manager .on_dlc_message(&offer_message, pubkey()) @@ -2250,11 +2570,11 @@ mod test { #[test] fn reject_channel_offer_with_existing_channel_id() { - let offer_message = Message::OfferChannel( + let offer_message = Message::Channel(ChannelMessage::Offer( serde_json::from_str(include_str!("../test_inputs/offer_channel.json")).unwrap(), - ); + )); - let mut manager = get_manager(); + let manager = get_manager(); manager .on_dlc_message(&offer_message, pubkey()) diff --git a/dlc-manager/src/payout_curve.rs b/dlc-manager/src/payout_curve.rs index 62f9a0dd..8ad954e3 100644 --- a/dlc-manager/src/payout_curve.rs +++ b/dlc-manager/src/payout_curve.rs @@ -174,7 +174,7 @@ trait Evaluable { ))); } - if payout_double > total_collateral as f64 { + if payout_double.round() > total_collateral as f64 { return Err(Error::InvalidParameters( "Computed payout is greater than total collateral".to_string(), )); @@ -311,7 +311,7 @@ impl Evaluable for PolynomialPayoutCurvePiece { return if left_point.outcome_payout == right_point.outcome_payout { right_point.outcome_payout as f64 } else { - let slope = (right_point.outcome_payout - left_point.outcome_payout) as f64 + let slope = (right_point.outcome_payout as f64 - left_point.outcome_payout as f64) / (right_point.event_outcome - left_point.event_outcome) as f64; (outcome - left_point.event_outcome) as f64 * slope + left_point.outcome_payout as f64 @@ -1215,4 +1215,79 @@ mod test { .to_range_payouts(7513, &rounding_intervals) .expect("To be able to compute the range payouts"); } + + #[test] + fn floating_point_error_doesn_fail() { + let function = PayoutFunction::new(vec![PayoutFunctionPiece::PolynomialPayoutCurvePiece( + PolynomialPayoutCurvePiece::new(vec![ + PayoutPoint { + event_outcome: 22352, + outcome_payout: 0, + extra_precision: 0, + }, + PayoutPoint { + event_outcome: 55881, + outcome_payout: 87455, + extra_precision: 0, + }, + ]) + .unwrap(), + )]) + .unwrap(); + + let rounding_mod = 1; + + let rounding_intervals = RoundingIntervals { + intervals: vec![RoundingInterval { + begin_interval: 0, + rounding_mod, + }], + }; + + function + .to_range_payouts(87455, &rounding_intervals) + .expect("Not to fail"); + } + + #[test] + fn monotonic_increasing_payout_curve_is_valid() { + let polynomial = PolynomialPayoutCurvePiece { + payout_points: vec![ + PayoutPoint { + event_outcome: 0, + outcome_payout: 1, + extra_precision: 0, + }, + PayoutPoint { + event_outcome: 2, + outcome_payout: 5, + extra_precision: 0, + }, + ], + }; + + assert_eq!(polynomial.evaluate(0), 1.0); + assert_eq!(polynomial.evaluate(2), 5.0); + } + + #[test] + fn monotonic_decreasing_payout_curve_is_valid() { + let polynomial = PolynomialPayoutCurvePiece { + payout_points: vec![ + PayoutPoint { + event_outcome: 0, + outcome_payout: 10, + extra_precision: 0, + }, + PayoutPoint { + event_outcome: 1, + outcome_payout: 8, + extra_precision: 0, + }, + ], + }; + + assert_eq!(polynomial.evaluate(0), 10.0); + assert_eq!(polynomial.evaluate(1), 8.0); + } } diff --git a/dlc-manager/src/sub_channel_manager.rs b/dlc-manager/src/sub_channel_manager.rs new file mode 100644 index 00000000..5f0f386b --- /dev/null +++ b/dlc-manager/src/sub_channel_manager.rs @@ -0,0 +1,4135 @@ +//! # Module containing a manager enabling set up and update of DLC channels embedded within +//! Lightning Network channels. + +use std::{collections::HashMap, marker::PhantomData, ops::Deref, sync::Mutex}; + +use bitcoin::{ + consensus::encode::serialize_hex, hashes::hex::ToHex, OutPoint, PackedLockTime, Script, + Sequence, Transaction, +}; +use dlc::{channel::sub_channel::LN_GLUE_TX_WEIGHT, PartyParams}; +use dlc_messages::{ + channel::{AcceptChannel, OfferChannel}, + oracle_msgs::OracleAnnouncement, + sub_channel::{ + Reject, SubChannelAccept, SubChannelCloseAccept, SubChannelCloseConfirm, + SubChannelCloseFinalize, SubChannelCloseOffer, SubChannelConfirm, SubChannelFinalize, + SubChannelOffer, SubChannelRevoke, + }, + FundingSignatures, SubChannelMessage, +}; +use lightning::{ + chain::chaininterface::FeeEstimator, + events::{ClosureReason, MessageSendEventsProvider}, + ln::{ + chan_utils::{ + build_commitment_secret, derive_private_key, derive_private_revocation_key, + CounterpartyCommitmentSecrets, + }, + channelmanager::ChannelDetails, + msgs::{ChannelMessageHandler, DecodeError, RevokeAndACK}, + }, + sign::ChannelSigner, + util::{ + errors::APIError, + ser::{Readable, Writeable, Writer}, + }, +}; +use log::{error, info, trace, warn}; +use secp256k1_zkp::{ecdsa::Signature, EcdsaAdaptorSignature, PublicKey, SecretKey}; + +use crate::{ + chain_monitor::{ChannelInfo, RevokedTxType, TxType}, + channel::{ + generate_temporary_contract_id, offered_channel::OfferedChannel, + party_points::PartyBasePoints, Channel, ClosedChannel, + }, + channel_updater::{ + self, FundingInfo, SubChannelSignInfo, SubChannelSignVerifyInfo, SubChannelVerifyInfo, + }, + contract::{contract_input::ContractInput, ClosedContract, Contract, FundingInputInfo}, + error::Error, + manager::{get_channel_in_state, get_contract_in_state, Manager, CET_NSEQUENCE}, + subchannel::{ + self, generate_temporary_channel_id, AcceptedSubChannel, CloseAcceptedSubChannel, + CloseConfirmedSubChannel, CloseOfferedSubChannel, ClosingSubChannel, ConfirmedSubChannel, + LNChainMonitor, LNChannelManager, LnDlcChannelSigner, LnDlcSignerProvider, + OfferedSubChannel, ReestablishFlag, SignedSubChannel, SubChannel, SubChannelState, + }, + Blockchain, ChannelId, ContractId, Oracle, Signer, Storage, Time, Wallet, +}; + +const INITIAL_SPLIT_NUMBER: u64 = (1 << 48) - 1; + +/// Returns the sub channel with given id if found and in the expected state. If a peer id is +/// provided also validates the the sub channel is established with it. +macro_rules! get_sub_channel_in_state { + ($manager: expr, $channel_id: expr, $state: ident, $peer_id: expr) => {{ + match $manager.get_store().get_sub_channel($channel_id)? { + Some(sub_channel) => match $peer_id as Option { + Some(p) if sub_channel.counter_party != p => { + Err(Error::InvalidParameters(format!( + "Peer {:02x?} is not involved with {} {:02x?}.", + $peer_id, + stringify!($object_type), + $channel_id + ))) + } + _ => { + if let SubChannelState::$state(s) = sub_channel.state.clone() { + Ok((sub_channel, s)) + } else { + Err(Error::InvalidState(format!( + "Expected {} state but got {:?}", + stringify!($state), + &sub_channel.state, + ))) + } + } + }, + None => Err(Error::InvalidParameters(format!( + "Unknown {} id.", + stringify!($object_type) + ))), + } + }}; +} + +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), + /// The included [`SubChannelCloseOffer`] message shoud be re-submitted to the peer with + /// included public key. + ResendCloseOffer((SubChannelCloseOffer, PublicKey)), + /// The sub channel with specified `channel_id` should be re-accepted. + ReAcceptCloseOffer { + /// The id of the sub channel. + channel_id: ChannelId, + /// The balance of the local party in the DLC sub channel for settling it. + own_balance: u64, + }, + /// The given [`SubChannelCloseFinalize`] message should be re-sent to the peer with given + /// `PublicKey`. + ResendCloseFinalize((SubChannelCloseFinalize, PublicKey)), +} + +impl_dlc_writeable_enum!(Action, + (0, ResendOffer), + (2, ForceSign), + (3, ResendCloseOffer), + (5, ResendCloseFinalize); + (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) + }), + (4, ReAcceptCloseOffer, { + (channel_id, writeable), + (own_balance, writeable) + });; +); + +/// Structure enabling management of DLC channels embedded within Lightning Network channels. +pub struct SubChannelManager< + W: Deref, + M: Deref, + C: Deref, + S: Deref, + B: Deref, + O: Deref, + T: Deref, + F: Deref, + D: Deref>, + CS: ChannelSigner, + SP: Deref, + LCS: LnDlcChannelSigner, +> where + W::Target: Wallet, + M::Target: LNChannelManager, + C::Target: LNChainMonitor, + S::Target: Storage, + B::Target: Blockchain, + O::Target: Oracle, + T::Target: Time, + F::Target: FeeEstimator, + SP::Target: LnDlcSignerProvider, +{ + ln_channel_manager: M, + dlc_channel_manager: D, + actions: Mutex>, + ln_chain_monitor: C, + phantom: std::marker::PhantomData, + signer_provider: SP, + ln_channel_signers: Mutex>, +} + +impl< + W: Deref, + M: Deref, + C: Deref, + S: Deref, + B: Deref, + O: Deref, + T: Deref, + F: Deref, + D: Deref>, + CS: ChannelSigner, + SP: Deref, + LCS: LnDlcChannelSigner, + > SubChannelManager +where + W::Target: Wallet, + M::Target: LNChannelManager, + C::Target: LNChainMonitor, + S::Target: Storage, + B::Target: Blockchain, + O::Target: Oracle, + T::Target: Time, + F::Target: FeeEstimator, + SP::Target: LnDlcSignerProvider, +{ + /// Creates a new [`SubChannelManager`]. + pub fn new( + ln_channel_manager: M, + dlc_channel_manager: D, + ln_chain_monitor: C, + signer_provider: SP, + ) -> Result { + let actions = dlc_channel_manager.get_store().get_sub_channel_actions()?; + Ok(Self { + ln_channel_manager, + dlc_channel_manager, + actions: Mutex::new(actions), + phantom: PhantomData, + ln_chain_monitor, + signer_provider, + ln_channel_signers: Mutex::new(HashMap::new()), + }) + } + + /// Get a reference to the [`Manager`] held by the instance. + pub fn get_dlc_manager(&self) -> &D { + &self.dlc_channel_manager + } + + /// Handles a [`SubChannelMessage`]. + pub fn on_sub_channel_message( + &self, + msg: &SubChannelMessage, + sender: &PublicKey, + ) -> Result, Error> { + match msg { + SubChannelMessage::Offer(offer) => { + self.on_subchannel_offer(offer, sender)?; + Ok(None) + } + SubChannelMessage::Accept(a) => { + let res = self.on_subchannel_accept(a, sender)?; + Ok(Some(SubChannelMessage::Confirm(res))) + } + SubChannelMessage::Confirm(c) => { + let res = self.on_subchannel_confirm(c, sender)?; + Ok(Some(SubChannelMessage::Finalize(res))) + } + SubChannelMessage::Finalize(f) => { + let res = self.on_sub_channel_finalize(f, sender)?; + Ok(Some(SubChannelMessage::Revoke(res))) + } + SubChannelMessage::Revoke(r) => { + self.on_sub_channel_revoke(r, sender)?; + Ok(None) + } + SubChannelMessage::CloseOffer(o) => { + self.on_sub_channel_close_offer(o, sender)?; + Ok(None) + } + SubChannelMessage::CloseAccept(a) => { + let res = self.on_sub_channel_close_accept(a, sender)?; + Ok(Some(SubChannelMessage::CloseConfirm(res))) + } + SubChannelMessage::CloseConfirm(c) => { + let res = self.on_sub_channel_close_confirm(c, sender)?; + Ok(Some(SubChannelMessage::CloseFinalize(res))) + } + SubChannelMessage::CloseFinalize(f) => { + self.on_sub_channel_close_finalize(f, sender)?; + Ok(None) + } + SubChannelMessage::Reject(r) => { + self.on_sub_channel_reject(r, sender)?; + Ok(None) + } + } + } + + /// Validates and stores contract information for a sub channel to be oferred. + /// Returns a [`SubChannelOffer`] message to be sent to the counter party. + pub fn offer_sub_channel( + &self, + channel_id: &[u8; 32], + contract_input: &ContractInput, + oracle_announcements: &[Vec], + ) -> Result { + // TODO(tibo): deal with already split channel + let channel_details = self + .ln_channel_manager + .get_channel_details(channel_id) + .ok_or_else(|| { + Error::InvalidParameters(format!("Unknown LN channel {channel_id:02x?}")) + })?; + + let sub_channel = + match self + .dlc_channel_manager + .get_store() + .get_sub_channel(channel_details.channel_id)? + { + Some(mut s) => match s.state { + SubChannelState::OffChainClosed => { + s.is_offer = true; + s.update_idx -= 1; + Some(s) + } + _ => return Err(Error::InvalidState( + "Received sub channel offer but a non closed sub channel already exists" + .to_string(), + )), + }, + None => None, + }; + + validate_and_get_ln_values_per_party( + &channel_details, + contract_input.offer_collateral, + contract_input.accept_collateral, + contract_input.fee_rate, + true, + )?; + + let (per_split_seed, update_idx) = match &sub_channel { + None => ( + self.dlc_channel_manager.get_wallet().get_new_secret_key()?, + INITIAL_SPLIT_NUMBER, + ), + Some(s) => { + let pub_seed = s.per_split_seed.expect("Should have a per split seed."); + let sec_seed = self + .dlc_channel_manager + .get_wallet() + .get_secret_key_for_pubkey(&pub_seed)?; + (sec_seed, s.update_idx) + } + }; + let per_split_secret = SecretKey::from_slice(&build_commitment_secret( + per_split_seed.as_ref(), + update_idx, + )) + .expect("a valid secret key."); + + let next_per_split_point = + PublicKey::from_secret_key(self.dlc_channel_manager.get_secp(), &per_split_secret); + let per_split_seed_pk = + PublicKey::from_secret_key(self.dlc_channel_manager.get_secp(), &per_split_seed); + + let temporary_channel_id: ContractId = + subchannel::generate_temporary_channel_id(*channel_id, update_idx, 0); + + let (offered_channel, mut offered_contract) = crate::channel_updater::offer_channel( + self.dlc_channel_manager.get_secp(), + contract_input, + &channel_details.counterparty.node_id, + oracle_announcements, + crate::manager::CET_NSEQUENCE, + crate::manager::REFUND_DELAY, + self.dlc_channel_manager.get_wallet(), + self.dlc_channel_manager.get_blockchain(), + self.dlc_channel_manager.get_time(), + temporary_channel_id, + true, + )?; + + // TODO(tibo): refactor properly. + offered_contract.offer_params.inputs = Vec::new(); + offered_contract.funding_inputs_info = Vec::new(); + + let offered_state = OfferedSubChannel { + per_split_point: next_per_split_point, + }; + + let sub_channel = match sub_channel { + Some(mut s) => { + s.state = SubChannelState::Offered(offered_state); + s + } + None => { + let party_base_points = crate::utils::get_party_base_points( + self.dlc_channel_manager.get_secp(), + self.dlc_channel_manager.get_wallet(), + )?; + SubChannel { + channel_id: channel_details.channel_id, + counter_party: channel_details.counterparty.node_id, + per_split_seed: Some(per_split_seed_pk), + fee_rate_per_vb: contract_input.fee_rate, + is_offer: true, + update_idx: INITIAL_SPLIT_NUMBER, + state: SubChannelState::Offered(offered_state), + counter_party_secrets: CounterpartyCommitmentSecrets::new(), + own_base_points: party_base_points, + counter_base_points: None, + fund_value_satoshis: channel_details.channel_value_satoshis, + original_funding_redeemscript: channel_details.funding_redeemscript.unwrap(), + own_fund_pk: channel_details.holder_funding_pubkey, + counter_fund_pk: channel_details.counter_funding_pubkey.ok_or_else(|| { + Error::InvalidState("Counter funding PK is missing".to_string()) + })?, + channel_keys_id: channel_details.channel_keys_id, + } + } + }; + + let msg = SubChannelOffer { + channel_id: channel_details.channel_id, + next_per_split_point, + revocation_basepoint: sub_channel.own_base_points.revocation_basepoint, + publish_basepoint: sub_channel.own_base_points.publish_basepoint, + own_basepoint: sub_channel.own_base_points.own_basepoint, + channel_own_basepoint: offered_channel.party_points.own_basepoint, + channel_publish_basepoint: offered_channel.party_points.publish_basepoint, + channel_revocation_basepoint: offered_channel.party_points.revocation_basepoint, + contract_info: (&offered_contract).into(), + channel_first_per_update_point: offered_channel.per_update_point, + payout_spk: offered_contract.offer_params.payout_script_pubkey.clone(), + payout_serial_id: offered_contract.offer_params.payout_serial_id, + offer_collateral: offered_contract.offer_params.collateral, + cet_locktime: offered_contract.cet_locktime, + refund_locktime: offered_contract.refund_locktime, + cet_nsequence: crate::manager::CET_NSEQUENCE, + fee_rate_per_vbyte: contract_input.fee_rate, + }; + + self.dlc_channel_manager.get_store().upsert_channel( + Channel::Offered(offered_channel), + Some(Contract::Offered(offered_contract)), + )?; + self.dlc_channel_manager + .get_store() + .upsert_sub_channel(&sub_channel)?; + + Ok(msg) + } + + /// Accept an offer to establish a sub-channel within the Lightning Network channel identified + /// by the given [`ChannelId`]. + 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, + *channel_id, + Offered, + None as Option + )?; + let counter_party = offered_sub_channel.counter_party; + + let channel_details = self + .ln_channel_manager + .get_channel_details(channel_id) + .ok_or_else(|| { + Error::InvalidParameters(format!("Unknown LN channel {channel_id:02x?}")) + })?; + let msg = self.ln_channel_manager.with_useable_channel_lock( + channel_id, + &counter_party, + None, + |channel_lock| { + let per_split_seed = + if let Some(per_split_seed_pk) = offered_sub_channel.per_split_seed { + self.dlc_channel_manager + .get_wallet() + .get_secret_key_for_pubkey(&per_split_seed_pk)? + } else { + self.dlc_channel_manager.get_wallet().get_new_secret_key()? + }; + let per_split_secret = SecretKey::from_slice(&build_commitment_secret( + per_split_seed.as_ref(), + offered_sub_channel.update_idx, + )) + .expect("a valid secret key."); + + offered_sub_channel.per_split_seed = Some(PublicKey::from_secret_key( + self.dlc_channel_manager.get_secp(), + &per_split_seed, + )); + + let next_per_split_point = PublicKey::from_secret_key( + self.dlc_channel_manager.get_secp(), + &per_split_secret, + ); + + let temporary_channel_id = + offered_sub_channel + .get_dlc_channel_id(0) + .ok_or(Error::InvalidParameters( + "Could not get dlc channel id".to_string(), + ))?; + + let offered_channel = get_channel_in_state!( + self.dlc_channel_manager, + &temporary_channel_id, + Offered, + None as Option + )?; + + let offered_contract = get_contract_in_state!( + self.dlc_channel_manager, + &offered_channel.offered_contract_id, + Offered, + None as Option + )?; + + // Revalidate in case channel capacity has changed since receiving the offer. + let (own_to_self_msat, _) = validate_and_get_ln_values_per_party( + &channel_details, + offered_contract.total_collateral - offered_contract.offer_params.collateral, + offered_contract.offer_params.collateral, + offered_contract.fee_rate_per_vb, + false, + )?; + + let funding_redeemscript = channel_details + .funding_redeemscript + .as_ref() + .unwrap() + .clone(); + + let funding_txo = channel_details + .funding_txo + .expect("to have a funding tx output"); + + let offer_revoke_params = offered_sub_channel + .counter_base_points + .as_ref() + .expect("to have counter base points") + .get_revokable_params( + self.dlc_channel_manager.get_secp(), + &offered_sub_channel.own_base_points.revocation_basepoint, + &state.per_split_point, + ); + + let accept_revoke_params = + offered_sub_channel.own_base_points.get_revokable_params( + self.dlc_channel_manager.get_secp(), + &offered_sub_channel + .counter_base_points + .as_ref() + .expect("to have counter base points") + .revocation_basepoint, + &next_per_split_point, + ); + + let own_base_secret_key = self + .dlc_channel_manager + .get_wallet() + .get_secret_key_for_pubkey( + &offered_sub_channel.own_base_points.own_basepoint, + )?; + let own_secret_key = derive_private_key( + self.dlc_channel_manager.get_secp(), + &next_per_split_point, + &own_base_secret_key, + ); + + let split_tx = dlc::channel::sub_channel::create_split_tx( + &offer_revoke_params, + &accept_revoke_params, + &OutPoint { + txid: funding_txo.txid, + vout: funding_txo.index as u32, + }, + channel_details.channel_value_satoshis, + offered_contract.total_collateral, + offered_contract.fee_rate_per_vb, + ) + .map_err(|e| APIError::ExternalError { err: e.to_string() })?; + + let ln_output_value = split_tx.transaction.output[0].value; + + let glue_tx_output_value = ln_output_value + - dlc::util::weight_to_fee(LN_GLUE_TX_WEIGHT, offered_contract.fee_rate_per_vb) + .map_err(|e| APIError::ExternalError { err: e.to_string() })?; + + let ln_glue_tx = dlc::channel::sub_channel::create_ln_glue_tx( + &OutPoint { + txid: split_tx.transaction.txid(), + vout: 0, + }, + &funding_redeemscript, + PackedLockTime::ZERO, + Sequence(crate::manager::CET_NSEQUENCE), + glue_tx_output_value, + ); + + let commitment_transactions = self + .ln_chain_monitor + .get_latest_holder_commitment_txn(&funding_txo)?; + + let (commitment_signed, commit_tx_number) = self + .ln_channel_manager + .get_updated_funding_outpoint_commitment_signed( + channel_lock, + &OutPoint { + txid: ln_glue_tx.txid(), + vout: 0, + }, + glue_tx_output_value, + own_to_self_msat, + )?; + + let sub_channel_info = SubChannelSignInfo { + funding_info: FundingInfo { + funding_tx: split_tx.transaction.clone(), + funding_script_pubkey: split_tx.output_script.clone(), + funding_input_value: split_tx.transaction.output[1].value, + }, + own_adaptor_sk: own_secret_key, + }; + + let (accepted_channel, mut accepted_contract, accept_channel) = + channel_updater::accept_channel_offer_internal( + self.dlc_channel_manager.get_secp(), + &offered_channel, + &offered_contract, + 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( + self.dlc_channel_manager.get_secp(), + &ln_glue_tx, + 0, + &split_tx.output_script, + ln_output_value, + &own_secret_key, + ) + .map_err(|e| APIError::ExternalError { err: e.to_string() })?; + + // TODO(tibo): refactor properly. + accepted_contract.accept_params.inputs = Vec::new(); + accepted_contract.funding_inputs = Vec::new(); + + let msg = SubChannelAccept { + channel_id: *channel_id, + first_per_split_point: next_per_split_point, + revocation_basepoint: offered_sub_channel.own_base_points.revocation_basepoint, + publish_basepoint: offered_sub_channel.own_base_points.publish_basepoint, + own_basepoint: offered_sub_channel.own_base_points.own_basepoint, + commit_signature: commitment_signed.signature, + commit_tx_number, + htlc_signatures: commitment_signed.htlc_signatures, + channel_revocation_basepoint: accept_channel.revocation_basepoint, + channel_publish_basepoint: accept_channel.publish_basepoint, + channel_own_basepoint: accept_channel.own_basepoint, + cet_adaptor_signatures: accept_channel.cet_adaptor_signatures, + buffer_adaptor_signature: accept_channel.buffer_adaptor_signature, + refund_signature: accept_channel.refund_signature, + first_per_update_point: accept_channel.first_per_update_point, + payout_spk: accept_channel.payout_spk, + payout_serial_id: accept_channel.payout_serial_id, + ln_glue_signature, + }; + + self.dlc_channel_manager + .get_chain_monitor() + .lock() + .unwrap() + .add_tx( + split_tx.transaction.txid(), + ChannelInfo { + channel_id: offered_sub_channel.channel_id, + tx_type: TxType::SplitTx, + }, + ); + + let accepted_sub_channel = AcceptedSubChannel { + offer_per_split_point: state.per_split_point, + accept_per_split_point: next_per_split_point, + split_tx, + ln_glue_transaction: ln_glue_tx, + ln_rollback: (&channel_details).into(), + commitment_transactions, + }; + + offered_sub_channel.state = SubChannelState::Accepted(accepted_sub_channel); + + self.dlc_channel_manager.get_store().upsert_channel( + Channel::Accepted(accepted_channel), + Some(Contract::Accepted(accepted_contract)), + )?; + self.dlc_channel_manager + .get_store() + .upsert_sub_channel(&offered_sub_channel)?; + self.dlc_channel_manager.get_store().persist_chain_monitor( + &self.dlc_channel_manager.get_chain_monitor().lock().unwrap(), + )?; + Ok(msg) + }, + )?; + + Ok((offered_sub_channel.counter_party, msg)) + } + + /// Start force closing the sub channel with given [`ChannelId`]. + pub fn force_close_sub_channel(&self, channel_id: &ChannelId) -> Result<(), Error> { + let sub_channel = self + .dlc_channel_manager + .get_store() + .get_sub_channel(*channel_id)? + .ok_or(Error::InvalidParameters(format!( + "Unknown sub channel {:?}", + channel_id + )))?; + self.force_close_sub_channel_internal(sub_channel) + } + + fn force_close_sub_channel_internal(&self, mut sub_channel: SubChannel) -> Result<(), Error> { + match sub_channel.state { + // Force close by simply asking LDK to force close as the channel funding outpoint has + // not yet been updated (also updated sub-channel/channel/contract state). + SubChannelState::Offered(_) => self.force_close_with_ldk(sub_channel)?, + // Force close by using the saved LN commitment transactions from before the spliting of the + // channel. + SubChannelState::Accepted(ref a) => { + let commitment_transactions = a.commitment_transactions.clone(); + + self.force_close_with_saved_commitment(sub_channel, &commitment_transactions)?; + } + // Force close by using the saved LN commitment transactions from before the spliting of the + // channel. + SubChannelState::Confirmed(ref c) => { + let commitment_transactions = c.commitment_transactions.clone(); + self.force_close_with_saved_commitment(sub_channel, &commitment_transactions)?; + } + // Force close by broadcasting the split transaction and marking the sub-channel as + // closing, which will trigger the periodic check to watch when the split transaction + // has enough confirmation to close both the LN channel and DLC channel (through LDK + // and the `Manager` respectively). + SubChannelState::Signed(_) | SubChannelState::Finalized(_) => { + self.force_close_signed_channel(sub_channel, None)?; + } + // Same as above, we simply forget that we made an offchain close offer. As we are + // force closing it is anyway not relevant. + SubChannelState::CloseOffered(c) => { + sub_channel.state = SubChannelState::Signed(c.signed_subchannel); + self.force_close_signed_channel(sub_channel, None)?; + } + // The closing mechanism is the same as for the `Signed` state, but we need to rollback + // the Lightning channel funding outpoint to use the glue transaction so that LDK + // reacts properly during the closing process. + SubChannelState::CloseAccepted(c) => { + self.ln_channel_manager.with_channel_lock_no_check( + &sub_channel.channel_id, + &sub_channel.counter_party, + |channel_lock| { + self.ln_channel_manager.set_funding_outpoint( + channel_lock, + &lightning::chain::transaction::OutPoint { + txid: c.signed_subchannel.ln_glue_transaction.txid(), + index: 0, + }, + c.ln_rollback.channel_value_satoshis, + c.ln_rollback.value_to_self_msat, + ); + Ok(()) + }, + )?; + sub_channel.state = SubChannelState::Signed(c.signed_subchannel); + self.force_close_signed_channel(sub_channel, Some(c.commitment_transactions))?; + } + // Once we are in `CloseConfirmed`, we can simply use LDK to close the Lightning + // channel. + SubChannelState::CloseConfirmed(_) => { + self.force_close_with_ldk(sub_channel)?; + } + // In these states, either the channel is already closed, or it should be force closed + // through LDK directly. + SubChannelState::OnChainClosed + | SubChannelState::CounterOnChainClosed + | SubChannelState::OffChainClosed + | SubChannelState::ClosedPunished(_) + | SubChannelState::Rejected + | SubChannelState::Closing(_) => { + return Err(Error::InvalidParameters(format!( + "Tried to force close channel with {:?} state", + sub_channel.state, + ))); + } + }; + + Ok(()) + } + + fn force_close_with_ldk(&self, mut sub_channel: SubChannel) -> Result<(), Error> { + let dlc_channel_id = sub_channel + .get_dlc_channel_id(0) + .expect("to have a channel id in offered state"); + let (closed_channel, closed_contract) = + self.get_closed_dlc_channel_and_contract(dlc_channel_id, false)?; + self.ln_channel_manager + .force_close_channel(&sub_channel.channel_id, &sub_channel.counter_party)?; + sub_channel.state = SubChannelState::OnChainClosed; + self.dlc_channel_manager + .get_store() + .upsert_sub_channel(&sub_channel)?; + self.dlc_channel_manager + .get_store() + .upsert_channel(closed_channel, Some(closed_contract))?; + //TODO(tibo): this is actually unsafe, we shouldn't clean up the chain monitor before + //having the commitment transaction confirmed on chain. + let mut chain_monitor = self.dlc_channel_manager.get_chain_monitor().lock().unwrap(); + chain_monitor.cleanup_channel(sub_channel.channel_id); + chain_monitor.cleanup_channel(dlc_channel_id); + self.dlc_channel_manager + .get_store() + .persist_chain_monitor(&chain_monitor) + } + + fn force_close_with_saved_commitment( + &self, + mut sub_channel: SubChannel, + commitment_transactions: &Vec, + ) -> Result<(), Error> { + // The Lightning commitment transaction is always first in the vector so this is safe. + for tx in commitment_transactions { + self.dlc_channel_manager + .get_blockchain() + .send_transaction(tx)?; + } + + let dlc_channel_id = sub_channel + .get_dlc_channel_id(0) + .expect("to have a channel id in offered state"); + let (closed_channel, closed_contract) = + self.get_closed_dlc_channel_and_contract(dlc_channel_id, false)?; + sub_channel.state = SubChannelState::OnChainClosed; + self.dlc_channel_manager + .get_store() + .upsert_channel(closed_channel, Some(closed_contract))?; + self.dlc_channel_manager + .get_store() + .upsert_sub_channel(&sub_channel)?; + let mut chain_monitor = self.dlc_channel_manager.get_chain_monitor().lock().unwrap(); + chain_monitor.cleanup_channel(sub_channel.channel_id); + chain_monitor.cleanup_channel(dlc_channel_id); + self.dlc_channel_manager + .get_store() + .persist_chain_monitor(&chain_monitor)?; + Ok(()) + } + + fn force_close_signed_channel( + &self, + mut sub_channel: SubChannel, + commitment_transactions: Option>, + ) -> Result<(), Error> { + if let SubChannelState::Signed(state) | SubChannelState::Finalized(state) = + sub_channel.state.clone() + { + let publish_base_secret = self + .dlc_channel_manager + .get_wallet() + .get_secret_key_for_pubkey(&sub_channel.own_base_points.publish_basepoint)?; + + let publish_sk = derive_private_key( + self.dlc_channel_manager.get_secp(), + &state.own_per_split_point, + &publish_base_secret, + ); + + let counter_split_signature = state + .counter_split_adaptor_signature + .decrypt(&publish_sk) + .map_err(|e| APIError::ExternalError { err: e.to_string() })?; + + let mut split_tx = state.split_tx.transaction.clone(); + + let own_split_sig = self.get_holder_split_tx_signature(&sub_channel, &split_tx)?; + + dlc::util::finalize_multi_sig_input_transaction( + &mut split_tx, + vec![ + (sub_channel.own_fund_pk, own_split_sig), + (sub_channel.counter_fund_pk, counter_split_signature), + ], + &sub_channel.original_funding_redeemscript, + 0, + ); + + self.dlc_channel_manager + .get_blockchain() + .send_transaction(&split_tx)?; + + let closing_sub_channel = ClosingSubChannel { + signed_sub_channel: state, + is_initiator: true, + commitment_transactions, + }; + + sub_channel.state = SubChannelState::Closing(closing_sub_channel); + + self.dlc_channel_manager + .get_store() + .upsert_sub_channel(&sub_channel)?; + } else { + unreachable!("Should not call this method if not in Signed or Finalized state"); + } + + Ok(()) + } + + /// Finalize the closing of the sub channel with specified [`ChannelId`]. + fn finalize_force_close_sub_channels(&self, channel_id: &ChannelId) -> Result<(), Error> { + let (mut closing, state) = get_sub_channel_in_state!( + self.dlc_channel_manager, + *channel_id, + Closing, + None:: + )?; + + let split_tx_confs = self + .dlc_channel_manager + .get_blockchain() + .get_transaction_confirmations(&state.signed_sub_channel.split_tx.transaction.txid())?; + + if split_tx_confs < crate::manager::CET_NSEQUENCE { + return Err(Error::InvalidState(format!( + "NSequence hasn't elapsed yet, need {} more blocks", + crate::manager::CET_NSEQUENCE - split_tx_confs + ))); + } + + let signed_sub_channel = &state.signed_sub_channel; + let counter_party = closing.counter_party; + let mut glue_tx = state.signed_sub_channel.ln_glue_transaction.clone(); + + if self + .dlc_channel_manager + .get_blockchain() + .get_transaction_confirmations(&glue_tx.txid()) + .unwrap_or(0) + == 0 + { + let own_revoke_params = closing.own_base_points.get_revokable_params( + self.dlc_channel_manager.get_secp(), + &closing + .counter_base_points + .as_ref() + .expect("to have counter base points") + .revocation_basepoint, + &signed_sub_channel.own_per_split_point, + ); + + let counter_revoke_params = closing + .counter_base_points + .as_ref() + .expect("to have counter base points") + .get_revokable_params( + self.dlc_channel_manager.get_secp(), + &closing.own_base_points.revocation_basepoint, + &signed_sub_channel.counter_per_split_point, + ); + + let (offer_params, accept_params) = if closing.is_offer { + (&own_revoke_params, &counter_revoke_params) + } else { + (&counter_revoke_params, &own_revoke_params) + }; + + let own_base_secret_key = self + .dlc_channel_manager + .get_wallet() + .get_secret_key_for_pubkey(&closing.own_base_points.own_basepoint)?; + let own_secret_key = derive_private_key( + self.dlc_channel_manager.get_secp(), + &signed_sub_channel.own_per_split_point, + &own_base_secret_key, + ); + + let own_signature = dlc::util::get_raw_sig_for_tx_input( + self.dlc_channel_manager.get_secp(), + &glue_tx, + 0, + &signed_sub_channel.split_tx.output_script, + signed_sub_channel.split_tx.transaction.output[0].value, + &own_secret_key, + )?; + + dlc::channel::satisfy_buffer_descriptor( + &mut glue_tx, + offer_params, + accept_params, + &own_revoke_params.own_pk.inner, + &own_signature, + &counter_revoke_params.own_pk, + &signed_sub_channel.counter_glue_signature, + )?; + + self.dlc_channel_manager + .get_blockchain() + .send_transaction(&glue_tx)?; + } + + let dlc_channel_id = closing.get_dlc_channel_id(0).ok_or(Error::InvalidState( + "Could not get dlc channel id.".to_string(), + ))?; + + if let Err(e) = self + .dlc_channel_manager + .force_close_sub_channel(&dlc_channel_id, (closing.clone(), &state)) + { + error!("Error force closing DLC subchannel {}", e); + } + + if let Some(commitment_transactions) = &state.commitment_transactions { + for tx in commitment_transactions { + if let Err(e) = self + .dlc_channel_manager + .get_blockchain() + .send_transaction(tx) + { + error!("Could not broadcast transaction {}: {}", tx.txid(), e); + } + } + } else if let Err(e) = self + .ln_channel_manager + .force_close_channel(channel_id, &counter_party) + { + error!("Error force closing LN side of channel: {}", e); + }; + + closing.state = if state.is_initiator { + SubChannelState::OnChainClosed + } else { + SubChannelState::CounterOnChainClosed + }; + + let mut chain_monitor = self.dlc_channel_manager.get_chain_monitor().lock().unwrap(); + chain_monitor.cleanup_channel(closing.channel_id); + self.dlc_channel_manager + .get_store() + .persist_chain_monitor(&chain_monitor)?; + + self.dlc_channel_manager + .get_store() + .upsert_sub_channel(&closing)?; + + Ok(()) + } + + /// Notify that LDK has decided to close the channel with given id. This MUST be called within + /// the event handler provided to LDK in reaction to the `ChannelClosed` event. + pub fn notify_ln_channel_closed( + &self, + channel_id: ChannelId, + closure_reason: &ClosureReason, + ) -> Result<(), Error> { + let mut sub_channel = self + .dlc_channel_manager + .get_store() + .get_sub_channel(channel_id)? + .ok_or(Error::InvalidParameters(format!( + "No channel with id {:?} found", + channel_id + )))?; + + let (updated_channel, updated_contract) = match closure_reason { + ClosureReason::CounterpartyForceClosed { .. } + | ClosureReason::CommitmentTxConfirmed => match sub_channel.state { + SubChannelState::Offered(_) + | SubChannelState::Accepted(_) + | SubChannelState::Confirmed(_) + | SubChannelState::CloseAccepted(_) + | SubChannelState::CloseConfirmed(_) + | SubChannelState::Finalized(_) => { + let dlc_channel_id = sub_channel + .get_dlc_channel_id(0) + .expect("to have a channel id"); + let (closed_channel, closed_contract) = + self.get_closed_dlc_channel_and_contract(dlc_channel_id, true)?; + sub_channel.state = SubChannelState::CounterOnChainClosed; + let mut chain_monitor = + self.dlc_channel_manager.get_chain_monitor().lock().unwrap(); + chain_monitor.cleanup_channel(sub_channel.channel_id); + chain_monitor.cleanup_channel(dlc_channel_id); + (Some(closed_channel), Some(closed_contract)) + } + SubChannelState::OffChainClosed => { + let mut chain_monitor = + self.dlc_channel_manager.get_chain_monitor().lock().unwrap(); + chain_monitor.cleanup_channel(sub_channel.channel_id); + sub_channel.state = SubChannelState::CounterOnChainClosed; + (None, None) + } + SubChannelState::Signed(_) + | SubChannelState::Closing(_) + | SubChannelState::CloseOffered(_) + | SubChannelState::OnChainClosed + | SubChannelState::CounterOnChainClosed => { + info!("Got notification of LN channel ({}) closure by counter party in state {} where we do not have to react.", serialize_hex(&channel_id), sub_channel.state); + return Ok(()); + } + SubChannelState::ClosedPunished(_) => { + warn!("Got close notification while in ClosedPunished."); + return Ok(()); + } + SubChannelState::Rejected => { + info!( + "Counterparty closed channel in rejected state, marking as counter closed" + ); + sub_channel.state = SubChannelState::CounterOnChainClosed; + (None, None) + } + }, + ClosureReason::HolderForceClosed | ClosureReason::ProcessingError { .. } => { + match sub_channel.state { + SubChannelState::Offered(_) | SubChannelState::CloseConfirmed(_) => { + let dlc_channel_id = sub_channel + .get_dlc_channel_id(0) + .expect("to have a channel id"); + let (closed_channel, closed_contract) = + self.get_closed_dlc_channel_and_contract(dlc_channel_id, false)?; + sub_channel.state = SubChannelState::OnChainClosed; + let mut chain_monitor = + self.dlc_channel_manager.get_chain_monitor().lock().unwrap(); + chain_monitor.cleanup_channel(sub_channel.channel_id); + chain_monitor.cleanup_channel(dlc_channel_id); + (Some(closed_channel), Some(closed_contract)) + } + SubChannelState::OffChainClosed => { + let mut chain_monitor = + self.dlc_channel_manager.get_chain_monitor().lock().unwrap(); + chain_monitor.cleanup_channel(sub_channel.channel_id); + sub_channel.state = SubChannelState::OnChainClosed; + (None, None) + } + SubChannelState::Accepted(_) | SubChannelState::Confirmed(_) => { + return self.force_close_sub_channel_internal(sub_channel); + } + SubChannelState::CloseAccepted(ref c) => { + let split_input = &c.signed_subchannel.split_tx.transaction.input[0]; + let funding_txo = lightning::chain::transaction::OutPoint { + txid: split_input.previous_output.txid, + index: split_input.previous_output.vout as u16, + }; + self.ln_chain_monitor.update_channel_funding_txo( + &funding_txo, + &c.ln_rollback.funding_outpoint, + c.ln_rollback.channel_value_satoshis, + )?; + let commitment_transactions = Some( + self.ln_chain_monitor + .get_latest_holder_commitment_txn(&funding_txo)?, + ); + let mut signed_subchannel = sub_channel.clone(); + signed_subchannel.state = + SubChannelState::Signed(c.signed_subchannel.clone()); + self.force_close_signed_channel( + signed_subchannel, + commitment_transactions, + )?; + return Ok(()); + } + SubChannelState::CloseOffered(ref c) => { + let split_input = &c.signed_subchannel.split_tx.transaction.input[0]; + let funding_txo = lightning::chain::transaction::OutPoint { + txid: split_input.previous_output.txid, + index: split_input.previous_output.vout as u16, + }; + let commitment_transactions = Some( + self.ln_chain_monitor + .get_latest_holder_commitment_txn(&funding_txo)?, + ); + let mut signed_subchannel = sub_channel.clone(); + signed_subchannel.state = + SubChannelState::Signed(c.signed_subchannel.clone()); + self.force_close_signed_channel( + signed_subchannel, + commitment_transactions, + )?; + return Ok(()); + } + SubChannelState::Signed(ref s) | SubChannelState::Finalized(ref s) => { + let split_input = &s.split_tx.transaction.input[0]; + let funding_txo = lightning::chain::transaction::OutPoint { + txid: split_input.previous_output.txid, + index: split_input.previous_output.vout as u16, + }; + let commitment_transactions = Some( + self.ln_chain_monitor + .get_latest_holder_commitment_txn(&funding_txo)?, + ); + self.force_close_signed_channel(sub_channel, commitment_transactions)?; + return Ok(()); + } + _ => return Ok(()), + } + } + ClosureReason::CooperativeClosure => { + info!("Channel {channel_id:?} was cooperatively closed"); + return Ok(()); + } + ClosureReason::FundingTimedOut => { + info!("Channel {channel_id:?} timed out"); + return Ok(()); + } + ClosureReason::DisconnectedPeer => { + info!("Channel {channel_id:?} closed due to disconnected peer before funding completed."); + return Ok(()); + } + ClosureReason::OutdatedChannelManager => { + info!("Channel {channel_id:?} closed due to outdated channel manager."); + return Ok(()); + } + ClosureReason::CounterpartyCoopClosedUnfundedChannel => { + info!( + "Channel {channel_id:?} closed due to counter party closing unfunded channel." + ); + return Ok(()); + } + }; + + if let Some(channel) = updated_channel { + self.dlc_channel_manager + .get_store() + .upsert_channel(channel, updated_contract)?; + } + + let chain_monitor = self.dlc_channel_manager.get_chain_monitor().lock().unwrap(); + self.dlc_channel_manager + .get_store() + .persist_chain_monitor(&chain_monitor)?; + self.dlc_channel_manager + .get_store() + .upsert_sub_channel(&sub_channel)?; + + Ok(()) + } + + /// Generates an offer to collaboratively close a sub channel off chain, updating its state. + pub fn offer_subchannel_close( + &self, + channel_id: &ChannelId, + accept_balance: u64, + ) -> Result<(SubChannelCloseOffer, PublicKey), Error> { + let (mut signed_subchannel, state) = get_sub_channel_in_state!( + self.dlc_channel_manager, + *channel_id, + Signed, + None:: + )?; + + let dlc_channel_id = signed_subchannel + .get_dlc_channel_id(0) + .ok_or(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 offer_balance = match dlc_channel.state { + crate::channel::signed_channel::SignedChannelState::Established { + total_collateral, + .. + } => { + if total_collateral < accept_balance { + return Err(Error::InvalidParameters( + "Accept balance must be smaller than total collateral in DLC channel." + .to_string(), + )); + } + + total_collateral - accept_balance + } + crate::channel::signed_channel::SignedChannelState::Settled { + counter_payout, + own_payout, + .. + } => { + if accept_balance != counter_payout { + return Err(Error::InvalidParameters("Accept balance must be equal to the counter payout when DLC channel is settled.".to_string())); + } + + own_payout + } + _ => { + return Err(Error::InvalidState( + "Can only close subchannel that are established or settled".to_string(), + )); + } + }; + + let close_offer = SubChannelCloseOffer { + channel_id: *channel_id, + accept_balance, + }; + + let counter_party = signed_subchannel.counter_party; + let close_offered_subchannel = CloseOfferedSubChannel { + signed_subchannel: state, + offer_balance, + accept_balance, + is_offer: true, + }; + + signed_subchannel.state = SubChannelState::CloseOffered(close_offered_subchannel); + + self.dlc_channel_manager + .get_store() + .upsert_sub_channel(&signed_subchannel)?; + + Ok((close_offer, counter_party)) + } + + /// Accept an offer to collaboratively close a sub channel off chain, updating its state. + pub fn accept_subchannel_close_offer( + &self, + channel_id: &ChannelId, + ) -> Result<(SubChannelCloseAccept, PublicKey), Error> { + let (mut sub_channel, state) = get_sub_channel_in_state!( + self.dlc_channel_manager, + *channel_id, + CloseOffered, + None:: + )?; + + if state.is_offer { + return Err(Error::InvalidParameters( + "Cannot accept own offer".to_string(), + )); + } + + let channel_details = self + .ln_channel_manager + .get_channel_details(channel_id) + .ok_or_else(|| Error::InvalidParameters(format!("Unknown channel {channel_id:?}")))?; + + let ((commitment_signed, commit_tx_number), commitment_transactions) = + self.ln_channel_manager.with_useable_channel_lock( + channel_id, + &sub_channel.counter_party, + None, + |channel_lock| { + let dlc_channel_id = + sub_channel + .get_dlc_channel_id(0) + .ok_or(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 funding_txo = lightning::chain::transaction::OutPoint { + txid: state.signed_subchannel.split_tx.transaction.input[0] + .previous_output + .txid, + index: state.signed_subchannel.split_tx.transaction.input[0] + .previous_output + .vout as u16, + }; + let commitment_transactions = self + .ln_chain_monitor + .get_latest_holder_commitment_txn(&funding_txo)?; + + let total_collateral = + dlc_channel.own_params.collateral + dlc_channel.counter_params.collateral; + + debug_assert_eq!(state.accept_balance + state.offer_balance, total_collateral); + + let (_, accept_fees) = per_party_fee(sub_channel.fee_rate_per_vb)?; + + let ln_own_balance_msats = channel_details.outbound_capacity_msat + + channel_details.unspendable_punishment_reserve.unwrap() * 1000 + + accept_fees * 1000 + + state.accept_balance * 1000; + + let fund_value = sub_channel.fund_value_satoshis; + + let commitment_signed = self + .ln_channel_manager + .get_updated_funding_outpoint_commitment_signed( + channel_lock, + &state.signed_subchannel.split_tx.transaction.input[0].previous_output, + fund_value, + ln_own_balance_msats, + )?; + + Ok((commitment_signed, commitment_transactions)) + }, + )?; + + let close_accept = SubChannelCloseAccept { + channel_id: *channel_id, + commit_signature: commitment_signed.signature, + commit_tx_number, + htlc_signatures: commitment_signed.htlc_signatures, + }; + + let close_accepted_subchannel = CloseAcceptedSubChannel { + signed_subchannel: state.signed_subchannel, + own_balance: state.accept_balance, + counter_balance: state.offer_balance, + ln_rollback: (&channel_details).into(), + commitment_transactions, + }; + + sub_channel.state = SubChannelState::CloseAccepted(close_accepted_subchannel); + + self.dlc_channel_manager + .get_store() + .upsert_sub_channel(&sub_channel)?; + + Ok((close_accept, sub_channel.counter_party)) + } + + /// Reject an offer to establish a sub channel. + pub fn reject_sub_channel_offer(&self, channel_id: ChannelId) -> Result { + let (mut sub_channel, _) = get_sub_channel_in_state!( + self.dlc_channel_manager, + channel_id, + Offered, + None:: + )?; + + sub_channel.state = SubChannelState::Rejected; + + self.dlc_channel_manager + .get_store() + .upsert_sub_channel(&sub_channel)?; + + Ok(Reject { channel_id }) + } + + /// Reject an offer to collaboratively close a sub channel off chain. + pub fn reject_sub_channel_close_offer(&self, channel_id: ChannelId) -> Result { + let (mut sub_channel, state) = get_sub_channel_in_state!( + self.dlc_channel_manager, + channel_id, + CloseOffered, + None:: + )?; + + sub_channel.state = SubChannelState::Signed(state.signed_subchannel); + + self.dlc_channel_manager + .get_store() + .upsert_sub_channel(&sub_channel)?; + + Ok(Reject { channel_id }) + } + + /// Moves the sub channel to the `[SubChannelState::Signed]` state when a disconnection happens while the peer is waiting for + /// the revocation message from the remote node (the revocation secret must have been given by the remote node during the + /// reestablishment protocol). + fn mark_channel_signed(&self, channel_id: ChannelId) -> Result<(), Error> { + let (mut signed_sub_channel, state) = get_sub_channel_in_state!( + self.dlc_channel_manager, + channel_id, + Finalized, + 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() + ))?, + Confirmed, + 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, + counter_party: &PublicKey, + ) -> Result<(), Error> { + let channel_details = self + .ln_channel_manager + .get_channel_details(&sub_channel_offer.channel_id) + .ok_or_else(|| { + Error::InvalidParameters(format!( + "Unknown channel {:02x?}", + sub_channel_offer.channel_id + )) + })?; + + let sub_channel = + match self + .dlc_channel_manager + .get_store() + .get_sub_channel(channel_details.channel_id)? + { + Some(mut s) => match s.state { + SubChannelState::OffChainClosed => { + s.is_offer = false; + s.update_idx -= 1; + Some(s) + } + _ => return Err(Error::InvalidState( + "Received sub channel offer but a non closed sub channel already exists" + .to_string(), + )), + }, + None => None, + }; + + validate_and_get_ln_values_per_party( + &channel_details, + sub_channel_offer.contract_info.get_total_collateral() + - sub_channel_offer.offer_collateral, + sub_channel_offer.offer_collateral, + sub_channel_offer.fee_rate_per_vbyte, + false, + )?; + + // TODO(tibo): validate subchannel is valid wrt current channel conditions. + + let offered_sub_channel = OfferedSubChannel { + per_split_point: sub_channel_offer.next_per_split_point, + }; + + let sub_channel = match sub_channel { + Some(mut s) => { + s.state = SubChannelState::Offered(offered_sub_channel); + s + } + None => SubChannel { + channel_id: channel_details.channel_id, + counter_party: channel_details.counterparty.node_id, + per_split_seed: None, + fee_rate_per_vb: sub_channel_offer.fee_rate_per_vbyte, + is_offer: false, + update_idx: INITIAL_SPLIT_NUMBER, + state: SubChannelState::Offered(offered_sub_channel), + counter_party_secrets: CounterpartyCommitmentSecrets::new(), + own_base_points: crate::utils::get_party_base_points( + self.dlc_channel_manager.get_secp(), + self.dlc_channel_manager.get_wallet(), + )?, + counter_base_points: Some(PartyBasePoints { + own_basepoint: sub_channel_offer.own_basepoint, + revocation_basepoint: sub_channel_offer.revocation_basepoint, + publish_basepoint: sub_channel_offer.publish_basepoint, + }), + fund_value_satoshis: channel_details.channel_value_satoshis, + original_funding_redeemscript: channel_details.funding_redeemscript.unwrap(), + own_fund_pk: channel_details.holder_funding_pubkey, + counter_fund_pk: channel_details.counter_funding_pubkey.ok_or_else(|| { + Error::InvalidState("Counter funding PK is missing".to_string()) + })?, + channel_keys_id: channel_details.channel_keys_id, + }, + }; + + let temporary_channel_id = + generate_temporary_channel_id(channel_details.channel_id, sub_channel.update_idx, 0); + + let temporary_contract_id = + generate_temporary_contract_id(temporary_channel_id, INITIAL_SPLIT_NUMBER); + + let offer_channel = OfferChannel { + protocol_version: 0, //unused + contract_flags: 0, //unused + chain_hash: [0; 32], //unused + temporary_contract_id, + temporary_channel_id, + contract_info: sub_channel_offer.contract_info.clone(), + // THIS IS INCORRECT!!! SHOULD BE KEY FROM SPLIT TX + funding_pubkey: channel_details.holder_funding_pubkey, + revocation_basepoint: sub_channel_offer.channel_revocation_basepoint, + publish_basepoint: sub_channel_offer.channel_publish_basepoint, + own_basepoint: sub_channel_offer.channel_own_basepoint, + first_per_update_point: sub_channel_offer.channel_first_per_update_point, + payout_spk: sub_channel_offer.payout_spk.clone(), + payout_serial_id: sub_channel_offer.payout_serial_id, + offer_collateral: sub_channel_offer.offer_collateral, + funding_inputs: vec![], + change_spk: Script::default(), + change_serial_id: 0, + fund_output_serial_id: 0, + fee_rate_per_vb: sub_channel_offer.fee_rate_per_vbyte, + cet_locktime: sub_channel_offer.cet_locktime, + refund_locktime: sub_channel_offer.refund_locktime, + cet_nsequence: sub_channel_offer.cet_nsequence, + }; + + let (offered_channel, offered_contract) = + OfferedChannel::from_offer_channel(&offer_channel, *counter_party)?; + + self.dlc_channel_manager.get_store().upsert_channel( + Channel::Offered(offered_channel), + Some(Contract::Offered(offered_contract)), + )?; + self.dlc_channel_manager + .get_store() + .upsert_sub_channel(&sub_channel)?; + + Ok(()) + } + + fn on_subchannel_accept( + &self, + sub_channel_accept: &SubChannelAccept, + counter_party: &PublicKey, + ) -> Result { + let (mut offered_sub_channel, state) = get_sub_channel_in_state!( + self.dlc_channel_manager, + sub_channel_accept.channel_id, + Offered, + Some(*counter_party) + )?; + + let channel_details = self + .ln_channel_manager + .get_channel_details(&sub_channel_accept.channel_id) + .ok_or_else(|| { + Error::InvalidParameters(format!( + "Unknown LN channel {:02x?}", + sub_channel_accept.channel_id + )) + })?; + + let ln_rollback = (&channel_details).into(); + + let offer_revoke_params = offered_sub_channel.own_base_points.get_revokable_params( + self.dlc_channel_manager.get_secp(), + &sub_channel_accept.revocation_basepoint, + &state.per_split_point, + ); + + let accept_points = PartyBasePoints { + own_basepoint: sub_channel_accept.own_basepoint, + revocation_basepoint: sub_channel_accept.revocation_basepoint, + publish_basepoint: sub_channel_accept.publish_basepoint, + }; + + let accept_revoke_params = accept_points.get_revokable_params( + self.dlc_channel_manager.get_secp(), + &offered_sub_channel.own_base_points.revocation_basepoint, + &sub_channel_accept.first_per_split_point, + ); + + let funding_txo = channel_details.funding_txo.expect("to have a funding txo"); + let funding_outpoint = OutPoint { + txid: funding_txo.txid, + vout: funding_txo.index as u32, + }; + let funding_redeemscript = channel_details + .funding_redeemscript + .as_ref() + .unwrap() + .clone(); + + let temporary_channel_id = + offered_sub_channel + .get_dlc_channel_id(0) + .ok_or(Error::InvalidParameters( + "Could not get dlc channel id".to_string(), + ))?; + + let offered_channel = get_channel_in_state!( + self.dlc_channel_manager, + &temporary_channel_id, + Offered, + None as Option + )?; + + let offered_contract = get_contract_in_state!( + self.dlc_channel_manager, + &offered_channel.offered_contract_id, + Offered, + None as Option + )?; + + let (own_to_self_value_msat, _) = validate_and_get_ln_values_per_party( + &channel_details, + offered_contract.offer_params.collateral, + offered_contract.total_collateral - offered_contract.offer_params.collateral, + offered_contract.fee_rate_per_vb, + true, + )?; + + let split_tx = dlc::channel::sub_channel::create_split_tx( + &offer_revoke_params, + &accept_revoke_params, + &funding_outpoint, + channel_details.channel_value_satoshis, + offered_contract.total_collateral, + offered_contract.fee_rate_per_vb, + )?; + + let ln_output_value = split_tx.transaction.output[0].value; + + let channel_id = &channel_details.channel_id; + + let own_base_secret_key = self + .dlc_channel_manager + .get_wallet() + .get_secret_key_for_pubkey(&offered_sub_channel.own_base_points.own_basepoint)?; + let own_secret_key = derive_private_key( + self.dlc_channel_manager.get_secp(), + &state.per_split_point, + &own_base_secret_key, + ); + + let glue_tx_output_value = ln_output_value + - dlc::util::weight_to_fee(LN_GLUE_TX_WEIGHT, offered_contract.fee_rate_per_vb) + .map_err(|e| APIError::ExternalError { err: e.to_string() })?; + + let ln_glue_tx = dlc::channel::sub_channel::create_ln_glue_tx( + &OutPoint { + txid: split_tx.transaction.txid(), + vout: 0, + }, + &funding_redeemscript, + PackedLockTime::ZERO, + Sequence(crate::manager::CET_NSEQUENCE), + glue_tx_output_value, + ); + + let ( + split_tx_adaptor_signature, + (commitment_signed, commit_tx_number), + revoke_and_ack, + commitment_transactions, + ) = self.ln_channel_manager.with_useable_channel_lock( + channel_id, + counter_party, + Some(sub_channel_accept.commit_tx_number), + |channel_lock| { + let split_tx_adaptor_signature = self.get_holder_split_tx_adaptor_signature( + *channel_id, + channel_details.channel_value_satoshis, + channel_details.channel_keys_id, + &split_tx.transaction, + &funding_redeemscript, + &accept_revoke_params.publish_pk.inner, + )?; + + let commitment_transactions = self + .ln_chain_monitor + .get_latest_holder_commitment_txn(&funding_txo)?; + + let commitment_signed = self + .ln_channel_manager + .get_updated_funding_outpoint_commitment_signed( + channel_lock, + &OutPoint { + txid: ln_glue_tx.txid(), + vout: 0, + }, + glue_tx_output_value, + own_to_self_value_msat, + )?; + let revoke_and_ack = self.ln_channel_manager.on_commitment_signed_get_raa( + channel_lock, + &sub_channel_accept.commit_signature, + &sub_channel_accept.htlc_signatures, + )?; + + Ok(( + split_tx_adaptor_signature, + commitment_signed, + revoke_and_ack, + commitment_transactions, + )) + }, + )?; + + let accept_channel = AcceptChannel { + temporary_channel_id: offered_channel.temporary_channel_id, + accept_collateral: offered_contract.total_collateral + - offered_contract.offer_params.collateral, + funding_pubkey: channel_details.holder_funding_pubkey, + revocation_basepoint: sub_channel_accept.channel_revocation_basepoint, + publish_basepoint: sub_channel_accept.channel_publish_basepoint, + own_basepoint: sub_channel_accept.channel_own_basepoint, + first_per_update_point: sub_channel_accept.first_per_update_point, + payout_serial_id: sub_channel_accept.payout_serial_id, + funding_inputs: vec![], + change_spk: Script::default(), + change_serial_id: 0, + cet_adaptor_signatures: sub_channel_accept.cet_adaptor_signatures.clone(), + buffer_adaptor_signature: sub_channel_accept.buffer_adaptor_signature, + refund_signature: sub_channel_accept.refund_signature, + negotiation_fields: None, + payout_spk: sub_channel_accept.payout_spk.clone(), + }; + + let sub_channel_info = SubChannelSignVerifyInfo { + funding_info: FundingInfo { + funding_tx: split_tx.transaction.clone(), + funding_script_pubkey: split_tx.output_script.clone(), + funding_input_value: split_tx.transaction.output[1].value, + }, + own_adaptor_sk: own_secret_key, + counter_adaptor_pk: accept_revoke_params.own_pk.inner, + sub_channel_id: sub_channel_accept.channel_id, + }; + + let (signed_channel, signed_contract, sign_channel) = + crate::channel_updater::verify_and_sign_accepted_channel_internal( + self.dlc_channel_manager.get_secp(), + &offered_channel, + &offered_contract, + &accept_channel, + //TODO(tibo): this should be parameterizable. + crate::manager::CET_NSEQUENCE, + self.dlc_channel_manager.get_wallet(), + Some(sub_channel_info), + self.dlc_channel_manager.get_chain_monitor(), + )?; + + dlc::verify_tx_input_sig( + self.dlc_channel_manager.get_secp(), + &sub_channel_accept.ln_glue_signature, + &ln_glue_tx, + 0, + &split_tx.output_script, + ln_output_value, + &accept_revoke_params.own_pk.inner, + )?; + + let ln_glue_signature = dlc::util::get_raw_sig_for_tx_input( + self.dlc_channel_manager.get_secp(), + &ln_glue_tx, + 0, + &split_tx.output_script, + ln_output_value, + &own_secret_key, + )?; + + let msg = SubChannelConfirm { + channel_id: sub_channel_accept.channel_id, + split_adaptor_signature: split_tx_adaptor_signature, + commit_signature: commitment_signed.signature, + commit_tx_number, + htlc_signatures: commitment_signed.htlc_signatures, + cet_adaptor_signatures: sign_channel.cet_adaptor_signatures, + buffer_adaptor_signature: sign_channel.buffer_adaptor_signature, + refund_signature: sign_channel.refund_signature, + ln_glue_signature, + }; + + self.dlc_channel_manager + .get_chain_monitor() + .lock() + .unwrap() + .add_tx( + split_tx.transaction.txid(), + ChannelInfo { + channel_id: offered_sub_channel.channel_id, + tx_type: TxType::SplitTx, + }, + ); + + let confirmed_sub_channel = ConfirmedSubChannel { + own_per_split_point: state.per_split_point, + counter_per_split_point: sub_channel_accept.first_per_split_point, + own_split_adaptor_signature: split_tx_adaptor_signature, + split_tx, + counter_glue_signature: sub_channel_accept.ln_glue_signature, + ln_glue_transaction: ln_glue_tx, + ln_rollback, + prev_commitment_secret: SecretKey::from_slice(&revoke_and_ack.per_commitment_secret) + .expect("a valid secret key"), + next_per_commitment_point: revoke_and_ack.next_per_commitment_point, + commitment_transactions, + }; + + offered_sub_channel.counter_base_points = Some(accept_points); + + offered_sub_channel.state = SubChannelState::Confirmed(confirmed_sub_channel); + + self.dlc_channel_manager.get_store().upsert_channel( + Channel::Signed(signed_channel), + Some(Contract::Signed(signed_contract)), + )?; + self.dlc_channel_manager + .get_store() + .upsert_sub_channel(&offered_sub_channel)?; + + self.dlc_channel_manager + .get_store() + .persist_chain_monitor(&self.dlc_channel_manager.get_chain_monitor().lock().unwrap())?; + + Ok(msg) + } + + fn on_subchannel_confirm( + &self, + sub_channel_confirm: &SubChannelConfirm, + counter_party: &PublicKey, + ) -> Result { + let channel_details = self + .ln_channel_manager + .get_channel_details(&sub_channel_confirm.channel_id) + .ok_or_else(|| { + Error::InvalidParameters(format!( + "Unknown LN channel {:02x?}", + sub_channel_confirm.channel_id + )) + })?; + let msg = self.ln_channel_manager.with_useable_channel_lock( + &sub_channel_confirm.channel_id, + counter_party, + Some(sub_channel_confirm.commit_tx_number), + |channel_lock| { + let (mut accepted_sub_channel, state) = get_sub_channel_in_state!( + self.dlc_channel_manager, + sub_channel_confirm.channel_id, + Accepted, + Some(*counter_party) + )?; + + let accept_revoke_params = + accepted_sub_channel.own_base_points.get_revokable_params( + self.dlc_channel_manager.get_secp(), + &accepted_sub_channel + .counter_base_points + .as_ref() + .expect("to have counter base points") + .revocation_basepoint, + &state.accept_per_split_point, + ); + + let funding_redeemscript = &accepted_sub_channel.original_funding_redeemscript; + + dlc::channel::verify_tx_adaptor_signature( + self.dlc_channel_manager.get_secp(), + &state.split_tx.transaction, + accepted_sub_channel.fund_value_satoshis, + funding_redeemscript, + &channel_details.counter_funding_pubkey.ok_or_else(|| { + Error::InvalidState("Counter funding PK is missing".to_string()) + })?, + &accept_revoke_params.publish_pk.inner, + &sub_channel_confirm.split_adaptor_signature, + ) + .map_err(|e| APIError::ExternalError { err: e.to_string() })?; + + let dlc_channel_id = + accepted_sub_channel + .get_dlc_channel_id(0) + .ok_or(Error::InvalidState( + "Could not get dlc channel id".to_string(), + ))?; + + let accepted_channel = get_channel_in_state!( + self.dlc_channel_manager, + &dlc_channel_id, + Accepted, + Some(*counter_party) + )?; + + let accepted_contract = get_contract_in_state!( + self.dlc_channel_manager, + &accepted_channel.accepted_contract_id, + Accepted, + Some(*counter_party) + )?; + + let revoke_and_ack = self.ln_channel_manager.on_commitment_signed_get_raa( + channel_lock, + &sub_channel_confirm.commit_signature, + &sub_channel_confirm.htlc_signatures, + )?; + + let sign_channel = dlc_messages::channel::SignChannel { + channel_id: sub_channel_confirm.channel_id, + cet_adaptor_signatures: sub_channel_confirm.cet_adaptor_signatures.clone(), + buffer_adaptor_signature: sub_channel_confirm.buffer_adaptor_signature, + refund_signature: sub_channel_confirm.refund_signature, + funding_signatures: FundingSignatures { + funding_signatures: vec![], + }, + }; + + let offer_revoke_params = accepted_sub_channel + .counter_base_points + .as_ref() + .expect("to have counter base points") + .get_revokable_params( + self.dlc_channel_manager.get_secp(), + &accepted_sub_channel.own_base_points.revocation_basepoint, + &state.offer_per_split_point, + ); + + let sub_channel_info = SubChannelVerifyInfo { + funding_info: FundingInfo { + funding_tx: state.split_tx.transaction.clone(), + funding_script_pubkey: state.split_tx.output_script.clone(), + funding_input_value: state.split_tx.transaction.output[1].value, + }, + counter_adaptor_pk: offer_revoke_params.own_pk.inner, + sub_channel_id: accepted_sub_channel.channel_id, + }; + + let (signed_channel, signed_contract) = + channel_updater::verify_signed_channel_internal( + self.dlc_channel_manager.get_secp(), + &accepted_channel, + &accepted_contract, + &sign_channel, + self.dlc_channel_manager.get_wallet(), + Some(sub_channel_info), + self.dlc_channel_manager.get_chain_monitor(), + )?; + + let split_adaptor_signature = self.get_holder_split_tx_adaptor_signature( + sub_channel_confirm.channel_id, + accepted_sub_channel.fund_value_satoshis, + channel_details.channel_keys_id, + &state.split_tx.transaction, + funding_redeemscript, + &offer_revoke_params.publish_pk.inner, + )?; + + let signed_sub_channel = SignedSubChannel { + own_per_split_point: state.accept_per_split_point, + counter_per_split_point: state.offer_per_split_point, + own_split_adaptor_signature: split_adaptor_signature, + counter_split_adaptor_signature: sub_channel_confirm.split_adaptor_signature, + 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 { + channel_id: sub_channel_confirm.channel_id, + per_commitment_secret: SecretKey::from_slice( + &revoke_and_ack.per_commitment_secret, + ) + .expect("a valid secret key"), + next_per_commitment_point: revoke_and_ack.next_per_commitment_point, + split_adaptor_signature, + }; + + accepted_sub_channel.state = SubChannelState::Finalized(signed_sub_channel); + + self.dlc_channel_manager.get_store().upsert_channel( + Channel::Signed(signed_channel), + Some(Contract::Confirmed(signed_contract)), + )?; + self.dlc_channel_manager + .get_store() + .upsert_sub_channel(&accepted_sub_channel)?; + self.dlc_channel_manager.get_store().persist_chain_monitor( + &self.dlc_channel_manager.get_chain_monitor().lock().unwrap(), + )?; + Ok(msg) + }, + )?; + + Ok(msg) + } + + fn on_sub_channel_finalize( + &self, + sub_channel_finalize: &SubChannelFinalize, + counter_party: &PublicKey, + ) -> Result { + let channel_details = self + .ln_channel_manager + .get_channel_details(&sub_channel_finalize.channel_id) + .ok_or_else(|| { + Error::InvalidParameters(format!( + "Unknown LN channel {:02x?}", + sub_channel_finalize.channel_id + )) + })?; + let msg = self.ln_channel_manager.with_useable_channel_lock( + &sub_channel_finalize.channel_id, + counter_party, + None, + |channel_lock| { + let (mut confirmed_sub_channel, state) = get_sub_channel_in_state!( + self.dlc_channel_manager, + sub_channel_finalize.channel_id, + Confirmed, + Some(*counter_party) + )?; + + let funding_redeemscript = &confirmed_sub_channel.original_funding_redeemscript; + + let offer_revoke_params = + confirmed_sub_channel.own_base_points.get_revokable_params( + self.dlc_channel_manager.get_secp(), + &confirmed_sub_channel + .counter_base_points + .as_ref() + .expect("to have counter base points") + .revocation_basepoint, + &state.own_per_split_point, + ); + + dlc::channel::verify_tx_adaptor_signature( + self.dlc_channel_manager.get_secp(), + &state.split_tx.transaction, + confirmed_sub_channel.fund_value_satoshis, + funding_redeemscript, + &channel_details.counter_funding_pubkey.ok_or_else(|| { + Error::InvalidState("Counter funding PK is missing".to_string()) + })?, + &offer_revoke_params.publish_pk.inner, + &sub_channel_finalize.split_adaptor_signature, + ) + .map_err(|e| APIError::ExternalError { err: e.to_string() })?; + + let dlc_channel_id = + confirmed_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, + Some(*counter_party) + )?; + 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, + Some(*counter_party) + )?; + let raa = RevokeAndACK { + channel_id: sub_channel_finalize.channel_id, + per_commitment_secret: sub_channel_finalize + .per_commitment_secret + .secret_bytes(), + next_per_commitment_point: sub_channel_finalize.next_per_commitment_point, + }; + + self.ln_channel_manager.revoke_and_ack(channel_lock, &raa)?; + + self.dlc_channel_manager.get_store().upsert_channel( + Channel::Signed(channel), + Some(Contract::Confirmed(contract)), + )?; + + let signed_sub_channel = SignedSubChannel { + own_per_split_point: state.own_per_split_point, + counter_per_split_point: state.counter_per_split_point, + own_split_adaptor_signature: state.own_split_adaptor_signature, + counter_split_adaptor_signature: sub_channel_finalize.split_adaptor_signature, + split_tx: state.split_tx, + ln_glue_transaction: state.ln_glue_transaction, + counter_glue_signature: state.counter_glue_signature, + ln_rollback: state.ln_rollback, + }; + + let msg = SubChannelRevoke { + channel_id: confirmed_sub_channel.channel_id, + per_commitment_secret: state.prev_commitment_secret, + next_per_commitment_point: state.next_per_commitment_point, + }; + + confirmed_sub_channel.state = SubChannelState::Signed(signed_sub_channel); + + self.dlc_channel_manager + .get_store() + .upsert_sub_channel(&confirmed_sub_channel)?; + Ok(msg) + }, + )?; + + Ok(msg) + } + + fn on_sub_channel_revoke( + &self, + sub_channel_revoke: &SubChannelRevoke, + counter_party: &PublicKey, + ) -> Result<(), Error> { + self.ln_channel_manager.with_useable_channel_lock( + &sub_channel_revoke.channel_id, + counter_party, + None, + |channel_lock| { + let (mut confirmed_sub_channel, state) = get_sub_channel_in_state!( + self.dlc_channel_manager, + sub_channel_revoke.channel_id, + Finalized, + Some(*counter_party) + )?; + + let raa = RevokeAndACK { + channel_id: sub_channel_revoke.channel_id, + per_commitment_secret: sub_channel_revoke.per_commitment_secret.secret_bytes(), + next_per_commitment_point: sub_channel_revoke.next_per_commitment_point, + }; + + self.ln_channel_manager.revoke_and_ack(channel_lock, &raa)?; + + let signed_sub_channel = SignedSubChannel { + own_per_split_point: state.own_per_split_point, + counter_per_split_point: state.counter_per_split_point, + own_split_adaptor_signature: state.own_split_adaptor_signature, + counter_split_adaptor_signature: state.counter_split_adaptor_signature, + split_tx: state.split_tx, + ln_glue_transaction: state.ln_glue_transaction, + counter_glue_signature: state.counter_glue_signature, + ln_rollback: state.ln_rollback, + }; + + confirmed_sub_channel.state = SubChannelState::Signed(signed_sub_channel); + + self.dlc_channel_manager + .get_store() + .upsert_sub_channel(&confirmed_sub_channel)?; + Ok(()) + }, + )?; + + Ok(()) + } + + fn on_sub_channel_close_offer( + &self, + offer: &SubChannelCloseOffer, + counter_party: &PublicKey, + ) -> Result<(), Error> { + let (mut sub_channel, state) = get_sub_channel_in_state!( + self.dlc_channel_manager, + offer.channel_id, + Signed, + Some(*counter_party) + )?; + + let dlc_channel_id = sub_channel + .get_dlc_channel_id(0) + .ok_or(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 offer_balance = match dlc_channel.state { + crate::channel::signed_channel::SignedChannelState::Established { + total_collateral, + .. + } => { + if total_collateral < offer.accept_balance { + return Err(Error::InvalidParameters( + "Accept balance must be smaller than total collateral in DLC channel." + .to_string(), + )); + } + + total_collateral - offer.accept_balance + } + crate::channel::signed_channel::SignedChannelState::Settled { + own_payout, + counter_payout, + .. + } => { + if offer.accept_balance != own_payout { + return Err(Error::InvalidParameters( + "Accept balance must be equal to own payout when DLC channel is settled." + .to_string(), + )); + } + + counter_payout + } + _ => { + return Err(Error::InvalidState( + "Can only close subchannel that are established or settled".to_string(), + )); + } + }; + + let updated = CloseOfferedSubChannel { + signed_subchannel: state, + offer_balance, + accept_balance: offer.accept_balance, + is_offer: false, + }; + + sub_channel.state = SubChannelState::CloseOffered(updated); + + self.dlc_channel_manager + .get_store() + .upsert_sub_channel(&sub_channel)?; + + Ok(()) + } + + fn on_sub_channel_close_accept( + &self, + accept: &SubChannelCloseAccept, + counter_party: &PublicKey, + ) -> Result { + let channel_details = self + .ln_channel_manager + .get_channel_details(&accept.channel_id) + .ok_or_else(|| { + Error::InvalidParameters(format!("Unknown channel {:?}", accept.channel_id)) + })?; + let close_confirm = self.ln_channel_manager.with_useable_channel_lock( + &accept.channel_id, + counter_party, + Some(accept.commit_tx_number), + |channel_lock| { + let (mut sub_channel, state) = get_sub_channel_in_state!( + self.dlc_channel_manager, + accept.channel_id, + CloseOffered, + Some(*counter_party) + )?; + + let (offer_fees, _) = per_party_fee(sub_channel.fee_rate_per_vb)?; + let ln_own_balance_msats = channel_details.outbound_capacity_msat + + channel_details.unspendable_punishment_reserve.unwrap_or(0) * 1000 + + offer_fees * 1000 + + state.offer_balance * 1000; + + let fund_value = sub_channel.fund_value_satoshis; + + let (commitment_signed, commit_tx_number) = self + .ln_channel_manager + .get_updated_funding_outpoint_commitment_signed( + channel_lock, + &state.signed_subchannel.split_tx.transaction.input[0].previous_output, + fund_value, + ln_own_balance_msats, + )?; + + let raa = self.ln_channel_manager.on_commitment_signed_get_raa( + channel_lock, + &accept.commit_signature, + &accept.htlc_signatures, + )?; + + let per_split_seed = self + .dlc_channel_manager + .get_wallet() + .get_secret_key_for_pubkey( + &sub_channel + .per_split_seed + .expect("to have a per split seed"), + )?; + + let per_split_secret = SecretKey::from_slice(&build_commitment_secret( + per_split_seed.as_ref(), + sub_channel.update_idx, + )) + .map_err(|e| APIError::ExternalError { err: e.to_string() })?; + + let close_confirm = SubChannelCloseConfirm { + channel_id: accept.channel_id, + commit_signature: commitment_signed.signature, + commit_tx_number, + htlc_signatures: commitment_signed.htlc_signatures, + split_revocation_secret: per_split_secret, + commit_revocation_secret: SecretKey::from_slice(&raa.per_commitment_secret) + .expect("a valid secret key"), + next_per_commitment_point: raa.next_per_commitment_point, + }; + + let funding_txo = lightning::chain::transaction::OutPoint { + txid: state.signed_subchannel.split_tx.transaction.input[0] + .previous_output + .txid, + index: state.signed_subchannel.split_tx.transaction.input[0] + .previous_output + .vout as u16, + }; + let commitment_transactions = self + .ln_chain_monitor + .get_latest_holder_commitment_txn(&funding_txo)?; + + let updated_channel = CloseConfirmedSubChannel { + signed_subchannel: state.signed_subchannel, + own_balance: state.offer_balance, + counter_balance: state.accept_balance, + ln_rollback: (&channel_details).into(), + check_ln_secret: true, + commitment_transactions, + }; + + sub_channel.state = SubChannelState::CloseConfirmed(updated_channel); + + self.dlc_channel_manager + .get_store() + .upsert_sub_channel(&sub_channel)?; + + self.dlc_channel_manager.get_store().persist_chain_monitor( + &self.dlc_channel_manager.get_chain_monitor().lock().unwrap(), + )?; + Ok(close_confirm) + }, + )?; + + Ok(close_confirm) + } + + fn on_sub_channel_close_confirm( + &self, + confirm: &SubChannelCloseConfirm, + counter_party: &PublicKey, + ) -> Result { + let finalize = self.ln_channel_manager.with_useable_channel_lock( + &confirm.channel_id, + counter_party, + Some(confirm.commit_tx_number), + |channel_lock| { + let (mut sub_channel, state) = get_sub_channel_in_state!( + self.dlc_channel_manager, + confirm.channel_id, + CloseAccepted, + Some(*counter_party) + )?; + + let dlc_channel_id = + sub_channel + .get_dlc_channel_id(0) + .ok_or(Error::InvalidState( + "Could not get dlc channel id.".to_string(), + ))?; + + let (dlc_channel, contract) = self + .dlc_channel_manager + .get_closed_sub_dlc_channel(dlc_channel_id, state.own_balance)?; + + if PublicKey::from_secret_key( + self.dlc_channel_manager.get_secp(), + &confirm.split_revocation_secret, + ) != state.signed_subchannel.counter_per_split_point + { + return Err(Error::InvalidParameters( + "Invalid per update secret in subchannel close confirm".to_string(), + ) + .into()); + } + + sub_channel + .counter_party_secrets + .provide_secret( + sub_channel.update_idx, + *confirm.split_revocation_secret.as_ref(), + ) + .map_err(|_| { + Error::InvalidParameters("Invalid split revocation secret".to_string()) + })?; + + let raa = RevokeAndACK { + channel_id: confirm.channel_id, + per_commitment_secret: *confirm.commit_revocation_secret.as_ref(), + next_per_commitment_point: confirm.next_per_commitment_point, + }; + + self.ln_channel_manager.revoke_and_ack(channel_lock, &raa)?; + + let own_raa = self.ln_channel_manager.on_commitment_signed_get_raa( + channel_lock, + &confirm.commit_signature, + &confirm.htlc_signatures, + )?; + + let per_split_seed = self + .dlc_channel_manager + .get_wallet() + .get_secret_key_for_pubkey( + &sub_channel + .per_split_seed + .expect("to have a per split seed"), + )?; + + let per_split_secret = SecretKey::from_slice(&build_commitment_secret( + per_split_seed.as_ref(), + sub_channel.update_idx, + )) + .map_err(|e| APIError::ExternalError { err: e.to_string() })?; + + let finalize = SubChannelCloseFinalize { + channel_id: confirm.channel_id, + split_revocation_secret: per_split_secret, + commit_revocation_secret: Some( + SecretKey::from_slice(&own_raa.per_commitment_secret) + .expect("a valid secret key"), + ), + next_per_commitment_point: Some(own_raa.next_per_commitment_point), + }; + + self.dlc_channel_manager + .get_chain_monitor() + .lock() + .unwrap() + .add_tx( + state.signed_subchannel.split_tx.transaction.txid(), + ChannelInfo { + channel_id: sub_channel.channel_id, + tx_type: TxType::Revoked { + update_idx: sub_channel.update_idx, + own_adaptor_signature: state + .signed_subchannel + .own_split_adaptor_signature, + is_offer: sub_channel.is_offer, + revoked_tx_type: RevokedTxType::Split, + }, + }, + ); + + sub_channel.state = SubChannelState::OffChainClosed; + + let mut chain_monitor = + self.dlc_channel_manager.get_chain_monitor().lock().unwrap(); + chain_monitor.cleanup_channel(dlc_channel_id); + + self.dlc_channel_manager + .get_store() + .upsert_channel(dlc_channel, contract)?; + + self.dlc_channel_manager + .get_store() + .upsert_sub_channel(&sub_channel)?; + + self.dlc_channel_manager + .get_store() + .persist_chain_monitor(&chain_monitor)?; + Ok(finalize) + }, + )?; + + Ok(finalize) + } + + fn on_sub_channel_close_finalize( + &self, + finalize: &SubChannelCloseFinalize, + counter_party: &PublicKey, + ) -> Result<(), Error> { + self.ln_channel_manager.with_useable_channel_lock( + &finalize.channel_id, + counter_party, + None, + |channel_lock| { + let (mut sub_channel, state) = get_sub_channel_in_state!( + self.dlc_channel_manager, + finalize.channel_id, + CloseConfirmed, + Some(*counter_party) + )?; + + if PublicKey::from_secret_key( + self.dlc_channel_manager.get_secp(), + &finalize.split_revocation_secret, + ) != state.signed_subchannel.counter_per_split_point + { + return Err(Error::InvalidParameters( + "Invalid per update secret in subchannel close finalize".to_string(), + ) + .into()); + } + + sub_channel + .counter_party_secrets + .provide_secret( + sub_channel.update_idx, + *finalize.split_revocation_secret.as_ref(), + ) + .map_err(|_| { + Error::InvalidParameters("Invalid split revocation secret".to_string()) + })?; + + let dlc_channel_id = + sub_channel + .get_dlc_channel_id(0) + .ok_or(Error::InvalidState( + "Could not get dlc channel id.".to_string(), + ))?; + + let (dlc_channel, contract) = self + .dlc_channel_manager + .get_closed_sub_dlc_channel(dlc_channel_id, state.own_balance)?; + + self.dlc_channel_manager + .get_chain_monitor() + .lock() + .unwrap() + .add_tx( + state.signed_subchannel.split_tx.transaction.txid(), + ChannelInfo { + channel_id: sub_channel.channel_id, + tx_type: TxType::Revoked { + update_idx: sub_channel.update_idx, + own_adaptor_signature: state + .signed_subchannel + .own_split_adaptor_signature, + is_offer: sub_channel.is_offer, + revoked_tx_type: RevokedTxType::Split, + }, + }, + ); + + if state.check_ln_secret { + match ( + finalize.commit_revocation_secret.as_ref(), + finalize.next_per_commitment_point.as_ref(), + ) { + (Some(secret), Some(point)) => { + let revoke_and_ack = RevokeAndACK { + channel_id: finalize.channel_id, + per_commitment_secret: *secret.as_ref(), + next_per_commitment_point: *point, + }; + + self.ln_channel_manager + .revoke_and_ack(channel_lock, &revoke_and_ack)?; + } + _ => return Err(APIError::ExternalError { + err: "Did not get expected revocation secret and next commitment point" + .to_string(), + }), + }; + } + + sub_channel.state = SubChannelState::OffChainClosed; + + let mut chain_monitor = + self.dlc_channel_manager.get_chain_monitor().lock().unwrap(); + chain_monitor.cleanup_channel(dlc_channel_id); + + self.dlc_channel_manager + .get_store() + .upsert_channel(dlc_channel, contract)?; + + self.dlc_channel_manager + .get_store() + .upsert_sub_channel(&sub_channel)?; + + self.dlc_channel_manager + .get_store() + .persist_chain_monitor(&chain_monitor)?; + Ok(()) + }, + )?; + + Ok(()) + } + + /// Process pending actions, potentially generating messages that should be sent to the + /// adequate peer. + 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_signed(id) { + error!("Unexpected error {} marking channel {:?} as signed, keeping the action to retry.", e, id); + retain.push(Action::ForceSign(id)); + } + } else { + info!("Could not mark channel {:?} as signed as it was not usable, keeping the action to retry.", id); + retain.push(Action::ForceSign(id)); + } + } else { + error!("Could not get channel details for id: {:?}", id); + }; + } + Action::ResendCloseOffer((offer, pk)) => { + msgs.push((SubChannelMessage::CloseOffer(offer), pk)); + } + Action::ReAcceptCloseOffer { + channel_id, + own_balance, + } => { + if let Some(details) = self.ln_channel_manager.get_channel_details(&channel_id) + { + if details.is_usable { + if let Ok((msg, p)) = self.accept_subchannel_close_offer(&channel_id) { + msgs.push((SubChannelMessage::CloseAccept(msg), p)); + } else { + error!( + "Could not re-accept close for sub channel {:?}, keeping the action", + channel_id + ); + retain.push(Action::ReAcceptCloseOffer { + channel_id, + own_balance, + }); + } + } else { + trace!( + "Channel {:?} not yet useable, keeping the re-accept close action", + channel_id + ); + retain.push(Action::ReAcceptCloseOffer { + channel_id, + own_balance, + }); + } + } else { + error!( + "Could not get channel details for id: {:?}, giving up re-accepting the close offer", + channel_id + ); + }; + } + Action::ResendCloseFinalize((msg, pk)) => { + msgs.push((SubChannelMessage::CloseFinalize(msg), pk)); + } + }; + } + + actions.append(&mut retain); + + msgs + } + + /// Checks for watched transactions, process pending actions and tries to finalize the closing + /// of sub channel whose closing has been initiated by the local or remote party. The returned + /// messages should be sent to the peer with the associated public key. + pub fn periodic_check(&self) -> Vec<(SubChannelMessage, PublicKey)> { + if let Err(e) = self.check_for_watched_tx() { + error!("Error checking for watched transactions: {}", e); + } + + let msgs = self.process_actions(); + + if let Ok(sub_channels) = self.dlc_channel_manager.get_store().get_sub_channels() { + let closing_sub_channels = sub_channels.iter().filter(|x| { + if let SubChannelState::Closing(_) = &x.state { + true + } else { + false + } + }); + + for c in closing_sub_channels { + if let Err(e) = self.finalize_force_close_sub_channels(&c.channel_id) { + warn!( + "Could not finalize force closing of sub channel {:?}: {}", + c.channel_id, e + ); + } + } + } + + if let Err(e) = self.dlc_channel_manager.periodic_check() { + error!( + "Error performing periodic check of the channel manager: {}", + e + ); + } + + msgs + } + + /// Check if any of the watched transactions have been confirmed on chain. + /// + /// All confirmed transactions are then processed depending on their [`TxType`]. + fn check_for_watched_tx(&self) -> Result<(), Error> { + let mut chain_monitor = self.dlc_channel_manager.get_chain_monitor().lock().unwrap(); + + //Todo(tibo): all db commit should happen at once otherwise state might get corrupted. + + let confirmed_txs = chain_monitor.confirmed_txs(); + + for (tx, channel_info) in &confirmed_txs { + log::info!( + "Transaction {} confirmed for channel {}. Type: {:?}", + tx.txid(), + channel_info.channel_id.to_hex(), + channel_info.tx_type, + ); + + let mut sub_channel = match self + .dlc_channel_manager + .get_store() + .get_sub_channel(channel_info.channel_id)? + { + None => { + log::error!( + "Cannot process confirmed transaction {} for unknown subchannel {}", + tx.txid(), + channel_info.channel_id.to_hex() + ); + continue; + } + Some(s) => s, + }; + + if let TxType::SplitTx = channel_info.tx_type { + // TODO(tibo): should only considered closed after some confirmations. + // Ideally should save previous state, and maybe restore in + // case of reorg, though if the counter party has sent the + // tx to close the channel it is unlikely that the tx will + // not be part of a future block. + let (state, commitment_transactions) = match &sub_channel.state { + SubChannelState::Signed(s) => (s, None), + SubChannelState::Closing(_) => { + log::info!("Spotted closing split transaction on chain"); + continue; + } + SubChannelState::CloseOffered(s) => (&s.signed_subchannel, None), + SubChannelState::CloseAccepted(s) => { + if let Err(e) = self.ln_channel_manager.with_channel_lock_no_check( + &sub_channel.channel_id, + &sub_channel.counter_party, + |channel_lock| { + self.ln_channel_manager.set_funding_outpoint( + channel_lock, + &lightning::chain::transaction::OutPoint { + txid: s.signed_subchannel.split_tx.transaction.txid(), + index: 0, + }, + s.ln_rollback.channel_value_satoshis, + s.ln_rollback.value_to_self_msat, + ); + Ok(()) + }, + ) { + log::error!("Could not reset funding outpoint: {:?}", e); + } + ( + &s.signed_subchannel, + Some(s.commitment_transactions.clone()), + ) + } + SubChannelState::CloseConfirmed(s) => { + if let Err(e) = self.ln_channel_manager.with_channel_lock_no_check( + &sub_channel.channel_id, + &sub_channel.counter_party, + |channel_lock| { + self.ln_channel_manager.set_funding_outpoint( + channel_lock, + &lightning::chain::transaction::OutPoint { + txid: s.signed_subchannel.split_tx.transaction.txid(), + index: 0, + }, + s.ln_rollback.channel_value_satoshis, + s.ln_rollback.value_to_self_msat, + ); + Ok(()) + }, + ) { + log::error!("Could not reset funding outpoint: {:?}", e); + } + ( + &s.signed_subchannel, + Some(s.commitment_transactions.clone()), + ) + } + _ => { + log::error!("Unexpected channel state"); + continue; + } + }; + + log::info!("Spotted split transaction, marking sub channel as closing"); + let closing_sub_channel = ClosingSubChannel { + signed_sub_channel: state.clone(), + is_initiator: false, + commitment_transactions, + }; + chain_monitor.remove_tx(&tx.txid()); + sub_channel.state = SubChannelState::Closing(closing_sub_channel); + self.dlc_channel_manager + .get_store() + .upsert_sub_channel(&sub_channel)?; + continue; + } else if let TxType::Revoked { + update_idx, + own_adaptor_signature, + is_offer, + revoked_tx_type, + } = channel_info.tx_type + { + if let RevokedTxType::Split = revoked_tx_type { + let secret = sub_channel + .counter_party_secrets + .get_secret(update_idx) + .expect("to be able to retrieve the per update secret"); + let counter_per_update_secret = SecretKey::from_slice(&secret) + .expect("to be able to parse the counter per update secret."); + + let per_update_seed_pk = sub_channel + .per_split_seed + .expect("to have a per split seed"); + + let per_update_seed_sk = self + .dlc_channel_manager + .get_wallet() + .get_secret_key_for_pubkey(&per_update_seed_pk)?; + + let per_update_secret = SecretKey::from_slice(&build_commitment_secret( + per_update_seed_sk.as_ref(), + update_idx, + )) + .expect("a valid secret key."); + + let per_update_point = PublicKey::from_secret_key( + self.dlc_channel_manager.get_secp(), + &per_update_secret, + ); + + let own_revocation_params = sub_channel.own_base_points.get_revokable_params( + self.dlc_channel_manager.get_secp(), + &sub_channel + .counter_base_points + .as_ref() + .expect("to have counter base points") + .revocation_basepoint, + &per_update_point, + ); + + let counter_per_update_point = PublicKey::from_secret_key( + self.dlc_channel_manager.get_secp(), + &counter_per_update_secret, + ); + + let base_own_sk = self + .dlc_channel_manager + .get_wallet() + .get_secret_key_for_pubkey(&sub_channel.own_base_points.own_basepoint)?; + + let own_sk = derive_private_key( + self.dlc_channel_manager.get_secp(), + &per_update_point, + &base_own_sk, + ); + + let counter_revocation_params = sub_channel + .counter_base_points + .as_ref() + .expect("to have counter base points") + .get_revokable_params( + self.dlc_channel_manager.get_secp(), + &sub_channel.own_base_points.revocation_basepoint, + &counter_per_update_point, + ); + + let witness = if sub_channel.own_fund_pk < sub_channel.counter_fund_pk { + tx.input[0].witness.to_vec().remove(1) + } else { + tx.input[0].witness.to_vec().remove(2) + }; + + let sig_data = witness + .iter() + .take(witness.len() - 1) + .cloned() + .collect::>(); + let own_sig = Signature::from_der(&sig_data)?; + + let counter_sk = own_adaptor_signature.recover( + self.dlc_channel_manager.get_secp(), + &own_sig, + &counter_revocation_params.publish_pk.inner, + )?; + + let own_revocation_base_secret = &self + .dlc_channel_manager + .get_wallet() + .get_secret_key_for_pubkey( + &sub_channel.own_base_points.revocation_basepoint, + )?; + + let counter_revocation_sk = derive_private_revocation_key( + self.dlc_channel_manager.get_secp(), + &counter_per_update_secret, + own_revocation_base_secret, + ); + + let (offer_params, accept_params) = if is_offer { + (&own_revocation_params, &counter_revocation_params) + } else { + (&counter_revocation_params, &own_revocation_params) + }; + + let fee_rate_per_vb: u64 = (self + .dlc_channel_manager + .get_fee_estimator() + .get_est_sat_per_1000_weight( + lightning::chain::chaininterface::ConfirmationTarget::HighPriority, + ) + / 250) + .into(); + + let signed_tx = + dlc::channel::sub_channel::create_and_sign_punish_split_transaction( + self.dlc_channel_manager.get_secp(), + offer_params, + accept_params, + &own_sk, + &counter_sk, + &counter_revocation_sk, + tx, + &self.dlc_channel_manager.get_wallet().get_new_address()?, + 0, + fee_rate_per_vb, + )?; + + self.dlc_channel_manager + .get_blockchain() + .send_transaction(&signed_tx)?; + + sub_channel.state = SubChannelState::ClosedPunished(signed_tx.txid()); + + self.dlc_channel_manager + .get_store() + .upsert_sub_channel(&sub_channel)?; + } + } else if let TxType::CollaborativeClose = channel_info.tx_type { + todo!(); + // signed_channel.state = SignedChannelState::CollaborativelyClosed; + // self.dlc_channel_manager.get_store() + // .upsert_channel(Channel::Signed(signed_channel), None)?; + } + } + + self.dlc_channel_manager + .get_store() + .persist_chain_monitor(&chain_monitor)?; + + 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 mut updated_state = None; + match &channel.state { + SubChannelState::Offered(o) if channel.is_offer => { + let has_not_received = match peer_state { + None => true, + Some(state) if state == ReestablishFlag::OffChainClosed as u8 => true, + Some(state) if state == ReestablishFlag::CloseConfirmed as u8 => { + let finalize = self.get_reconnect_close_finalize(&channel, true)?; + + self.actions + .lock() + .unwrap() + .push(Action::ResendCloseFinalize(( + finalize, + channel.counter_party, + ))); + true + } + _ => false, + }; + if has_not_received { + 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))); + } + } + SubChannelState::Accepted(a) => { + self.ln_channel_manager + .with_useable_channel_lock( + &channel.channel_id, + peer_id, + None, + |channel_lock| { + 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, + }); + + self.ln_channel_manager.set_funding_outpoint( + channel_lock, + &a.ln_rollback.funding_outpoint, + a.ln_rollback.channel_value_satoshis, + a.ln_rollback.value_to_self_msat, + ); + updated_state = Some(SubChannelState::Offered(OfferedSubChannel { + per_split_point: a.offer_per_split_point, + })); + + Ok(()) + }, + ) + .map_err(|e| Error::InvalidState(format!("{:?}", e)))?; + } + SubChannelState::Confirmed(a) => { + self.ln_channel_manager + .with_useable_channel_lock( + &channel.channel_id, + peer_id, + None, + |channel_lock| { + 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: Some(dlc_channel.own_per_update_seed), + is_offer_party: true, + counter_party: dlc_channel.counter_party, + // TODO(tibo): use value from original offer + cet_nsequence: CET_NSEQUENCE, + }; + self.ln_channel_manager.set_funding_outpoint( + channel_lock, + &a.ln_rollback.funding_outpoint, + a.ln_rollback.channel_value_satoshis, + a.ln_rollback.value_to_self_msat, + ); + self.dlc_channel_manager.get_store().upsert_channel( + Channel::Offered(offered_channel), + Some(Contract::Offered( + contract.accepted_contract.offered_contract, + )), + )?; + updated_state = Some(SubChannelState::Offered(OfferedSubChannel { + per_split_point: a.own_per_split_point, + })); + Ok(()) + }, + ) + .map_err(|e| Error::InvalidState(format!("{:?}", e)))?; + } + SubChannelState::Finalized(signed) => { + if let Some(counter_state) = peer_state { + if counter_state == ReestablishFlag::Signed as u8 { + self.actions + .lock() + .unwrap() + .push(Action::ForceSign(channel_id)); + } else { + self.ln_channel_manager + .with_useable_channel_lock( + &channel.channel_id, + peer_id, + None, + |channel_lock| { + 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" + ), + Confirmed, + 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.counter_points, + per_update_point: dlc_channel.counter_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.accepted_contract.offered_contract, + )), + )?; + let party_params = + contract.accepted_contract.accept_params.clone(); + let funding_inputs_info = + contract.accepted_contract.funding_inputs; + let accept_points = dlc_channel.own_points.clone(); + let per_update_seed_pk = dlc_channel.own_per_update_seed; + self.actions.lock().unwrap().push(Action::ReAccept { + channel_id, + party_params, + funding_inputs_info, + accept_points, + per_update_seed_pk, + }); + + self.ln_channel_manager.set_funding_outpoint( + channel_lock, + &signed.ln_rollback.funding_outpoint, + signed.ln_rollback.channel_value_satoshis, + signed.ln_rollback.value_to_self_msat, + ); + updated_state = + Some(SubChannelState::Offered(OfferedSubChannel { + per_split_point: signed.counter_per_split_point, + })); + Ok(()) + }, + ) + .map_err(|e| Error::InvalidState(format!("{:?}", e)))?; + } + } + } + SubChannelState::CloseOffered(offered) if offered.is_offer => { + if let Some(counter_state) = peer_state { + if counter_state == ReestablishFlag::Signed as u8 { + let message = SubChannelCloseOffer { + channel_id, + accept_balance: offered.accept_balance, + }; + + self.actions + .lock() + .unwrap() + .push(Action::ResendCloseOffer((message, channel.counter_party))); + } + } + } + SubChannelState::CloseAccepted(accepted) => { + self.ln_channel_manager.with_useable_channel_lock( + &channel.channel_id, + &channel.counter_party, + None, + |channel_lock| { + self.ln_channel_manager.set_funding_outpoint( + channel_lock, + &accepted.ln_rollback.funding_outpoint, + accepted.ln_rollback.channel_value_satoshis, + accepted.ln_rollback.value_to_self_msat, + ); + self.actions + .lock() + .unwrap() + .push(Action::ReAcceptCloseOffer { + channel_id, + own_balance: accepted.own_balance, + }); + + let close_offered = CloseOfferedSubChannel { + signed_subchannel: accepted.signed_subchannel.clone(), + offer_balance: accepted.counter_balance, + accept_balance: accepted.own_balance, + is_offer: false, + }; + + updated_state = Some(SubChannelState::CloseOffered(close_offered)); + Ok(()) + }, + )?; + } + SubChannelState::CloseConfirmed(confirmed) => { + if let Some(counter_state) = peer_state { + if counter_state == ReestablishFlag::CloseAccepted as u8 { + self.ln_channel_manager.with_useable_channel_lock( + &channel.channel_id, + &channel.counter_party, + None, + |channel_lock| { + self.ln_channel_manager.set_funding_outpoint( + channel_lock, + &confirmed.ln_rollback.funding_outpoint, + confirmed.ln_rollback.channel_value_satoshis, + confirmed.ln_rollback.value_to_self_msat, + ); + updated_state = Some(SubChannelState::CloseOffered( + CloseOfferedSubChannel { + signed_subchannel: confirmed.signed_subchannel.clone(), + offer_balance: confirmed.own_balance, + accept_balance: confirmed.counter_balance, + is_offer: true, + }, + )); + Ok(()) + }, + )?; + } else { + // LDK will provide the revocation secret through reestablishment. + let mut confirmed = confirmed.clone(); + confirmed.check_ln_secret = false; + updated_state = Some(SubChannelState::CloseConfirmed(confirmed)); + } + } + } + SubChannelState::OffChainClosed => { + if let Some(counter_state) = peer_state { + let finalize = self.get_reconnect_close_finalize(&channel, false)?; + if counter_state == ReestablishFlag::CloseConfirmed as u8 { + self.actions + .lock() + .unwrap() + .push(Action::ResendCloseFinalize(( + finalize, + channel.counter_party, + ))); + } + } + } + _ => {} + }; + + 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 get_reconnect_close_finalize( + &self, + channel: &SubChannel, + // when the channel has been offered again already, the offer party will have decreased its + // update index, so we need to restore it to provide a proper split secret. + restore_index: bool, + ) -> Result { + let per_split_seed = self + .dlc_channel_manager + .get_wallet() + .get_secret_key_for_pubkey( + &channel.per_split_seed.expect("to have a per split seed"), + )?; + + let update_index = if restore_index { + channel.update_idx + 1 + } else { + channel.update_idx + }; + let per_split_secret = SecretKey::from_slice(&build_commitment_secret( + per_split_seed.as_ref(), + update_index, + )) + .map_err(|e| APIError::ExternalError { err: e.to_string() })?; + + Ok(SubChannelCloseFinalize { + channel_id: channel.channel_id, + split_revocation_secret: per_split_secret, + commit_revocation_secret: None, + next_per_commitment_point: None, + }) + } + + fn on_sub_channel_reject(&self, reject: &Reject, peer_id: &PublicKey) -> Result<(), Error> { + let sub_channel = self + .dlc_channel_manager + .get_store() + .get_sub_channel(reject.channel_id)?; + + match sub_channel { + None => { + return Err(Error::InvalidParameters(format!( + "No such subchannel: {:?}", + reject.channel_id + ))) + } + Some(mut s) => { + if s.counter_party != *peer_id { + return Err(Error::InvalidParameters( + "Message from invalid peer".to_string(), + )); + } + match s.state { + SubChannelState::Offered(_) => { + s.state = SubChannelState::Rejected; + } + SubChannelState::CloseOffered(o) => { + s.state = SubChannelState::Signed(o.signed_subchannel); + } + _ => { + return Err(Error::InvalidParameters( + "Not in a state to be rejected".to_string(), + )) + } + }; + + self.dlc_channel_manager + .get_store() + .upsert_sub_channel(&s)?; + } + } + + Ok(()) + } + + fn get_closed_dlc_channel_and_contract( + &self, + channel_id: [u8; 32], + counter_closed: bool, + ) -> Result<(Channel, Contract), Error> { + let channel = self + .dlc_channel_manager + .get_store() + .get_channel(&channel_id)? + .ok_or(Error::InvalidParameters(format!( + "No such channel {:?}", + channel_id + )))?; + let closed_channel_data = ClosedChannel { + counter_party: channel.get_counter_party_id(), + temporary_channel_id: channel.get_temporary_id(), + channel_id: channel.get_id(), + }; + let closed_channel = if counter_closed { + Channel::CounterClosed(closed_channel_data) + } else { + Channel::Closed(closed_channel_data) + }; + let contract_id = channel + .get_contract_id() + .ok_or(Error::InvalidParameters(format!( + "Channel {:?} does not have a contract associated", + channel_id + )))?; + let contract = self + .dlc_channel_manager + .get_store() + .get_contract(&contract_id)? + .ok_or(Error::InvalidParameters(format!( + "No such contract {:?}", + contract_id + )))?; + let closed_contract = Contract::Closed(ClosedContract { + attestations: None, + signed_cet: None, + contract_id, + temporary_contract_id: contract.get_id(), + counter_party_id: contract.get_counter_party_id(), + pnl: 0, + }); + Ok((closed_channel, closed_contract)) + } + + fn get_holder_split_tx_signature( + &self, + sub_channel: &SubChannel, + split_tx: &Transaction, + ) -> Result { + let mut signers = self.ln_channel_signers.lock().unwrap(); + let signer = signers.entry(sub_channel.channel_id).or_insert( + self.signer_provider.derive_ln_dlc_channel_signer( + sub_channel.fund_value_satoshis, + sub_channel.channel_keys_id, + ), + ); + signer.get_holder_split_tx_signature( + self.dlc_channel_manager.get_secp(), + split_tx, + &sub_channel.original_funding_redeemscript, + sub_channel.fund_value_satoshis, + ) + } + + fn get_holder_split_tx_adaptor_signature( + &self, + channel_id: [u8; 32], + channel_value_satoshis: u64, + channel_keys_id: [u8; 32], + split_tx: &Transaction, + funding_redeemscript: &Script, + counter_publish_pk: &PublicKey, + ) -> Result { + let mut signers = self.ln_channel_signers.lock().unwrap(); + + let signer = signers.entry(channel_id).or_insert( + self.signer_provider + .derive_ln_dlc_channel_signer(channel_value_satoshis, channel_keys_id), + ); + + signer.get_holder_split_tx_adaptor_signature( + self.dlc_channel_manager.get_secp(), + split_tx, + channel_value_satoshis, + funding_redeemscript, + counter_publish_pk, + ) + } +} + +impl< + W: Deref, + M: Deref, + C: Deref, + S: Deref, + B: Deref, + O: Deref, + T: Deref, + F: Deref, + D: Deref>, + CS: ChannelSigner, + SP: Deref, + LCS: LnDlcChannelSigner, + > ChannelMessageHandler for SubChannelManager +where + W::Target: Wallet, + M::Target: LNChannelManager, + C::Target: LNChainMonitor, + S::Target: Storage, + B::Target: Blockchain, + O::Target: Oracle, + T::Target: Time, + F::Target: FeeEstimator, + SP::Target: LnDlcSignerProvider, +{ + fn handle_open_channel( + &self, + their_node_id: &PublicKey, + msg: &lightning::ln::msgs::OpenChannel, + ) { + self.ln_channel_manager + .handle_open_channel(their_node_id, msg) + } + + fn handle_accept_channel( + &self, + their_node_id: &PublicKey, + msg: &lightning::ln::msgs::AcceptChannel, + ) { + self.ln_channel_manager + .handle_accept_channel(their_node_id, 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, msg: &lightning::ln::msgs::Shutdown) { + self.ln_channel_manager.handle_shutdown(their_node_id, 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) { + self.ln_channel_manager.peer_disconnected(their_node_id) + } + + fn peer_connected( + &self, + their_node_id: &PublicKey, + msg: &lightning::ln::msgs::Init, + inbound: bool, + ) -> Result<(), ()> { + self.ln_channel_manager + .peer_connected(their_node_id, msg, inbound) + } + + 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) + } + + fn handle_open_channel_v2( + &self, + their_node_id: &PublicKey, + msg: &lightning::ln::msgs::OpenChannelV2, + ) { + self.ln_channel_manager + .handle_open_channel_v2(their_node_id, msg) + } + + fn handle_accept_channel_v2( + &self, + their_node_id: &PublicKey, + msg: &lightning::ln::msgs::AcceptChannelV2, + ) { + self.ln_channel_manager + .handle_accept_channel_v2(their_node_id, msg) + } + + fn handle_tx_add_input( + &self, + their_node_id: &PublicKey, + msg: &lightning::ln::msgs::TxAddInput, + ) { + self.ln_channel_manager + .handle_tx_add_input(their_node_id, msg) + } + + fn handle_tx_add_output( + &self, + their_node_id: &PublicKey, + msg: &lightning::ln::msgs::TxAddOutput, + ) { + self.ln_channel_manager + .handle_tx_add_output(their_node_id, msg) + } + + fn handle_tx_remove_input( + &self, + their_node_id: &PublicKey, + msg: &lightning::ln::msgs::TxRemoveInput, + ) { + self.ln_channel_manager + .handle_tx_remove_input(their_node_id, msg) + } + + fn handle_tx_remove_output( + &self, + their_node_id: &PublicKey, + msg: &lightning::ln::msgs::TxRemoveOutput, + ) { + self.ln_channel_manager + .handle_tx_remove_output(their_node_id, msg) + } + + fn handle_tx_complete(&self, their_node_id: &PublicKey, msg: &lightning::ln::msgs::TxComplete) { + self.ln_channel_manager + .handle_tx_complete(their_node_id, msg) + } + + fn handle_tx_signatures( + &self, + their_node_id: &PublicKey, + msg: &lightning::ln::msgs::TxSignatures, + ) { + self.ln_channel_manager + .handle_tx_signatures(their_node_id, msg) + } + + fn handle_tx_init_rbf(&self, their_node_id: &PublicKey, msg: &lightning::ln::msgs::TxInitRbf) { + self.ln_channel_manager + .handle_tx_init_rbf(their_node_id, msg) + } + + fn handle_tx_ack_rbf(&self, their_node_id: &PublicKey, msg: &lightning::ln::msgs::TxAckRbf) { + self.ln_channel_manager + .handle_tx_ack_rbf(their_node_id, msg) + } + + fn handle_tx_abort(&self, their_node_id: &PublicKey, msg: &lightning::ln::msgs::TxAbort) { + self.ln_channel_manager.handle_tx_abort(their_node_id, msg) + } + + fn get_genesis_hashes(&self) -> Option> { + self.ln_channel_manager.get_genesis_hashes() + } +} + +impl< + W: Deref, + M: Deref, + C: Deref, + S: Deref, + B: Deref, + O: Deref, + T: Deref, + F: Deref, + D: Deref>, + CS: ChannelSigner, + SP: Deref, + LCS: LnDlcChannelSigner, + > MessageSendEventsProvider for SubChannelManager +where + W::Target: Wallet, + M::Target: LNChannelManager, + C::Target: LNChainMonitor, + S::Target: Storage, + B::Target: Blockchain, + O::Target: Oracle, + T::Target: Time, + F::Target: FeeEstimator, + SP::Target: LnDlcSignerProvider, +{ + fn get_and_clear_pending_msg_events(&self) -> Vec { + let mut msg_events = self.ln_channel_manager.get_and_clear_pending_msg_events(); + + for event in msg_events.iter_mut() { + if let lightning::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, + counter_collateral: u64, + fee_rate: u64, + is_offer: bool, +) -> Result<(u64, u64), Error> { + let (offer_fees, accept_fees) = per_party_fee(fee_rate)?; + let (own_fees, counter_fees) = if is_offer { + (offer_fees, accept_fees) + } else { + (accept_fees, offer_fees) + }; + + let own_reserve_msat = channel_details.unspendable_punishment_reserve.unwrap_or(0) * 1000; + let counter_reserve_msat = channel_details.counterparty.unspendable_punishment_reserve * 1000; + + let own_value_to_self_msat = (channel_details.outbound_capacity_msat + own_reserve_msat) + .checked_sub((own_collateral + own_fees) * 1000) + .ok_or_else(|| { + Error::InvalidParameters(format!( + "Not enough outbound capacity to establish given contract. Want {} but have {}", + (own_collateral + own_fees) * 1000, + channel_details.outbound_capacity_msat + own_reserve_msat + )) + })?; + // TODO(tibo): find better ways to validate amounts + take into account increased fees. + if own_value_to_self_msat < dlc::DUST_LIMIT * 1000 { + return Err(Error::InvalidParameters(format!( + "Not enough outbound capacity to establish given contract. Want {} but have {}", + dlc::DUST_LIMIT * 1000, + own_value_to_self_msat + ))); + } + + let counter_value_to_self_msat = (channel_details.inbound_capacity_msat + counter_reserve_msat) + .checked_sub((counter_collateral + counter_fees) * 1000) + .ok_or_else(|| { + Error::InvalidParameters(format!( + "Not enough inbound capacity to establish given contract. Want {} but have {}", + (counter_collateral + counter_fees) * 1000, + channel_details.inbound_capacity_msat + counter_reserve_msat + )) + })?; + // TODO(tibo): find better ways to validate amounts + take into account increased fees. + if counter_value_to_self_msat < dlc::DUST_LIMIT * 1000 { + return Err(Error::InvalidParameters(format!( + "Not enough inbound capacity to establish given contract. Want {} but have {}", + dlc::DUST_LIMIT * 1000, + counter_value_to_self_msat + ))); + } + + Ok((own_value_to_self_msat, counter_value_to_self_msat)) +} + +// Return fees for offer and accept parties (in that order). Offer pays 1 more +// if total fee is not even. +fn per_party_fee(fee_rate: u64) -> Result<(u64, u64), Error> { + let total_fee = (dlc::channel::sub_channel::dlc_channel_and_split_fee(fee_rate)? + + dlc::util::weight_to_fee(LN_GLUE_TX_WEIGHT, fee_rate)?) as f64; + Ok(( + (total_fee / 2.0).ceil() as u64, + (total_fee / 2.0).floor() as u64, + )) +} diff --git a/dlc-manager/src/subchannel/mod.rs b/dlc-manager/src/subchannel/mod.rs new file mode 100644 index 00000000..f2ff072a --- /dev/null +++ b/dlc-manager/src/subchannel/mod.rs @@ -0,0 +1,696 @@ +//! # Module containing structures and methods for working with DLC channels embedded in Lightning +//! channels. + +use std::{fmt::Display, ops::Deref}; + +use bitcoin::{hashes::Hash, OutPoint, Script, Transaction, Txid}; +use dlc::channel::sub_channel::SplitTx; +use lightning::{ + chain::{ + chaininterface::{BroadcasterInterface, FeeEstimator}, + chainmonitor::ChainMonitor, + Watch, + }, + ln::{ + chan_utils::CounterpartyCommitmentSecrets, + channelmanager::{ChannelDetails, ChannelLock, ChannelManager}, + msgs::{ChannelMessageHandler, CommitmentSigned, RevokeAndACK}, + }, + routing::router::Router, + sign::{EntropySource, NodeSigner, SignerProvider, WriteableEcdsaChannelSigner}, + util::{errors::APIError, logger::Logger}, +}; +use secp256k1_zkp::{ecdsa::Signature, EcdsaAdaptorSignature, PublicKey, SecretKey}; + +use crate::{channel::party_points::PartyBasePoints, error::Error, ChannelId, ContractId}; + +pub mod ser; + +#[derive(Clone, PartialEq, Eq)] +/// Contains information about a DLC channel embedded within a Lightning Network Channel. +pub struct SubChannel { + /// The index for the channel. + pub channel_id: ChannelId, + /// The [`secp256k1_zkp::PublicKey`] of the counter party's node. + pub counter_party: PublicKey, + /// The update index of the sub channel. + pub update_idx: u64, + /// The state of the sub channel. + pub state: SubChannelState, + /// The image of the seed used by the local party to derive all per update + /// points (Will be `None` on the accept party side before the sub channel is accepted.) + pub per_split_seed: Option, + /// The current fee rate to be used to create transactions. + pub fee_rate_per_vb: u64, + /// The points used by the local party to derive revocation secrets for the split transaction. + pub own_base_points: PartyBasePoints, + /// The points used by the remote party to derive revocation secrets for the split transaction. + pub counter_base_points: Option, + /// The value of the original funding output. + pub fund_value_satoshis: u64, + /// The locking script of the original funding output. + pub original_funding_redeemscript: Script, + /// Whether the local party is the one who offered the sub channel. + pub is_offer: bool, + /// The public key used by the local party for the funding output script. + pub own_fund_pk: PublicKey, + /// The public key used by the remote party for the funding output script. + pub counter_fund_pk: PublicKey, + /// The revocation secrets from the remote party for already revoked split transactions. + pub counter_party_secrets: CounterpartyCommitmentSecrets, + /// The id used to derive the keys for the Lightning channel. + pub channel_keys_id: [u8; 32], +} + +impl std::fmt::Debug for SubChannel { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("SubChannel") + .field("channel_id", &self.channel_id) + .field("state", &self.state) + .finish() + } +} + +impl SubChannel { + /// Return the channel ID of the DLC channel at given index if in a state where such a channel + /// is supposed to exist. + pub fn get_dlc_channel_id(&self, index: u8) -> Option { + let temporary_channel_id = + generate_temporary_channel_id(self.channel_id, self.update_idx, index); + match &self.state { + SubChannelState::Offered(_) => Some(temporary_channel_id), + SubChannelState::Accepted(a) => Some(a.get_dlc_channel_id(temporary_channel_id, index)), + SubChannelState::Confirmed(s) => { + Some(s.get_dlc_channel_id(temporary_channel_id, index)) + } + SubChannelState::Signed(s) | SubChannelState::Finalized(s) => { + Some(s.get_dlc_channel_id(temporary_channel_id, index)) + } + SubChannelState::Closing(c) => Some( + c.signed_sub_channel + .get_dlc_channel_id(temporary_channel_id, index), + ), + SubChannelState::CloseOffered(c) => Some( + c.signed_subchannel + .get_dlc_channel_id(temporary_channel_id, index), + ), + SubChannelState::CloseAccepted(c) => Some( + c.signed_subchannel + .get_dlc_channel_id(temporary_channel_id, index), + ), + SubChannelState::CloseConfirmed(c) => Some( + c.signed_subchannel + .get_dlc_channel_id(temporary_channel_id, index), + ), + _ => 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::Finalized(_) => Some(ReestablishFlag::Finalized as u8), + SubChannelState::Signed(_) => Some(ReestablishFlag::Signed as u8), + SubChannelState::CloseOffered(_) => Some(ReestablishFlag::CloseOffered as u8), + SubChannelState::CloseAccepted(_) => Some(ReestablishFlag::CloseAccepted as u8), + SubChannelState::CloseConfirmed(_) => Some(ReestablishFlag::CloseConfirmed as u8), + SubChannelState::OffChainClosed => Some(ReestablishFlag::OffChainClosed as u8), + _ => None, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +/// Represents the state of a [`SubChannel`]. +pub enum SubChannelState { + /// The sub channel was offered (sent or received). + Offered(OfferedSubChannel), + /// The sub channel was accepted. + Accepted(AcceptedSubChannel), + /// The sub channel was confirmed. + Confirmed(ConfirmedSubChannel), + /// The sub channel transactions have been signed, awaiting revocation of the previous + /// commitment transaction. + Finalized(SignedSubChannel), + /// The sub channel transactions have been signed and the previous commitment transaction + /// revoked. + Signed(SignedSubChannel), + /// The sub channel is closing. + Closing(ClosingSubChannel), + /// The sub channel has been closed on chain by the local party. + OnChainClosed, + /// The sub channel has been closed on chain by the remote party. + CounterOnChainClosed, + /// An offer to collaboratively close the sub channel has been made. + CloseOffered(CloseOfferedSubChannel), + /// An offer to collaboratively close the sub channel was accepted. + CloseAccepted(CloseAcceptedSubChannel), + /// An offer to collaboratively close the sub channel was confirmed. + CloseConfirmed(CloseConfirmedSubChannel), + /// The sub channel was closed off chain (reverted to a regular LN channel). + OffChainClosed, + /// The sub channel was closed by broadcasting a punishment transaction. + ClosedPunished(Txid), + /// An offer to establish a sub channel was rejected. + Rejected, +} + +impl Display for SubChannelState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SubChannelState::Offered(_) => write!(f, "Offered"), + SubChannelState::Accepted(_) => write!(f, "Accepted"), + SubChannelState::Confirmed(_) => write!(f, "Confirmed"), + SubChannelState::Finalized(_) => write!(f, "Finalized"), + SubChannelState::Signed(_) => write!(f, "Signed"), + SubChannelState::Closing(_) => write!(f, "Closing"), + SubChannelState::OnChainClosed => write!(f, "OnChainClosed"), + SubChannelState::CounterOnChainClosed => write!(f, "CounterOnChainClosed"), + SubChannelState::CloseOffered(_) => write!(f, "CloseOffered"), + SubChannelState::CloseAccepted(_) => write!(f, "CloseAccepted"), + SubChannelState::CloseConfirmed(_) => write!(f, "CloseConfirmed"), + SubChannelState::OffChainClosed => write!(f, "OffChainClosed"), + SubChannelState::ClosedPunished(_) => write!(f, "ClosedPunished"), + SubChannelState::Rejected => write!(f, "Rejected"), + } + } +} + +/// 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, + Finalized = 4, + Signed = 5, + CloseOffered = 6, + CloseAccepted = 7, + CloseConfirmed = 8, + OffChainClosed = 9, +} + +#[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, 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. + pub offer_per_split_point: PublicKey, + /// The current per split point of the accept party. + pub accept_per_split_point: PublicKey, + /// Information about the split transaction for the sub channel. + 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, + /// Commitment transactions of the previous Lightning channel state to broadcast in + /// order to force close the Lightning channel (in the `Accepted` state, the funding outpoint + /// has already been updated but we don't have the signatures required to force close the split + /// channel yet, so we need to keep the previous commitment transaction(s) to be able to force + /// close). + pub commitment_transactions: Vec, +} + +#[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, + /// The original funding outpoint + pub funding_outpoint: lightning::chain::transaction::OutPoint, +} + +impl From<&ChannelDetails> for LnRollBackInfo { + fn from(value: &ChannelDetails) -> Self { + Self { + channel_value_satoshis: value.channel_value_satoshis, + value_to_self_msat: value.balance_msat, + funding_outpoint: value + .funding_txo + .expect("to have a defined funding outpoint"), + } + } +} + +impl AcceptedSubChannel { + fn get_dlc_channel_id(&self, temporary_channel_id: ChannelId, channel_idx: u8) -> ChannelId { + crate::utils::compute_id( + self.split_tx.transaction.txid(), + channel_idx as u16 + 1, + &temporary_channel_id, + ) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +/// Information about a sub channel offered by the local party whose transactions have been signed, +/// but whose previous commitment transaction has not been revoked yet. +pub struct ConfirmedSubChannel { + /// The current per split point of the local party. + pub own_per_split_point: PublicKey, + /// The current per split point of the remote party. + pub counter_per_split_point: PublicKey, + /// Adaptor signature of the local party for the split transaction. + pub own_split_adaptor_signature: EcdsaAdaptorSignature, + /// Information about the split transaction for the sub channel. + pub split_tx: SplitTx, + /// Glue transaction that bridges the split transaction to the Lightning sub channel. + pub ln_glue_transaction: Transaction, + /// Signature of the remote party for the glue transaction. + pub counter_glue_signature: Signature, + /// The secret to revoke the previous commitment transaction of the LN channel. + pub prev_commitment_secret: SecretKey, + /// The image of the next commitment point to be used to build a commitment transaction. + pub next_per_commitment_point: PublicKey, + /// Information used to facilitate the rollback of a channel split. + pub ln_rollback: LnRollBackInfo, + /// Commitment transactions of the previous Lightning channel state to broadcast in + /// order to force close the Lightning channel (in the `Confirmed` state, the funding outpoint + /// has already been updated but we don't have the signatures required to force close the split + /// channel yet, so we need to keep the previous commitment transaction(s) to be able to force + /// close). + pub commitment_transactions: Vec, +} + +impl ConfirmedSubChannel { + fn get_dlc_channel_id(&self, temporary_channel_id: ChannelId, channel_idx: u8) -> ChannelId { + crate::utils::compute_id( + self.split_tx.transaction.txid(), + channel_idx as u16 + 1, + &temporary_channel_id, + ) + } +} + +#[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. + pub own_per_split_point: PublicKey, + /// The current per split point of the remote party. + pub counter_per_split_point: PublicKey, + /// Adaptor signature of the local party for the split transaction. + pub own_split_adaptor_signature: EcdsaAdaptorSignature, + /// Adaptor signature of the remote party for the split transaction. + pub counter_split_adaptor_signature: EcdsaAdaptorSignature, + /// Information about the split transaction for the sub channel. + pub split_tx: SplitTx, + /// Glue transaction that bridges the split transaction to the Lightning sub channel. + 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 { + fn get_dlc_channel_id(&self, temporary_channel_id: ChannelId, channel_idx: u8) -> ChannelId { + crate::utils::compute_id( + self.split_tx.transaction.txid(), + channel_idx as u16 + 1, + &temporary_channel_id, + ) + } +} + +#[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. + pub signed_subchannel: SignedSubChannel, + /// The proposed balance of the offer party for the DLC sub channel. + pub offer_balance: u64, + /// The proposed balance of the accpet party for the DLC sub channel. + pub accept_balance: u64, + /// Indicates if the local party is the one who made the offer. + pub is_offer: bool, +} + +#[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. + pub signed_subchannel: SignedSubChannel, + /// The balance of the local party for the DLC sub channel. + pub own_balance: u64, + /// The balance of the remote party for the DLC sub channel. + pub counter_balance: u64, + /// Rollback information about the split channel + pub ln_rollback: LnRollBackInfo, + /// Commitment transactions of the Lightning channel in the previous split state to use in + /// order to force close the Lightning channel (in the `CloseAccepted` state, the funding outpoint + /// has already been updated to remove the split transaction but we don't have the signature + /// for the new commitment transaction yet so we need to keep the commitment transaction from + /// the previous state to be able to force close). + pub commitment_transactions: Vec, +} + +#[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. + pub signed_subchannel: SignedSubChannel, + /// The balance of the local party for the DLC sub channel. + pub own_balance: u64, + /// The balance of the remote party for the DLC sub channel. + pub counter_balance: u64, + /// Rollback information about the split channel + pub ln_rollback: LnRollBackInfo, + /// Whether to check for LN secret (to deal with reestblishments) + pub check_ln_secret: bool, + /// Commitment transactions of the Lightning channel in the previous split state to use in + /// order to force close the Lightning channel (in the `CloseConfirmed` state, the funding outpoint + /// has already been updated to remove the split transaction but we don't have the signature + /// for the new commitment transaction yet so we need to keep the commitment transaction from + /// the previous state to be able to force close). + pub commitment_transactions: Vec, +} + +/// Information about a sub channel that is in the process of being unilateraly closed. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ClosingSubChannel { + /// The signed sub channel that is being closed. + pub signed_sub_channel: SignedSubChannel, + /// Whether the local party initiated the closing. + pub is_initiator: bool, + /// Commitment transactions to use to close the lightning side of the split channel for cases + /// where the LDK `ChannelManager` has been updated to a state which would prevent it to + /// properly force close, like when we are force closing during establishment or off chain + /// closing of a split channel. + pub commitment_transactions: Option>, +} + +/// Provides the ability to access and update Lightning Network channels. +pub trait LNChannelManager: ChannelMessageHandler +where + SP: lightning::sign::ChannelSigner, +{ + /// Returns the details of the channel with given `channel_id` if found. + fn get_channel_details(&self, channel_id: &ChannelId) -> Option; + /// Enable executing the provided callback while holding the lock of the channel with provided + /// id, making sure that the channel is in a useable state and that a connection is established + /// with the peer. + fn with_useable_channel_lock( + &self, + channel_id: &ChannelId, + counter_party_node_id: &PublicKey, + commit_tx_number: Option, + cb: F, + ) -> Result + where + F: FnOnce(&mut ChannelLock) -> Result; + /// Enable executing the provided callback while holding the lock of the channel without + /// checking the channel state or peer connection status. + fn with_channel_lock_no_check( + &self, + channel_id: &ChannelId, + counter_party_node_id: &PublicKey, + cb: F, + ) -> Result + where + F: FnOnce(&mut ChannelLock) -> Result; + /// Updates the funding output for the channel and returns the [`CommitmentSigned`] message + /// with signatures for the updated commitment transaction and HTLCs. + fn get_updated_funding_outpoint_commitment_signed( + &self, + channel_lock: &mut ChannelLock, + funding_outpoint: &OutPoint, + channel_value_satoshis: u64, + value_to_self_msat: u64, + ) -> Result<(CommitmentSigned, u64), APIError>; + /// Provides commitment transaction and HTLCs signatures and returns a [`RevokeAndACK`] + /// message. + fn on_commitment_signed_get_raa( + &self, + channel_lock: &mut ChannelLock, + commitment_signature: &Signature, + htlc_signatures: &[Signature], + ) -> Result; + + /// Provides and verify a [`RevokeAndACK`] message. + fn revoke_and_ack( + &self, + channel_lock: &mut ChannelLock, + revoke_and_ack: &RevokeAndACK, + ) -> Result<(), APIError>; + + /// Force close the channel with given `channel_id` and `counter_party_node_id`. + fn force_close_channel( + &self, + channel_id: &[u8; 32], + counter_party_node_id: &PublicKey, + ) -> Result<(), Error>; + + /// Set the funding outpoint to the given one and sets the channel values to the given + /// ones. + fn set_funding_outpoint( + &self, + channel_lock: &mut ChannelLock, + funding_outpoint: &lightning::chain::transaction::OutPoint, + channel_value_satoshis: u64, + value_to_self_msat: u64, + ); +} + +/// Provides methods to interact with a `ChainMonitor` for a Lightning channel (in particular the +/// one from LDK). +pub trait LNChainMonitor { + /// Gets the latest commitment transactions and HTLC transactions that can be used to close the + /// channel. + fn get_latest_holder_commitment_txn( + &self, + funding_txo: &lightning::chain::transaction::OutPoint, + ) -> Result, Error>; + + /// Updates the funding transaction output of the channel monitor associated with the channel + /// specified by `old_funding_txo`. + fn update_channel_funding_txo( + &self, + old_funding_txo: &lightning::chain::transaction::OutPoint, + new_funding_txo: &lightning::chain::transaction::OutPoint, + channel_value_satoshis: u64, + ) -> Result<(), Error>; +} + +impl + LNChannelManager<::Signer> + for ChannelManager +where + M::Target: lightning::chain::Watch<::Signer>, + T::Target: BroadcasterInterface, + ES::Target: EntropySource, + NS::Target: NodeSigner, + K::Target: SignerProvider, + F::Target: FeeEstimator, + R::Target: Router, + L::Target: Logger, +{ + fn get_channel_details(&self, channel_id: &ChannelId) -> Option { + let channel_details = self.list_channels(); + let res = channel_details + .iter() + .find(|x| &x.channel_id == channel_id)?; + Some(res.clone()) + } + + fn get_updated_funding_outpoint_commitment_signed( + &self, + channel_lock: &mut ChannelLock<::Signer>, + funding_outpoint: &OutPoint, + channel_value_satoshis: u64, + value_to_self_msat: u64, + ) -> Result<(CommitmentSigned, u64), APIError> { + self.get_updated_funding_outpoint_commitment_signed( + channel_lock, + &lightning::chain::transaction::OutPoint { + txid: funding_outpoint.txid, + index: funding_outpoint.vout as u16, + }, + channel_value_satoshis, + value_to_self_msat, + ) + } + + fn on_commitment_signed_get_raa( + &self, + channel_lock: &mut ChannelLock<::Signer>, + commitment_signature: &Signature, + htlc_signatures: &[Signature], + ) -> Result { + self.on_commitment_signed_get_raa(channel_lock, commitment_signature, htlc_signatures) + } + + fn revoke_and_ack( + &self, + channel_lock: &mut ChannelLock<::Signer>, + revoke_and_ack: &RevokeAndACK, + ) -> Result<(), APIError> { + self.revoke_and_ack_commitment(channel_lock, revoke_and_ack) + } + + fn force_close_channel( + &self, + channel_id: &[u8; 32], + counter_party_node_id: &PublicKey, + ) -> Result<(), Error> { + self.force_close_broadcasting_latest_txn(channel_id, counter_party_node_id) + .map_err(|e| Error::InvalidParameters(format!("{e:?}"))) + } + + fn set_funding_outpoint( + &self, + channel_lock: &mut ChannelLock<::Signer>, + funding_outpoint: &lightning::chain::transaction::OutPoint, + channel_value_satoshis: u64, + value_to_self_msat: u64, + ) { + self.set_funding_outpoint( + channel_lock, + funding_outpoint, + channel_value_satoshis, + value_to_self_msat, + ); + } + + fn with_useable_channel_lock( + &self, + channel_id: &ChannelId, + counter_party_node_id: &PublicKey, + commit_tx_number: Option, + cb: C, + ) -> Result + where + C: FnOnce( + &mut ChannelLock<<::Target as SignerProvider>::Signer>, + ) -> Result, + { + self.with_useable_channel_lock(channel_id, counter_party_node_id, commit_tx_number, cb) + } + + fn with_channel_lock_no_check( + &self, + channel_id: &ChannelId, + counter_party_node_id: &PublicKey, + cb: C, + ) -> Result + where + C: FnOnce( + &mut ChannelLock<<::Target as SignerProvider>::Signer>, + ) -> Result, + { + self.with_channel_lock_no_check(channel_id, counter_party_node_id, cb) + } +} + +impl< + ChannelSigner: WriteableEcdsaChannelSigner, + C: Deref, + T: Deref, + F: Deref, + L: Deref, + P: Deref, + > LNChainMonitor for ChainMonitor +where + C::Target: lightning::chain::Filter, + T::Target: BroadcasterInterface, + F::Target: FeeEstimator, + L::Target: Logger, + P::Target: lightning::chain::chainmonitor::Persist, +{ + fn get_latest_holder_commitment_txn( + &self, + funding_txo: &lightning::chain::transaction::OutPoint, + ) -> Result, Error> { + self.get_latest_holder_commitment_txn(funding_txo) + .map_err(|e| { + Error::InvalidParameters(format!("Could not get channel monitor: {:?}", e)) + }) + } + + fn update_channel_funding_txo( + &self, + old_funding_txo: &lightning::chain::transaction::OutPoint, + new_funding_txo: &lightning::chain::transaction::OutPoint, + channel_value_satoshis: u64, + ) -> Result<(), Error> { + match Watch::update_channel_funding_txo( + self, + *old_funding_txo, + *new_funding_txo, + channel_value_satoshis, + ) { + lightning::chain::ChannelMonitorUpdateStatus::Completed => Ok(()), + s => Err(Error::InvalidState(format!( + "Unexpected channel monitor status {:?}", + s + ))), + } + } +} + +/// Generate a temporary channel id for a DLC channel based on the LN channel id, the update index of the +/// split transaction and the index of the DLC channel within the sub channel. +pub fn generate_temporary_channel_id( + channel_id: ChannelId, + split_update_idx: u64, + channel_index: u8, +) -> ContractId { + let mut data = Vec::with_capacity(65); + data.extend_from_slice(&channel_id); + data.extend_from_slice(&split_update_idx.to_be_bytes()); + data.extend_from_slice(&channel_index.to_be_bytes()); + bitcoin::hashes::sha256::Hash::hash(&data).into_inner() +} + +/// Trait to be implemented by a signing component providing the ability to sign LN/DLC split +/// transactions. It is required that the signer's keys are identical to the ones used for the +/// original Lightning channel. +pub trait LnDlcChannelSigner { + /// Get the signature for the split transaction using the LN channel holder funding secret key. + fn get_holder_split_tx_signature( + &self, + secp: &secp256k1_zkp::Secp256k1, + split_tx: &Transaction, + original_funding_redeemscript: &Script, + original_channel_value_satoshis: u64, + ) -> Result; + + /// Get an adaptor signature for the split transaction using the LN channel holder funding + /// secret key as the signing key, and the remote party publish public key as adaptor key. + fn get_holder_split_tx_adaptor_signature( + &self, + secp: &secp256k1_zkp::Secp256k1, + split_tx: &Transaction, + original_channel_value_satoshis: u64, + original_funding_redeemscript: &Script, + other_publish_key: &PublicKey, + ) -> Result; +} + +/// Generates `Signer` that are able to sign split transaction for LN/DLC channels. +pub trait LnDlcSignerProvider { + /// Derives the private key material backing a `Signer`. + /// + /// To derive a new `Signer`, the same `channel_keys_id` and `channel_value_satoshis` parameter + /// that were provided to generate the LDK `ChannelSigner` shoud be passed, and the + /// implementation should derive the same key values. + fn derive_ln_dlc_channel_signer( + &self, + channel_value_satoshis: u64, + channel_keys_id: [u8; 32], + ) -> Signer; +} diff --git a/dlc-manager/src/subchannel/ser.rs b/dlc-manager/src/subchannel/ser.rs new file mode 100644 index 00000000..0dece5c0 --- /dev/null +++ b/dlc-manager/src/subchannel/ser.rs @@ -0,0 +1,99 @@ +//! Serialization of DLC on Lightning related data structures. +use dlc::channel::sub_channel::SplitTx; +use dlc_messages::ser_impls::{read_ecdsa_adaptor_signature, write_ecdsa_adaptor_signature}; +use lightning::ln::msgs::DecodeError; +use lightning::util::ser::{Readable, Writeable, Writer}; + +use super::{ + AcceptedSubChannel, CloseAcceptedSubChannel, CloseConfirmedSubChannel, CloseOfferedSubChannel, + ClosingSubChannel, ConfirmedSubChannel, LnRollBackInfo, OfferedSubChannel, SignedSubChannel, + SubChannel, SubChannelState, +}; + +impl_dlc_writeable!(SubChannel, { + (channel_id, writeable), + (counter_party, writeable), + (update_idx, writeable), + (state, writeable), + (per_split_seed, option), + (fee_rate_per_vb, writeable), + (own_base_points, writeable), + (counter_base_points, option), + (fund_value_satoshis, writeable), + (original_funding_redeemscript, writeable), + (is_offer, writeable), + (own_fund_pk, writeable), + (counter_fund_pk, writeable), + (counter_party_secrets, writeable), + (channel_keys_id, writeable) +}); + +impl_dlc_writeable_enum!(SubChannelState, + (0, Offered), + (1, Accepted), + (2, Confirmed), + (13, Finalized), + (3, Signed), + (4, Closing), + (5, CloseOffered), + (6, CloseAccepted), + (7, CloseConfirmed), + (8, ClosedPunished) + ;;; + (9, OnChainClosed), + (10, CounterOnChainClosed), + (11, OffChainClosed), + (12, Rejected) +); + +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), (funding_outpoint, writeable) }); + +impl_dlc_writeable!(AcceptedSubChannel, { + (offer_per_split_point, writeable), + (accept_per_split_point, writeable), + (split_tx, {cb_writeable, split_tx::write, split_tx::read}), + (ln_glue_transaction, writeable), + (ln_rollback, writeable), + (commitment_transactions, vec) +}); + +impl_dlc_writeable!(ConfirmedSubChannel, { + (own_per_split_point, writeable), + (counter_per_split_point, writeable), + (own_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), + (ln_rollback, writeable), + (prev_commitment_secret, writeable), + (next_per_commitment_point, writeable), + (commitment_transactions, vec) +}); + +impl_dlc_writeable!(SignedSubChannel, { + (own_per_split_point, writeable), + (counter_per_split_point, writeable), + (own_split_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}), + (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), + (ln_rollback, writeable) +}); + +impl_dlc_writeable!(CloseOfferedSubChannel, { + (signed_subchannel, writeable), + (offer_balance, writeable), + (accept_balance, writeable), + (is_offer, writeable) +}); + +impl_dlc_writeable!(CloseAcceptedSubChannel, { (signed_subchannel, writeable), (own_balance, writeable), (counter_balance, writeable), (ln_rollback, writeable), (commitment_transactions, vec) }); + +impl_dlc_writeable!(CloseConfirmedSubChannel, { (signed_subchannel, writeable), (own_balance, writeable), (counter_balance, writeable), (ln_rollback, writeable), (check_ln_secret, writeable), (commitment_transactions, vec) }); + +impl_dlc_writeable!(ClosingSubChannel, { (signed_sub_channel, writeable), (is_initiator, writeable), (commitment_transactions, {option_cb, dlc_messages::ser_impls::write_vec, dlc_messages::ser_impls::read_vec}) }); diff --git a/dlc-manager/src/utils.rs b/dlc-manager/src/utils.rs index 9f9d5ff0..654bac53 100644 --- a/dlc-manager/src/utils.rs +++ b/dlc-manager/src/utils.rs @@ -21,6 +21,36 @@ use crate::{ const APPROXIMATE_CET_VBYTES: u64 = 190; const APPROXIMATE_CLOSING_VBYTES: u64 = 168; +macro_rules! get_object_in_state { + ($manager: expr, $id: expr, $state: ident, $peer_id: expr, $object_type: ident, $get_call: ident) => {{ + let object = $manager.get_store().$get_call($id)?; + match object { + Some(c) => match $peer_id as Option { + Some(p) if c.get_counter_party_id() != p => Err(Error::InvalidParameters(format!( + "Peer {:02x?} is not involved with {} {:02x?}.", + $peer_id, + stringify!($object_type), + $id + ))), + _ => match c { + $object_type::$state(s) => Ok(s), + _ => Err(Error::InvalidState(format!( + "Invalid state {:?} expected {}.", + c, + stringify!($state), + ))), + }, + }, + None => Err(Error::InvalidParameters(format!( + "Unknown {} id.", + stringify!($object_type) + ))), + } + }}; +} + +pub(crate) use get_object_in_state; + pub fn get_common_fee(fee_rate: u64) -> u64 { (APPROXIMATE_CET_VBYTES + APPROXIMATE_CLOSING_VBYTES) * fee_rate } @@ -71,6 +101,7 @@ pub(crate) fn get_party_params( fee_rate: u64, wallet: &W, blockchain: &B, + needs_utxo: bool, ) -> Result<(PartyParams, SecretKey, Vec), Error> where W::Target: Wallet, @@ -86,35 +117,37 @@ where let change_spk = change_addr.script_pubkey(); let change_serial_id = get_new_serial_id(); - let appr_required_amount = own_collateral + get_half_common_fee(fee_rate); - let utxos = wallet.get_utxos_for_amount(appr_required_amount, Some(fee_rate), true)?; - let mut funding_inputs_info: Vec = Vec::new(); let mut funding_tx_info: Vec = Vec::new(); let mut total_input = 0; - for utxo in utxos { - let prev_tx = blockchain.get_transaction(&utxo.outpoint.txid)?; - let mut writer = Vec::new(); - prev_tx.consensus_encode(&mut writer)?; - let prev_tx_vout = utxo.outpoint.vout; - let sequence = 0xffffffff; - // TODO(tibo): this assumes P2WPKH with low R - let max_witness_len = 107; - let funding_input = FundingInput { - input_serial_id: get_new_serial_id(), - prev_tx: writer, - prev_tx_vout, - sequence, - max_witness_len, - redeem_script: utxo.redeem_script, - }; - total_input += prev_tx.output[prev_tx_vout as usize].value; - funding_tx_info.push((&funding_input).into()); - let funding_input_info = FundingInputInfo { - funding_input, - address: Some(utxo.address.clone()), - }; - funding_inputs_info.push(funding_input_info); + + if needs_utxo { + let appr_required_amount = own_collateral + get_half_common_fee(fee_rate); + let utxos = wallet.get_utxos_for_amount(appr_required_amount, Some(fee_rate), true)?; + for utxo in utxos { + let prev_tx = blockchain.get_transaction(&utxo.outpoint.txid)?; + let mut writer = Vec::new(); + prev_tx.consensus_encode(&mut writer)?; + let prev_tx_vout = utxo.outpoint.vout; + let sequence = 0xffffffff; + // TODO(tibo): this assumes P2WPKH with low R + let max_witness_len = 107; + let funding_input = FundingInput { + input_serial_id: get_new_serial_id(), + prev_tx: writer, + prev_tx_vout, + sequence, + max_witness_len, + redeem_script: utxo.redeem_script, + }; + total_input += prev_tx.output[prev_tx_vout as usize].value; + funding_tx_info.push((&funding_input).into()); + let funding_input_info = FundingInputInfo { + funding_input, + address: Some(utxo.address.clone()), + }; + funding_inputs_info.push(funding_input_info); + } } let party_params = PartyParams { diff --git a/dlc-manager/tests/channel_execution_tests.rs b/dlc-manager/tests/channel_execution_tests.rs index 5d69bc8b..aa9a6973 100644 --- a/dlc-manager/tests/channel_execution_tests.rs +++ b/dlc-manager/tests/channel_execution_tests.rs @@ -6,13 +6,9 @@ use bitcoin_test_utils::rpc_helpers::init_clients; use bitcoincore_rpc::RpcApi; use dlc_manager::contract::contract_input::ContractInput; use dlc_manager::manager::Manager; -use dlc_manager::{ - channel::{signed_channel::SignedChannelState, Channel}, - contract::Contract, - Blockchain, Oracle, Storage, Wallet, -}; +use dlc_manager::{channel::Channel, contract::Contract, Blockchain, Oracle, Storage, Wallet}; use dlc_manager::{ChannelId, ContractId}; -use dlc_messages::Message; +use dlc_messages::{ChannelMessage, Message}; use electrs_blockchain_provider::ElectrsBlockchainProvider; use lightning::util::ser::Writeable; use mocks::memory_storage_provider::MemoryStorage; @@ -26,11 +22,12 @@ use test_utils::{get_enum_test_params, TestParams}; use std::sync::mpsc::{Receiver, Sender}; use std::thread; +use std::time::Duration; use std::{ collections::HashMap, sync::{ atomic::{AtomicBool, Ordering}, - mpsc::channel, + mpsc::{channel, sync_channel}, Arc, Mutex, }, }; @@ -72,6 +69,16 @@ fn alter_adaptor_sig(input: &EcdsaAdaptorSignature) -> EcdsaAdaptorSignature { EcdsaAdaptorSignature::from_slice(©).expect("to be able to create an adaptor signature") } +/// We wrap updating the state of the chain monitor and calling the +/// `Manager::periodic_check` because the latter will only be aware of +/// newly confirmed transactions if the former processes new blocks. +fn periodic_check(dlc_party: DlcParty) { + let dlc_manager = dlc_party.lock().unwrap(); + + dlc_manager.periodic_chain_monitor().unwrap(); + dlc_manager.periodic_check().unwrap(); +} + #[derive(Eq, PartialEq, Clone)] enum TestPath { Close, @@ -91,6 +98,7 @@ enum TestPath { RenewOfferTimeout, RenewAcceptTimeout, RenewConfirmTimeout, + RenewFinalizeTimeout, RenewReject, RenewRace, RenewEstablishedClose, @@ -237,6 +245,15 @@ fn channel_renew_confirm_timeout_test() { ); } +#[test] +#[ignore] +fn channel_renew_finalize_timeout_test() { + channel_execution_test( + get_enum_test_params(1, 1, None), + TestPath::RenewFinalizeTimeout, + ); +} + #[test] #[ignore] fn channel_renew_reject_test() { @@ -253,9 +270,8 @@ fn channel_execution_test(test_params: TestParams, path: TestPath) { env_logger::init(); let (alice_send, bob_receive) = channel::>(); let (bob_send, alice_receive) = channel::>(); - let (sync_send, sync_receive) = channel::<()>(); - let alice_sync_send = sync_send.clone(); - let bob_sync_send = sync_send; + let (alice_sync_send, alice_sync_receive) = sync_channel::<()>(0); + let (bob_sync_send, bob_sync_receive) = sync_channel::<()>(0); let (_, _, sink_rpc) = init_clients(); let mut alice_oracles = HashMap::with_capacity(1); @@ -380,22 +396,27 @@ fn channel_execution_test(test_params: TestParams, path: TestPath) { let path_copy = path.clone(); let msg_filter = move |msg| { if let TestPath::SettleAcceptTimeout = path_copy { - if let Message::SettleConfirm(_) = msg { + if let Message::Channel(ChannelMessage::SettleConfirm(_)) = msg { return None; } } if let TestPath::SettleConfirmTimeout = path_copy { - if let Message::SettleFinalize(_) = msg { + if let Message::Channel(ChannelMessage::SettleFinalize(_)) = msg { return None; } } if let TestPath::RenewAcceptTimeout = path_copy { - if let Message::RenewConfirm(_) = msg { + if let Message::Channel(ChannelMessage::RenewConfirm(_)) = msg { return None; } } if let TestPath::RenewConfirmTimeout = path_copy { - if let Message::RenewFinalize(_) = msg { + if let Message::Channel(ChannelMessage::RenewFinalize(_)) = msg { + return None; + } + } + if let TestPath::RenewFinalizeTimeout = path_copy { + if let Message::Channel(ChannelMessage::RenewRevoke(_)) = msg { return None; } } @@ -405,12 +426,12 @@ fn channel_execution_test(test_params: TestParams, path: TestPath) { let msg_filter_copy = msg_filter.clone(); let path_copy = path.clone(); let alter_sign = move |msg| match msg { - Message::SignChannel(mut sign_channel) => { + Message::Channel(ChannelMessage::Sign(mut sign_channel)) => { if path_copy == TestPath::BadSignBufferAdaptorSignature { sign_channel.buffer_adaptor_signature = alter_adaptor_sig(&sign_channel.buffer_adaptor_signature); } - Some(Message::SignChannel(sign_channel)) + Some(Message::Channel(ChannelMessage::Sign(sign_channel))) } _ => msg_filter_copy(msg), }; @@ -448,12 +469,12 @@ fn channel_execution_test(test_params: TestParams, path: TestPath) { let temporary_channel_id = offer_msg.temporary_channel_id; bob_send - .send(Some(Message::OfferChannel(offer_msg))) + .send(Some(Message::Channel(ChannelMessage::Offer(offer_msg)))) .unwrap(); assert_channel_state!(bob_manager_send, temporary_channel_id, Offered); - sync_receive.recv().expect("Error synchronizing"); + alice_sync_receive.recv().expect("Error synchronizing"); assert_channel_state!(alice_manager_send, temporary_channel_id, Offered); @@ -470,31 +491,31 @@ fn channel_execution_test(test_params: TestParams, path: TestPath) { alter_adaptor_sig(&accept_msg.buffer_adaptor_signature); bob_expect_error.store(true, Ordering::Relaxed); alice_send - .send(Some(Message::AcceptChannel(accept_msg))) + .send(Some(Message::Channel(ChannelMessage::Accept(accept_msg)))) .unwrap(); - sync_receive.recv().expect("Error synchronizing"); + bob_sync_receive.recv().expect("Error synchronizing"); assert_channel_state!(bob_manager_send, temporary_channel_id, FailedAccept); } TestPath::BadSignBufferAdaptorSignature => { alice_expect_error.store(true, Ordering::Relaxed); alice_send - .send(Some(Message::AcceptChannel(accept_msg))) + .send(Some(Message::Channel(ChannelMessage::Accept(accept_msg)))) .unwrap(); // Bob receives accept message - sync_receive.recv().expect("Error synchronizing"); + bob_sync_receive.recv().expect("Error synchronizing"); // Alice receives sign message - sync_receive.recv().expect("Error synchronizing"); + alice_sync_receive.recv().expect("Error synchronizing"); assert_channel_state!(alice_manager_send, channel_id, FailedSign); } _ => { alice_send - .send(Some(Message::AcceptChannel(accept_msg))) + .send(Some(Message::Channel(ChannelMessage::Accept(accept_msg)))) .unwrap(); - sync_receive.recv().expect("Error synchronizing"); + bob_sync_receive.recv().expect("Error synchronizing"); assert_channel_state!(bob_manager_send, channel_id, Signed, Established); - sync_receive.recv().expect("Error synchronizing"); + alice_sync_receive.recv().expect("Error synchronizing"); assert_channel_state!(alice_manager_send, channel_id, Signed, Established); @@ -502,27 +523,34 @@ fn channel_execution_test(test_params: TestParams, path: TestPath) { mocks::mock_time::set_time((EVENT_MATURITY as u64) + 1); - alice_manager_send - .lock() - .unwrap() - .periodic_check() - .expect("to be able to do the periodic check"); + periodic_check(alice_manager_send.clone()); - bob_manager_send - .lock() - .unwrap() - .periodic_check() - .expect("to be able to do the periodic check"); + periodic_check(bob_manager_send.clone()); assert_contract_state!(alice_manager_send, contract_id, Confirmed); assert_contract_state!(bob_manager_send, contract_id, Confirmed); // Select the first one to close or refund randomly - let (first, first_send, second, second_send) = if thread_rng().next_u32() % 2 == 0 { - (alice_manager_send, &alice_send, bob_manager_send, &bob_send) - } else { - (bob_manager_send, &bob_send, alice_manager_send, &alice_send) - }; + let (first, first_send, first_receive, second, second_send, second_receive) = + if thread_rng().next_u32() % 2 == 0 { + ( + alice_manager_send, + &alice_send, + &alice_sync_receive, + bob_manager_send, + &bob_send, + &bob_sync_receive, + ) + } else { + ( + bob_manager_send, + &bob_send, + &bob_sync_receive, + alice_manager_send, + &alice_send, + &alice_sync_receive, + ) + }; match path { TestPath::Close => { @@ -534,7 +562,7 @@ fn channel_execution_test(test_params: TestParams, path: TestPath) { first_send, second, channel_id, - &sync_receive, + second_receive, &generate_blocks, ); } @@ -544,10 +572,11 @@ fn channel_execution_test(test_params: TestParams, path: TestPath) { settle_timeout( first, first_send, + first_receive, second, second_send, + second_receive, channel_id, - &sync_receive, path, ); } @@ -555,42 +584,59 @@ fn channel_execution_test(test_params: TestParams, path: TestPath) { settle_reject( first, first_send, + first_receive, second, second_send, + second_receive, channel_id, - &sync_receive, ); } TestPath::SettleRace => { settle_race( first, first_send, + first_receive, second, second_send, + second_receive, channel_id, - &sync_receive, ); } _ => { // Shuffle positions - let (first, first_send, second, second_send) = + let (first, first_send, first_receive, second, second_send, second_receive) = if thread_rng().next_u32() % 2 == 0 { - (first, first_send, second, second_send) + ( + first, + first_send, + first_receive, + second, + second_send, + second_receive, + ) } else { - (second, second_send, first, first_send) + ( + second, + second_send, + second_receive, + first, + first_send, + first_receive, + ) }; - first.lock().unwrap().get_mut_store().save(); + first.lock().unwrap().get_store().save(); if let TestPath::RenewEstablishedClose = path { } else { settle_channel( first.clone(), first_send, + first_receive, second.clone(), second_send, + second_receive, channel_id, - &sync_receive, ); } @@ -613,26 +659,30 @@ fn channel_execution_test(test_params: TestParams, path: TestPath) { } TestPath::RenewOfferTimeout | TestPath::RenewAcceptTimeout - | TestPath::RenewConfirmTimeout => { + | TestPath::RenewConfirmTimeout + | TestPath::RenewFinalizeTimeout => { renew_timeout( first, first_send, + first_receive, second, second_send, + second_receive, channel_id, - &sync_receive, &test_params.contract_input, path, + &generate_blocks, ); } TestPath::RenewReject => { renew_reject( first, first_send, + first_receive, second, second_send, + second_receive, channel_id, - &sync_receive, &test_params.contract_input, ); } @@ -640,17 +690,18 @@ fn channel_execution_test(test_params: TestParams, path: TestPath) { renew_race( first, first_send, + first_receive, second, second_send, + second_receive, channel_id, - &sync_receive, &test_params.contract_input, ); } TestPath::RenewedClose | TestPath::SettleCheat | TestPath::RenewEstablishedClose => { - first.lock().unwrap().get_mut_store().save(); + first.lock().unwrap().get_store().save(); let check_prev_contract_close = if let TestPath::RenewEstablishedClose = path { @@ -662,10 +713,11 @@ fn channel_execution_test(test_params: TestParams, path: TestPath) { renew_channel( first.clone(), first_send, + first_receive, second.clone(), second_send, + second_receive, channel_id, - &sync_receive, &test_params.contract_input, check_prev_contract_close, ); @@ -685,10 +737,11 @@ fn channel_execution_test(test_params: TestParams, path: TestPath) { renew_channel( first.clone(), first_send, + first_receive, second.clone(), second_send, + second_receive, channel_id, - &sync_receive, &test_params.contract_input, false, ); @@ -696,10 +749,11 @@ fn channel_execution_test(test_params: TestParams, path: TestPath) { settle_channel( first, first_send, + first_receive, second, second_send, + second_receive, channel_id, - &sync_receive, ); } _ => (), @@ -733,51 +787,40 @@ fn close_established_channel( let contract_id = get_established_channel_contract_id(&first, &channel_id); - first - .lock() - .unwrap() - .periodic_check() - .expect("to be able to do the periodic check"); + periodic_check(first.clone()); let wait = dlc_manager::manager::CET_NSEQUENCE; generate_blocks(10); - first - .lock() - .unwrap() - .periodic_check() - .expect("to be able to do the periodic check"); + periodic_check(second.clone()); + + assert_channel_state!(second, channel_id, Signed, Closing); + + periodic_check(first.clone()); // Should not have changed state before the CET is spendable. assert_channel_state!(first, channel_id, Signed, Closing); generate_blocks(wait as u64 - 9); - first - .lock() - .unwrap() - .periodic_check() - .expect("to be able to do the periodic check"); + periodic_check(first.clone()); - // - assert_channel_state!(first, channel_id, Signed, Closed); + assert_channel_state!(first, channel_id, Closed); assert_contract_state!(first, contract_id, PreClosed); - second - .lock() - .unwrap() - .periodic_check() - .expect("to be able to do the periodic check"); + generate_blocks(1); + + periodic_check(second.clone()); - assert_channel_state!(second, channel_id, Signed, CounterClosed); + assert_channel_state!(second, channel_id, CounterClosed); assert_contract_state!(second, contract_id, PreClosed); - generate_blocks(6); + generate_blocks(5); - first.lock().unwrap().periodic_check().unwrap(); - second.lock().unwrap().periodic_check().unwrap(); + periodic_check(first.clone()); + periodic_check(second.clone()); assert_contract_state!(first, contract_id, Closed); assert_contract_state!(second, contract_id, Closed); @@ -790,7 +833,7 @@ fn cheat_punish( generate_blocks: &F, established: bool, ) { - first.lock().unwrap().get_mut_store().rollback(); + first.lock().unwrap().get_store().rollback(); if established { first @@ -808,36 +851,33 @@ fn cheat_punish( generate_blocks(2); - second - .lock() - .unwrap() - .periodic_check() - .expect("the check to succeed"); + periodic_check(second.clone()); - assert_channel_state!(second, channel_id, Signed, ClosedPunished); + assert_channel_state!(second, channel_id, ClosedPunished); } fn settle_channel( first: DlcParty, first_send: &Sender>, + first_receive: &Receiver<()>, second: DlcParty, second_send: &Sender>, + second_receive: &Receiver<()>, channel_id: ChannelId, - sync_receive: &Receiver<()>, ) { - let contract_id = get_established_channel_contract_id(&first, &channel_id); - let (settle_offer, _) = first .lock() .unwrap() - .settle_offer(&channel_id, 100000000) + .settle_offer(&channel_id, test_utils::ACCEPT_COLLATERAL) .expect("to be able to offer a settlement of the contract."); first_send - .send(Some(Message::SettleOffer(settle_offer))) + .send(Some(Message::Channel(ChannelMessage::SettleOffer( + settle_offer, + )))) .unwrap(); - sync_receive.recv().expect("Error synchronizing"); + second_receive.recv().expect("Error synchronizing"); assert_channel_state!(first, channel_id, Signed, SettledOffered); @@ -850,18 +890,17 @@ fn settle_channel( .expect("to be able to accept a settlement offer"); second_send - .send(Some(Message::SettleAccept(settle_accept))) + .send(Some(Message::Channel(ChannelMessage::SettleAccept( + settle_accept, + )))) .unwrap(); // Process Accept - sync_receive.recv().expect("Error synchronizing"); + first_receive.recv().expect("Error synchronizing"); // Process Confirm - sync_receive.recv().expect("Error synchronizing"); + second_receive.recv().expect("Error synchronizing"); // Process Finalize - sync_receive.recv().expect("Error synchronizing"); - - assert_contract_state!(first, contract_id, Closed); - assert_contract_state!(second, contract_id, Closed); + first_receive.recv().expect("Error synchronizing"); assert_channel_state!(first, channel_id, Signed, Settled); @@ -871,22 +910,25 @@ fn settle_channel( fn settle_reject( first: DlcParty, first_send: &Sender>, + first_receive: &Receiver<()>, second: DlcParty, second_send: &Sender>, + second_receive: &Receiver<()>, channel_id: ChannelId, - sync_receive: &Receiver<()>, ) { let (settle_offer, _) = first .lock() .unwrap() - .settle_offer(&channel_id, 100000000) + .settle_offer(&channel_id, test_utils::ACCEPT_COLLATERAL) .expect("to be able to reject a settlement of the contract."); first_send - .send(Some(Message::SettleOffer(settle_offer))) + .send(Some(Message::Channel(ChannelMessage::SettleOffer( + settle_offer, + )))) .unwrap(); - sync_receive.recv().expect("Error synchronizing"); + second_receive.recv().expect("Error synchronizing"); assert_channel_state!(first, channel_id, Signed, SettledOffered); @@ -899,10 +941,12 @@ fn settle_reject( .expect("to be able to reject a settlement offer"); second_send - .send(Some(Message::Reject(settle_reject))) + .send(Some(Message::Channel(ChannelMessage::Reject( + settle_reject, + )))) .unwrap(); - sync_receive.recv().expect("Error synchronizing"); + first_receive.recv().expect("Error synchronizing"); assert_channel_state!(first, channel_id, Signed, Established); @@ -912,36 +956,49 @@ fn settle_reject( fn settle_race( first: DlcParty, first_send: &Sender>, + first_receive: &Receiver<()>, second: DlcParty, second_send: &Sender>, + second_receive: &Receiver<()>, channel_id: ChannelId, - sync_receive: &Receiver<()>, ) { let (settle_offer, _) = first .lock() .unwrap() - .settle_offer(&channel_id, 100000000) + .settle_offer(&channel_id, test_utils::ACCEPT_COLLATERAL) .expect("to be able to offer a settlement of the contract."); let (settle_offer_2, _) = second .lock() .unwrap() - .settle_offer(&channel_id, 100000000) + .settle_offer(&channel_id, test_utils::ACCEPT_COLLATERAL) .expect("to be able to offer a settlement of the contract."); first_send - .send(Some(Message::SettleOffer(settle_offer))) + .send(Some(Message::Channel(ChannelMessage::SettleOffer( + settle_offer, + )))) .unwrap(); second_send - .send(Some(Message::SettleOffer(settle_offer_2))) + .send(Some(Message::Channel(ChannelMessage::SettleOffer( + settle_offer_2, + )))) .unwrap(); // Process 2 offers + 2 rejects - sync_receive.recv().expect("Error synchronizing"); - sync_receive.recv().expect("Error synchronizing"); - sync_receive.recv().expect("Error synchronizing"); - sync_receive.recv().expect("Error synchronizing"); + first_receive + .recv_timeout(Duration::from_secs(2)) + .expect("Error synchronizing 1"); + second_receive + .recv_timeout(Duration::from_secs(2)) + .expect("Error synchronizing 2"); + first_receive + .recv_timeout(Duration::from_secs(2)) + .expect("Error synchronizing 3"); + second_receive + .recv_timeout(Duration::from_secs(2)) + .expect("Error synchronizing 4"); assert_channel_state!(first, channel_id, Signed, Established); @@ -951,10 +1008,11 @@ fn settle_race( fn renew_channel( first: DlcParty, first_send: &Sender>, + first_receive: &Receiver<()>, second: DlcParty, second_send: &Sender>, + second_receive: &Receiver<()>, channel_id: ChannelId, - sync_receive: &Receiver<()>, contract_input: &ContractInput, check_prev_contract_close: bool, ) { @@ -967,15 +1025,17 @@ fn renew_channel( let (renew_offer, _) = first .lock() .unwrap() - .renew_offer(&channel_id, 100000000, contract_input) + .renew_offer(&channel_id, test_utils::ACCEPT_COLLATERAL, contract_input) .expect("to be able to renew channel contract"); first_send - .send(Some(Message::RenewOffer(renew_offer))) + .send(Some(Message::Channel(ChannelMessage::RenewOffer( + renew_offer, + )))) .expect("to be able to send the renew offer"); // Process Renew Offer - sync_receive.recv().expect("Error synchronizing"); + second_receive.recv().expect("Error synchronizing"); assert_channel_state!(first, channel_id, Signed, RenewOffered); assert_channel_state!(second, channel_id, Signed, RenewOffered); @@ -987,16 +1047,20 @@ fn renew_channel( .expect("to be able to accept the renewal"); second_send - .send(Some(Message::RenewAccept(accept_renew))) + .send(Some(Message::Channel(ChannelMessage::RenewAccept( + accept_renew, + )))) .expect("to be able to send the accept renew"); // Process Renew Accept - sync_receive.recv().expect("Error synchronizing"); + first_receive.recv().expect("Error synchronizing"); assert_channel_state!(first, channel_id, Signed, RenewConfirmed); // Process Renew Confirm - sync_receive.recv().expect("Error synchronizing"); + second_receive.recv().expect("Error synchronizing"); // Process Renew Finalize - sync_receive.recv().expect("Error synchronizing"); + first_receive.recv().expect("Error synchronizing"); + // Process Renew Revoke + second_receive.recv().expect("Error synchronizing"); if let Some(prev_contract_id) = prev_contract_id { assert_contract_state!(first, prev_contract_id, Closed); @@ -1014,24 +1078,27 @@ fn renew_channel( fn renew_reject( first: DlcParty, first_send: &Sender>, + first_receive: &Receiver<()>, second: DlcParty, second_send: &Sender>, + second_receive: &Receiver<()>, channel_id: ChannelId, - sync_receive: &Receiver<()>, contract_input: &ContractInput, ) { let (renew_offer, _) = first .lock() .unwrap() - .renew_offer(&channel_id, 100000000, contract_input) + .renew_offer(&channel_id, test_utils::ACCEPT_COLLATERAL, contract_input) .expect("to be able to renew channel contract"); first_send - .send(Some(Message::RenewOffer(renew_offer))) + .send(Some(Message::Channel(ChannelMessage::RenewOffer( + renew_offer, + )))) .expect("to be able to send the renew offer"); // Process Renew Offer - sync_receive.recv().expect("Error synchronizing"); + second_receive.recv().expect("Error synchronizing"); assert_channel_state!(first, channel_id, Signed, RenewOffered); assert_channel_state!(second, channel_id, Signed, RenewOffered); @@ -1043,11 +1110,11 @@ fn renew_reject( .expect("to be able to reject the renewal"); second_send - .send(Some(Message::Reject(renew_reject))) + .send(Some(Message::Channel(ChannelMessage::Reject(renew_reject)))) .expect("to be able to send the renew reject"); // Process Renew Reject - sync_receive.recv().expect("Error synchronizing"); + first_receive.recv().expect("Error synchronizing"); assert_channel_state!(first, channel_id, Signed, Settled); assert_channel_state!(second, channel_id, Signed, Settled); } @@ -1055,37 +1122,54 @@ fn renew_reject( fn renew_race( first: DlcParty, first_send: &Sender>, + first_receive: &Receiver<()>, second: DlcParty, second_send: &Sender>, + second_receive: &Receiver<()>, channel_id: ChannelId, - sync_receive: &Receiver<()>, contract_input: &ContractInput, ) { let (renew_offer, _) = first .lock() .unwrap() - .renew_offer(&channel_id, 100000000, contract_input) + .renew_offer(&channel_id, test_utils::OFFER_COLLATERAL, contract_input) .expect("to be able to renew channel contract"); + let mut contract_input_2 = contract_input.clone(); + contract_input_2.accept_collateral = contract_input.offer_collateral; + contract_input_2.offer_collateral = contract_input.accept_collateral; + let (renew_offer_2, _) = second .lock() .unwrap() - .renew_offer(&channel_id, 100000000, contract_input) + .renew_offer(&channel_id, test_utils::OFFER_COLLATERAL, &contract_input_2) .expect("to be able to renew channel contract"); first_send - .send(Some(Message::RenewOffer(renew_offer))) + .send(Some(Message::Channel(ChannelMessage::RenewOffer( + renew_offer, + )))) .expect("to be able to send the renew offer"); second_send - .send(Some(Message::RenewOffer(renew_offer_2))) + .send(Some(Message::Channel(ChannelMessage::RenewOffer( + renew_offer_2, + )))) .expect("to be able to send the renew offer"); // Process 2 offers + 2 rejects - sync_receive.recv().expect("Error synchronizing"); - sync_receive.recv().expect("Error synchronizing"); - sync_receive.recv().expect("Error synchronizing"); - sync_receive.recv().expect("Error synchronizing"); + first_receive + .recv_timeout(Duration::from_secs(2)) + .expect("Error synchronizing 1"); + second_receive + .recv_timeout(Duration::from_secs(2)) + .expect("Error synchronizing 2"); + first_receive + .recv_timeout(Duration::from_secs(2)) + .expect("Error synchronizing 3"); + second_receive + .recv_timeout(Duration::from_secs(2)) + .expect("Error synchronizing 4"); assert_channel_state!(first, channel_id, Signed, Settled); assert_channel_state!(second, channel_id, Signed, Settled); @@ -1103,10 +1187,12 @@ fn collaborative_close( let close_offer = first .lock() .unwrap() - .offer_collaborative_close(&channel_id, 100000000) + .offer_collaborative_close(&channel_id, test_utils::ACCEPT_COLLATERAL) .expect("to be able to propose a collaborative close"); first_send - .send(Some(Message::CollaborativeCloseOffer(close_offer))) + .send(Some(Message::Channel( + ChannelMessage::CollaborativeCloseOffer(close_offer), + ))) .expect("to be able to send collaborative close"); sync_receive.recv().expect("Error synchronizing"); @@ -1119,55 +1205,51 @@ fn collaborative_close( .accept_collaborative_close(&channel_id) .expect("to be able to accept a collaborative close"); - assert_channel_state!(second, channel_id, Signed, CollaborativelyClosed); + assert_channel_state!(second, channel_id, CollaborativelyClosed); assert_contract_state!(second, contract_id, Closed); generate_blocks(2); - first - .lock() - .unwrap() - .periodic_check() - .expect("the check to succeed"); + periodic_check(first.clone()); - assert_channel_state!(first, channel_id, Signed, CollaborativelyClosed); + assert_channel_state!(first, channel_id, CollaborativelyClosed); assert_contract_state!(first, contract_id, Closed); } -fn renew_timeout( +fn renew_timeout( first: DlcParty, first_send: &Sender>, + first_receive: &Receiver<()>, second: DlcParty, second_send: &Sender>, + second_receive: &Receiver<()>, channel_id: ChannelId, - sync_receive: &Receiver<()>, contract_input: &ContractInput, path: TestPath, + generate_blocks: &F, ) { { let (renew_offer, _) = first .lock() .unwrap() - .renew_offer(&channel_id, 100000000, contract_input) + .renew_offer(&channel_id, test_utils::ACCEPT_COLLATERAL, contract_input) .expect("to be able to offer a settlement of the contract."); first_send - .send(Some(Message::RenewOffer(renew_offer))) + .send(Some(Message::Channel(ChannelMessage::RenewOffer( + renew_offer, + )))) .unwrap(); - sync_receive.recv().expect("Error synchronizing"); + second_receive.recv().expect("Error synchronizing"); if let TestPath::RenewOfferTimeout = path { mocks::mock_time::set_time( (EVENT_MATURITY as u64) + dlc_manager::manager::PEER_TIMEOUT + 2, ); - first - .lock() - .unwrap() - .periodic_check() - .expect("not to error"); + periodic_check(first.clone()); - assert_channel_state!(first, channel_id, Signed, Closed); + assert_channel_state!(first, channel_id, Closed); } else { let (renew_accept, _) = second .lock() @@ -1176,36 +1258,43 @@ fn renew_timeout( .expect("to be able to accept a settlement offer"); second_send - .send(Some(Message::RenewAccept(renew_accept))) + .send(Some(Message::Channel(ChannelMessage::RenewAccept( + renew_accept, + )))) .unwrap(); // Process Accept - sync_receive.recv().expect("Error synchronizing"); + first_receive.recv().expect("Error synchronizing"); if let TestPath::RenewAcceptTimeout = path { mocks::mock_time::set_time( (EVENT_MATURITY as u64) + dlc_manager::manager::PEER_TIMEOUT + 2, ); - second - .lock() - .unwrap() - .periodic_check() - .expect("not to error"); + periodic_check(second.clone()); - assert_channel_state!(second, channel_id, Signed, Closed); + assert_channel_state!(second, channel_id, Closed); } else if let TestPath::RenewConfirmTimeout = path { // Process Confirm - sync_receive.recv().expect("Error synchronizing"); + second_receive.recv().expect("Error synchronizing"); + mocks::mock_time::set_time( + (EVENT_MATURITY as u64) + dlc_manager::manager::PEER_TIMEOUT + 2, + ); + periodic_check(first.clone()); + + assert_channel_state!(first, channel_id, Closed); + } else if let TestPath::RenewFinalizeTimeout = path { + //Process confirm + second_receive.recv().expect("Error synchronizing"); + // Process Finalize + first_receive.recv().expect("Error synchronizing"); mocks::mock_time::set_time( (EVENT_MATURITY as u64) + dlc_manager::manager::PEER_TIMEOUT + 2, ); - first - .lock() - .unwrap() - .periodic_check() - .expect("not to error"); + periodic_check(second.clone()); + generate_blocks(289); + periodic_check(second.clone()); - assert_channel_state!(first, channel_id, Signed, Closed); + assert_channel_state!(second, channel_id, Closed); } } } @@ -1214,33 +1303,32 @@ fn renew_timeout( fn settle_timeout( first: DlcParty, first_send: &Sender>, + first_receive: &Receiver<()>, second: DlcParty, second_send: &Sender>, + second_receive: &Receiver<()>, channel_id: ChannelId, - sync_receive: &Receiver<()>, path: TestPath, ) { let (settle_offer, _) = first .lock() .unwrap() - .settle_offer(&channel_id, 100000000) + .settle_offer(&channel_id, test_utils::ACCEPT_COLLATERAL) .expect("to be able to offer a settlement of the contract."); first_send - .send(Some(Message::SettleOffer(settle_offer))) + .send(Some(Message::Channel(ChannelMessage::SettleOffer( + settle_offer, + )))) .unwrap(); - sync_receive.recv().expect("Error synchronizing"); + second_receive.recv().expect("Error synchronizing"); if let TestPath::SettleOfferTimeout = path { mocks::mock_time::set_time( (EVENT_MATURITY as u64) + dlc_manager::manager::PEER_TIMEOUT + 2, ); - first - .lock() - .unwrap() - .periodic_check() - .expect("not to error"); + periodic_check(first.clone()); assert_channel_state!(first, channel_id, Signed, Closing); } else { @@ -1251,34 +1339,34 @@ fn settle_timeout( .expect("to be able to accept a settlement offer"); second_send - .send(Some(Message::SettleAccept(settle_accept))) + .send(Some(Message::Channel(ChannelMessage::SettleAccept( + settle_accept, + )))) .unwrap(); // Process Accept - sync_receive.recv().expect("Error synchronizing"); + first_receive.recv().expect("Error synchronizing"); if let TestPath::SettleAcceptTimeout = path { mocks::mock_time::set_time( (EVENT_MATURITY as u64) + dlc_manager::manager::PEER_TIMEOUT + 2, ); + periodic_check(second.clone()); + second .lock() .unwrap() - .periodic_check() - .expect("not to error"); - + .get_store() + .get_channel(&channel_id) + .unwrap(); assert_channel_state!(second, channel_id, Signed, Closing); } else if let TestPath::SettleConfirmTimeout = path { // Process Confirm - sync_receive.recv().expect("Error synchronizing"); + second_receive.recv().expect("Error synchronizing"); mocks::mock_time::set_time( (EVENT_MATURITY as u64) + dlc_manager::manager::PEER_TIMEOUT + 2, ); - first - .lock() - .unwrap() - .periodic_check() - .expect("not to error"); + periodic_check(first.clone()); assert_channel_state!(first, channel_id, Signed, Closing); } diff --git a/dlc-manager/tests/console_logger.rs b/dlc-manager/tests/console_logger.rs new file mode 100644 index 00000000..eb622ab7 --- /dev/null +++ b/dlc-manager/tests/console_logger.rs @@ -0,0 +1,26 @@ +use chrono::Utc; +use lightning::util::logger::{Logger, Record}; + +#[derive(Debug)] +pub(crate) struct ConsoleLogger { + pub name: String, +} + +impl Logger for ConsoleLogger { + fn log(&self, record: &Record) { + let raw_log = record.args.to_string(); + let log = format!( + "From {}: {} {:<5} [{}:{}] {}\n", + // Note that a "real" lightning node almost certainly does *not* want subsecond + // precision for message-receipt information as it makes log entries a target for + // deanonymization attacks. For testing, however, its quite useful. + self.name, + Utc::now().format("%Y-%m-%d %H:%M:%S%.3f"), + record.level.to_string(), + record.module_path, + record.line, + raw_log + ); + println!("{log}"); + } +} diff --git a/dlc-manager/tests/custom_signer.rs b/dlc-manager/tests/custom_signer.rs new file mode 100644 index 00000000..2554a4d6 --- /dev/null +++ b/dlc-manager/tests/custom_signer.rs @@ -0,0 +1,432 @@ +use std::{ + ops::Deref, + sync::{Arc, Mutex}, +}; + +use bitcoin::{Script, Transaction, TxOut}; +use dlc_manager::{ + error::Error, + subchannel::{LnDlcChannelSigner, LnDlcSignerProvider}, +}; +use lightning::{ + ln::{chan_utils::ChannelPublicKeys, msgs::DecodeError, script::ShutdownScript}, + sign::{ + ChannelSigner, EcdsaChannelSigner, EntropySource, InMemorySigner, KeysManager, NodeSigner, + SignerProvider, SpendableOutputDescriptor, WriteableEcdsaChannelSigner, + }, + util::ser::{ReadableArgs, Writeable}, +}; +use secp256k1_zkp::{Secp256k1, SecretKey, Signing}; + +pub struct CustomSigner { + in_memory_signer: Arc>, + // TODO(tibo): this might not be safe. + channel_public_keys: ChannelPublicKeys, +} + +impl CustomSigner { + pub fn new(in_memory_signer: InMemorySigner) -> Self { + Self { + channel_public_keys: in_memory_signer.pubkeys().clone(), + in_memory_signer: Arc::new(Mutex::new(in_memory_signer)), + } + } +} + +impl Clone for CustomSigner { + fn clone(&self) -> Self { + Self { + in_memory_signer: self.in_memory_signer.clone(), + channel_public_keys: self.channel_public_keys.clone(), + } + } +} + +impl ChannelSigner for CustomSigner { + fn get_per_commitment_point( + &self, + idx: u64, + secp_ctx: &Secp256k1, + ) -> secp256k1_zkp::PublicKey { + self.in_memory_signer + .lock() + .unwrap() + .get_per_commitment_point(idx, secp_ctx) + } + + fn release_commitment_secret(&self, idx: u64) -> [u8; 32] { + self.in_memory_signer + .lock() + .unwrap() + .release_commitment_secret(idx) + } + + fn validate_holder_commitment( + &self, + holder_tx: &lightning::ln::chan_utils::HolderCommitmentTransaction, + preimages: Vec, + ) -> Result<(), ()> { + self.in_memory_signer + .lock() + .unwrap() + .validate_holder_commitment(holder_tx, preimages) + } + + fn pubkeys(&self) -> &ChannelPublicKeys { + &self.channel_public_keys + } + + fn channel_keys_id(&self) -> [u8; 32] { + self.in_memory_signer.lock().unwrap().channel_keys_id() + } + + fn provide_channel_parameters( + &mut self, + channel_parameters: &lightning::ln::chan_utils::ChannelTransactionParameters, + ) { + self.in_memory_signer + .lock() + .unwrap() + .provide_channel_parameters(channel_parameters) + } + + fn set_channel_value_satoshis(&mut self, value: u64) { + self.in_memory_signer + .lock() + .unwrap() + .set_channel_value_satoshis(value) + } +} + +impl EcdsaChannelSigner for CustomSigner { + fn sign_counterparty_commitment( + &self, + commitment_tx: &lightning::ln::chan_utils::CommitmentTransaction, + preimages: Vec, + secp_ctx: &Secp256k1, + ) -> Result< + ( + secp256k1_zkp::ecdsa::Signature, + Vec, + ), + (), + > { + self.in_memory_signer + .lock() + .unwrap() + .sign_counterparty_commitment(commitment_tx, preimages, secp_ctx) + } + + fn validate_counterparty_revocation(&self, idx: u64, secret: &SecretKey) -> Result<(), ()> { + self.in_memory_signer + .lock() + .unwrap() + .validate_counterparty_revocation(idx, secret) + } + + fn sign_holder_commitment_and_htlcs( + &self, + commitment_tx: &lightning::ln::chan_utils::HolderCommitmentTransaction, + secp_ctx: &Secp256k1, + ) -> Result< + ( + secp256k1_zkp::ecdsa::Signature, + Vec, + ), + (), + > { + self.in_memory_signer + .lock() + .unwrap() + .sign_holder_commitment_and_htlcs(commitment_tx, secp_ctx) + } + + fn sign_justice_revoked_output( + &self, + justice_tx: &Transaction, + input: usize, + amount: u64, + per_commitment_key: &SecretKey, + secp_ctx: &Secp256k1, + ) -> Result { + self.in_memory_signer + .lock() + .unwrap() + .sign_justice_revoked_output(justice_tx, input, amount, per_commitment_key, secp_ctx) + } + + fn sign_justice_revoked_htlc( + &self, + justice_tx: &Transaction, + input: usize, + amount: u64, + per_commitment_key: &SecretKey, + htlc: &lightning::ln::chan_utils::HTLCOutputInCommitment, + secp_ctx: &Secp256k1, + ) -> Result { + self.in_memory_signer + .lock() + .unwrap() + .sign_justice_revoked_htlc( + justice_tx, + input, + amount, + per_commitment_key, + htlc, + secp_ctx, + ) + } + + fn sign_counterparty_htlc_transaction( + &self, + htlc_tx: &Transaction, + input: usize, + amount: u64, + per_commitment_point: &secp256k1_zkp::PublicKey, + htlc: &lightning::ln::chan_utils::HTLCOutputInCommitment, + secp_ctx: &Secp256k1, + ) -> Result { + self.in_memory_signer + .lock() + .unwrap() + .sign_counterparty_htlc_transaction( + htlc_tx, + input, + amount, + per_commitment_point, + htlc, + secp_ctx, + ) + } + + fn sign_closing_transaction( + &self, + closing_tx: &lightning::ln::chan_utils::ClosingTransaction, + secp_ctx: &Secp256k1, + ) -> Result { + self.in_memory_signer + .lock() + .unwrap() + .sign_closing_transaction(closing_tx, secp_ctx) + } + + fn sign_holder_anchor_input( + &self, + anchor_tx: &Transaction, + input: usize, + secp_ctx: &Secp256k1, + ) -> Result { + self.in_memory_signer + .lock() + .unwrap() + .sign_holder_anchor_input(anchor_tx, input, secp_ctx) + } + + fn sign_channel_announcement_with_funding_key( + &self, + msg: &lightning::ln::msgs::UnsignedChannelAnnouncement, + secp_ctx: &Secp256k1, + ) -> Result { + self.in_memory_signer + .lock() + .unwrap() + .sign_channel_announcement_with_funding_key(msg, secp_ctx) + } + + fn sign_holder_htlc_transaction( + &self, + htlc_tx: &Transaction, + input: usize, + htlc_descriptor: &lightning::events::bump_transaction::HTLCDescriptor, + secp_ctx: &Secp256k1, + ) -> Result { + self.in_memory_signer + .lock() + .unwrap() + .sign_holder_htlc_transaction(htlc_tx, input, htlc_descriptor, secp_ctx) + } +} + +impl Writeable for CustomSigner { + fn write(&self, writer: &mut W) -> Result<(), std::io::Error> { + self.in_memory_signer.lock().unwrap().write(writer) + } +} + +impl ReadableArgs for CustomSigner +where + ES::Target: EntropySource, +{ + fn read(reader: &mut R, entropy_source: ES) -> Result { + let in_memory_signer = InMemorySigner::read(reader, entropy_source)?; + Ok(Self::new(in_memory_signer)) + } +} + +impl LnDlcChannelSigner for CustomSigner { + fn get_holder_split_tx_signature( + &self, + secp: &Secp256k1, + split_tx: &Transaction, + original_funding_redeemscript: &Script, + original_channel_value_satoshis: u64, + ) -> Result { + dlc::util::get_raw_sig_for_tx_input( + secp, + split_tx, + 0, + original_funding_redeemscript, + original_channel_value_satoshis, + &self.in_memory_signer.lock().unwrap().funding_key, + ) + .map_err(|e| e.into()) + } + + fn get_holder_split_tx_adaptor_signature( + &self, + secp: &Secp256k1, + split_tx: &Transaction, + original_channel_value_satoshis: u64, + original_funding_redeemscript: &Script, + other_publish_key: &secp256k1_zkp::PublicKey, + ) -> Result { + dlc::channel::get_tx_adaptor_signature( + secp, + split_tx, + original_channel_value_satoshis, + original_funding_redeemscript, + &self.in_memory_signer.lock().unwrap().funding_key, + other_publish_key, + ) + .map_err(|e| e.into()) + } +} + +impl WriteableEcdsaChannelSigner for CustomSigner {} + +pub struct CustomKeysManager { + keys_manager: Arc, +} + +impl CustomKeysManager { + pub fn new(keys_manager: Arc) -> Self { + Self { keys_manager } + } +} + +impl CustomKeysManager { + #[allow(clippy::result_unit_err)] + pub fn spend_spendable_outputs( + &self, + descriptors: &[&SpendableOutputDescriptor], + outputs: Vec, + change_destination_script: Script, + feerate_sat_per_1000_weight: u32, + secp_ctx: &Secp256k1, + ) -> Result { + self.keys_manager.spend_spendable_outputs( + descriptors, + outputs, + change_destination_script, + feerate_sat_per_1000_weight, + None, + secp_ctx, + ) + } +} + +impl SignerProvider for CustomKeysManager { + type Signer = CustomSigner; + fn generate_channel_keys_id( + &self, + inbound: bool, + channel_value_satoshis: u64, + user_channel_id: u128, + ) -> [u8; 32] { + self.keys_manager + .generate_channel_keys_id(inbound, channel_value_satoshis, user_channel_id) + } + + fn derive_channel_signer( + &self, + channel_value_satoshis: u64, + channel_keys_id: [u8; 32], + ) -> Self::Signer { + let inner = self + .keys_manager + .derive_channel_signer(channel_value_satoshis, channel_keys_id); + let pubkeys = inner.pubkeys(); + + CustomSigner { + channel_public_keys: pubkeys.clone(), + in_memory_signer: Arc::new(Mutex::new(inner)), + } + } + + fn read_chan_signer(&self, reader: &[u8]) -> Result { + CustomSigner::read(&mut std::io::Cursor::new(reader), self) + } + + fn get_destination_script(&self) -> Result { + self.keys_manager.get_destination_script() + } + + fn get_shutdown_scriptpubkey(&self) -> Result { + self.keys_manager.get_shutdown_scriptpubkey() + } +} + +impl LnDlcSignerProvider for CustomKeysManager { + fn derive_ln_dlc_channel_signer( + &self, + channel_value_satoshis: u64, + channel_keys_id: [u8; 32], + ) -> CustomSigner { + self.derive_channel_signer(channel_value_satoshis, channel_keys_id) + } +} + +impl EntropySource for CustomKeysManager { + fn get_secure_random_bytes(&self) -> [u8; 32] { + self.keys_manager.get_secure_random_bytes() + } +} + +impl NodeSigner for CustomKeysManager { + fn get_inbound_payment_key_material(&self) -> lightning::sign::KeyMaterial { + self.keys_manager.get_inbound_payment_key_material() + } + + fn get_node_id( + &self, + recipient: lightning::sign::Recipient, + ) -> Result { + self.keys_manager.get_node_id(recipient) + } + + fn ecdh( + &self, + recipient: lightning::sign::Recipient, + other_key: &secp256k1_zkp::PublicKey, + tweak: Option<&secp256k1_zkp::Scalar>, + ) -> Result { + self.keys_manager.ecdh(recipient, other_key, tweak) + } + + fn sign_invoice( + &self, + hrp_bytes: &[u8], + invoice_data: &[bitcoin::bech32::u5], + recipient: lightning::sign::Recipient, + ) -> Result { + self.keys_manager + .sign_invoice(hrp_bytes, invoice_data, recipient) + } + + fn sign_gossip_message( + &self, + msg: lightning::ln::msgs::UnsignedGossipMessage, + ) -> Result { + self.keys_manager.sign_gossip_message(msg) + } +} diff --git a/dlc-manager/tests/ln_dlc_channel_execution_tests.rs b/dlc-manager/tests/ln_dlc_channel_execution_tests.rs new file mode 100644 index 00000000..d5fa7e93 --- /dev/null +++ b/dlc-manager/tests/ln_dlc_channel_execution_tests.rs @@ -0,0 +1,3645 @@ +#[macro_use] +mod test_utils; +mod console_logger; +mod custom_signer; + +use std::{ + collections::HashMap, + convert::TryInto, + sync::{atomic::AtomicU8, Arc, Mutex}, + time::SystemTime, +}; + +use crate::test_utils::{ + get_enum_test_params_custom_collateral, refresh_wallet, TestParams, EVENT_MATURITY, +}; +use bitcoin::{ + hashes::Hash, Address, Amount, Network, PackedLockTime, Script, Sequence, Transaction, TxIn, + TxOut, Witness, +}; +use bitcoin_bech32::WitnessProgram; +use bitcoin_test_utils::rpc_helpers::init_clients; +use bitcoincore_rpc::{Client, RpcApi}; +use console_logger::ConsoleLogger; +use custom_signer::{CustomKeysManager, CustomSigner}; +use dlc_manager::{ + channel::Channel, contract::Contract, manager::Manager, sub_channel_manager::SubChannelManager, + subchannel::SubChannelState, Blockchain, ChannelId, Oracle, Signer, Storage, Utxo, Wallet, +}; +use dlc_messages::{ + sub_channel::{SubChannelAccept, SubChannelOffer}, + ChannelMessage, Message, SubChannelMessage, +}; +use electrs_blockchain_provider::{ElectrsBlockchainProvider, OutSpendResp}; +use lightning::{ + chain::{ + chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator}, + BestBlock, Confirm, + }, + events::{Event, EventHandler, EventsProvider, PaymentPurpose}, + ln::{ + channelmanager::{ChainParameters, PaymentId, RecipientOnionFields}, + peer_handler::{IgnoringMessageHandler, MessageHandler}, + }, + routing::{ + gossip::{NetworkGraph, NodeId}, + router::{DefaultRouter, Path, RouteParameters}, + scoring::{ChannelUsage, Score}, + }, + sign::{EntropySource, KeysManager}, + util::{config::UserConfig, ser::Writeable}, +}; +use lightning_persister::FilesystemPersister; +use lightning_transaction_sync::EsploraSyncClient; +use log::error; +use mocks::{ + memory_storage_provider::MemoryStorage, + mock_blockchain::MockBlockchain, + mock_oracle_provider::MockOracle, + mock_time::{self, MockTime}, +}; +use secp256k1_zkp::{ + rand::{thread_rng, RngCore}, + PublicKey, Secp256k1, +}; +use simple_wallet::SimpleWallet; +use simple_wallet::WalletStorage; + +type ChainMonitor = lightning::chain::chainmonitor::ChainMonitor< + CustomSigner, + Arc>>, + Arc>>, + Arc>>, + Arc, + Arc, +>; + +pub(crate) type ChannelManager = lightning::ln::channelmanager::ChannelManager< + Arc, + Arc>>, + Arc, + Arc, + Arc, + Arc>>, + Arc< + DefaultRouter< + Arc>>, + Arc, + Arc>, + (), + TestScorer, + >, + >, + Arc, +>; + +pub(crate) type PeerManager = lightning::ln::peer_handler::PeerManager< + MockSocketDescriptor, + Arc, + Arc, + Arc, + Arc, + Arc, + Arc, +>; + +type DlcChannelManager = Manager< + Arc, Arc>>, + Arc, + Arc, + Arc, + Arc, + Arc, +>; + +type DlcSubChannelManager = SubChannelManager< + Arc, Arc>>, + Arc, + Arc, + Arc, + Arc, + Arc, + Arc, + Arc, + Arc, + CustomSigner, + Arc, + CustomSigner, +>; + +struct LnDlcParty { + peer_manager: Arc, + channel_manager: Arc, + chain_monitor: Arc, + keys_manager: Arc, + logger: Arc, + network_graph: NetworkGraph>, + sub_channel_manager: Arc, + dlc_manager: Arc, + blockchain: Arc, + mock_blockchain: Arc>>, + wallet: Arc, Arc>>, + persister: Arc, + esplora_sync: Arc>>, +} + +impl Drop for LnDlcParty { + fn drop(&mut self) { + let data_dir = self.persister.get_data_dir(); + std::fs::remove_dir_all(data_dir).unwrap(); + } +} + +impl LnDlcParty { + fn update_to_chain_tip(&mut self) { + let confirmables = vec![ + &*self.channel_manager as &(dyn Confirm + Sync + Send), + &*self.chain_monitor as &(dyn Confirm + Sync + Send), + ]; + + self.esplora_sync.sync(confirmables).unwrap(); + self.dlc_manager.periodic_chain_monitor().unwrap(); + + self.sub_channel_manager.periodic_check(); + self.dlc_manager.periodic_check().unwrap(); + } + + fn process_events(&self) { + self.channel_manager.process_pending_events(self); + self.peer_manager.process_events(); + self.channel_manager.timer_tick_occurred(); + self.chain_monitor.process_pending_events(self); + } +} + +struct LnDlcTestParams { + alice_node: LnDlcParty, + bob_node: LnDlcParty, + alice_node_id: PublicKey, + bob_node_id: PublicKey, + alice_descriptor: MockSocketDescriptor, + bob_descriptor: MockSocketDescriptor, + electrs: Arc, + sink_rpc: Client, + funding_txo: lightning::chain::transaction::OutPoint, + channel_id: ChannelId, + test_params: TestParams, +} + +impl LnDlcTestParams { + fn generate_blocks(&self, nb_blocks: u64) { + generate_blocks(nb_blocks, &self.electrs, &self.sink_rpc); + } +} + +#[derive(PartialEq, Eq)] +enum TargetState { + OfferSent, + OfferReceived, + Accepted, + Confirmed, + Finalized, +} + +static PAYMENT_COUNTER: AtomicU8 = AtomicU8::new(0); + +#[derive(Clone)] +struct MockSocketDescriptor { + counter_peer_mng: Arc, + counter_descriptor: Option>, + id: u64, +} + +impl std::hash::Hash for MockSocketDescriptor { + fn hash(&self, state: &mut H) { + self.id.hash(state); + } +} + +impl PartialEq for MockSocketDescriptor { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl Eq for MockSocketDescriptor {} + +impl MockSocketDescriptor { + fn new(id: u64, counter_peer_mng: Arc) -> Self { + MockSocketDescriptor { + counter_peer_mng, + id, + counter_descriptor: None, + } + } +} + +impl lightning::ln::peer_handler::SocketDescriptor for MockSocketDescriptor { + fn send_data(&mut self, data: &[u8], _resume_read: bool) -> usize { + self.counter_peer_mng + .clone() + .read_event(self.counter_descriptor.as_mut().unwrap(), data) + .unwrap(); + data.len() + } + + fn disconnect_socket(&mut self) {} +} + +#[derive(Clone)] +/// [`Score`] implementation that uses a fixed penalty. +pub struct TestScorer { + penalty_msat: u64, +} + +impl TestScorer { + /// Creates a new scorer using `penalty_msat`. + pub fn with_penalty(penalty_msat: u64) -> Self { + Self { penalty_msat } + } +} + +impl Score for TestScorer { + type ScoreParams = (); + fn channel_penalty_msat( + &self, + _short_channel_id: u64, + _source: &NodeId, + _target: &NodeId, + _usage: ChannelUsage, + _score_params: &Self::ScoreParams, + ) -> u64 { + self.penalty_msat + } + + fn payment_path_failed(&mut self, _actual_path: &Path, _actual_short_channel_id: u64) {} + + fn payment_path_successful(&mut self, _actual_path: &Path) {} + + fn probe_failed(&mut self, _actual_path: &Path, _: u64) {} + + fn probe_successful(&mut self, _actual_path: &Path) {} +} + +impl EventHandler for LnDlcParty { + fn handle_event(&self, event: lightning::events::Event) { + match event { + Event::FundingGenerationReady { + temporary_channel_id, + counterparty_node_id, + channel_value_satoshis, + output_script, + .. + } => { + // Construct the raw transaction with one output, that is paid the amount of the + // channel. + let addr = WitnessProgram::from_scriptpubkey( + &output_script[..], + bitcoin_bech32::constants::Network::Regtest, + ) + .expect("Lightning funding tx should always be to a SegWit output") + .to_address(); + let address: Address = addr.parse().unwrap(); + let mut tx = Transaction { + version: 2, + lock_time: PackedLockTime::ZERO, + input: vec![TxIn::default()], + output: vec![TxOut { + value: channel_value_satoshis, + script_pubkey: address.script_pubkey(), + }], + }; + + let expected_size = (tx.weight() / 4) as u64; + let required_amount = channel_value_satoshis + + expected_size + * (self + .blockchain + .get_est_sat_per_1000_weight(ConfirmationTarget::Normal) + / 25) as u64; + + let utxos: Vec = self + .wallet + .get_utxos_for_amount(required_amount, None, false) + .unwrap(); + + tx.input = Vec::new(); + + let change_address = self.wallet.get_new_address().unwrap(); + + tx.output.push(TxOut { + value: utxos.iter().map(|x| x.tx_out.value).sum::() - required_amount, + script_pubkey: change_address.script_pubkey(), + }); + + for (i, utxo) in utxos.iter().enumerate() { + tx.input.push(TxIn { + previous_output: utxo.outpoint, + script_sig: Script::default(), + sequence: Sequence::MAX, + witness: Witness::default(), + }); + self.wallet + .sign_tx_input(&mut tx, i, &utxo.tx_out, None) + .unwrap(); + } + + // Give the funding transaction back to LDK for opening the channel. + self.channel_manager + .funding_transaction_generated(&temporary_channel_id, &counterparty_node_id, tx) + .unwrap(); + } + Event::PendingHTLCsForwardable { .. } => { + self.channel_manager.process_pending_htlc_forwards(); + } + Event::PaymentClaimable { purpose, .. } => { + let payment_preimage = match purpose { + PaymentPurpose::InvoicePayment { + payment_preimage, .. + } => payment_preimage, + PaymentPurpose::SpontaneousPayment(preimage) => Some(preimage), + }; + self.channel_manager.claim_funds(payment_preimage.unwrap()); + } + Event::SpendableOutputs { outputs } => { + let destination_address = self.wallet.get_new_address().unwrap(); + let output_descriptors = &outputs.iter().collect::>(); + let tx_feerate = self + .blockchain + .get_est_sat_per_1000_weight(ConfirmationTarget::Normal); + let spending_tx = self + .keys_manager + .spend_spendable_outputs( + output_descriptors, + Vec::new(), + destination_address.script_pubkey(), + tx_feerate, + &Secp256k1::new(), + ) + .unwrap(); + self.blockchain.broadcast_transactions(&[&spending_tx]); + } + Event::ChannelClosed { + channel_id, reason, .. + } => { + if let Err(error) = self + .sub_channel_manager + .notify_ln_channel_closed(channel_id, &reason) + { + error!( + "Error notifying sub channel manager of LN channel closing: {}", + error + ); + } + } + _ => { + //Ignore + } + } + } +} + +fn create_ln_node( + name: String, + data_dir: &str, + test_params: &TestParams, + blockchain_provider: &Arc, +) -> LnDlcParty { + let mut key = [0; 32]; + thread_rng().fill_bytes(&mut key); + let cur = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap(); + let keys_manager = Arc::new(KeysManager::new(&key, cur.as_secs(), cur.subsec_nanos())); + let consistent_keys_manager = Arc::new(CustomKeysManager::new(keys_manager.clone())); + let logger = Arc::new(console_logger::ConsoleLogger { name }); + + std::fs::create_dir_all(data_dir).unwrap(); + let persister = Arc::new(FilesystemPersister::new(data_dir.to_string())); + + let mock_blockchain = Arc::new(MockBlockchain::new(blockchain_provider.clone())); + + let tx_sync = Arc::new(EsploraSyncClient::new( + "http://localhost:3004".to_string(), + Arc::clone(&logger), + )); + + let chain_monitor: Arc = + Arc::new(lightning::chain::chainmonitor::ChainMonitor::new( + Some(tx_sync.clone()), + mock_blockchain.clone(), + logger.clone(), + mock_blockchain.clone(), + persister.clone(), + )); + + let mut user_config = UserConfig::default(); + user_config.channel_handshake_limits.max_funding_satoshis = 200000000; + user_config + .channel_handshake_limits + .force_announced_channel_preference = false; + user_config + .channel_handshake_config + .max_inbound_htlc_value_in_flight_percent_of_channel = 55; + + let network_graph = Arc::new(NetworkGraph::new(Network::Regtest, logger.clone())); + let scorer = Arc::new(Mutex::new(TestScorer::with_penalty(0))); + let router = Arc::new(DefaultRouter::new( + network_graph, + logger.clone(), + keys_manager.get_secure_random_bytes(), + scorer, + (), + )); + + let channel_manager = { + let height = blockchain_provider.get_blockchain_height().unwrap(); + let last_block = blockchain_provider.get_block_at_height(height).unwrap(); + + let chain_params = ChainParameters { + network: Network::Regtest, + best_block: BestBlock::new(last_block.block_hash(), height as u32), + }; + + Arc::new(ChannelManager::new( + mock_blockchain.clone(), + chain_monitor.clone(), + mock_blockchain.clone(), + router, + logger.clone(), + consistent_keys_manager.clone(), + consistent_keys_manager.clone(), + consistent_keys_manager.clone(), + user_config, + chain_params, + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs() as u32, + )) + }; + + // Step 12: Initialize the PeerManager + let current_time = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs(); + + let mut ephemeral_bytes = [0; 32]; + thread_rng().fill_bytes(&mut ephemeral_bytes); + + let network_graph = NetworkGraph::new(Network::Regtest, logger.clone()); + + let storage = Arc::new(MemoryStorage::new()); + + let mut oracles = HashMap::with_capacity(1); + + for oracle in &test_params.oracles { + let oracle = Arc::new(oracle.clone()); + oracles.insert(oracle.get_public_key(), oracle.clone()); + } + + let wallet = Arc::new(simple_wallet::SimpleWallet::new( + blockchain_provider.clone(), + storage.clone(), + Network::Regtest, + )); + + let dlc_manager = Arc::new( + Manager::new( + wallet.clone(), + blockchain_provider.clone(), + storage, + oracles, + Arc::new(mock_time::MockTime {}), + blockchain_provider.clone(), + ) + .unwrap(), + ); + + let sub_channel_manager = Arc::new( + SubChannelManager::new( + channel_manager.clone(), + dlc_manager.clone(), + chain_monitor.clone(), + consistent_keys_manager.clone(), + ) + .unwrap(), + ); + + let lightning_msg_handler = MessageHandler { + chan_handler: sub_channel_manager.clone(), + route_handler: Arc::new(IgnoringMessageHandler {}), + onion_message_handler: Arc::new(IgnoringMessageHandler {}), + custom_message_handler: Arc::new(IgnoringMessageHandler {}), + }; + let peer_manager = PeerManager::new( + lightning_msg_handler, + current_time.try_into().unwrap(), + &ephemeral_bytes, + logger.clone(), + consistent_keys_manager.clone(), + ); + + LnDlcParty { + peer_manager: Arc::new(peer_manager), + channel_manager, + chain_monitor, + keys_manager: consistent_keys_manager, + logger, + network_graph, + sub_channel_manager, + dlc_manager, + blockchain: blockchain_provider.clone(), + mock_blockchain, + wallet, + persister, + esplora_sync: tx_sync, + } +} + +#[test] +#[ignore] +fn ln_dlc_established_close() { + let mut test_params = test_init(); + + make_ln_payment(&test_params.alice_node, &test_params.bob_node, 900000); + + open_sub_channel(&test_params); + + mocks::mock_time::set_time(EVENT_MATURITY as u64); + + force_close_stable(&mut test_params); +} + +#[test] +#[ignore] +fn ln_dlc_renewed_close() { + let mut test_params = test_init(); + + make_ln_payment(&test_params.alice_node, &test_params.bob_node, 900000); + + open_sub_channel(&test_params); + let sub_channel = test_params + .alice_node + .dlc_manager + .get_store() + .get_sub_channel(test_params.channel_id) + .unwrap() + .unwrap(); + let dlc_channel_id = sub_channel.get_dlc_channel_id(0).unwrap(); + let contract_id = assert_channel_contract_state!( + test_params.alice_node.dlc_manager, + dlc_channel_id, + Confirmed + ); + + renew(&test_params, &dlc_channel_id); + + assert_contract_state_unlocked!(test_params.alice_node.dlc_manager, contract_id, Closed); + assert_contract_state_unlocked!(test_params.bob_node.dlc_manager, contract_id, Closed); + + mocks::mock_time::set_time(EVENT_MATURITY as u64); + + force_close_stable(&mut test_params); +} + +#[test] +#[ignore] +fn ln_dlc_open_disconnect_renewed_close() { + let mut test_params = test_init(); + + make_ln_payment(&test_params.alice_node, &test_params.bob_node, 900000); + + offer_sub_channel_with_reconnect(&test_params); + let sub_channel = test_params + .alice_node + .dlc_manager + .get_store() + .get_sub_channel(test_params.channel_id) + .unwrap() + .unwrap(); + let dlc_channel_id = sub_channel.get_dlc_channel_id(0).unwrap(); + let contract_id = assert_channel_contract_state!( + test_params.alice_node.dlc_manager, + dlc_channel_id, + Confirmed + ); + + renew(&test_params, &dlc_channel_id); + + assert_contract_state_unlocked!(test_params.alice_node.dlc_manager, contract_id, Closed); + assert_contract_state_unlocked!(test_params.bob_node.dlc_manager, contract_id, Closed); + + mocks::mock_time::set_time(EVENT_MATURITY as u64); + + force_close_stable(&mut test_params); +} + +#[test] +#[ignore] +fn ln_dlc_open_disconnect_settled_close() { + let mut test_params = test_init(); + + make_ln_payment(&test_params.alice_node, &test_params.bob_node, 900000); + + offer_sub_channel_with_reconnect(&test_params); + let sub_channel = test_params + .alice_node + .dlc_manager + .get_store() + .get_sub_channel(test_params.channel_id) + .unwrap() + .unwrap(); + let dlc_channel_id = sub_channel.get_dlc_channel_id(0).unwrap(); + let contract_id = assert_channel_contract_state!( + test_params.alice_node.dlc_manager, + dlc_channel_id, + Confirmed + ); + + settle(&test_params, &dlc_channel_id); + + assert_contract_state_unlocked!(test_params.alice_node.dlc_manager, contract_id, Closed); + assert_contract_state_unlocked!(test_params.bob_node.dlc_manager, contract_id, Closed); + + mocks::mock_time::set_time(EVENT_MATURITY as u64); + + force_close_stable(&mut test_params); +} + +#[test] +#[ignore] +fn ln_dlc_settled_close() { + let mut test_params = test_init(); + + make_ln_payment(&test_params.alice_node, &test_params.bob_node, 900000); + + open_sub_channel(&test_params); + let sub_channel = test_params + .alice_node + .dlc_manager + .get_store() + .get_sub_channel(test_params.channel_id) + .unwrap() + .unwrap(); + let dlc_channel_id = sub_channel.get_dlc_channel_id(0).unwrap(); + let contract_id = assert_channel_contract_state!( + test_params.alice_node.dlc_manager, + dlc_channel_id, + Confirmed + ); + + settle(&test_params, &dlc_channel_id); + + assert_contract_state_unlocked!(test_params.alice_node.dlc_manager, contract_id, Closed); + assert_contract_state_unlocked!(test_params.bob_node.dlc_manager, contract_id, Closed); + + mocks::mock_time::set_time(EVENT_MATURITY as u64); + + force_close_stable(&mut test_params); +} + +#[test] +#[ignore] +fn ln_dlc_settled_renewed_close() { + let mut test_params = test_init(); + + make_ln_payment(&test_params.alice_node, &test_params.bob_node, 900000); + + open_sub_channel(&test_params); + let sub_channel = test_params + .alice_node + .dlc_manager + .get_store() + .get_sub_channel(test_params.channel_id) + .unwrap() + .unwrap(); + let dlc_channel_id = sub_channel.get_dlc_channel_id(0).unwrap(); + let contract_id = assert_channel_contract_state!( + test_params.alice_node.dlc_manager, + dlc_channel_id, + Confirmed + ); + + settle(&test_params, &dlc_channel_id); + renew(&test_params, &dlc_channel_id); + + assert_contract_state_unlocked!(test_params.alice_node.dlc_manager, contract_id, Closed); + assert_contract_state_unlocked!(test_params.bob_node.dlc_manager, contract_id, Closed); + + mocks::mock_time::set_time(EVENT_MATURITY as u64); + + force_close_stable(&mut test_params); +} + +#[test] +#[ignore] +fn ln_dlc_pre_split_cheat() { + let mut test_params = test_init(); + + make_ln_payment(&test_params.alice_node, &test_params.bob_node, 900000); + + let pre_split_commit_tx = + get_commit_tx_from_node(&test_params.alice_node, &test_params.funding_txo); + open_sub_channel(&test_params); + cheat_with_revoked_tx(&pre_split_commit_tx[0], &mut test_params); +} + +#[test] +#[ignore] +fn ln_dlc_post_split_cheat() { + let mut test_params = test_init(); + + make_ln_payment(&test_params.alice_node, &test_params.bob_node, 900000); + + open_sub_channel(&test_params); + + test_params.alice_node.mock_blockchain.start_discard(); + + let post_split_commit_tx = + get_commit_tx_from_node(&test_params.alice_node, &test_params.funding_txo); + + make_ln_payment(&test_params.alice_node, &test_params.bob_node, 90000); + + let alice_commit = get_commit_tx_from_node(&test_params.alice_node, &test_params.funding_txo); + test_params + .alice_node + .mock_blockchain + .discard_id(alice_commit[0].txid()); + + let bob_commit = get_commit_tx_from_node(&test_params.bob_node, &test_params.funding_txo); + test_params + .bob_node + .mock_blockchain + .discard_id(bob_commit[0].txid()); + + mocks::mock_time::set_time(EVENT_MATURITY as u64); + test_params + .alice_node + .sub_channel_manager + .force_close_sub_channel(&test_params.channel_id) + .unwrap(); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + Closing + ); + + test_params.generate_blocks(1); + + test_params.alice_node.update_to_chain_tip(); + test_params.bob_node.update_to_chain_tip(); + test_params.alice_node.process_events(); + test_params.bob_node.process_events(); + + test_params.generate_blocks(700); + + test_params.alice_node.update_to_chain_tip(); + test_params.bob_node.update_to_chain_tip(); + test_params.alice_node.process_events(); + test_params.bob_node.process_events(); + + test_params.generate_blocks(1); + + test_params.alice_node.update_to_chain_tip(); + test_params.bob_node.update_to_chain_tip(); + test_params.alice_node.process_events(); + test_params.bob_node.process_events(); + + test_params.generate_blocks(1); + + cheat_with_revoked_tx(&post_split_commit_tx[0], &mut test_params); +} + +#[test] +#[ignore] +fn ln_dlc_force_close_after_off_chain_close() { + let mut test_params = test_init(); + + make_ln_payment(&test_params.alice_node, &test_params.bob_node, 900000); + + open_sub_channel(&test_params); + + off_chain_close(&test_params); + + let alice_commit = get_commit_tx_from_node(&test_params.alice_node, &test_params.funding_txo); + + test_params + .alice_node + .channel_manager + .force_close_broadcasting_latest_txn(&test_params.channel_id, &test_params.bob_node_id) + .unwrap(); + + test_params.generate_blocks(501); + test_params.alice_node.update_to_chain_tip(); + test_params.bob_node.update_to_chain_tip(); + test_params.alice_node.process_events(); + test_params.bob_node.process_events(); + + test_params.generate_blocks(1); + + let all_spent = test_params + .electrs + .get_outspends(&alice_commit[0].txid()) + .unwrap() + .into_iter() + .all(|x| { + if let OutSpendResp::Spent(_) = x { + true + } else { + false + } + }); + + assert!(all_spent); +} + +#[test] +#[ignore] +fn ln_dlc_split_cheat() { + let mut test_params = test_init(); + + make_ln_payment(&test_params.alice_node, &test_params.bob_node, 900000); + + // Open DLC sub-channel. + open_sub_channel(&test_params); + // Save the state after first sub-channel opening. + test_params.alice_node.dlc_manager.get_store().save(); + + // Close the DLC sub-channel off-chain (reverting to regular LN channel). + off_chain_close(&test_params); + + // Re-open a DLC sub-channel. + open_sub_channel(&test_params); + + // Restore the state of alice storage to that of the first opened DLC sub-channel. + test_params.alice_node.dlc_manager.get_store().rollback(); + + // Get the transaction id of the split transaction for the first DLC sub-channel. + let split_tx_id = match test_params + .alice_node + .dlc_manager + .get_store() + .get_sub_channel(test_params.channel_id) + .unwrap() + .unwrap() + .state + { + SubChannelState::Signed(s) => s.split_tx.transaction.txid(), + a => panic!("Unexpected state {:?}", a), + }; + + // Make Alice close the transaction. She will force close with the first split transaction + // which has already been revoked because we rolled back her state. + test_params + .alice_node + .sub_channel_manager + .force_close_sub_channel(&test_params.channel_id) + .unwrap(); + + test_params.generate_blocks(1); + + // On seeing the revoked split transaction, Bob should react by spending both outputs. + test_params.bob_node.update_to_chain_tip(); + + let outspends = test_params.electrs.get_outspends(&split_tx_id).unwrap(); + + let spent = outspends + .iter() + .filter_map(|x| { + if let OutSpendResp::Spent(s) = x { + Some(s) + } else { + None + } + }) + .collect::>(); + + assert_eq!(spent.len(), 2); + // Bob should have used the same transaction to spend both outputs. + assert_eq!(spent[0].txid, spent[1].txid); + + let spending_tx = test_params.electrs.get_transaction(&spent[0].txid).unwrap(); + + // We make sure that the output address of the penalty transaction belongs to Bob. + let receive_addr = + Address::from_script(&spending_tx.output[0].script_pubkey, Network::Regtest).unwrap(); + + assert!(test_params + .bob_node + .dlc_manager + .get_store() + .get_addresses() + .unwrap() + .iter() + .any(|x| *x == receive_addr)); +} + +#[test] +#[ignore] +fn ln_dlc_rejected_offer() { + let test_params = test_init(); + + make_ln_payment(&test_params.alice_node, &test_params.bob_node, 900000); + + reject_offer(&test_params); +} + +#[test] +#[ignore] +fn ln_dlc_rejected_close() { + let test_params = test_init(); + + make_ln_payment(&test_params.alice_node, &test_params.bob_node, 900000); + + open_sub_channel(&test_params); + + off_chain_close_offer(&test_params, false); + + let reject = test_params + .bob_node + .sub_channel_manager + .reject_sub_channel_close_offer(test_params.channel_id) + .unwrap(); + + test_params + .alice_node + .sub_channel_manager + .on_sub_channel_message( + &SubChannelMessage::Reject(reject), + &test_params.bob_node.channel_manager.get_our_node_id(), + ) + .unwrap(); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + Signed + ); + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + Signed + ); +} + +#[test] +#[ignore] +fn ln_dlc_reconnect() { + let mut test_params = test_init(); + + make_ln_payment(&test_params.alice_node, &test_params.bob_node, 900000); + + offer_sub_channel_with_reconnect(&test_params); + + off_chain_close_with_reconnect(&test_params); + + offer_sub_channel_with_reconnect(&test_params); + + mocks::mock_time::set_time(EVENT_MATURITY as u64); + + force_close_stable(&mut test_params); +} + +#[test] +#[ignore] +fn ln_dlc_force_close_after_three_sub_channel_open() { + let mut test_params = test_init(); + + make_ln_payment(&test_params.alice_node, &test_params.bob_node, 900000); + + open_sub_channel(&test_params); + + off_chain_close(&test_params); + + open_sub_channel(&test_params); + + off_chain_close(&test_params); + + open_sub_channel(&test_params); + + mocks::mock_time::set_time(EVENT_MATURITY as u64); + + force_close_stable(&mut test_params); +} + +#[test] +#[ignore] +fn ln_dlc_offer_after_offchain_close_disconnect() { + let test_params = test_init(); + + make_ln_payment(&test_params.alice_node, &test_params.bob_node, 900000); + + open_sub_channel(&test_params); + + off_chain_close_offer(&test_params, false); + + let (close_accept, _) = test_params + .bob_node + .sub_channel_manager + .accept_subchannel_close_offer(&test_params.channel_id) + .unwrap(); + + let close_confirm = test_params + .alice_node + .sub_channel_manager + .on_sub_channel_message( + &SubChannelMessage::CloseAccept(close_accept), + &test_params.bob_node_id, + ) + .unwrap() + .unwrap(); + let _ = test_params + .bob_node + .sub_channel_manager + .on_sub_channel_message( + &close_confirm, + &test_params.alice_node.channel_manager.get_our_node_id(), + ) + .unwrap() + .unwrap(); + + generate_offer( + &test_params.test_params, + &test_params.bob_node, + &test_params.channel_id, + ); + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + Offered + ); + + reconnect(&test_params); + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + Offered + ); + + assert_eq!( + 0, + test_params + .alice_node + .sub_channel_manager + .periodic_check() + .len() + ); + let msgs = test_params.bob_node.sub_channel_manager.periodic_check(); + + let (close_finalize, offer) = match msgs.as_slice() { + [(c @ SubChannelMessage::CloseFinalize(_), _), (o @ SubChannelMessage::Offer(_), _)] => { + (c, o) + } + msgs => panic!("Unexpected messages: {:?}", msgs), + }; + + test_params + .alice_node + .sub_channel_manager + .on_sub_channel_message( + close_finalize, + &test_params.bob_node.channel_manager.get_our_node_id(), + ) + .unwrap(); + assert_sub_channel_state!(test_params.alice_node.sub_channel_manager, &test_params.channel_id; OffChainClosed); + + test_params + .alice_node + .sub_channel_manager + .on_sub_channel_message( + offer, + &test_params.bob_node.channel_manager.get_our_node_id(), + ) + .unwrap(); + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + Offered + ); +} + +#[test] +#[ignore] +fn ln_dlc_disconnected_force_close() { + let mut test_params = test_init(); + + make_ln_payment(&test_params.alice_node, &test_params.bob_node, 900000); + + open_sub_channel(&test_params); + + mocks::mock_time::set_time(EVENT_MATURITY as u64); + + test_params + .alice_node + .peer_manager + .socket_disconnected(&test_params.alice_descriptor); + + test_params + .bob_node + .peer_manager + .socket_disconnected(&test_params.bob_descriptor); + + force_close_stable(&mut test_params); +} + +#[test] +#[ignore] +/// Force close triggered by the party who sent the subchannel offer. +fn ln_dlc_offered_force_close() { + let mut test_params = test_init(); + + go_to_established_target_state(&test_params, TargetState::OfferReceived, false); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + Offered + ); + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + Offered + ); + + let commit_tx = get_commit_tx_from_node(&test_params.alice_node, &test_params.funding_txo); + force_close_mid_protocol(&mut test_params, false, &commit_tx[0]); +} + +#[test] +#[ignore] +/// Force close triggered by the party who received the subchannel offer. +fn ln_dlc_offered_force_close2() { + let mut test_params = test_init(); + + go_to_established_target_state(&test_params, TargetState::OfferReceived, false); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + Offered + ); + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + Offered + ); + + let commit_tx = get_commit_tx_from_node(&test_params.bob_node, &test_params.funding_txo); + force_close_mid_protocol(&mut test_params, true, &commit_tx[0]); +} + +#[test] +#[ignore] +/// Force close triggered by the party who sent the subchannel offer, while the counterparty +/// has accepted the offer (the offering party has not yet processed the accept message). +fn ln_dlc_accepted_force_close() { + let mut test_params = test_init(); + + go_to_established_target_state(&test_params, TargetState::Accepted, false); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + Offered + ); + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + Accepted + ); + + let commit_tx = get_commit_tx_from_node(&test_params.alice_node, &test_params.funding_txo); + force_close_mid_protocol(&mut test_params, false, &commit_tx[0]); +} + +#[test] +#[ignore] +/// Force close triggered by the party who accepted the subchannel offer (the counter party has +/// not yet processed the accept message). +fn ln_dlc_accepted_force_close2() { + let mut test_params = test_init(); + + go_to_established_target_state(&test_params, TargetState::Accepted, false); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + Offered + ); + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + Accepted + ); + + let sub_channel = test_params + .bob_node + .dlc_manager + .get_store() + .get_sub_channel(test_params.channel_id) + .unwrap() + .unwrap(); + let commit_tx = if let SubChannelState::Accepted(a) = &sub_channel.state { + a.commitment_transactions[0].clone() + } else { + unreachable!(); + }; + force_close_mid_protocol(&mut test_params, true, &commit_tx); +} + +#[test] +#[ignore] +/// Force close triggered by the offer party, after processing the accept message from their +/// counter party. +fn ln_dlc_confirmed_force_close() { + let mut test_params = test_init(); + + go_to_established_target_state(&test_params, TargetState::Confirmed, false); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + Confirmed + ); + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + Accepted + ); + + let sub_channel = test_params + .alice_node + .dlc_manager + .get_store() + .get_sub_channel(test_params.channel_id) + .unwrap() + .unwrap(); + let commit_tx = if let SubChannelState::Confirmed(c) = &sub_channel.state { + c.commitment_transactions[0].clone() + } else { + unreachable!(); + }; + + force_close_mid_protocol(&mut test_params, false, &commit_tx); +} + +#[test] +#[ignore] +/// Force close triggered by the accept party, after their counter party processed the accepted +/// message (but before they process the confirm message). +fn ln_dlc_confirmed_force_close2() { + let mut test_params = test_init(); + + go_to_established_target_state(&test_params, TargetState::Confirmed, false); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + Confirmed + ); + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + Accepted + ); + + let sub_channel = test_params + .bob_node + .dlc_manager + .get_store() + .get_sub_channel(test_params.channel_id) + .unwrap() + .unwrap(); + let commit_tx = if let SubChannelState::Accepted(a) = &sub_channel.state { + a.commitment_transactions[0].clone() + } else { + unreachable!(); + }; + + force_close_mid_protocol(&mut test_params, true, &commit_tx); +} + +#[test] +#[ignore] +/// Force close triggered by the offer party, after their counter party processed the confirm +/// message (but before they process the finalize message). +fn ln_dlc_finalized_force_close() { + let mut test_params = test_init(); + + go_to_established_target_state(&test_params, TargetState::Finalized, false); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + Confirmed + ); + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + Finalized + ); + + let sub_channel = test_params + .alice_node + .dlc_manager + .get_store() + .get_sub_channel(test_params.channel_id) + .unwrap() + .unwrap(); + let commit_tx = if let SubChannelState::Confirmed(c) = &sub_channel.state { + c.commitment_transactions[0].clone() + } else { + unreachable!(); + }; + + mocks::mock_time::set_time(EVENT_MATURITY as u64); + + force_close_mid_protocol(&mut test_params, false, &commit_tx); +} + +#[test] +#[ignore] +/// Force close triggered by the accept party, after processing the confirm message (but before +/// their counter party has processed the finalize message). +fn ln_dlc_finalized_force_close2() { + let mut test_params = test_init(); + + go_to_established_target_state(&test_params, TargetState::Finalized, false); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + Confirmed + ); + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + Finalized + ); + + let commit_tx = get_commit_tx_from_node(&test_params.bob_node, &test_params.funding_txo); + + mocks::mock_time::set_time(EVENT_MATURITY as u64); + + force_close_mid_protocol(&mut test_params, true, &commit_tx[0]); +} + +#[test] +#[ignore] +/// Force close triggered by the party who offered to force close the channel, before their +/// counter party received the offer. +fn ln_dlc_close_offered_force_close() { + let mut test_params = test_init(); + + make_ln_payment(&test_params.alice_node, &test_params.bob_node, 900000); + + open_sub_channel(&test_params); + + go_to_off_chain_close_state(&test_params, TargetState::OfferSent, false); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + CloseOffered + ); + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + Signed + ); + + mocks::mock_time::set_time(EVENT_MATURITY as u64); + + let commit_tx = + get_commit_tx_from_node(&test_params.alice_node, &test_params.funding_txo).remove(0); + + force_close_mid_protocol(&mut test_params, false, &commit_tx); +} + +#[test] +#[ignore] +/// Force close triggered by the counter party of the node who made the offer to close the channel +/// off-chain, before the offer was processed. +fn ln_dlc_close_offered_force_close2() { + let mut test_params = test_init(); + + make_ln_payment(&test_params.alice_node, &test_params.bob_node, 900000); + + open_sub_channel(&test_params); + + go_to_off_chain_close_state(&test_params, TargetState::OfferSent, false); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + CloseOffered + ); + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + Signed + ); + + mocks::mock_time::set_time(EVENT_MATURITY as u64); + + let commit_tx = + get_commit_tx_from_node(&test_params.bob_node, &test_params.funding_txo).remove(0); + + force_close_mid_protocol(&mut test_params, true, &commit_tx); +} + +#[test] +#[ignore] +/// Force close triggered by the party who offered to force close the channel, after their +/// counter party received the offer. +fn ln_dlc_close_offered_force_close3() { + let mut test_params = test_init(); + + make_ln_payment(&test_params.alice_node, &test_params.bob_node, 900000); + + open_sub_channel(&test_params); + + go_to_off_chain_close_state(&test_params, TargetState::OfferReceived, false); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + CloseOffered + ); + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + CloseOffered + ); + + mocks::mock_time::set_time(EVENT_MATURITY as u64); + + let commit_tx = + get_commit_tx_from_node(&test_params.alice_node, &test_params.funding_txo).remove(0); + + force_close_mid_protocol(&mut test_params, false, &commit_tx); +} + +#[test] +#[ignore] +/// Force close triggered by the party who received the offer to force close the channel. +fn ln_dlc_close_offered_force_close4() { + let mut test_params = test_init(); + + make_ln_payment(&test_params.alice_node, &test_params.bob_node, 900000); + + open_sub_channel(&test_params); + + go_to_off_chain_close_state(&test_params, TargetState::OfferReceived, false); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + CloseOffered + ); + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + CloseOffered + ); + + mocks::mock_time::set_time(EVENT_MATURITY as u64); + + let commit_tx = + get_commit_tx_from_node(&test_params.bob_node, &test_params.funding_txo).remove(0); + + force_close_mid_protocol(&mut test_params, true, &commit_tx); +} + +#[test] +#[ignore] +/// Force close triggered by the party who offered to force close the channel, after their +/// counter party accepted the close offer, but before they processed the close accept message. +fn ln_dlc_close_accepted_force_close() { + let mut test_params = test_init(); + + make_ln_payment(&test_params.alice_node, &test_params.bob_node, 900000); + + open_sub_channel(&test_params); + + go_to_off_chain_close_state(&test_params, TargetState::Accepted, false); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + CloseOffered + ); + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + CloseAccepted + ); + + mocks::mock_time::set_time(EVENT_MATURITY as u64); + + let commit_tx = + get_commit_tx_from_node(&test_params.alice_node, &test_params.funding_txo).remove(0); + + force_close_mid_protocol(&mut test_params, false, &commit_tx); +} + +#[test] +#[ignore] +/// Force close triggered by the party who accepted the close offer, before their counter party +/// processed the close offer message. +fn ln_dlc_close_accepted_force_close2() { + let mut test_params = test_init(); + + make_ln_payment(&test_params.alice_node, &test_params.bob_node, 900000); + + open_sub_channel(&test_params); + + go_to_off_chain_close_state(&test_params, TargetState::Accepted, false); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + CloseOffered + ); + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + CloseAccepted + ); + + mocks::mock_time::set_time(EVENT_MATURITY as u64); + + let commit_tx = + get_commit_tx_from_node(&test_params.bob_node, &test_params.funding_txo).remove(0); + + force_close_mid_protocol(&mut test_params, true, &commit_tx); +} + +#[test] +#[ignore] +/// Force close triggered by the party who offered to force close the channel, after they +/// processed the close accept message. +fn ln_dlc_close_confirmed_force_close() { + let mut test_params = test_init(); + + make_ln_payment(&test_params.alice_node, &test_params.bob_node, 900000); + + open_sub_channel(&test_params); + + go_to_off_chain_close_state(&test_params, TargetState::Confirmed, false); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + CloseConfirmed + ); + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + CloseAccepted + ); + + mocks::mock_time::set_time(EVENT_MATURITY as u64); + + let commit_tx = + get_commit_tx_from_node(&test_params.alice_node, &test_params.funding_txo).remove(0); + + force_close_mid_protocol(&mut test_params, false, &commit_tx); +} + +#[test] +#[ignore] +/// Force close triggered by the party who accepted the close offer, after their counter party +/// processed the close accept message. +fn ln_dlc_close_confirmed_force_close2() { + let mut test_params = test_init(); + + make_ln_payment(&test_params.alice_node, &test_params.bob_node, 900000); + + open_sub_channel(&test_params); + + go_to_off_chain_close_state(&test_params, TargetState::Confirmed, false); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + CloseConfirmed + ); + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + CloseAccepted + ); + + let commit_tx = + get_commit_tx_from_node(&test_params.bob_node, &test_params.funding_txo).remove(0); + + mocks::mock_time::set_time(EVENT_MATURITY as u64); + + force_close_mid_protocol(&mut test_params, true, &commit_tx); +} + +#[test] +#[ignore] +/// Force close triggered by the party who offered to force close the channel, after their +/// counter party processed the close confirm message, but before they processed the close +/// finalize message. +fn ln_dlc_close_finalized_force_close() { + let mut test_params = test_init(); + + make_ln_payment(&test_params.alice_node, &test_params.bob_node, 900000); + + open_sub_channel(&test_params); + + mocks::mock_time::set_time(EVENT_MATURITY as u64); + + go_to_off_chain_close_state(&test_params, TargetState::Finalized, false); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + CloseConfirmed + ); + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id; + OffChainClosed + ); + + let commit_tx = + get_commit_tx_from_node(&test_params.alice_node, &test_params.funding_txo).remove(0); + + force_close_mid_protocol(&mut test_params, false, &commit_tx); +} + +#[test] +#[ignore] +fn ln_dlc_ldk_auto_close_established_test() { + let mut test_params = test_init(); + + open_sub_channel(&test_params); + + let commit_tx = get_commit_tx_from_node(&test_params.bob_node, &test_params.funding_txo); + + mocks::mock_time::set_time(EVENT_MATURITY as u64); + + ldk_auto_close(&mut test_params, &commit_tx[0]); +} + +#[test] +#[ignore] +fn ln_dlc_ldk_auto_close_offered_test() { + let mut test_params = test_init(); + + go_to_established_target_state(&test_params, TargetState::OfferReceived, false); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + Offered + ); + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + Offered + ); + + let commit_tx = get_commit_tx_from_node(&test_params.bob_node, &test_params.funding_txo); + ldk_auto_close(&mut test_params, &commit_tx[0]); +} + +#[test] +#[ignore] +fn ln_dlc_ldk_auto_close_accepted_test() { + let mut test_params = test_init(); + + go_to_established_target_state(&test_params, TargetState::Accepted, false); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + Offered + ); + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + Accepted + ); + + let sub_channel = test_params + .bob_node + .dlc_manager + .get_store() + .get_sub_channel(test_params.channel_id) + .unwrap() + .unwrap(); + let commit_tx = if let SubChannelState::Accepted(a) = &sub_channel.state { + a.commitment_transactions[0].clone() + } else { + unreachable!(); + }; + + ldk_auto_close(&mut test_params, &commit_tx); +} + +#[test] +#[ignore] +fn ln_dlc_ldk_auto_close_confirmed_test() { + let mut test_params = test_init(); + + go_to_established_target_state(&test_params, TargetState::Confirmed, true); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + Accepted + ); + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + Confirmed + ); + + let sub_channel = test_params + .bob_node + .dlc_manager + .get_store() + .get_sub_channel(test_params.channel_id) + .unwrap() + .unwrap(); + let commit_tx = if let SubChannelState::Confirmed(a) = &sub_channel.state { + a.commitment_transactions[0].clone() + } else { + unreachable!(); + }; + + ldk_auto_close(&mut test_params, &commit_tx); +} +#[test] +#[ignore] +fn ln_dlc_ldk_auto_close_finalized_test() { + let mut test_params = test_init(); + + go_to_established_target_state(&test_params, TargetState::Finalized, false); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + Confirmed + ); + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + Finalized + ); + + let commit_tx = + get_commit_tx_from_node(&test_params.bob_node, &test_params.funding_txo).remove(0); + + mocks::mock_time::set_time(EVENT_MATURITY as u64); + + ldk_auto_close(&mut test_params, &commit_tx); +} + +#[test] +#[ignore] +fn ln_dlc_ldk_auto_close_close_offered_test() { + let mut test_params = test_init(); + + make_ln_payment(&test_params.alice_node, &test_params.bob_node, 900000); + + open_sub_channel(&test_params); + + go_to_off_chain_close_state(&test_params, TargetState::OfferReceived, false); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + CloseOffered + ); + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + CloseOffered + ); + + mocks::mock_time::set_time(EVENT_MATURITY as u64); + + let commit_tx = + get_commit_tx_from_node(&test_params.bob_node, &test_params.funding_txo).remove(0); + + ldk_auto_close(&mut test_params, &commit_tx); +} + +#[test] +#[ignore] +fn ln_dlc_ldk_auto_close_close_accepted_test() { + let mut test_params = test_init(); + + make_ln_payment(&test_params.alice_node, &test_params.bob_node, 900000); + + open_sub_channel(&test_params); + + go_to_off_chain_close_state(&test_params, TargetState::Accepted, false); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + CloseOffered + ); + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + CloseAccepted + ); + + mocks::mock_time::set_time(EVENT_MATURITY as u64); + + let commit_tx = + get_commit_tx_from_node(&test_params.bob_node, &test_params.funding_txo).remove(0); + + ldk_auto_close(&mut test_params, &commit_tx); +} + +#[test] +#[ignore] +fn ln_dlc_ldk_auto_close_close_confirmed_test() { + let mut test_params = test_init(); + + make_ln_payment(&test_params.alice_node, &test_params.bob_node, 900000); + + open_sub_channel(&test_params); + + go_to_off_chain_close_state(&test_params, TargetState::Confirmed, true); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + CloseAccepted + ); + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + CloseConfirmed + ); + + mocks::mock_time::set_time(EVENT_MATURITY as u64); + + let commit_tx = + get_commit_tx_from_node(&test_params.bob_node, &test_params.funding_txo).remove(0); + + ldk_auto_close(&mut test_params, &commit_tx); +} + +#[test] +#[ignore] +fn ln_dlc_ldk_auto_close_close_finalized_test() { + let mut test_params = test_init(); + + make_ln_payment(&test_params.alice_node, &test_params.bob_node, 900000); + + open_sub_channel(&test_params); + + mocks::mock_time::set_time(EVENT_MATURITY as u64); + + go_to_off_chain_close_state(&test_params, TargetState::Finalized, true); + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + CloseConfirmed + ); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id; + OffChainClosed + ); + + let commit_tx = + get_commit_tx_from_node(&test_params.bob_node, &test_params.funding_txo).remove(0); + + ldk_auto_close(&mut test_params, &commit_tx); +} + +fn test_init() -> LnDlcTestParams { + env_logger::init(); + let (_, _, sink_rpc) = init_clients(); + + let test_params = get_enum_test_params_custom_collateral(1, 1, None, 60000, 40000); + + let electrs = Arc::new(ElectrsBlockchainProvider::new( + "http://localhost:3004/".to_string(), + Network::Regtest, + )); + + let mut alice_node = create_ln_node( + "Alice".to_string(), + "./.ldk/.alicedir", + &test_params, + &electrs, + ); + let mut bob_node = create_ln_node("Bob".to_string(), "./.ldk/.bobdir", &test_params, &electrs); + + let alice_fund_address = alice_node.wallet.get_new_address().unwrap(); + + sink_rpc + .send_to_address( + &alice_fund_address, + Amount::from_btc(0.002).unwrap(), + None, + None, + None, + None, + None, + None, + ) + .unwrap(); + + let generate_blocks = |nb_blocks: u64| generate_blocks(nb_blocks, &electrs, &sink_rpc); + + generate_blocks(6); + + refresh_wallet(&alice_node.wallet, 200000); + + alice_node.update_to_chain_tip(); + bob_node.update_to_chain_tip(); + + let mut alice_descriptor = MockSocketDescriptor::new(0, bob_node.peer_manager.clone()); + let mut bob_descriptor = MockSocketDescriptor::new(1, alice_node.peer_manager.clone()); + + alice_descriptor.counter_descriptor = Some(Box::new(bob_descriptor.clone())); + bob_descriptor.counter_descriptor = Some(Box::new(alice_descriptor.clone())); + + ln_channel_setup( + &mut alice_node, + &mut bob_node, + &alice_descriptor, + &mut bob_descriptor, + &generate_blocks, + ); + + std::thread::sleep(std::time::Duration::from_secs(2)); + + let channel_details = alice_node.channel_manager.list_channels().remove(0); + let funding_txo = channel_details.funding_txo.expect("to have a funding txo"); + let channel_id = channel_details.channel_id; + let alice_node_id = alice_node.channel_manager.get_our_node_id(); + let bob_node_id = bob_node.channel_manager.get_our_node_id(); + + LnDlcTestParams { + alice_node, + bob_node, + alice_node_id, + bob_node_id, + alice_descriptor, + bob_descriptor, + electrs, + sink_rpc, + funding_txo, + channel_id, + test_params, + } +} + +fn generate_blocks(nb_blocks: u64, electrs: &Arc, sink_rpc: &Client) { + let prev_blockchain_height = electrs.get_blockchain_height().unwrap(); + + let sink_address = sink_rpc.get_new_address(None, None).expect("RPC Error"); + sink_rpc + .generate_to_address(nb_blocks, &sink_address) + .expect("RPC Error"); + + // Wait for electrs to have processed the new blocks + let mut cur_blockchain_height = prev_blockchain_height; + while cur_blockchain_height < prev_blockchain_height + nb_blocks { + std::thread::sleep(std::time::Duration::from_millis(200)); + cur_blockchain_height = electrs.get_blockchain_height().unwrap(); + } +} + +fn ln_channel_setup( + alice_node: &mut LnDlcParty, + bob_node: &mut LnDlcParty, + alice_descriptor: &MockSocketDescriptor, + bob_descriptor: &mut MockSocketDescriptor, + generate_blocks: &F, +) where + F: Fn(u64), +{ + let initial_send = alice_node + .peer_manager + .new_outbound_connection( + bob_node.channel_manager.get_our_node_id(), + alice_descriptor.clone(), + None, + ) + .unwrap(); + + bob_node + .peer_manager + .new_inbound_connection(bob_descriptor.clone(), None) + .unwrap(); + + // bob_node.peer_manager.timer_tick_occurred(); + + bob_node + .peer_manager + .read_event(bob_descriptor, &initial_send) + .unwrap(); + bob_node.peer_manager.process_events(); + alice_node.peer_manager.process_events(); + bob_node.peer_manager.process_events(); + + alice_node + .channel_manager + .create_channel( + bob_node.channel_manager.get_our_node_id(), + 180000, + 0, + 1, + None, + ) + .unwrap(); + + bob_node.process_events(); + alice_node.process_events(); + bob_node.process_events(); + + alice_node.process_events(); + bob_node.process_events(); + + generate_blocks(6); + + alice_node.update_to_chain_tip(); + bob_node.update_to_chain_tip(); + + alice_node.process_events(); + bob_node.process_events(); + + assert_eq!(1, alice_node.channel_manager.list_channels().len()); + + while alice_node.channel_manager.list_usable_channels().len() != 1 { + generate_blocks(1); + alice_node.update_to_chain_tip(); + bob_node.update_to_chain_tip(); + alice_node.peer_manager.process_events(); + bob_node.peer_manager.process_events(); + std::thread::sleep(std::time::Duration::from_millis(200)); + } + + assert_eq!(1, alice_node.channel_manager.list_usable_channels().len()); + + make_ln_payment(alice_node, bob_node, 90000000); +} + +fn make_ln_payment(alice_node: &LnDlcParty, bob_node: &LnDlcParty, final_value_msat: u64) { + let payment_params = lightning::routing::router::PaymentParameters::from_node_id( + bob_node.channel_manager.get_our_node_id(), + 70, + ); + + let payment_preimage = lightning::ln::PaymentPreimage([0; 32]); + let payment_hash = lightning::ln::PaymentHash( + bitcoin::hashes::sha256::Hash::hash(&payment_preimage.0[..]).into_inner(), + ); + let _ = bob_node + .channel_manager + .create_inbound_payment_for_hash(payment_hash, None, 7200, None) + .unwrap(); + + let scorer = TestScorer::with_penalty(0); + let random_seed_bytes = bob_node.keys_manager.get_secure_random_bytes(); + let route_params = RouteParameters { + payment_params, + final_value_msat, + }; + + let route = lightning::routing::router::find_route( + &alice_node.channel_manager.get_our_node_id(), + &route_params, + &alice_node.network_graph, + Some( + &alice_node + .channel_manager + .list_usable_channels() + .iter() + .collect::>(), + ), + alice_node.logger.clone(), + &scorer, + &(), + &random_seed_bytes, + ) + .unwrap(); + + let mut payment_id_val = [0u8; 32]; + + payment_id_val[31] += PAYMENT_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + + let payment_id = PaymentId(payment_id_val); + + alice_node + .channel_manager + .send_spontaneous_payment( + &route, + Some(payment_preimage), + RecipientOnionFields::spontaneous_empty(), + payment_id, + ) + .unwrap(); + + bob_node.process_events(); + alice_node.process_events(); + bob_node.process_events(); + alice_node.process_events(); + bob_node.process_events(); + alice_node.process_events(); + bob_node.process_events(); + alice_node.process_events(); + bob_node.process_events(); + alice_node.process_events(); + bob_node.process_events(); +} + +fn get_commit_tx_from_node( + node: &LnDlcParty, + funding_txo: &lightning::chain::transaction::OutPoint, +) -> Vec { + node.chain_monitor + .get_latest_holder_commitment_txn(funding_txo) + .expect("to be able to get latest holder commitment transaction") +} + +fn settle(test_params: &LnDlcTestParams, channel_id: &ChannelId) { + let (settle_offer, bob_key) = test_params + .alice_node + .dlc_manager + .settle_offer( + channel_id, + test_params.test_params.contract_input.accept_collateral, + ) + .unwrap(); + + test_params + .bob_node + .dlc_manager + .on_dlc_message( + &Message::Channel(ChannelMessage::SettleOffer(settle_offer)), + test_params.alice_node.channel_manager.get_our_node_id(), + ) + .unwrap(); + + let (settle_accept, alice_key) = test_params + .bob_node + .dlc_manager + .accept_settle_offer(channel_id) + .unwrap(); + + let msg = test_params + .alice_node + .dlc_manager + .on_dlc_message( + &Message::Channel(ChannelMessage::SettleAccept(settle_accept)), + bob_key, + ) + .unwrap() + .unwrap(); + + let msg = test_params + .bob_node + .dlc_manager + .on_dlc_message(&msg, alice_key) + .unwrap() + .unwrap(); + + test_params + .alice_node + .dlc_manager + .on_dlc_message(&msg, bob_key) + .unwrap(); +} + +fn renew(test_params: &LnDlcTestParams, dlc_channel_id: &ChannelId) { + let (renew_offer, _) = test_params + .alice_node + .dlc_manager + .renew_offer( + dlc_channel_id, + test_params.test_params.contract_input.accept_collateral, + &test_params.test_params.contract_input, + ) + .unwrap(); + + test_params + .bob_node + .dlc_manager + .on_dlc_message( + &Message::Channel(ChannelMessage::RenewOffer(renew_offer)), + test_params.alice_node.channel_manager.get_our_node_id(), + ) + .unwrap(); + + let (accept, _) = test_params + .bob_node + .dlc_manager + .accept_renew_offer(dlc_channel_id) + .unwrap(); + + let msg = test_params + .alice_node + .dlc_manager + .on_dlc_message( + &Message::Channel(ChannelMessage::RenewAccept(accept)), + test_params.bob_node_id, + ) + .unwrap() + .unwrap(); + + let msg = test_params + .bob_node + .dlc_manager + .on_dlc_message(&msg, test_params.alice_node_id) + .unwrap() + .unwrap(); + + let msg = test_params + .alice_node + .dlc_manager + .on_dlc_message(&msg, test_params.bob_node_id) + .unwrap() + .unwrap(); + + test_params + .bob_node + .dlc_manager + .on_dlc_message(&msg, test_params.alice_node_id) + .unwrap(); +} + +fn cheat_with_revoked_tx(cheat_tx: &Transaction, test_params: &mut LnDlcTestParams) { + test_params.electrs.broadcast_transactions(&[cheat_tx]); + + // wait for cheat tx to be confirmed + test_params.generate_blocks(6); + + test_params.bob_node.update_to_chain_tip(); + + test_params.bob_node.process_events(); + + // LDK should have reacted, this should include a punish tx + test_params.generate_blocks(1); + + test_params.bob_node.update_to_chain_tip(); + + test_params.bob_node.process_events(); + + std::thread::sleep(std::time::Duration::from_secs(1)); + + let vout = cheat_tx + .output + .iter() + .position(|x| x.script_pubkey.is_v0_p2wsh()) + .expect("to have a p2wsh output"); + + let outspends = test_params.electrs.get_outspends(&cheat_tx.txid()).unwrap(); + + let outspend_info = outspends + .iter() + .filter_map(|x| { + if let OutSpendResp::Spent(s) = x { + Some(s) + } else { + None + } + }) + .collect::>(); + + let spend_tx = test_params + .electrs + .get_transaction(&outspend_info[vout].txid) + .unwrap(); + + test_params.generate_blocks(6); + + test_params.bob_node.update_to_chain_tip(); + + test_params.bob_node.process_events(); + + let mut outspend_info = vec![]; + while outspend_info.is_empty() { + let outspends = test_params.electrs.get_outspends(&spend_tx.txid()).unwrap(); + outspend_info = outspends + .iter() + .filter_map(|x| { + if let OutSpendResp::Spent(s) = x { + Some(s) + } else { + None + } + }) + .cloned() + .collect::>(); + std::thread::sleep(std::time::Duration::from_secs(1)); + } + + let claim_tx = test_params + .electrs + .get_transaction(&outspend_info[0].txid) + .unwrap(); + + let receive_addr = + Address::from_script(&claim_tx.output[0].script_pubkey, Network::Regtest).unwrap(); + + assert!(test_params + .bob_node + .dlc_manager + .get_store() + .get_addresses() + .unwrap() + .iter() + .any(|x| *x == receive_addr)); +} + +fn generate_offer( + test_params: &TestParams, + offerer: &LnDlcParty, + channel_id: &ChannelId, +) -> SubChannelOffer { + let oracle_announcements = test_params + .oracles + .iter() + .map(|x| { + x.get_announcement( + &test_params.contract_input.contract_infos[0] + .oracles + .event_id, + ) + .unwrap() + }) + .collect::>(); + + let offer = offerer + .sub_channel_manager + .offer_sub_channel( + channel_id, + &test_params.contract_input, + &[oracle_announcements], + ) + .unwrap(); + + assert_sub_channel_state!(offerer.sub_channel_manager, channel_id, Offered); + + offer +} + +fn open_sub_channel(test_params: &LnDlcTestParams) { + offer_sub_channel_internal(test_params, false); +} + +fn offer_sub_channel_with_reconnect(test_params: &LnDlcTestParams) { + offer_sub_channel_internal(test_params, true); +} + +fn offer_sub_channel_internal(test_params: &LnDlcTestParams, do_reconnect: bool) { + let offer = generate_offer( + &test_params.test_params, + &test_params.alice_node, + &test_params.channel_id, + ); + + if do_reconnect { + reconnect(test_params); + // Alice should resend the offer message to bob as he has not received it yet. + let mut msgs = test_params.alice_node.sub_channel_manager.periodic_check(); + assert_eq!(1, msgs.len()); + if let (SubChannelMessage::Offer(o), p) = msgs.pop().unwrap() { + assert_eq!(p, test_params.bob_node.channel_manager.get_our_node_id()); + assert_eq!(o, offer); + } else { + panic!("Expected an offer message"); + } + + assert_eq!( + 0, + test_params + .bob_node + .sub_channel_manager + .periodic_check() + .len() + ); + } + + test_params + .bob_node + .sub_channel_manager + .on_sub_channel_message( + &SubChannelMessage::Offer(offer), + &test_params.alice_node.channel_manager.get_our_node_id(), + ) + .unwrap(); + + if do_reconnect { + reconnect(test_params); + assert_eq!( + 0, + test_params + .alice_node + .sub_channel_manager + .periodic_check() + .len() + ); + assert_eq!( + 0, + test_params + .bob_node + .sub_channel_manager + .periodic_check() + .len() + ); + } + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + Offered + ); + + let (_, mut accept) = test_params + .bob_node + .sub_channel_manager + .accept_sub_channel(&test_params.channel_id) + .unwrap(); + + if do_reconnect { + reconnect(test_params); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + Offered + ); + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + Offered + ); + + test_params + .alice_node + .sub_channel_manager + .on_sub_channel_message( + &SubChannelMessage::Accept(accept.clone()), + &test_params.bob_node.channel_manager.get_our_node_id(), + ) + .expect_err("Should not accept a stale accept message"); + + // Bob should re-send the accept message + let mut msgs = test_params.bob_node.sub_channel_manager.periodic_check(); + assert_eq!(1, msgs.len()); + if let (SubChannelMessage::Accept(a), p) = msgs.pop().unwrap() { + assert_eq!(p, test_params.alice_node.channel_manager.get_our_node_id()); + assert_eq_accept(&a, &accept); + accept = a; + } else { + panic!("Expected an accept message"); + } + + assert_eq!( + 0, + test_params + .alice_node + .sub_channel_manager + .periodic_check() + .len() + ); + } + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + Accepted + ); + + let mut confirm = test_params + .alice_node + .sub_channel_manager + .on_sub_channel_message( + &SubChannelMessage::Accept(accept), + &test_params.bob_node.channel_manager.get_our_node_id(), + ) + .unwrap() + .unwrap(); + + if do_reconnect { + reconnect(test_params); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + Offered + ); + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + Offered + ); + + // Bob should re-send the accept message + let mut msgs = test_params.bob_node.sub_channel_manager.periodic_check(); + assert_eq!(1, msgs.len()); + assert_eq!( + 0, + test_params + .alice_node + .sub_channel_manager + .periodic_check() + .len() + ); + if let (SubChannelMessage::Accept(a), p) = msgs.pop().unwrap() { + assert_eq!(p, test_params.alice_node.channel_manager.get_our_node_id()); + confirm = test_params + .alice_node + .sub_channel_manager + .on_sub_channel_message( + &SubChannelMessage::Accept(a), + &test_params.bob_node.channel_manager.get_our_node_id(), + ) + .unwrap() + .unwrap(); + } else { + panic!("Expected an accept message"); + } + } + + test_params.alice_node.process_events(); + let mut finalize = test_params + .bob_node + .sub_channel_manager + .on_sub_channel_message( + &confirm, + &test_params.alice_node.channel_manager.get_our_node_id(), + ) + .unwrap() + .unwrap(); + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + Finalized + ); + + test_params.bob_node.process_events(); + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + Confirmed + ); + + if do_reconnect { + reconnect(test_params); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + Offered + ); + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + Offered + ); + + // Bob should re-send the accept message + let mut msgs = test_params.bob_node.sub_channel_manager.periodic_check(); + assert_eq!(1, msgs.len()); + assert_eq!( + 0, + test_params + .alice_node + .sub_channel_manager + .periodic_check() + .len() + ); + if let (SubChannelMessage::Accept(a), p) = msgs.pop().unwrap() { + assert_eq!(p, test_params.alice_node.channel_manager.get_our_node_id()); + let _ = test_params + .alice_node + .sub_channel_manager + .on_sub_channel_message( + &SubChannelMessage::Accept(a), + &test_params.bob_node.channel_manager.get_our_node_id(), + ) + .unwrap() + .unwrap(); + reconnect(test_params); + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + Offered + ); + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + Offered + ); + let mut msgs = test_params.bob_node.sub_channel_manager.periodic_check(); + assert_eq!(1, msgs.len()); + assert_eq!( + 0, + test_params + .alice_node + .sub_channel_manager + .periodic_check() + .len() + ); + if let (SubChannelMessage::Accept(a), p) = msgs.pop().unwrap() { + assert_eq!(p, test_params.alice_node.channel_manager.get_our_node_id()); + let confirm = test_params + .alice_node + .sub_channel_manager + .on_sub_channel_message( + &SubChannelMessage::Accept(a), + &test_params.bob_node.channel_manager.get_our_node_id(), + ) + .unwrap() + .unwrap(); + finalize = test_params + .bob_node + .sub_channel_manager + .on_sub_channel_message( + &confirm, + &test_params.alice_node.channel_manager.get_our_node_id(), + ) + .unwrap() + .unwrap(); + } else { + panic!(); + } + } else { + panic!("Expected an accept message"); + } + } + + let revoke = test_params + .alice_node + .sub_channel_manager + .on_sub_channel_message( + &finalize, + &test_params.bob_node.channel_manager.get_our_node_id(), + ) + .unwrap() + .unwrap(); + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + Signed + ); + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + Finalized + ); + + if do_reconnect { + reconnect(test_params); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + Signed + ); + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + Finalized + ); + + assert_eq!( + 0, + test_params + .alice_node + .sub_channel_manager + .periodic_check() + .len() + ); + assert_eq!( + 0, + test_params + .bob_node + .sub_channel_manager + .periodic_check() + .len() + ); + } else { + test_params + .bob_node + .sub_channel_manager + .on_sub_channel_message( + &revoke, + &test_params.alice_node.channel_manager.get_our_node_id(), + ) + .unwrap(); + } + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + Signed + ); + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + Signed + ); + + test_params.alice_node.process_events(); +} + +fn reconnect(test_params: &LnDlcTestParams) { + test_params + .alice_node + .peer_manager + .socket_disconnected(&test_params.alice_descriptor); + + test_params + .bob_node + .peer_manager + .socket_disconnected(&test_params.bob_descriptor); + + let initial_send = test_params + .alice_node + .peer_manager + .new_outbound_connection( + test_params.bob_node.channel_manager.get_our_node_id(), + test_params.alice_descriptor.clone(), + None, + ) + .unwrap(); + + test_params + .bob_node + .peer_manager + .new_inbound_connection(test_params.bob_descriptor.clone(), None) + .unwrap(); + + test_params + .bob_node + .peer_manager + .read_event(&mut test_params.bob_descriptor.clone(), &initial_send) + .unwrap(); + test_params.bob_node.peer_manager.process_events(); + test_params.alice_node.peer_manager.process_events(); + test_params.bob_node.peer_manager.process_events(); + test_params.bob_node.peer_manager.process_events(); + test_params.alice_node.peer_manager.process_events(); + test_params.bob_node.peer_manager.process_events(); + + test_params.alice_node.process_events(); + test_params.bob_node.process_events(); + test_params.alice_node.process_events(); + test_params.bob_node.process_events(); + test_params.alice_node.process_events(); + test_params.bob_node.process_events(); + test_params.alice_node.process_events(); + test_params.bob_node.process_events(); +} + +fn reject_offer(test_params: &LnDlcTestParams) { + let offer = generate_offer( + &test_params.test_params, + &test_params.alice_node, + &test_params.channel_id, + ); + + test_params + .bob_node + .sub_channel_manager + .on_sub_channel_message( + &SubChannelMessage::Offer(offer), + &test_params.alice_node.channel_manager.get_our_node_id(), + ) + .unwrap(); + + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + Offered + ); + + let reject = test_params + .bob_node + .sub_channel_manager + .reject_sub_channel_offer(test_params.channel_id) + .unwrap(); + + assert_sub_channel_state!(test_params.bob_node.sub_channel_manager, &test_params.channel_id; Rejected); + + test_params + .alice_node + .sub_channel_manager + .on_sub_channel_message( + &SubChannelMessage::Reject(reject), + &test_params.bob_node.channel_manager.get_our_node_id(), + ) + .unwrap(); + + assert_sub_channel_state!(test_params.alice_node.sub_channel_manager, &test_params.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 + ); +} + +fn off_chain_close_offer(test_params: &LnDlcTestParams, do_reconnect: bool) { + let sub_channel = test_params + .alice_node + .dlc_manager + .get_store() + .get_sub_channel(test_params.channel_id) + .unwrap() + .unwrap(); + + let dlc_channel_id = sub_channel.get_dlc_channel_id(0).unwrap(); + assert_channel_contract_state!( + test_params.alice_node.dlc_manager, + dlc_channel_id, + Confirmed + ); + + let (close_offer, _) = test_params + .alice_node + .sub_channel_manager + .offer_subchannel_close( + &test_params.channel_id, + test_params.test_params.contract_input.accept_collateral, + ) + .unwrap(); + + if do_reconnect { + reconnect(test_params); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + CloseOffered + ); + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + Signed + ); + let mut msgs = test_params.alice_node.sub_channel_manager.periodic_check(); + assert_eq!(1, msgs.len()); + if let (SubChannelMessage::CloseOffer(c), p) = msgs.pop().unwrap() { + assert_eq!(p, test_params.bob_node_id); + assert_eq!(c, close_offer); + } else { + panic!("Expected a close offer message"); + } + } + + test_params + .bob_node + .sub_channel_manager + .on_sub_channel_message( + &SubChannelMessage::CloseOffer(close_offer), + &test_params.alice_node_id, + ) + .unwrap(); + + if do_reconnect { + reconnect(test_params); + + assert_eq!( + 0, + test_params + .alice_node + .sub_channel_manager + .periodic_check() + .len() + ); + assert_eq!( + 0, + test_params + .bob_node + .sub_channel_manager + .periodic_check() + .len() + ); + } +} + +fn off_chain_close_finalize(test_params: &LnDlcTestParams, do_reconnect: bool) { + let sub_channel = test_params + .alice_node + .dlc_manager + .get_store() + .get_sub_channel(test_params.channel_id) + .unwrap() + .unwrap(); + + let dlc_channel_id = sub_channel.get_dlc_channel_id(0).unwrap(); + let contract_id = assert_channel_contract_state!( + test_params.alice_node.dlc_manager, + dlc_channel_id, + Confirmed + ); + let (mut close_accept, _) = test_params + .bob_node + .sub_channel_manager + .accept_subchannel_close_offer(&test_params.channel_id) + .unwrap(); + + if do_reconnect { + reconnect(test_params); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + CloseOffered + ); + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + CloseOffered + ); + + assert_eq!( + 0, + test_params + .alice_node + .sub_channel_manager + .periodic_check() + .len() + ); + + test_params + .alice_node + .sub_channel_manager + .on_sub_channel_message( + &SubChannelMessage::CloseAccept(close_accept), + &test_params.bob_node_id, + ) + .expect_err("Should not accept a stale CloseAccept message"); + + let mut msgs = test_params.bob_node.sub_channel_manager.periodic_check(); + assert_eq!(1, msgs.len()); + if let (SubChannelMessage::CloseAccept(c), p) = msgs.pop().unwrap() { + assert_eq!(p, test_params.alice_node_id); + close_accept = c; + } else { + panic!("Expected a close accept message"); + } + } + + let mut close_confirm = test_params + .alice_node + .sub_channel_manager + .on_sub_channel_message( + &SubChannelMessage::CloseAccept(close_accept), + &test_params.bob_node_id, + ) + .unwrap() + .unwrap(); + + if do_reconnect { + reconnect(test_params); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &test_params.channel_id, + CloseOffered + ); + assert_sub_channel_state!( + test_params.bob_node.sub_channel_manager, + &test_params.channel_id, + CloseOffered + ); + + assert_eq!( + 0, + test_params + .alice_node + .sub_channel_manager + .periodic_check() + .len() + ); + let mut msgs = test_params.bob_node.sub_channel_manager.periodic_check(); + assert_eq!(1, msgs.len()); + if let (SubChannelMessage::CloseAccept(c), _) = msgs.pop().unwrap() { + let close_confirm2 = test_params + .alice_node + .sub_channel_manager + .on_sub_channel_message( + &SubChannelMessage::CloseAccept(c), + &test_params.bob_node.channel_manager.get_our_node_id(), + ) + .unwrap() + .unwrap(); + close_confirm = close_confirm2; + } else { + panic!("Expected a close accept message"); + } + } + + let mut close_finalize = test_params + .bob_node + .sub_channel_manager + .on_sub_channel_message( + &close_confirm, + &test_params.alice_node.channel_manager.get_our_node_id(), + ) + .unwrap() + .unwrap(); + + if do_reconnect { + reconnect(test_params); + + assert_eq!( + 0, + test_params + .alice_node + .sub_channel_manager + .periodic_check() + .len() + ); + let mut msgs = test_params.bob_node.sub_channel_manager.periodic_check(); + assert_eq!(1, msgs.len()); + if let (SubChannelMessage::CloseFinalize(c), _) = msgs.pop().unwrap() { + close_finalize = SubChannelMessage::CloseFinalize(c); + } else { + panic!("Expected a close finalize message"); + } + } + + test_params + .alice_node + .sub_channel_manager + .on_sub_channel_message( + &close_finalize, + &test_params.bob_node.channel_manager.get_our_node_id(), + ) + .unwrap(); + + assert_contract_state_unlocked!(test_params.alice_node.dlc_manager, contract_id, Closed); + assert_contract_state_unlocked!(test_params.bob_node.dlc_manager, contract_id, Closed); + + assert_channel_state_unlocked!( + test_params.alice_node.dlc_manager, + dlc_channel_id, + CollaborativelyClosed + ); + assert_channel_state_unlocked!( + test_params.bob_node.dlc_manager, + dlc_channel_id, + CollaborativelyClosed + ); + + assert_sub_channel_state!(test_params.alice_node.sub_channel_manager, &test_params.channel_id; OffChainClosed); + assert_sub_channel_state!(test_params.bob_node.sub_channel_manager, &test_params.channel_id; OffChainClosed); +} + +fn off_chain_close(test_params: &LnDlcTestParams) { + off_chain_close_internal(test_params, false); +} + +fn off_chain_close_with_reconnect(test_params: &LnDlcTestParams) { + off_chain_close_internal(test_params, true); +} + +fn off_chain_close_internal(test_params: &LnDlcTestParams, do_reconnect: bool) { + off_chain_close_offer(test_params, do_reconnect); + off_chain_close_finalize(test_params, do_reconnect); +} + +fn force_close_stable(test_params: &mut LnDlcTestParams) { + let commit_tx = + get_commit_tx_from_node(&test_params.alice_node, &test_params.funding_txo).remove(0); + let sub_channel = test_params + .alice_node + .dlc_manager + .get_store() + .get_sub_channel(test_params.channel_id) + .unwrap() + .unwrap(); + + let dlc_channel_id_alice = sub_channel.get_dlc_channel_id(0).unwrap(); + + let channel = test_params + .alice_node + .dlc_manager + .get_store() + .get_channel(&dlc_channel_id_alice) + .unwrap() + .unwrap(); + + let contract_id = channel.get_contract_id(); + + let sub_channel = test_params + .bob_node + .dlc_manager + .get_store() + .get_sub_channel(sub_channel.channel_id) + .unwrap() + .unwrap(); + let dlc_channel_id_bob = sub_channel.get_dlc_channel_id(0); + + let channel_id = sub_channel.channel_id; + + test_params + .alice_node + .sub_channel_manager + .force_close_sub_channel(&channel_id) + .expect("To be able to force close offered channel"); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &channel_id, + Closing + ); + + test_params.generate_blocks(1); + + assert_sub_channel_state!( + test_params.alice_node.sub_channel_manager, + &channel_id, + Closing + ); + + test_params.generate_blocks(500); + + test_params.alice_node.update_to_chain_tip(); + test_params.alice_node.process_events(); + test_params.bob_node.update_to_chain_tip(); + test_params.bob_node.process_events(); + + assert_sub_channel_state!(test_params.alice_node.sub_channel_manager, &channel_id; OnChainClosed); + assert_sub_channel_state!(test_params.bob_node.sub_channel_manager, &channel_id; CounterOnChainClosed); + + if let Some(contract_id) = contract_id { + assert_channel_state_unlocked!( + test_params.alice_node.dlc_manager, + dlc_channel_id_alice, + Signed, + Closing + ); + assert_channel_state_unlocked!( + test_params.bob_node.dlc_manager, + dlc_channel_id_alice, + Signed, + Closing + ); + + test_params.generate_blocks(dlc_manager::manager::CET_NSEQUENCE as u64); + + test_params.generate_blocks(1); + test_params.alice_node.update_to_chain_tip(); + test_params.bob_node.update_to_chain_tip(); + + assert_channel_state_unlocked!( + test_params.alice_node.dlc_manager, + dlc_channel_id_alice, + Closed + ); + assert_channel_state_unlocked!( + test_params.bob_node.dlc_manager, + dlc_channel_id_alice, + CounterClosed + ); + + assert_contract_state_unlocked!(test_params.alice_node.dlc_manager, contract_id, PreClosed); + assert_contract_state_unlocked!(test_params.bob_node.dlc_manager, contract_id, PreClosed); + + test_params.generate_blocks(6); + + test_params.alice_node.update_to_chain_tip(); + test_params.bob_node.update_to_chain_tip(); + + assert_contract_state_unlocked!(test_params.alice_node.dlc_manager, contract_id, Closed); + assert_contract_state_unlocked!(test_params.bob_node.dlc_manager, contract_id, Closed); + } else { + assert_channel_state_unlocked!( + test_params.alice_node.dlc_manager, + dlc_channel_id_alice, + Closed + ); + if let Some(dlc_channel_id_bob) = dlc_channel_id_bob { + assert_channel_state_unlocked!( + test_params.bob_node.dlc_manager, + dlc_channel_id_bob, + CounterClosed + ); + } + } + + test_params.generate_blocks(500); + + test_params.alice_node.update_to_chain_tip(); + test_params.alice_node.process_events(); + test_params.bob_node.update_to_chain_tip(); + test_params.bob_node.process_events(); + + test_params.generate_blocks(2); + + test_params.alice_node.update_to_chain_tip(); + test_params.alice_node.process_events(); + test_params.bob_node.update_to_chain_tip(); + test_params.bob_node.process_events(); + + assert!(test_params + .alice_node + .dlc_manager + .get_chain_monitor() + .lock() + .unwrap() + .is_empty()); + assert!(test_params + .bob_node + .dlc_manager + .get_chain_monitor() + .lock() + .unwrap() + .is_empty()); + + let all_spent = test_params + .electrs + .get_outspends(&commit_tx.txid()) + .unwrap() + .into_iter() + .all(|x| { + if let OutSpendResp::Spent(_) = x { + true + } else { + false + } + }); + + assert!(all_spent); +} + +fn force_close_mid_protocol( + test_params: &mut LnDlcTestParams, + revert_closer: bool, + commit_tx: &Transaction, +) { + let electrs = &test_params.electrs; + let sink_rpc = &test_params.sink_rpc; + + let generate_blocks = |nb_blocks: u64| generate_blocks(nb_blocks, electrs, sink_rpc); + + let (closer, closee) = if !revert_closer { + (&mut test_params.alice_node, &mut test_params.bob_node) + } else { + (&mut test_params.bob_node, &mut test_params.alice_node) + }; + + let sub_channel = closer + .dlc_manager + .get_store() + .get_sub_channel(test_params.channel_id) + .unwrap() + .unwrap(); + let dlc_channel_id_closer = sub_channel.get_dlc_channel_id(0).unwrap(); + + let sub_channel = closee + .dlc_manager + .get_store() + .get_sub_channel(sub_channel.channel_id) + .unwrap() + .unwrap(); + let dlc_channel_id_closee = sub_channel.get_dlc_channel_id(0); + + let channel_id = sub_channel.channel_id; + + closer + .sub_channel_manager + .force_close_sub_channel(&channel_id) + .expect("To be able to force close offered channel"); + + generate_blocks(500); + closer.update_to_chain_tip(); + closer.process_events(); + + assert_sub_channel_state!(closer.sub_channel_manager, &channel_id; OnChainClosed); + + generate_blocks(3); + + closer.update_to_chain_tip(); + closee.update_to_chain_tip(); + closee.process_events(); + + assert_sub_channel_state!(closee.sub_channel_manager, &channel_id; CounterOnChainClosed); + + generate_blocks(500); + + closer.update_to_chain_tip(); + closer.process_events(); + closee.update_to_chain_tip(); + closee.process_events(); + + generate_blocks(2); + + closer.update_to_chain_tip(); + closer.process_events(); + closee.update_to_chain_tip(); + closee.process_events(); + + assert_channel_state_unlocked!(closer.dlc_manager, dlc_channel_id_closer, Closed); + if let Some(dlc_channel_id_closee) = dlc_channel_id_closee { + assert_channel_state_unlocked!(closee.dlc_manager, dlc_channel_id_closee, CounterClosed); + } + + assert!(closer + .dlc_manager + .get_chain_monitor() + .lock() + .unwrap() + .is_empty()); + assert!(closee + .dlc_manager + .get_chain_monitor() + .lock() + .unwrap() + .is_empty()); + + let all_spent = electrs + .get_outspends(&commit_tx.txid()) + .unwrap() + .into_iter() + .all(|x| { + if let OutSpendResp::Spent(_) = x { + true + } else { + false + } + }); + + assert!(all_spent); +} + +fn go_to_established_target_state( + test_params: &LnDlcTestParams, + target_state: TargetState, + reverse_offerer: bool, +) { + let (offerer, accepter) = if !reverse_offerer { + (&test_params.alice_node, &test_params.bob_node) + } else { + (&test_params.bob_node, &test_params.alice_node) + }; + + let offer = generate_offer(&test_params.test_params, offerer, &test_params.channel_id); + + accepter + .sub_channel_manager + .on_sub_channel_message( + &SubChannelMessage::Offer(offer), + &offerer.channel_manager.get_our_node_id(), + ) + .unwrap(); + + if target_state == TargetState::OfferReceived { + return; + } + + let (_, accept) = accepter + .sub_channel_manager + .accept_sub_channel(&test_params.channel_id) + .unwrap(); + + if target_state == TargetState::Accepted { + return; + } + + let confirm = offerer + .sub_channel_manager + .on_sub_channel_message( + &SubChannelMessage::Accept(accept), + &accepter.channel_manager.get_our_node_id(), + ) + .unwrap() + .unwrap(); + + if target_state == TargetState::Confirmed { + return; + } + + accepter + .sub_channel_manager + .on_sub_channel_message(&confirm, &offerer.channel_manager.get_our_node_id()) + .unwrap(); +} + +fn go_to_off_chain_close_state( + test_params: &LnDlcTestParams, + target_state: TargetState, + reverse_closer: bool, +) { + let (closer, closee) = if reverse_closer { + (&test_params.bob_node, &test_params.alice_node) + } else { + (&test_params.alice_node, &test_params.bob_node) + }; + let (close_offer, _) = closer + .sub_channel_manager + .offer_subchannel_close( + &test_params.channel_id, + test_params.test_params.contract_input.accept_collateral, + ) + .unwrap(); + + if target_state == TargetState::OfferSent { + return; + } + + closee + .sub_channel_manager + .on_sub_channel_message( + &SubChannelMessage::CloseOffer(close_offer), + &closer.channel_manager.get_our_node_id(), + ) + .unwrap(); + + if target_state == TargetState::OfferReceived { + return; + } + + let (accept, _) = closee + .sub_channel_manager + .accept_subchannel_close_offer(&test_params.channel_id) + .unwrap(); + + if target_state == TargetState::Accepted { + return; + } + + let confirm = closer + .sub_channel_manager + .on_sub_channel_message( + &SubChannelMessage::CloseAccept(accept), + &closee.channel_manager.get_our_node_id(), + ) + .unwrap() + .unwrap(); + + if target_state == TargetState::Confirmed { + return; + } + + closee + .sub_channel_manager + .on_sub_channel_message(&confirm, &closer.channel_manager.get_our_node_id()) + .unwrap(); +} + +fn ldk_auto_close(test_params: &mut LnDlcTestParams, commit_tx: &Transaction) { + let sub_channel = test_params + .alice_node + .dlc_manager + .get_store() + .get_sub_channel(test_params.channel_id) + .unwrap() + .unwrap(); + let dlc_channel_id_alice = sub_channel.get_dlc_channel_id(0); + + let sub_channel = test_params + .bob_node + .dlc_manager + .get_store() + .get_sub_channel(sub_channel.channel_id) + .unwrap() + .unwrap(); + let dlc_channel_id_bob = sub_channel.get_dlc_channel_id(0); + + let channel_id = sub_channel.channel_id; + + test_params + .bob_node + .channel_manager + .force_close_broadcasting_latest_txn(&test_params.channel_id, &test_params.alice_node_id) + .unwrap(); + test_params.alice_node.process_events(); + test_params.bob_node.process_events(); + test_params.alice_node.process_events(); + test_params.bob_node.process_events(); + test_params.alice_node.process_events(); + test_params.bob_node.process_events(); + test_params.alice_node.process_events(); + test_params.bob_node.process_events(); + + test_params.generate_blocks(500); + test_params.bob_node.update_to_chain_tip(); + test_params.bob_node.process_events(); + + assert_sub_channel_state!(test_params.bob_node.sub_channel_manager, &channel_id; OnChainClosed); + + test_params.generate_blocks(3); + + test_params.bob_node.update_to_chain_tip(); + test_params.alice_node.update_to_chain_tip(); + test_params.alice_node.process_events(); + + assert_sub_channel_state!(test_params.alice_node.sub_channel_manager, &channel_id; CounterOnChainClosed); + + test_params.generate_blocks(500); + + test_params.bob_node.update_to_chain_tip(); + test_params.bob_node.process_events(); + test_params.alice_node.update_to_chain_tip(); + test_params.alice_node.process_events(); + + test_params.generate_blocks(2); + + test_params.bob_node.update_to_chain_tip(); + test_params.bob_node.process_events(); + test_params.alice_node.update_to_chain_tip(); + test_params.alice_node.process_events(); + + if let Some(dlc_channel_id_alice) = dlc_channel_id_alice { + assert_channel_state_unlocked!( + test_params.alice_node.dlc_manager, + dlc_channel_id_alice, + CounterClosed + ); + } + if let Some(dlc_channel_id_bob) = dlc_channel_id_bob { + assert_channel_state_unlocked!( + test_params.bob_node.dlc_manager, + dlc_channel_id_bob, + Closed + ); + } + + assert!(test_params + .bob_node + .dlc_manager + .get_chain_monitor() + .lock() + .unwrap() + .is_empty()); + assert!(test_params + .alice_node + .dlc_manager + .get_chain_monitor() + .lock() + .unwrap() + .is_empty()); + + let all_spent = test_params + .electrs + .get_outspends(&commit_tx.txid()) + .unwrap() + .into_iter() + .all(|x| { + if let OutSpendResp::Spent(_) = x { + true + } else { + false + } + }); + + assert!(all_spent); +} diff --git a/dlc-manager/tests/manager_execution_tests.rs b/dlc-manager/tests/manager_execution_tests.rs index 5d5109f8..be6907d9 100644 --- a/dlc-manager/tests/manager_execution_tests.rs +++ b/dlc-manager/tests/manager_execution_tests.rs @@ -19,7 +19,7 @@ use bitcoincore_rpc::RpcApi; use dlc_manager::contract::{numerical_descriptor::DifferenceParams, Contract}; use dlc_manager::manager::Manager; use dlc_manager::{Blockchain, Oracle, Storage, Wallet}; -use dlc_messages::{AcceptDlc, OfferDlc, SignDlc}; +use dlc_messages::{AcceptDlc, OfferDlc, OnChainMessage, SignDlc}; use dlc_messages::{CetAdaptorSignatures, Message}; use lightning::ln::wire::Type; use lightning::util::ser::Writeable; @@ -534,7 +534,7 @@ fn manager_execution_test(test_params: TestParams, path: TestPath) { let path_copy = path.clone(); let alter_sign = move |msg| match msg { - Message::Sign(mut sign_dlc) => { + Message::OnChain(OnChainMessage::Sign(mut sign_dlc)) => { match path_copy { TestPath::BadSignCetSignature => { alter_adaptor_sig(&mut sign_dlc.cet_adaptor_signatures) @@ -544,13 +544,13 @@ fn manager_execution_test(test_params: TestParams, path: TestPath) { } _ => {} } - Some(Message::Sign(sign_dlc)) + Some(Message::OnChain(OnChainMessage::Sign(sign_dlc))) } _ => Some(msg), }; let msg_callback = |msg: &Message| { - if let Message::Sign(s) = msg { + if let Message::OnChain(OnChainMessage::Sign(s)) = msg { write_message("sign_message", s.clone()); } }; @@ -588,7 +588,9 @@ fn manager_execution_test(test_params: TestParams, path: TestPath) { write_message("offer_message", offer_msg.clone()); let temporary_contract_id = offer_msg.temporary_contract_id; - bob_send.send(Some(Message::Offer(offer_msg))).unwrap(); + bob_send + .send(Some(Message::OnChain(OnChainMessage::Offer(offer_msg)))) + .unwrap(); assert_contract_state!(bob_manager_send, temporary_contract_id, Offered); @@ -618,13 +620,17 @@ fn manager_execution_test(test_params: TestParams, path: TestPath) { _ => {} }; bob_expect_error.store(true, Ordering::Relaxed); - alice_send.send(Some(Message::Accept(accept_msg))).unwrap(); + alice_send + .send(Some(Message::OnChain(OnChainMessage::Accept(accept_msg)))) + .unwrap(); sync_receive.recv().expect("Error synchronizing"); assert_contract_state!(bob_manager_send, temporary_contract_id, FailedAccept); } TestPath::BadSignCetSignature | TestPath::BadSignRefundSignature => { alice_expect_error.store(true, Ordering::Relaxed); - alice_send.send(Some(Message::Accept(accept_msg))).unwrap(); + alice_send + .send(Some(Message::OnChain(OnChainMessage::Accept(accept_msg)))) + .unwrap(); // Bob receives accept message sync_receive.recv().expect("Error synchronizing"); // Alice receives sign message @@ -632,7 +638,9 @@ fn manager_execution_test(test_params: TestParams, path: TestPath) { assert_contract_state!(alice_manager_send, contract_id, FailedSign); } _ => { - alice_send.send(Some(Message::Accept(accept_msg))).unwrap(); + alice_send + .send(Some(Message::OnChain(OnChainMessage::Accept(accept_msg)))) + .unwrap(); sync_receive.recv().expect("Error synchronizing"); assert_contract_state!(bob_manager_send, contract_id, Signed); diff --git a/dlc-manager/tests/test_utils.rs b/dlc-manager/tests/test_utils.rs index cf90edd1..ae0f6b66 100644 --- a/dlc-manager/tests/test_utils.rs +++ b/dlc-manager/tests/test_utils.rs @@ -48,36 +48,40 @@ 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 { match $receive.recv() { - Ok(Some(msg)) => match $manager.lock().unwrap().on_dlc_message( - &msg, - "0218845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166" - .parse() - .unwrap(), - ) { - Ok(opt) => { - if $expect_err.load(Ordering::Relaxed) != false { - panic!("Expected error not raised"); - } - match opt { - Some(msg) => { - let msg_opt = $rcv_callback(msg); - if let Some(msg) = msg_opt { - $msg_callback(&msg); - (&$send).send(Some(msg)).expect("Error sending"); + Ok(Some(msg)) => { + let res = $manager.lock().unwrap().on_dlc_message( + &msg, + "0218845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166" + .parse() + .unwrap(), + ); + $sync_send.send(()).expect("Error syncing"); + match res { + Ok(opt) => { + if $expect_err.load(Ordering::Relaxed) != false { + panic!("Expected error not raised"); + } + match opt { + Some(msg) => { + let msg_opt = $rcv_callback(msg); + if let Some(msg) = msg_opt { + #[allow(clippy::redundant_closure_call)] + $msg_callback(&msg); + (&$send).send(Some(msg)).expect("Error sending"); + } } + None => {} } - None => {} } - } - Err(e) => { - if $expect_err.load(Ordering::Relaxed) != true { - panic!("Unexpected error {}", e); + Err(e) => { + if $expect_err.load(Ordering::Relaxed) != true { + panic!("Unexpected error {}", e); + } } } - }, + } Ok(None) | Err(_) => return, }; - $sync_send.send(()).expect("Error syncing"); }) }; } @@ -100,10 +104,15 @@ macro_rules! write_contract { #[macro_export] macro_rules! assert_contract_state { + ($d:expr, $id:expr, $p:ident) => { + assert_contract_state_unlocked!($d.lock().unwrap(), $id, $p); + }; +} + +#[macro_export] +macro_rules! assert_contract_state_unlocked { ($d:expr, $id:expr, $p:ident) => { let res = $d - .lock() - .unwrap() .get_store() .get_contract(&$id) .expect("Could not retrieve contract"); @@ -121,6 +130,21 @@ macro_rules! assert_contract_state { }; } +#[macro_export] +macro_rules! assert_channel_contract_state { + ($d: expr, $id: expr, $p: ident) => {{ + let channel = $d + .get_store() + .get_channel(&$id) + .expect("Could not retrieve contract") + .expect(&format!("No such channel: {:?}", $id)); + let contract_id = channel.get_contract_id().expect("No contract id"); + + assert_contract_state_unlocked!($d, contract_id, $p); + contract_id + }}; +} + #[macro_export] macro_rules! write_channel { ($channel: ident, $state: ident) => { @@ -142,17 +166,36 @@ macro_rules! write_channel { }; } +#[macro_export] +macro_rules! write_sub_channel { + ($channel: ident, $state: ident) => { + use lightning::util::ser::Writeable; + + let mut buf = Vec::new(); + $channel + .write(&mut buf) + .expect("to be able to serialize the sub channel"); + std::fs::write(format!("{}SubChannel", stringify!($state)), buf) + .expect("to be able to save the sub channel to file"); + }; +} + #[macro_export] macro_rules! assert_channel_state { + ($d:expr, $id:expr, $p:ident $(, $s: ident)?) => {{ + assert_channel_state_unlocked!($d.lock().unwrap(), $id, $p $(, $s)?) + }}; +} + +#[allow(unused_macros)] +macro_rules! assert_channel_state_unlocked { ($d:expr, $id:expr, $p:ident $(, $s: ident)?) => {{ let res = $d - .lock() - .unwrap() .get_store() .get_channel(&$id) - .expect("Could not retrieve contract"); + .expect("Could not retrieve channel"); if let Some(Channel::$p(c)) = res { - $(if let SignedChannelState::$s { .. } = c.state { + $(if let dlc_manager::channel::signed_channel::SignedChannelState::$s { .. } = c.state { } else { panic!("Unexpected signed channel state {:?}", c.state); })? @@ -160,20 +203,69 @@ macro_rules! assert_channel_state { let channel = Channel::$p(c); write_channel!(channel, $p); } + } else if let Some(c) = res { + panic!("Unexpected channel state {:?}", c); } else { - let state = match res { - Some(Channel::Offered(_)) => "offered", - Some(Channel::Accepted(_)) => "accepted", - Some(Channel::Signed(_)) => "signed", - Some(Channel::FailedAccept(_)) => "failed accept", - Some(Channel::FailedSign(_)) => "failed sign", - None => "none", - }; - panic!("Unexpected channel state {}", state); + panic!("Could not find requested channel"); } }}; } +#[macro_export] +macro_rules! assert_sub_channel_state { + ($d:expr, $id:expr $(, $s_tuple: ident)? $(;$s_simple: ident)?) => {{ + let res = $d + .get_dlc_manager() + .get_store() + .get_sub_channel(*$id) + .expect("Could not retrieve contract"); + if let Some(sub_channel) = res { + $(if let SubChannelState::$s_tuple(_) = sub_channel.state { + } else { + panic!("Unexpected sub channel state {:?}", sub_channel.state); + } + if std::env::var("GENERATE_SERIALIZED_SUB_CHANNEL").is_ok() { + write_sub_channel!(sub_channel, $s_tuple); + })? + $(if let SubChannelState::$s_simple = sub_channel.state { + } else { + panic!("Unexpected sub channel state {:?}", sub_channel.state); + } + if std::env::var("GENERATE_SERIALIZED_SUB_CHANNEL").is_ok() { + write_sub_channel!(sub_channel, $s_simple); + })? + + let dlc_channel_id = sub_channel.get_dlc_channel_id(0); + + if let Some(dlc_channel_id) = dlc_channel_id { + match sub_channel.state { + SubChannelState::Offered(_) => { + assert_channel_state_unlocked!($d.get_dlc_manager(), dlc_channel_id, Offered); + assert_channel_contract_state!($d.get_dlc_manager(), dlc_channel_id, Offered); + } + SubChannelState::Accepted(_) => { + assert_channel_state_unlocked!($d.get_dlc_manager(), dlc_channel_id, Accepted); + assert_channel_contract_state!($d.get_dlc_manager(), dlc_channel_id, Accepted); + } + _ => {} + } + } + + } else { + panic!("Sub channel not found"); + } + }}; +} + +#[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(), @@ -218,20 +310,20 @@ pub fn get_difference_params() -> DifferenceParams { } } -pub fn get_enum_contract_descriptor() -> ContractDescriptor { +pub fn get_enum_contract_descriptor(total_collateral: u64) -> ContractDescriptor { let outcome_payouts: Vec<_> = enum_outcomes() .iter() .enumerate() .map(|(i, x)| { let payout = if i % 2 == 0 { Payout { - offer: TOTAL_COLLATERAL, + offer: total_collateral, accept: 0, } } else { Payout { offer: 0, - accept: TOTAL_COLLATERAL, + accept: total_collateral, } }; EnumerationPayout { @@ -274,9 +366,25 @@ pub fn get_enum_test_params( nb_oracles: usize, threshold: usize, oracles: Option>, +) -> TestParams { + get_enum_test_params_custom_collateral( + nb_oracles, + threshold, + oracles, + OFFER_COLLATERAL, + ACCEPT_COLLATERAL, + ) +} + +pub fn get_enum_test_params_custom_collateral( + nb_oracles: usize, + threshold: usize, + oracles: Option>, + offer_collateral: u64, + accept_collateral: u64, ) -> TestParams { let oracles = oracles.unwrap_or_else(|| get_enum_oracles(nb_oracles, threshold)); - let contract_descriptor = get_enum_contract_descriptor(); + let contract_descriptor = get_enum_contract_descriptor(offer_collateral + accept_collateral); let contract_info = ContractInputInfo { contract_descriptor, oracles: OracleInput { @@ -287,9 +395,9 @@ pub fn get_enum_test_params( }; let contract_input = ContractInput { - offer_collateral: OFFER_COLLATERAL, - accept_collateral: ACCEPT_COLLATERAL, - fee_rate: 2, + offer_collateral, + accept_collateral, + fee_rate: 1, contract_infos: vec![contract_info], }; @@ -472,6 +580,26 @@ pub fn get_numerical_test_params( with_diff: bool, contract_descriptor: ContractDescriptor, use_max_value: bool, +) -> TestParams { + get_numerical_test_params_custom_collateral( + oracle_numeric_infos, + threshold, + with_diff, + contract_descriptor, + use_max_value, + OFFER_COLLATERAL, + ACCEPT_COLLATERAL, + ) +} + +pub fn get_numerical_test_params_custom_collateral( + oracle_numeric_infos: &OracleNumericInfo, + threshold: usize, + with_diff: bool, + contract_descriptor: ContractDescriptor, + use_max_value: bool, + offer_collateral: u64, + accept_collateral: u64, ) -> TestParams { let oracles = get_digit_decomposition_oracles(oracle_numeric_infos, threshold, with_diff, use_max_value); @@ -485,9 +613,9 @@ pub fn get_numerical_test_params( }; let contract_input = ContractInput { - offer_collateral: OFFER_COLLATERAL, - accept_collateral: ACCEPT_COLLATERAL, - fee_rate: 2, + offer_collateral, + accept_collateral, + fee_rate: 1, contract_infos: vec![contract_info], }; @@ -505,7 +633,8 @@ pub fn get_enum_and_numerical_test_params( ) -> TestParams { let oracle_numeric_infos = get_same_num_digits_oracle_numeric_infos(nb_oracles); let enum_oracles = get_enum_oracles(nb_oracles, threshold); - let enum_contract_descriptor = get_enum_contract_descriptor(); + let enum_contract_descriptor = + get_enum_contract_descriptor(OFFER_COLLATERAL + ACCEPT_COLLATERAL); let enum_contract_info = ContractInputInfo { oracles: OracleInput { public_keys: enum_oracles.iter().map(|x| x.get_public_key()).collect(), @@ -542,15 +671,12 @@ pub fn get_enum_and_numerical_test_params( let contract_input = ContractInput { offer_collateral: OFFER_COLLATERAL, accept_collateral: ACCEPT_COLLATERAL, - fee_rate: 2, + fee_rate: 1, contract_infos, }; TestParams { - oracles: enum_oracles - .into_iter() - .chain(numerical_oracles.into_iter()) - .collect(), + oracles: enum_oracles.into_iter().chain(numerical_oracles).collect(), contract_input, } } diff --git a/dlc-messages/Cargo.toml b/dlc-messages/Cargo.toml index 6d84a6d9..2721b8be 100644 --- a/dlc-messages/Cargo.toml +++ b/dlc-messages/Cargo.toml @@ -13,9 +13,10 @@ use-serde = ["serde", "secp256k1-zkp/use-serde"] [dependencies] bitcoin = {version = "0.29.2"} dlc = {version = "0.4.0", path = "../dlc"} -lightning = {version = "0.0.113" } +lightning = {version = "0.0.116"} secp256k1-zkp = {version = "0.7.0", features = ["bitcoin_hashes", "rand", "rand-std"]} serde = {version = "1.0", features = ["derive"], optional = true} +log = "0.4.14" [dev-dependencies] bitcoin = {version = "0.29.2"} diff --git a/dlc-messages/src/channel.rs b/dlc-messages/src/channel.rs index b6a39422..c5981b3f 100644 --- a/dlc-messages/src/channel.rs +++ b/dlc-messages/src/channel.rs @@ -384,8 +384,6 @@ pub struct RenewOffer { )] /// The id of the channel referred to by the message. pub channel_id: [u8; 32], - /// The temporary id of the offered contract. - pub temporary_contract_id: [u8; 32], /// The proposed payout for the receiving party for the previous channel /// state. pub counter_payout: u64, @@ -404,7 +402,6 @@ pub struct RenewOffer { impl_dlc_writeable!(RenewOffer, { (channel_id, writeable), - (temporary_contract_id, writeable), (counter_payout, writeable), (next_per_update_point, writeable), (contract_info, writeable), @@ -433,9 +430,6 @@ pub struct RenewAccept { /// The per update point to be used by the sending party to setup the next /// channel state. pub next_per_update_point: PublicKey, - /// The adaptor signature for the buffer transaction generated by the offer - /// party. - pub buffer_adaptor_signature: EcdsaAdaptorSignature, /// The adaptor signatures for all CETs generated by the offer party. pub cet_adaptor_signatures: CetAdaptorSignatures, /// The refund signature generated by the offer party. @@ -445,7 +439,6 @@ pub struct RenewAccept { impl_dlc_writeable!(RenewAccept, { (channel_id, writeable), (next_per_update_point, writeable), - (buffer_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}), (cet_adaptor_signatures, writeable), (refund_signature, writeable) }); @@ -467,9 +460,6 @@ pub struct RenewConfirm { )] /// The id of the channel referred to by the message. pub channel_id: [u8; 32], - /// The pre image of the per update point used by the sending party to setup - /// the previous channel state. - pub per_update_secret: SecretKey, /// The adaptor signature for the buffer transaction generated by the offer /// party. pub buffer_adaptor_signature: EcdsaAdaptorSignature, @@ -481,7 +471,6 @@ pub struct RenewConfirm { impl_dlc_writeable!(RenewConfirm, { (channel_id, writeable), - (per_update_secret, writeable), (buffer_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}), (cet_adaptor_signatures, writeable), (refund_signature, writeable) @@ -507,9 +496,40 @@ pub struct RenewFinalize { /// The pre image of the per update point used by the sending party to setup /// the previous channel state. pub per_update_secret: SecretKey, + /// The adaptor signature for the buffer transaction generated by the accept + /// party. + pub buffer_adaptor_signature: EcdsaAdaptorSignature, } impl_dlc_writeable!(RenewFinalize, { + (channel_id, writeable), + (per_update_secret, writeable), + (buffer_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}) +}); + +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] +/// Message used to finalize the establishment of a new contract within a channel. +pub struct RenewRevoke { + #[cfg_attr( + feature = "serde", + serde( + serialize_with = "crate::serde_utils::serialize_hex", + deserialize_with = "crate::serde_utils::deserialize_hex_array" + ) + )] + /// The id of the channel referred to by the message. + pub channel_id: [u8; 32], + /// The pre image of the per update point used by the sending party to setup + /// the previous channel state. + pub per_update_secret: SecretKey, +} + +impl_dlc_writeable!(RenewRevoke, { (channel_id, writeable), (per_update_secret, writeable) }); diff --git a/dlc-messages/src/lib.rs b/dlc-messages/src/lib.rs index 1ba20a1c..b27a237e 100644 --- a/dlc-messages/src/lib.rs +++ b/dlc-messages/src/lib.rs @@ -31,6 +31,7 @@ pub mod contract_msgs; pub mod message_handler; pub mod oracle_msgs; pub mod segmentation; +pub mod sub_channel; #[cfg(any(test, feature = "serde"))] pub mod serde_utils; @@ -41,8 +42,8 @@ use crate::ser_impls::{read_ecdsa_adaptor_signature, write_ecdsa_adaptor_signatu use bitcoin::{consensus::Decodable, OutPoint, Script, Transaction}; use channel::{ AcceptChannel, CollaborativeCloseOffer, OfferChannel, Reject, RenewAccept, RenewConfirm, - RenewFinalize, RenewOffer, SettleAccept, SettleConfirm, SettleFinalize, SettleOffer, - SignChannel, + RenewFinalize, RenewOffer, RenewRevoke, SettleAccept, SettleConfirm, SettleFinalize, + SettleOffer, SignChannel, }; use contract_msgs::ContractInfo; use dlc::{Error, TxInputInfo}; @@ -52,6 +53,11 @@ use lightning::util::ser::{Readable, Writeable, Writer}; use secp256k1_zkp::Verification; use secp256k1_zkp::{ecdsa::Signature, EcdsaAdaptorSignature, PublicKey, Secp256k1}; use segmentation::{SegmentChunk, SegmentStart}; +use sub_channel::{ + Reject as SubChannelReject, SubChannelAccept, SubChannelCloseAccept, SubChannelCloseConfirm, + SubChannelCloseFinalize, SubChannelCloseOffer, SubChannelConfirm, SubChannelFinalize, + SubChannelOffer, SubChannelRevoke, +}; macro_rules! impl_type { ($const_name: ident, $type_name: ident, $type_val: expr) => { @@ -80,12 +86,23 @@ impl_type!(RENEW_CHANNEL_OFFER_TYPE, RenewOffer, 43014); impl_type!(RENEW_CHANNEL_ACCEPT_TYPE, RenewAccept, 43016); impl_type!(RENEW_CHANNEL_CONFIRM_TYPE, RenewConfirm, 43018); impl_type!(RENEW_CHANNEL_FINALIZE_TYPE, RenewFinalize, 43020); +impl_type!(RENEW_CHANNEL_REVOKE_TYPE, RenewRevoke, 43026); impl_type!( COLLABORATIVE_CLOSE_OFFER_TYPE, CollaborativeCloseOffer, 43022 ); impl_type!(REJECT, Reject, 43024); +impl_type!(SUB_CHANNEL_OFFER, SubChannelOffer, 43034); +impl_type!(SUB_CHANNEL_ACCEPT, SubChannelAccept, 43036); +impl_type!(SUB_CHANNEL_CONFIRM, SubChannelConfirm, 43038); +impl_type!(SUB_CHANNEL_FINALIZE, SubChannelFinalize, 43040); +impl_type!(SUB_CHANNEL_REVOKE, SubChannelRevoke, 43052); +impl_type!(SUB_CHANNEL_CLOSE_OFFER, SubChannelCloseOffer, 43042); +impl_type!(SUB_CHANNEL_CLOSE_ACCEPT, SubChannelCloseAccept, 43044); +impl_type!(SUB_CHANNEL_CLOSE_CONFIRM, SubChannelCloseConfirm, 43046); +impl_type!(SUB_CHANNEL_CLOSE_FINALIZE, SubChannelCloseFinalize, 43048); +impl_type!(SUB_CHANNEL_REJECT, SubChannelReject, 43050); #[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr( @@ -503,12 +520,25 @@ impl_dlc_writeable!(SignDlc, { #[allow(missing_docs)] #[derive(Debug, Clone)] pub enum Message { + OnChain(OnChainMessage), + Channel(ChannelMessage), + SubChannel(SubChannelMessage), +} + +#[allow(missing_docs)] +#[derive(Debug, Clone)] +pub enum OnChainMessage { Offer(OfferDlc), Accept(AcceptDlc), Sign(SignDlc), - OfferChannel(OfferChannel), - AcceptChannel(AcceptChannel), - SignChannel(SignChannel), +} + +#[allow(missing_docs)] +#[derive(Debug, Clone)] +pub enum ChannelMessage { + Offer(OfferChannel), + Accept(AcceptChannel), + Sign(SignChannel), SettleOffer(SettleOffer), SettleAccept(SettleAccept), SettleConfirm(SettleConfirm), @@ -517,10 +547,26 @@ pub enum Message { RenewAccept(RenewAccept), RenewConfirm(RenewConfirm), RenewFinalize(RenewFinalize), + RenewRevoke(RenewRevoke), CollaborativeCloseOffer(CollaborativeCloseOffer), Reject(Reject), } +#[allow(missing_docs)] +#[derive(Debug, Clone)] +pub enum SubChannelMessage { + Offer(SubChannelOffer), + Accept(SubChannelAccept), + Confirm(SubChannelConfirm), + Finalize(SubChannelFinalize), + Revoke(SubChannelRevoke), + CloseOffer(SubChannelCloseOffer), + CloseAccept(SubChannelCloseAccept), + CloseConfirm(SubChannelCloseConfirm), + CloseFinalize(SubChannelCloseFinalize), + Reject(SubChannelReject), +} + macro_rules! impl_type_writeable_for_enum { ($type_name: ident, {$($variant_name: ident),*}) => { impl Type for $type_name { @@ -542,13 +588,24 @@ macro_rules! impl_type_writeable_for_enum { } impl_type_writeable_for_enum!(Message, +{ + OnChain, + Channel, + SubChannel +}); + +impl_type_writeable_for_enum!(OnChainMessage, +{ + Offer, + Accept, + Sign +}); + +impl_type_writeable_for_enum!(ChannelMessage, { Offer, Accept, Sign, - OfferChannel, - AcceptChannel, - SignChannel, SettleOffer, SettleAccept, SettleConfirm, @@ -557,10 +614,25 @@ impl_type_writeable_for_enum!(Message, RenewAccept, RenewConfirm, RenewFinalize, + RenewRevoke, CollaborativeCloseOffer, Reject }); +impl_type_writeable_for_enum!(SubChannelMessage, +{ + Offer, + Accept, + Confirm, + Finalize, + Revoke, + CloseOffer, + CloseAccept, + CloseConfirm, + CloseFinalize, + Reject +}); + #[derive(Debug, Clone)] /// Wrapper for DLC related message and segmentation related messages. pub enum WireMessage { diff --git a/dlc-messages/src/message_handler.rs b/dlc-messages/src/message_handler.rs index 85a20d9a..d7dd8491 100644 --- a/dlc-messages/src/message_handler.rs +++ b/dlc-messages/src/message_handler.rs @@ -9,6 +9,7 @@ use std::{ use lightning::{ ln::{ + features::{InitFeatures, NodeFeatures}, msgs::{DecodeError, LightningError}, peer_handler::CustomMessageHandler, wire::{CustomMessageReader, Type}, @@ -19,7 +20,7 @@ use secp256k1_zkp::PublicKey; use crate::{ segmentation::{get_segments, segment_reader::SegmentReader}, - Message, WireMessage, + ChannelMessage, Message, OnChainMessage, SubChannelMessage, WireMessage, }; /// MessageHandler is used to send and receive messages through the custom @@ -38,6 +39,15 @@ impl Default for MessageHandler { } } +impl lightning::events::OnionMessageProvider for MessageHandler { + fn next_onion_message_for_peer( + &self, + _peer_node_id: PublicKey, + ) -> Option { + None + } +} + impl MessageHandler { /// Creates a new instance of a [`MessageHandler`] pub fn new() -> Self { @@ -48,6 +58,11 @@ impl MessageHandler { } } + /// Returns whether there are any new received messages to process. + pub fn has_pending_messages_to_process(&self) -> bool { + !self.msg_received.lock().unwrap().is_empty() + } + /// Returns the messages received by the message handler and empty the /// receiving buffer. pub fn get_and_clear_received_messages(&self) -> Vec<(PublicKey, Message)> { @@ -82,11 +97,11 @@ impl MessageHandler { } macro_rules! handle_read_dlc_messages { - ($msg_type:ident, $buffer:ident, $(($type_id:ident, $variant:ident)),*) => {{ + ($msg_type:ident, $buffer:ident, $(($variant:ident, $subtype:ident, $(($type_id:ident, $subvariant:ident)),*)),*) => {{ let decoded = match $msg_type { - $( - $crate::$type_id => Message::$variant(Readable::read(&mut $buffer)?), - )* + $($( + $crate::$type_id => Message::$variant($subtype::$subvariant(Readable::read(&mut $buffer)?)), + )*)* _ => return Ok(None), }; Ok(Some(WireMessage::Message(decoded))) @@ -100,21 +115,44 @@ fn read_dlc_message( handle_read_dlc_messages!( msg_type, buffer, - (OFFER_TYPE, Offer), - (ACCEPT_TYPE, Accept), - (SIGN_TYPE, Sign), - (OFFER_CHANNEL_TYPE, OfferChannel), - (ACCEPT_CHANNEL_TYPE, AcceptChannel), - (SIGN_CHANNEL_TYPE, SignChannel), - (SETTLE_CHANNEL_OFFER_TYPE, SettleOffer), - (SETTLE_CHANNEL_ACCEPT_TYPE, SettleAccept), - (SETTLE_CHANNEL_CONFIRM_TYPE, SettleConfirm), - (SETTLE_CHANNEL_FINALIZE_TYPE, SettleFinalize), - (RENEW_CHANNEL_OFFER_TYPE, RenewOffer), - (RENEW_CHANNEL_ACCEPT_TYPE, RenewAccept), - (RENEW_CHANNEL_CONFIRM_TYPE, RenewConfirm), - (RENEW_CHANNEL_FINALIZE_TYPE, RenewFinalize), - (COLLABORATIVE_CLOSE_OFFER_TYPE, CollaborativeCloseOffer) + ( + OnChain, + OnChainMessage, + (OFFER_TYPE, Offer), + (ACCEPT_TYPE, Accept), + (SIGN_TYPE, Sign) + ), + ( + Channel, + ChannelMessage, + (OFFER_CHANNEL_TYPE, Offer), + (ACCEPT_CHANNEL_TYPE, Accept), + (SIGN_CHANNEL_TYPE, Sign), + (SETTLE_CHANNEL_OFFER_TYPE, SettleOffer), + (SETTLE_CHANNEL_ACCEPT_TYPE, SettleAccept), + (SETTLE_CHANNEL_CONFIRM_TYPE, SettleConfirm), + (SETTLE_CHANNEL_FINALIZE_TYPE, SettleFinalize), + (RENEW_CHANNEL_OFFER_TYPE, RenewOffer), + (RENEW_CHANNEL_ACCEPT_TYPE, RenewAccept), + (RENEW_CHANNEL_CONFIRM_TYPE, RenewConfirm), + (RENEW_CHANNEL_FINALIZE_TYPE, RenewFinalize), + (RENEW_CHANNEL_REVOKE_TYPE, RenewRevoke), + (COLLABORATIVE_CLOSE_OFFER_TYPE, CollaborativeCloseOffer) + ), + ( + SubChannel, + SubChannelMessage, + (SUB_CHANNEL_OFFER, Offer), + (SUB_CHANNEL_ACCEPT, Accept), + (SUB_CHANNEL_CONFIRM, Confirm), + (SUB_CHANNEL_FINALIZE, Finalize), + (SUB_CHANNEL_REVOKE, Revoke), + (SUB_CHANNEL_CLOSE_OFFER, CloseOffer), + (SUB_CHANNEL_CLOSE_ACCEPT, CloseAccept), + (SUB_CHANNEL_CLOSE_CONFIRM, CloseConfirm), + (SUB_CHANNEL_CLOSE_FINALIZE, CloseFinalize), + (SUB_CHANNEL_REJECT, Reject) + ) ) } @@ -150,9 +188,7 @@ impl CustomMessageHandler for MessageHandler { org: &PublicKey, ) -> Result<(), LightningError> { let mut segment_readers = self.segment_readers.lock().unwrap(); - let segment_reader = segment_readers - .entry(*org) - .or_insert_with(SegmentReader::new); + let segment_reader = segment_readers.entry(*org).or_default(); if segment_reader.expecting_chunk() { match msg { @@ -208,6 +244,14 @@ impl CustomMessageHandler for MessageHandler { fn get_and_clear_pending_msg(&self) -> Vec<(PublicKey, Self::CustomMessage)> { self.msg_events.lock().unwrap().drain(..).collect() } + + fn provided_node_features(&self) -> NodeFeatures { + NodeFeatures::empty() + } + + fn provided_init_features(&self, _their_node_id: &PublicKey) -> InitFeatures { + InitFeatures::empty() + } } #[inline] @@ -304,7 +348,7 @@ mod tests { let input = include_str!("./test_inputs/offer_msg.json"); let msg: OfferDlc = serde_json::from_str(input).unwrap(); let handler = MessageHandler::new(); - handler.send_message(some_pk(), Message::Offer(msg)); + handler.send_message(some_pk(), Message::OnChain(OnChainMessage::Offer(msg))); assert_eq!(handler.msg_events.lock().unwrap().len(), 1); } @@ -313,7 +357,7 @@ mod tests { let input = include_str!("./test_inputs/accept_msg.json"); let msg: AcceptDlc = serde_json::from_str(input).unwrap(); let handler = MessageHandler::new(); - handler.send_message(some_pk(), Message::Accept(msg)); + handler.send_message(some_pk(), Message::OnChain(OnChainMessage::Accept(msg))); assert!(handler.msg_events.lock().unwrap().len() > 1); } @@ -322,7 +366,7 @@ mod tests { let input = include_str!("./test_inputs/accept_msg.json"); let msg: AcceptDlc = serde_json::from_str(input).unwrap(); let handler = MessageHandler::new(); - handler.send_message(some_pk(), Message::Accept(msg)); + handler.send_message(some_pk(), Message::OnChain(OnChainMessage::Accept(msg))); handler.get_and_clear_pending_msg(); assert!(!handler.has_pending_messages()); } @@ -343,7 +387,7 @@ mod tests { .expect("to be able to process segment start"); let msg = handler.get_and_clear_received_messages(); assert_eq!(1, msg.len()); - if let (_, Message::Accept(_)) = msg[0] { + if let (_, Message::OnChain(OnChainMessage::Accept(_))) = msg[0] { } else { panic!("Expected an accept message"); } diff --git a/dlc-messages/src/oracle_msgs.rs b/dlc-messages/src/oracle_msgs.rs index d49eeacc..aa779a58 100644 --- a/dlc-messages/src/oracle_msgs.rs +++ b/dlc-messages/src/oracle_msgs.rs @@ -431,7 +431,7 @@ mod tests { let msg = Message::from_hashed_data::(&event_hex); let sig = SECP256K1.sign_schnorr(&msg, &key_pair); let mut sig_hex = *sig.as_ref(); - sig_hex[10] += 1; + sig_hex[10] = sig_hex[10].checked_add(1).unwrap_or(0); let sig = SchnorrSignature::from_slice(&sig_hex).unwrap(); let invalid_announcement = OracleAnnouncement { announcement_signature: sig, diff --git a/dlc-messages/src/sub_channel.rs b/dlc-messages/src/sub_channel.rs new file mode 100644 index 00000000..e1ef5c77 --- /dev/null +++ b/dlc-messages/src/sub_channel.rs @@ -0,0 +1,371 @@ +//! Module containing messages related to DLC on Lightning channels. + +use bitcoin::Script; +use secp256k1_zkp::{ecdsa::Signature, EcdsaAdaptorSignature, PublicKey, SecretKey}; + +use crate::ser_impls::{read_ecdsa_adaptor_signature, write_ecdsa_adaptor_signature}; +use crate::{contract_msgs::ContractInfo, CetAdaptorSignatures}; +use lightning::ln::msgs::DecodeError; +use lightning::util::ser::{Readable, Writeable, Writer}; + +#[derive(Clone, PartialEq, Debug)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] +/// A message to offer the establishment of a DLC channel within a preexisting Lightning channel. +pub struct SubChannelOffer { + /// The id of the Lightning channel the message refers to. + pub channel_id: [u8; 32], + /// The base point that will be used by the offer party for revocation of the DLC channel + /// transactions. + pub revocation_basepoint: PublicKey, + /// The base point that will be used by the offer party for generating adaptor signatures to + /// revocable transactions within the DLC channel. + pub publish_basepoint: PublicKey, + /// The base point that will be used by the offer party in the 2 of 2 output + /// of buffer transactions. + pub own_basepoint: PublicKey, + /// The point that will be used by the offer party to derive public private key pairs required + /// for the split transaction. + pub next_per_split_point: PublicKey, + /// Information about the contract to be used to setup the DLC channel. + pub contract_info: ContractInfo, + /// The base point that will be used by the offer party for revocation of buffer transactions. + pub channel_revocation_basepoint: PublicKey, + /// The base point that will be used by the offer party for generating + /// adaptor signatures to buffer transactions. + pub channel_publish_basepoint: PublicKey, + /// The base point that will be used by the offer party in the 2 of 2 output + /// of buffer transactions. + pub channel_own_basepoint: PublicKey, + /// The point that will be used by the offer party to derive public private key pairs required + /// for the DLC channel. + pub channel_first_per_update_point: PublicKey, + /// Script used by the offer party to receive their payout on channel close. + pub payout_spk: Script, + /// Serial id used to order CET outputs. + pub payout_serial_id: u64, + /// The collateral input by the offer party in the channel. + pub offer_collateral: u64, + /// Lock time for the CETs. + pub cet_locktime: u32, + /// Lock time for the refund transaction. + pub refund_locktime: u32, + /// The nSequence value to use for the CETs. + pub cet_nsequence: u32, + /// The fee rate to use for creating the split and DLC channel transactions. + pub fee_rate_per_vbyte: u64, +} + +impl_dlc_writeable!( + SubChannelOffer, { + (channel_id, writeable), + (revocation_basepoint, writeable), + (publish_basepoint, writeable), + (own_basepoint, writeable), + (next_per_split_point, writeable), + (contract_info, writeable), + (channel_revocation_basepoint, writeable), + (channel_publish_basepoint, writeable), + (channel_own_basepoint, writeable), + (channel_first_per_update_point, writeable), + (payout_spk, writeable), + (payout_serial_id, writeable), + (offer_collateral, writeable), + (cet_locktime, writeable), + (refund_locktime, writeable), + (cet_nsequence, writeable), + (fee_rate_per_vbyte, writeable) + } +); + +/// A message to accept an offer to establish a DLC channel within an existing Lightning channel. +#[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], + /// The base point that will be used by the offer party for revocation of the split + /// transaction. + pub revocation_basepoint: PublicKey, + /// The base point that will be used by the offer party for generating + /// adaptor signatures used to revoke the split transaction. + pub publish_basepoint: PublicKey, + /// The base point that will be used by the offer party in the 2 of 2 output + /// of split transactions. + pub own_basepoint: PublicKey, + /// The signature for the new commit transaction of the Lightning channel. + pub commit_signature: Signature, + /// The commit transaction number for which the above signature is intended. + pub commit_tx_number: u64, + /// The htlc signatures for the new commit transaction of the Lightning channel. + pub htlc_signatures: Vec, + /// The first point used to derive public private key pairs used for the split transaction. + pub first_per_split_point: PublicKey, + /// The base point that will be used by the offer party for revocation of the DLC channel + /// buffer transactions. + pub channel_revocation_basepoint: PublicKey, + /// The base point that will be used by the offer party for generating + /// adaptor signatures to buffer transactions within the DLC channel. + pub channel_publish_basepoint: PublicKey, + /// The base point that will be used by the offer party in the 2 of 2 output + /// of buffer transactions within the DLC channel. + pub channel_own_basepoint: PublicKey, + /// The adaptor signatures for all CETs generated by the accept party. + pub cet_adaptor_signatures: CetAdaptorSignatures, + /// The adaptor signature for the buffer transaction generated by the accept + /// party. + pub buffer_adaptor_signature: EcdsaAdaptorSignature, + /// The refund signature generated by the accept party. + pub refund_signature: Signature, + /// The signature for the glue transaction of the Lightning channel. + pub ln_glue_signature: Signature, + /// The point used to derive public private key pairs within the DLC channel. + pub first_per_update_point: PublicKey, + /// Script used by the offer party to receive their payout on channel close. + pub payout_spk: Script, + /// Serial id used to order CET outputs. + pub payout_serial_id: u64, +} + +impl_dlc_writeable!( + SubChannelAccept, { + (channel_id, writeable), + (revocation_basepoint, writeable), + (publish_basepoint, writeable), + (own_basepoint, writeable), + (commit_signature, writeable), + (commit_tx_number, writeable), + (htlc_signatures, writeable), + (first_per_split_point, writeable), + (channel_revocation_basepoint, writeable), + (channel_publish_basepoint, writeable), + (channel_own_basepoint, writeable), + (cet_adaptor_signatures, writeable), + (buffer_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}), + (refund_signature, writeable), + (ln_glue_signature, writeable), + (first_per_update_point, writeable), + (payout_spk, writeable), + (payout_serial_id, writeable) + } +); + +/// A message sent by the offer party 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], + /// The adaptor signature used for revocation of the split transaction. + pub split_adaptor_signature: EcdsaAdaptorSignature, + /// The signature for the new commitment transaction. + pub commit_signature: Signature, + /// The commit transaction number for which the above signature is intended. + pub commit_tx_number: u64, + /// The htlc signatures for the new commitment transaction. + pub htlc_signatures: Vec, + /// The adaptor signatures for the DLC channel CETs. + pub cet_adaptor_signatures: CetAdaptorSignatures, + /// The adaptor signature for the buffer transaction generated by the offer + /// party. + pub buffer_adaptor_signature: EcdsaAdaptorSignature, + /// The refund signature generated by the offer party. + pub refund_signature: Signature, + /// The signature for the glue transaction of the Lightning channel. + pub ln_glue_signature: Signature, +} + +impl_dlc_writeable!(SubChannelConfirm, { + (channel_id, writeable), + (split_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}), + (commit_signature, writeable), + (commit_tx_number, writeable), + (htlc_signatures, writeable), + (cet_adaptor_signatures, writeable), + (buffer_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}), + (refund_signature, writeable), + (ln_glue_signature, writeable) +}); + +/// A message sent by the accept party to finalize the establishment of a DLC channel within an +/// existing Lightning channel and revoking the previous commitment transaction. +#[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], + /// The pre-image of the revocation point used for the old commitment transaction. + pub per_commitment_secret: SecretKey, + /// The commitment point for the next Lightning commitment transaction. + pub next_per_commitment_point: PublicKey, + /// The adaptor signature for the split transaction. + pub split_adaptor_signature: EcdsaAdaptorSignature, +} + +impl_dlc_writeable!(SubChannelFinalize, { + (channel_id, writeable), + (per_commitment_secret, writeable), + (next_per_commitment_point, writeable), + (split_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}) +}); + +/// A message sent by the offer party to revoke the previous commitment transaction. +#[derive(Clone, Debug)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] +pub struct SubChannelRevoke { + /// The id of the Lightning channel the message relates to. + pub channel_id: [u8; 32], + /// The pre-image of the revocation point used for the old commitment transaction. + pub per_commitment_secret: SecretKey, + /// The commitment point for the next Lightning commitment transaction. + pub next_per_commitment_point: PublicKey, +} + +impl_dlc_writeable!(SubChannelRevoke, { + (channel_id, writeable), + (per_commitment_secret, writeable), + (next_per_commitment_point, writeable) +}); + +/// A message to offer the collaborative (off-chain) closing of a DLC channel embedded within a +/// Lightning channel. +#[derive(Clone, Debug, PartialEq, Eq)] +#[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], + /// The balance proposed to the counter party. + pub accept_balance: u64, +} + +impl_dlc_writeable!(SubChannelCloseOffer, { + (channel_id, writeable), + (accept_balance, writeable) +}); + +/// A message to accept the collaborative closing of a DLC channel embedded within a Lightning +/// channel. +#[derive(Clone, Debug, PartialEq, Eq)] +#[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], + /// The signature for the new commitment transaction The signature for the new commitment + /// transaction. + pub commit_signature: Signature, + /// The commit transaction number for which the above signature is intended. + pub commit_tx_number: u64, + /// The htlc signatures for the new commitment transactions. + pub htlc_signatures: Vec, +} + +impl_dlc_writeable!(SubChannelCloseAccept, { + (channel_id, writeable), + (commit_signature, writeable), + (commit_tx_number, writeable), + (htlc_signatures, writeable) +}); + +/// A message to confirm the collaborative closing of a DLC channel embedded within a Lightning +/// channel. +#[derive(Clone, Debug, PartialEq, Eq)] +#[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], + /// The signature for the new commitment transaction The signature for the new commitment + /// transaction. + pub commit_signature: Signature, + /// The commit transaction number for which the above signature is intended. + pub commit_tx_number: u64, + /// The htlc signatures for the new commitment transactions. + pub htlc_signatures: Vec, + /// The pre-image of the split transaction revocation point. + pub split_revocation_secret: SecretKey, + /// The pre-image of the commit transaction revocation point. + pub commit_revocation_secret: SecretKey, + /// The point to be used for computing the revocation of the next commitment transaction. + pub next_per_commitment_point: PublicKey, +} + +impl_dlc_writeable!(SubChannelCloseConfirm, { + (channel_id, writeable), + (commit_signature, writeable), + (commit_tx_number, writeable), + (htlc_signatures, writeable), + (split_revocation_secret, writeable), + (commit_revocation_secret, writeable), + (next_per_commitment_point, writeable) +}); + +/// A message to finalize the collaborative closing of a DLC channel embedded within a Lightning +/// channel. +#[derive(Clone, Debug, Eq, PartialEq)] +#[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], + /// The pre-image of the split transaction revocation point. + pub split_revocation_secret: SecretKey, + /// The pre-image of the commit transaction revocation point. + pub commit_revocation_secret: Option, + /// The point to be used for computing the revocation of the next commitment transaction. + pub next_per_commitment_point: Option, +} + +impl_dlc_writeable!(SubChannelCloseFinalize, { + (channel_id, writeable), + (split_revocation_secret, writeable), + (commit_revocation_secret, option), + (next_per_commitment_point, option) +}); + +/// 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], +} + +impl_dlc_writeable!(Reject, { (channel_id, writeable) }); diff --git a/dlc-sled-storage-provider/Cargo.toml b/dlc-sled-storage-provider/Cargo.toml index 5f45a8ef..f627f6d5 100644 --- a/dlc-sled-storage-provider/Cargo.toml +++ b/dlc-sled-storage-provider/Cargo.toml @@ -9,12 +9,18 @@ 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.116"} +log = "0.4.14" 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" +dlc-manager = {path = "../dlc-manager", features = ["use-serde"]} diff --git a/dlc-sled-storage-provider/Readme.md b/dlc-sled-storage-provider/Readme.md index 50d911ef..8a3c6682 100644 --- a/dlc-sled-storage-provider/Readme.md +++ b/dlc-sled-storage-provider/Readme.md @@ -1,3 +1,8 @@ # Sled storage provider -Implementation of the storage trait required by the [dlc-manager](../dlc-manager) using the [Sled](https://github.com/spacejam/sled) embedded data base. \ No newline at end of file +Implementation of the storage trait required by the [dlc-manager](../dlc-manager) using the [Sled](https://github.com/spacejam/sled) embedded database. + +## Tests + +We have roundtrip tests to check the behavior of all the methods defined by the `dlc_manager::Storage` trait. +For all types which can be saved and loaded via the `Storage` trait, after their serialization format changes we must run the [generate_serialized_contract_files.sh](../scripts/generate_serialized_contract_files.sh) script to update the static files used in these roundtrip tests. diff --git a/dlc-sled-storage-provider/src/lib.rs b/dlc-sled-storage-provider/src/lib.rs index 2c1e092b..eec82724 100644 --- a/dlc-sled-storage-provider/src/lib.rs +++ b/dlc-sled-storage-provider/src/lib.rs @@ -20,7 +20,9 @@ use dlc_manager::chain_monitor::ChainMonitor; use dlc_manager::channel::accepted_channel::AcceptedChannel; use dlc_manager::channel::offered_channel::OfferedChannel; use dlc_manager::channel::signed_channel::{SignedChannel, SignedChannelStateType}; -use dlc_manager::channel::{Channel, FailedAccept, FailedSign}; +use dlc_manager::channel::{ + Channel, ClosedChannel, ClosedPunishedChannel, ClosingChannel, FailedAccept, FailedSign, +}; use dlc_manager::contract::accepted_contract::AcceptedContract; use dlc_manager::contract::offered_contract::OfferedContract; use dlc_manager::contract::ser::Serializable; @@ -28,10 +30,10 @@ use dlc_manager::contract::signed_contract::SignedContract; use dlc_manager::contract::{ ClosedContract, Contract, FailedAcceptContract, FailedSignContract, PreClosedContract, }; +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}; @@ -40,18 +42,20 @@ use simple_wallet::WalletStorage; use sled::transaction::{ConflictableTransactionResult, UnabortableTransactionError}; use sled::{Db, Transactional, Tree}; use std::convert::TryInto; -use std::io::{Cursor, Read}; +use std::io::{Cursor, Read, Seek, SeekFrom}; const CONTRACT_TREE: u8 = 1; const CHANNEL_TREE: u8 = 2; const CHAIN_MONITOR_TREE: u8 = 3; const CHAIN_MONITOR_KEY: u8 = 4; #[cfg(feature = "wallet")] -const UTXO_TREE: u8 = 6; +const UTXO_TREE: u8 = 5; #[cfg(feature = "wallet")] -const KEY_PAIR_TREE: u8 = 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 { @@ -60,7 +64,7 @@ pub struct SledStorageProvider { macro_rules! convertible_enum { (enum $name:ident { - $($vname:ident $(= $val:expr)?,)*; + $($vname:ident $(= $val:expr)? $(; $subprefix:ident, $subfield:ident)?,)*; $($tname:ident $(= $tval:expr)?,)* }, $input:ident) => { #[derive(Debug)] @@ -82,7 +86,7 @@ macro_rules! convertible_enum { match v { $(x if x == u8::from($name::$vname) => Ok($name::$vname),)* $(x if x == u8::from($name::$tname) => Ok($name::$tname),)* - _ => Err(Error::StorageError("Unknown prefix".to_string())), + x => Err(Error::StorageError(format!("Unknown prefix {}", x))), } } } @@ -117,9 +121,14 @@ convertible_enum!( convertible_enum!( enum ChannelPrefix { - Offered = 100, + Offered = 1, Accepted, - Signed, + Signed; SignedChannelPrefix, state, + Closing, + Closed, + CounterClosed, + ClosedPunished, + CollaborativelyClosed, FailedAccept, FailedSign,; }, @@ -135,18 +144,35 @@ convertible_enum!( SettledConfirmed, Settled, Closing, - Closed, - CounterClosed, - ClosedPunished, CollaborativeCloseOffered, - CollaborativelyClosed, RenewAccepted, RenewOffered, RenewConfirmed, + RenewFinalized, }, SignedChannelStateType ); +convertible_enum!( + enum SubChannelPrefix {; + Offered = 1, + Accepted, + Confirmed, + Finalized, + Signed, + Closing, + OnChainClosed, + CounterOnChainClosed, + CloseOffered, + CloseAccepted, + CloseConfirmed, + OffChainClosed, + ClosedPunished, + Rejected, + }, + SubChannelState +); + fn to_storage_error(e: T) -> Error where T: std::fmt::Display, @@ -190,7 +216,7 @@ impl SledStorageProvider { fn open_tree(&self, tree_id: &[u8; 1]) -> Result { self.db .open_tree(tree_id) - .map_err(|e| Error::StorageError(format!("Error opening contract tree: {}", e))) + .map_err(|e| Error::StorageError(format!("Error opening contract tree: {e}"))) } fn contract_tree(&self) -> Result { @@ -200,6 +226,10 @@ impl SledStorageProvider { fn channel_tree(&self) -> Result { self.open_tree(&[CHANNEL_TREE]) } + + fn sub_channel_tree(&self) -> Result { + self.open_tree(&[SUB_CHANNEL_TREE]) + } } #[cfg(feature = "wallet")] @@ -230,11 +260,18 @@ impl Storage for SledStorageProvider { } fn get_contracts(&self) -> Result, Error> { - self.contract_tree()? + Ok(self + .contract_tree()? .iter() .values() - .map(|x| deserialize_contract(&x.unwrap())) - .collect::, Error>>() + .filter_map(|x| match deserialize_contract(&x.unwrap()) { + Ok(contract) => Some(contract), + Err(e) => { + log::error!("Failed to deserialize contract: {e}"); + None + } + }) + .collect::>()) } fn create_contract(&self, contract: &OfferedContract) -> Result<(), Error> { @@ -335,6 +372,8 @@ impl Storage for SledStorageProvider { }, ) .map_err(to_storage_error)?; + + channel_tree.flush().map_err(to_storage_error)?; Ok(()) } @@ -386,14 +425,14 @@ impl Storage for SledStorageProvider { fn persist_chain_monitor(&self, monitor: &ChainMonitor) -> Result<(), Error> { self.open_tree(&[CHAIN_MONITOR_TREE])? .insert([CHAIN_MONITOR_KEY], monitor.serialize()?) - .map_err(|e| Error::StorageError(format!("Error writing chain monitor: {}", e)))?; + .map_err(|e| Error::StorageError(format!("Error writing chain monitor: {e}")))?; Ok(()) } fn get_chain_monitor(&self) -> Result, dlc_manager::error::Error> { let serialized = self .open_tree(&[CHAIN_MONITOR_TREE])? .get([CHAIN_MONITOR_KEY]) - .map_err(|e| Error::StorageError(format!("Error reading chain monitor: {}", e)))?; + .map_err(|e| Error::StorageError(format!("Error reading chain monitor: {e}")))?; let deserialized = match serialized { Some(s) => Some( ChainMonitor::deserialize(&mut ::std::io::Cursor::new(s)) @@ -403,6 +442,92 @@ impl Storage for SledStorageProvider { }; Ok(deserialized) } + + fn upsert_sub_channel(&self, subchannel: &SubChannel) -> Result<(), Error> { + let serialized = serialize_sub_channel(subchannel)?; + self.sub_channel_tree()? + .insert(subchannel.channel_id, serialized) + .map_err(to_storage_error)?; + + self.sub_channel_tree()?.flush().map_err(to_storage_error)?; + Ok(()) + } + + fn get_sub_channel( + &self, + channel_id: dlc_manager::ChannelId, + ) -> Result, Error> { + match self + .sub_channel_tree()? + .get(channel_id) + .map_err(to_storage_error)? + { + Some(res) => Ok(Some(deserialize_sub_channel(&res)?)), + None => Ok(None), + } + } + + fn get_sub_channels(&self) -> Result, Error> { + Ok(self + .sub_channel_tree()? + .iter() + .values() + .filter_map(|x| match deserialize_sub_channel(&x.unwrap()) { + Ok(sub_channel) => Some(sub_channel), + Err(e) => { + log::error!("Failed to deserialize subchannel: {e}"); + None + } + }) + .collect::>()) + } + + fn get_offered_sub_channels(&self) -> Result, Error> { + self.get_data_with_prefix( + &self.sub_channel_tree()?, + &[SubChannelPrefix::Offered.into()], + 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) if buf.len() > 0 => buf, + Some(_) | None => return Ok(Vec::new()), + }; + + debug_assert!(buf.len() > 0); + + 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")] @@ -428,7 +553,7 @@ impl WalletStorage for SledStorageProvider { .keys() .map(|x| { Ok(String::from_utf8(x.map_err(to_storage_error)?.to_vec()) - .map_err(|e| Error::InvalidState(format!("Could not read address key {}", e)))? + .map_err(|e| Error::InvalidState(format!("Could not read address key {e}")))? .parse() .expect("to have a valid address as key")) }) @@ -498,7 +623,7 @@ impl WalletStorage for SledStorageProvider { let ivec = x.map_err(to_storage_error)?; let mut cursor = Cursor::new(&ivec); let res = - Utxo::read(&mut cursor).map_err(|x| Error::InvalidState(format!("{}", x)))?; + Utxo::read(&mut cursor).map_err(|x| Error::InvalidState(format!("{x}")))?; Ok(res) }) .collect::, Error>>() @@ -510,12 +635,7 @@ impl WalletStorage for SledStorageProvider { let mut utxo = match utxo_tree.get(&key).map_err(to_storage_error)? { Some(res) => Utxo::read(&mut Cursor::new(&res)) .map_err(|_| Error::InvalidState("Could not read UTXO".to_string()))?, - None => { - return Err(Error::InvalidState(format!( - "No utxo for {} {}", - txid, vout - ))) - } + None => return Err(Error::InvalidState(format!("No utxo for {txid} {vout}"))), }; utxo.reserved = false; @@ -605,6 +725,11 @@ fn serialize_channel(channel: &Channel) -> Result, ::std::io::Error> { Channel::Signed(s) => s.serialize(), Channel::FailedAccept(f) => f.serialize(), Channel::FailedSign(f) => f.serialize(), + Channel::Closing(c) => c.serialize(), + Channel::Closed(c) | Channel::CounterClosed(c) | Channel::CollaborativelyClosed(c) => { + c.serialize() + } + Channel::ClosedPunished(c) => c.serialize(), }; let mut serialized = serialized?; let mut res = Vec::with_capacity(serialized.len() + 1); @@ -639,6 +764,21 @@ fn deserialize_channel(buff: &sled::IVec) -> Result { ChannelPrefix::FailedSign => { Channel::FailedSign(FailedSign::deserialize(&mut cursor).map_err(to_storage_error)?) } + ChannelPrefix::Closing => { + Channel::Closing(ClosingChannel::deserialize(&mut cursor).map_err(to_storage_error)?) + } + ChannelPrefix::Closed => { + Channel::Closed(ClosedChannel::deserialize(&mut cursor).map_err(to_storage_error)?) + } + ChannelPrefix::CollaborativelyClosed => Channel::CollaborativelyClosed( + ClosedChannel::deserialize(&mut cursor).map_err(to_storage_error)?, + ), + ChannelPrefix::CounterClosed => Channel::CounterClosed( + ClosedChannel::deserialize(&mut cursor).map_err(to_storage_error)?, + ), + ChannelPrefix::ClosedPunished => Channel::ClosedPunished( + ClosedPunishedChannel::deserialize(&mut cursor).map_err(to_storage_error)?, + ), }; Ok(channel) } @@ -656,6 +796,23 @@ fn get_utxo_key(txid: &Txid, vout: u32) -> Vec { key } +fn serialize_sub_channel(sub_channel: &SubChannel) -> Result, ::std::io::Error> { + let prefix = SubChannelPrefix::get_prefix(&sub_channel.state); + let mut buf = Vec::new(); + + buf.push(prefix); + buf.append(&mut sub_channel.serialize()?); + + Ok(buf) +} + +fn deserialize_sub_channel(buff: &sled::IVec) -> Result { + let mut cursor = ::std::io::Cursor::new(buff); + // Skip prefix + cursor.seek(SeekFrom::Start(1))?; + SubChannel::deserialize(&mut cursor).map_err(to_storage_error) +} + #[cfg(test)] mod tests { use super::*; @@ -667,6 +824,7 @@ mod tests { let path = format!("{}{}", "test_files/sleddb/", std::stringify!($name)); { let storage = SledStorageProvider::new(&path).expect("Error opening sled DB"); + #[allow(clippy::redundant_closure_call)] $body(storage); } std::fs::remove_dir_all(path).unwrap(); @@ -812,6 +970,31 @@ mod tests { .expect("Error creating contract"); } + fn insert_sub_channels(storage: &mut SledStorageProvider) { + let serialized = include_bytes!("../test_files/OfferedSubChannel"); + let offered_sub_channel = deserialize_object(serialized); + storage + .upsert_sub_channel(&offered_sub_channel) + .expect("Error inserting sub channel"); + let serialized = include_bytes!("../test_files/OfferedSubChannel1"); + let offered_sub_channel = deserialize_object(serialized); + storage + .upsert_sub_channel(&offered_sub_channel) + .expect("Error inserting sub channel"); + + let serialized = include_bytes!("../test_files/SignedSubChannel"); + let signed_sub_channel = deserialize_object(serialized); + storage + .upsert_sub_channel(&signed_sub_channel) + .expect("Error inserting sub channel"); + + let serialized = include_bytes!("../test_files/AcceptedSubChannel"); + let accepted_sub_channel = deserialize_object(serialized); + storage + .upsert_sub_channel(&accepted_sub_channel) + .expect("Error inserting sub channel"); + } + sled_test!( get_signed_contracts_only_signed, |mut storage: SledStorageProvider| { @@ -901,6 +1084,8 @@ mod tests { .. } = &signed_channels[0].state { + let channel_id = signed_channels[0].channel_id; + storage.get_channel(&channel_id).unwrap(); } else { panic!( "Expected established state got {:?}", @@ -973,4 +1158,59 @@ mod tests { assert_eq!(chain_monitor, retrieved); } ); + + sled_test!( + get_offered_sub_channels_only_offered, + |mut storage: SledStorageProvider| { + insert_sub_channels(&mut storage); + + let offered_sub_channels = storage + .get_offered_sub_channels() + .expect("Error retrieving offered sub channels"); + assert_eq!(2, offered_sub_channels.len()); + } + ); + + sled_test!( + get_sub_channels_all_returned, + |mut storage: SledStorageProvider| { + insert_sub_channels(&mut storage); + + let offered_sub_channels = storage + .get_sub_channels() + .expect("Error retrieving offered sub channels"); + 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); + } + ); + + sled_test!(get_actions_unset_test, |storage: SledStorageProvider| { + let actions = storage + .get_sub_channel_actions() + .expect("Error getting sub channel actions"); + assert_eq!(actions.len(), 0); + }); + + sled_test!(get_empty_actions_test, |storage: SledStorageProvider| { + storage.save_sub_channel_actions(&[]).unwrap(); + let actions = storage + .get_sub_channel_actions() + .expect("Error getting sub channel actions"); + assert_eq!(actions.len(), 0); + }); } diff --git a/dlc-sled-storage-provider/test_files/Accepted b/dlc-sled-storage-provider/test_files/Accepted index bcb851ca..e2f44eeb 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 cc924b8b..742c297a 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 new file mode 100644 index 00000000..eb8792dd Binary files /dev/null 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 eca0e8e6..9008a5e9 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 e237b301..4d290999 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 8a4307d1..78cf360c 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 ec98c662..e93e61a7 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 dbbe53f9..e86a9031 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 new file mode 100644 index 00000000..94341f5f Binary files /dev/null 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 new file mode 100644 index 00000000..6defd78b Binary files /dev/null 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 b3442e9f..89a4954d 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 273dfad2..13fc561e 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 bfc34a1d..c0348412 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 8c7b2348..9e1b641e 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 5449383b..cbffe735 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 new file mode 100644 index 00000000..cf6aa80d Binary files /dev/null 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-trie/src/digit_decomposition.rs b/dlc-trie/src/digit_decomposition.rs index eb1b0060..00b59c7f 100644 --- a/dlc-trie/src/digit_decomposition.rs +++ b/dlc-trie/src/digit_decomposition.rs @@ -77,7 +77,7 @@ pub fn pad_range_payouts( fn take_prefix(start: &mut Vec, end: &mut Vec) -> Vec { if start == end { end.clear(); - return start.drain(0..).collect(); + return std::mem::take(start); } let mut i = 0; while start[i] == end[i] { diff --git a/dlc-trie/src/digit_trie.rs b/dlc-trie/src/digit_trie.rs index 4e649518..6f035a4d 100644 --- a/dlc-trie/src/digit_trie.rs +++ b/dlc-trie/src/digit_trie.rs @@ -259,7 +259,7 @@ impl<'a, T> Iterator for DigitTrieIter<'a, T> { while cur_child < (self.trie.base as isize) { self.index_stack.push((Some(cur_index), cur_child + 1)); self.index_stack - .push((cur_children[(cur_child as usize)], -1)); + .push((cur_children[cur_child as usize], -1)); match self.next() { None => { self.index_stack.pop(); diff --git a/dlc-trie/src/multi_oracle.rs b/dlc-trie/src/multi_oracle.rs index f3de47c9..7b6620db 100644 --- a/dlc-trie/src/multi_oracle.rs +++ b/dlc-trie/src/multi_oracle.rs @@ -70,7 +70,7 @@ fn compute_min_support_covering_prefix( left_bound .into_iter() - .zip(right_bound.into_iter()) + .zip(right_bound) .take_while(|(x, y)| x == y) .map(|(x, _)| x) .collect() @@ -800,7 +800,7 @@ mod tests { } fn variable_len_test_cases() -> Vec { - let black_list = vec![5, 7]; + let black_list = [5, 7]; let mut test_cases = test_cases() .into_iter() .enumerate() diff --git a/dlc/Cargo.toml b/dlc/Cargo.toml index e2039838..c74d446b 100644 --- a/dlc/Cargo.toml +++ b/dlc/Cargo.toml @@ -17,7 +17,7 @@ serde = {version = "1.0", default-features = false, optional = true} [features] # for benchmarks unstable = [] -use-serde = ["serde", "secp256k1-zkp/use-serde"] +use-serde = ["serde", "secp256k1-zkp/use-serde", "bitcoin/serde"] [dev-dependencies] bitcoin = {version = "0.29.2"} diff --git a/dlc/src/channel/mod.rs b/dlc/src/channel/mod.rs index 174dff5e..d97d2580 100644 --- a/dlc/src/channel/mod.rs +++ b/dlc/src/channel/mod.rs @@ -11,10 +11,12 @@ use bitcoin::{ }; use miniscript::Descriptor; use secp256k1_zkp::{ - schnorr::Signature as SchnorrSignature, EcdsaAdaptorSignature, PublicKey as SecpPublicKey, - Secp256k1, SecretKey, Signing, Verification, + ecdsa::Signature, schnorr::Signature as SchnorrSignature, EcdsaAdaptorSignature, + PublicKey as SecpPublicKey, Secp256k1, SecretKey, Signing, Verification, }; +pub mod sub_channel; + /** * Weight of the buffer transaction: * INPUT @@ -24,14 +26,14 @@ use secp256k1_zkp::{ * scriptSig -> 0 * nSequence -> 4 * 4 * Witness item count -> 1 - * Witness -> 220 + * Witness -> 366 * OUTPUT: * nValue -> 8 * 4 * scriptPubkeyLen -> 1 * 4 * scriptPubkey -> 34 * 4 * TOTAL: 599 */ -const BUFFER_TX_WEIGHT: usize = 599; +pub const BUFFER_TX_WEIGHT: usize = 748; /** * Due to the buffer output script being more complex than the funding output @@ -47,7 +49,7 @@ const BUFFER_TX_WEIGHT: usize = 599; * Regular CET witness size: 220 * Extra size => 330 */ -const CET_EXTRA_WEIGHT: usize = 330; +pub const CET_EXTRA_WEIGHT: usize = 330; // Same as buffer input const SETTLE_INPUT_WEIGHT: usize = 428; @@ -163,7 +165,7 @@ pub fn verify_tx_adaptor_signature( /// Returns a settle transaction. pub fn create_settle_transaction( - fund_tx_in: &TxIn, + prev_outpoint: &OutPoint, offer_revoke_params: &RevokeParams, accept_revoke_params: &RevokeParams, offer_payout: u64, @@ -204,19 +206,26 @@ pub fn create_settle_transaction( - offer_payout - accept_payout - crate::util::weight_to_fee( - SETTLE_INPUT_WEIGHT + output.len() * SETTLE_OUTPUT_WEIGHT, + SETTLE_INPUT_WEIGHT + output.len() * SETTLE_OUTPUT_WEIGHT + 148, fee_rate_per_vb, )?) / (output.len() as u64); - for mut o in &mut output { + for o in &mut output { o.value += remaining_fee; } + let input = TxIn { + previous_output: *prev_outpoint, + script_sig: Script::default(), + sequence: crate::util::get_sequence(lock_time), + witness: Witness::default(), + }; + Ok(Transaction { version: super::TX_VERSION, lock_time: PackedLockTime(lock_time), - input: vec![fund_tx_in.clone()], + input: vec![input], output, }) } @@ -258,6 +267,8 @@ pub fn create_channel_transactions( fee_rate_per_vb, cet_lock_time, cet_nsequence, + None, + None, ) } @@ -275,13 +286,24 @@ pub fn create_renewal_channel_transactions( fee_rate_per_vb: u64, cet_lock_time: u32, cet_nsequence: Sequence, + fund_vout: Option, + buffer_nsequence: Option, ) -> Result { let extra_fee = super::util::weight_to_fee(BUFFER_TX_WEIGHT + CET_EXTRA_WEIGHT, fee_rate_per_vb)?; - let (fund_vout, fund_output) = - super::util::get_output_for_script_pubkey(fund_tx, &funding_script_pubkey.to_v0_p2wsh()) - .expect("to find the funding script pubkey"); + let (fund_vout, fund_output) = { + if let Some(fund_vout) = fund_vout { + (fund_vout, &fund_tx.output[fund_vout]) + } else { + super::util::get_output_for_script_pubkey(fund_tx, &funding_script_pubkey.to_v0_p2wsh()) + .expect("to find the funding script pubkey") + } + }; + + if fund_output.value <= extra_fee + super::DUST_LIMIT { + return Err(Error::InvalidArgument); + } let outpoint = OutPoint { txid: fund_tx.txid(), @@ -290,7 +312,7 @@ pub fn create_renewal_channel_transactions( let tx_in = TxIn { previous_output: outpoint, - sequence: super::util::get_sequence(cet_lock_time), + sequence: buffer_nsequence.unwrap_or_else(|| crate::util::get_sequence(cet_lock_time)), script_sig: Script::default(), witness: Witness::default(), }; @@ -375,6 +397,36 @@ pub fn sign_cet( Ok(()) } +/// Use the given parameters to build the descriptor of the given buffer transaction and inserts +/// the signatures in the transaction witness. +pub fn satisfy_buffer_descriptor( + tx: &mut Transaction, + offer_params: &RevokeParams, + accept_params: &RevokeParams, + own_pubkey: &SecpPublicKey, + own_signature: &Signature, + counter_pubkey: &PublicKey, + counter_signature: &Signature, +) -> Result<(), Error> { + let descriptor = buffer_descriptor(offer_params, accept_params); + let sigs = HashMap::from([ + ( + PublicKey { + inner: *own_pubkey, + compressed: true, + }, + EcdsaSig::sighash_all(*own_signature), + ), + (*counter_pubkey, EcdsaSig::sighash_all(*counter_signature)), + ]); + + descriptor + .satisfy(&mut tx.input[0], sigs) + .map_err(|_| Error::InvalidArgument)?; + + Ok(()) +} + /// Returns a signed transaction to punish the publication of a revoked buffer /// transaction. pub fn create_and_sign_punish_buffer_transaction( @@ -593,15 +645,7 @@ pub fn buffer_descriptor( }; // heavily inspired by: https://github.com/comit-network/maia/blob/main/src/protocol.rs#L283 // policy: or(and(pk(offer_pk),pk(accept_pk)),or(and(pk(offer_pk),and(pk(accept_publish_pk), pk(accept_rev_pk))),and(pk(accept_pk),and(pk(offer_publish_pk),pk(offer_rev_pk))))) - let script = format!("wsh(c:andor(pk({first_pk}),pk_k({second_pk}),or_i(and_v(v:pkh({offer_pk_hash}),and_v(v:pkh({accept_publish_pk_hash}),pk_h({accept_revoke_pk_hash}))),and_v(v:pkh({accept_pk_hash}),and_v(v:pkh({offer_publish_pk_hash}),pk_h({offer_revoke_pk_hash}))))))", - first_pk = first_pk, - second_pk = second_pk, - offer_pk_hash = offer_pk, - accept_pk_hash = accept_pk, - accept_publish_pk_hash = accept_publish_pk, - accept_revoke_pk_hash = accept_revoke_pk, - offer_publish_pk_hash = offer_publish_pk, - offer_revoke_pk_hash = offer_revoke_pk); + let script = format!("wsh(c:andor(pk({first_pk}),pk_k({second_pk}),or_i(and_v(v:pkh({offer_pk}),and_v(v:pkh({accept_publish_pk}),pk_h({accept_revoke_pk}))),and_v(v:pkh({accept_pk}),and_v(v:pkh({offer_publish_pk}),pk_h({offer_revoke_pk}))))))"); script.parse().expect("a valid miniscript") } @@ -795,7 +839,7 @@ mod tests { let payout = 100000000; let csv_timelock = 100; let settle_tx = create_settle_transaction( - &TxIn::default(), + &OutPoint::default(), &offer_params, &accept_params, payout, diff --git a/dlc/src/channel/sub_channel.rs b/dlc/src/channel/sub_channel.rs new file mode 100644 index 00000000..bb0d59ab --- /dev/null +++ b/dlc/src/channel/sub_channel.rs @@ -0,0 +1,245 @@ +//! Module containing utility functions to create transactions for DLC channels embedded in +//! Lightning channels. +//! + +use std::collections::HashMap; + +use bitcoin::{ + Address, EcdsaSig, OutPoint, PackedLockTime, PublicKey, Script, Sequence, Transaction, TxIn, + TxOut, Witness, +}; +use secp256k1_zkp::{PublicKey as SecpPublicKey, Secp256k1, SecretKey, Signing}; + +use crate::{channel::buffer_descriptor, Error}; + +use super::{RevokeParams, BUFFER_TX_WEIGHT}; + +/** + * Weight of the split transaction: + * INPUT + * Overhead -> 10.5 * 4 + * Outpoint -> 36 * 4 + * scriptSigLength -> 1 * 4 + * scriptSig -> 0 + * nSequence -> 4 * 4 + * Witness item count -> 1 + * Witness -> 220 + * OUTPUT (x2): + * nValue -> 8 * 4 + * scriptPubkeyLen -> 1 * 4 + * scriptPubkey -> 34 * 4 + * TOTAL: 771 +*/ +pub const SPLIT_TX_WEIGHT: usize = 771; + +/** + * Weight of the ln glue transaction is the same as the buffer transaction. +*/ +pub const LN_GLUE_TX_WEIGHT: usize = BUFFER_TX_WEIGHT; + +/// Computes the total amount of fee required for a split transaction plus transactions of a DLC +/// channel. +/// # Errors +/// Returns an error if the given `fee_rate_per_vb` is too large resulting in an overflow during +/// the computation. +pub fn dlc_channel_and_split_fee(fee_rate_per_vb: u64) -> Result { + Ok(crate::util::weight_to_fee( + crate::channel::sub_channel::SPLIT_TX_WEIGHT, + fee_rate_per_vb, + )? + crate::util::weight_to_fee(crate::channel::BUFFER_TX_WEIGHT, fee_rate_per_vb)? + + crate::util::weight_to_fee( + crate::channel::CET_EXTRA_WEIGHT + + crate::CET_BASE_WEIGHT + + crate::P2WPKH_WITNESS_SIZE * 2 + + 18, + fee_rate_per_vb, + )?) +} + +#[derive(Clone, Debug, PartialEq, Eq)] +/// Structure containing a split transaction and its associated output script. +pub struct SplitTx { + /// The actual Bitcoin transaction representation. + pub transaction: Transaction, + /// The script used to lock the outputs. + pub output_script: Script, +} + +/// Creates a [`SplitTx`] struct from the given parameter. +/// # Errors +/// Returns an error if the given fee rate makes fee computation overflow, or if the given +/// `channel_value` is not enough for the given `dlc_collateral` and fee. +pub fn create_split_tx( + offer_revoke_params: &RevokeParams, + accept_revoke_params: &RevokeParams, + fund_tx_outpoint: &OutPoint, + channel_value: u64, + dlc_collateral: u64, + fee_rate_per_vb: u64, +) -> Result { + let output_desc = buffer_descriptor(offer_revoke_params, accept_revoke_params); + + let dlc_fee = crate::util::weight_to_fee(super::BUFFER_TX_WEIGHT, fee_rate_per_vb)? + + crate::util::weight_to_fee( + super::CET_EXTRA_WEIGHT + crate::CET_BASE_WEIGHT + 2 * crate::P2WPKH_WITNESS_SIZE + 18, + fee_rate_per_vb, + )?; + + let dlc_output_value = dlc_collateral + .checked_add(dlc_fee) + .ok_or(Error::InvalidArgument)?; + + if dlc_output_value + > channel_value + .checked_add(crate::DUST_LIMIT) + .ok_or(Error::InvalidArgument)? + { + return Err(Error::InvalidArgument); + } + + let ln_output_value = channel_value + - dlc_output_value + - crate::util::weight_to_fee(SPLIT_TX_WEIGHT, fee_rate_per_vb)?; + + let output_values = [ln_output_value, dlc_output_value]; + + let output = output_values + .iter() + .map(|value| TxOut { + value: *value, + script_pubkey: output_desc.script_pubkey(), + }) + .collect::>(); + + Ok(SplitTx { + transaction: Transaction { + version: 2, + lock_time: PackedLockTime::ZERO, + input: vec![TxIn { + previous_output: *fund_tx_outpoint, + script_sig: Script::default(), + sequence: Sequence::ZERO, + witness: Witness::default(), + }], + output, + }, + output_script: output_desc.script_code().unwrap(), + }) +} + +/// Creates a "glue" transaction for the Lightning side of the split channel. +pub fn create_ln_glue_tx( + split_tx_outpoint: &OutPoint, + ln_fund_script: &Script, + lock_time: PackedLockTime, + nsequence: Sequence, + output_value: u64, +) -> Transaction { + Transaction { + version: 2, + lock_time, + input: { + vec![TxIn { + previous_output: *split_tx_outpoint, + script_sig: Script::default(), + sequence: nsequence, + witness: Witness::default(), + }] + }, + output: vec![TxOut { + value: output_value, + script_pubkey: ln_fund_script.to_v0_p2wsh(), + }], + } +} + +/// Creates and signs a transaction spending both output of a revoked split transaction that was +/// published on-chain. +pub fn create_and_sign_punish_split_transaction( + secp: &Secp256k1, + offer_params: &RevokeParams, + accept_params: &RevokeParams, + own_sk: &SecretKey, + counter_publish_sk: &SecretKey, + counter_revoke_sk: &SecretKey, + prev_tx: &Transaction, + dest_address: &Address, + lock_time: u32, + fee_rate_per_vb: u64, +) -> Result { + let descriptor = buffer_descriptor(offer_params, accept_params); + + let tx_in = vec![ + TxIn { + previous_output: OutPoint { + txid: prev_tx.txid(), + vout: 0, + }, + sequence: Sequence::ZERO, + script_sig: Script::default(), + witness: Witness::default(), + }, + TxIn { + previous_output: OutPoint { + txid: prev_tx.txid(), + vout: 1, + }, + sequence: Sequence::ZERO, + script_sig: Script::default(), + witness: Witness::default(), + }, + ]; + + let dest_script_pk_len = dest_address.script_pubkey().len(); + let var_int_prefix_len = crate::util::compute_var_int_prefix_size(dest_script_pk_len); + let output_weight = super::N_VALUE_WEIGHT + var_int_prefix_len + dest_script_pk_len * 4; + let tx_fee = crate::util::weight_to_fee( + super::PUNISH_BUFFER_INPUT_WEIGHT * 2 + output_weight, + fee_rate_per_vb, + )?; + + let output_value = prev_tx.output[0].value + prev_tx.output[1].value - tx_fee; + + let mut tx = Transaction { + version: crate::TX_VERSION, + lock_time: PackedLockTime(lock_time), + input: tx_in, + output: vec![TxOut { + value: output_value, + script_pubkey: dest_address.script_pubkey(), + }], + }; + + for i in 0..2 { + let mut sigs = HashMap::new(); + + for sk in &[&own_sk, &counter_publish_sk, &counter_revoke_sk] { + let pk = PublicKey { + inner: SecpPublicKey::from_secret_key(secp, sk), + compressed: true, + }; + + let pkh = pk.pubkey_hash().as_hash(); + sigs.insert( + pkh, + ( + pk, + EcdsaSig::sighash_all(crate::util::get_raw_sig_for_tx_input( + secp, + &tx, + i, + &descriptor.script_code()?, + prev_tx.output[i].value, + sk, + )?), + ), + ); + } + + descriptor + .satisfy(&mut tx.input[i], sigs.clone()) + .map_err(|_| Error::InvalidArgument)?; + } + + Ok(tx) +} diff --git a/dlc/src/lib.rs b/dlc/src/lib.rs index 1d184ef8..f9e0fd3a 100644 --- a/dlc/src/lib.rs +++ b/dlc/src/lib.rs @@ -45,7 +45,7 @@ pub mod util; /// Minimum value that can be included in a transaction output. Under this value, /// outputs are discarded /// See: https://github.com/discreetlogcontracts/dlcspecs/blob/master/Transactions.md#change-outputs -const DUST_LIMIT: u64 = 1000; +pub const DUST_LIMIT: u64 = 1000; /// The transaction version /// See: https://github.com/discreetlogcontracts/dlcspecs/blob/master/Transactions.md#funding-transaction @@ -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), @@ -236,7 +236,7 @@ impl std::error::Error 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/dlc/src/util.rs b/dlc/src/util.rs index f15e70a4..68948f05 100644 --- a/dlc/src/util.rs +++ b/dlc/src/util.rs @@ -17,6 +17,8 @@ pub(crate) const DISABLE_LOCKTIME: Sequence = Sequence(0xffffffff); // RBF but enables nLockTime usage. pub(crate) const ENABLE_LOCKTIME: Sequence = Sequence(0xfffffffe); +const MIN_FEE: u64 = 153; + /// Get a BIP143 (https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki) /// signature hash with sighash all flag for a segwit transaction input as /// a Message instance @@ -94,10 +96,15 @@ pub fn get_sig_for_p2wpkh_input( ) } -pub(crate) fn weight_to_fee(weight: usize, fee_rate: u64) -> Result { - (f64::ceil((weight as f64) / 4.0) as u64) - .checked_mul(fee_rate) - .ok_or(Error::InvalidArgument) +/// Computes the required fee for a transaction based on the given weight and fee +/// rate per vbyte. +pub fn weight_to_fee(weight: usize, fee_rate: u64) -> Result { + Ok(u64::max( + (f64::ceil((weight as f64) / 4.0) as u64) + .checked_mul(fee_rate) + .ok_or(Error::InvalidArgument)?, + MIN_FEE, + )) } fn get_pkh_script_pubkey_from_sk(secp: &Secp256k1, sk: &SecretKey) -> Script { @@ -158,39 +165,47 @@ pub fn sign_multi_sig_input( input_value: u64, input_index: usize, ) -> Result<(), Error> { - let own_sig = get_sig_for_tx_input( + let own_sig = get_raw_sig_for_tx_input( secp, transaction, input_index, script_pubkey, input_value, - EcdsaSighashType::All, sk, )?; - let own_pk = &PublicKey::from_secret_key(secp, sk); + let own_pk = PublicKey::from_secret_key(secp, sk); - let other_finalized_sig = finalize_sig(other_sig, EcdsaSighashType::All); - - transaction.input[input_index].witness = if own_pk < other_pk { - Witness::from_vec(vec![ - Vec::new(), - own_sig, - other_finalized_sig, - script_pubkey.to_bytes(), - ]) - } else { - Witness::from_vec(vec![ - Vec::new(), - other_finalized_sig, - own_sig, - script_pubkey.to_bytes(), - ]) - }; + finalize_multi_sig_input_transaction( + transaction, + vec![(*other_pk, *other_sig), (own_pk, own_sig)], + script_pubkey, + input_index, + ); Ok(()) } +/// Sorts signatures based on the lexicographical order of associated public keys, appends +/// `EcdsaSighashType::All` to each signature, and insert them in the transaction witness for the +/// provided input index, together with the given script pubkey. +pub fn finalize_multi_sig_input_transaction( + transaction: &mut Transaction, + mut signature_pubkey_pairs: Vec<(PublicKey, Signature)>, + script_pubkey: &Script, + input_index: usize, +) { + signature_pubkey_pairs.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); + let mut signatures = signature_pubkey_pairs + .into_iter() + .map(|(_, s)| finalize_sig(&s, EcdsaSighashType::All)) + .collect(); + let mut witness = vec![Vec::new()]; + witness.append(&mut signatures); + witness.push(script_pubkey.to_bytes()); + transaction.input[input_index].witness = Witness::from_vec(witness); +} + /// Transforms a redeem script for a p2sh-p2w* output to a script signature. pub(crate) fn redeem_script_to_script_sig(redeem: &Script) -> Script { match redeem.len() { @@ -202,7 +217,7 @@ pub(crate) fn redeem_script_to_script_sig(redeem: &Script) -> Script { /// Sorts the given inputs in following the order of the ids. pub(crate) fn order_by_serial_ids(inputs: Vec, ids: &[u64]) -> Vec { debug_assert!(inputs.len() == ids.len()); - let mut combined: Vec<(&u64, T)> = ids.iter().zip(inputs.into_iter()).collect(); + let mut combined: Vec<(&u64, T)> = ids.iter().zip(inputs).collect(); combined.sort_by(|a, b| a.0.partial_cmp(b.0).unwrap()); combined.into_iter().map(|x| x.1).collect() } diff --git a/electrs-blockchain-provider/Cargo.toml b/electrs-blockchain-provider/Cargo.toml index 84d5c069..958f5318 100644 --- a/electrs-blockchain-provider/Cargo.toml +++ b/electrs-blockchain-provider/Cargo.toml @@ -9,10 +9,10 @@ version = "0.1.0" bitcoin = {version = "0.29"} bitcoin-test-utils = {path = "../bitcoin-test-utils"} dlc-manager = {path = "../dlc-manager"} -lightning = {version = "0.0.113"} -lightning-block-sync = {version = "0.0.113"} +lightning = {version = "0.0.116"} +lightning-block-sync = {version = "0.0.116"} +log = "0.4.14" reqwest = {version = "0.11", features = ["blocking", "json"]} -rust-bitcoin-coin-selection = {version = "0.1.0", git = "https://github.com/p2pderivatives/rust-bitcoin-coin-selection", features = ["rand"]} serde = {version = "*", features = ["derive"]} simple-wallet = {path = "../simple-wallet"} tokio = "1" diff --git a/electrs-blockchain-provider/src/lib.rs b/electrs-blockchain-provider/src/lib.rs index c3d04b7d..87ddd817 100644 --- a/electrs-blockchain-provider/src/lib.rs +++ b/electrs-blockchain-provider/src/lib.rs @@ -12,33 +12,27 @@ use bitcoin_test_utils::tx_to_string; use dlc_manager::{error::Error, Blockchain, Utxo}; use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator}; use lightning_block_sync::{BlockData, BlockHeaderData, BlockSource, BlockSourceError}; +use log::error; use reqwest::blocking::Response; use serde::Deserialize; use serde::Serialize; const MIN_FEERATE: u32 = 253; -#[derive(Clone, Eq, Hash, PartialEq)] -pub enum Target { - Background = 144, - Normal = 18, - HighPriority = 6, -} - pub struct ElectrsBlockchainProvider { host: String, client: reqwest::blocking::Client, async_client: reqwest::Client, network: Network, - fees: Arc>, + fees: Arc>, } impl ElectrsBlockchainProvider { pub fn new(host: String, network: Network) -> Self { - let mut fees: HashMap = HashMap::new(); - fees.insert(Target::Background, AtomicU32::new(MIN_FEERATE)); - fees.insert(Target::Normal, AtomicU32::new(2000)); - fees.insert(Target::HighPriority, AtomicU32::new(5000)); + let mut fees: HashMap = HashMap::new(); + fees.insert(ConfirmationTarget::Background, AtomicU32::new(MIN_FEERATE)); + fees.insert(ConfirmationTarget::Normal, AtomicU32::new(2000)); + fees.insert(ConfirmationTarget::HighPriority, AtomicU32::new(5000)); let fees = Arc::new(fees); poll_for_fee_estimates(fees.clone(), &host); Self { @@ -202,19 +196,24 @@ impl simple_wallet::WalletBlockchainProvider for ElectrsBlockchainProvider { impl FeeEstimator for ElectrsBlockchainProvider { fn get_est_sat_per_1000_weight(&self, confirmation_target: ConfirmationTarget) -> u32 { let est = match confirmation_target { + ConfirmationTarget::MempoolMinimum => self + .fees + .get(&ConfirmationTarget::MempoolMinimum) + .unwrap() + .load(Ordering::Acquire), ConfirmationTarget::Background => self .fees - .get(&Target::Background) + .get(&ConfirmationTarget::Background) .unwrap() .load(Ordering::Acquire), ConfirmationTarget::Normal => self .fees - .get(&Target::Normal) + .get(&ConfirmationTarget::Normal) .unwrap() .load(Ordering::Acquire), ConfirmationTarget::HighPriority => self .fees - .get(&Target::HighPriority) + .get(&ConfirmationTarget::HighPriority) .unwrap() .load(Ordering::Acquire), }; @@ -304,20 +303,27 @@ impl BlockSource for ElectrsBlockchainProvider { } impl BroadcasterInterface for ElectrsBlockchainProvider { - fn broadcast_transaction(&self, tx: &Transaction) { + fn broadcast_transactions(&self, txs: &[&Transaction]) { let client = self.client.clone(); let host = self.host.clone(); - let body = bitcoin_test_utils::tx_to_string(tx); + let bodies = txs + .iter() + .map(|tx| bitcoin_test_utils::tx_to_string(tx)) + .collect::>(); std::thread::spawn(move || { - match client.post(format!("{host}tx")).body(body).send() { - Err(_) => {} - Ok(res) => { - if res.error_for_status_ref().is_err() { - // let body = res.text().unwrap_or_default(); - // TODO(tibo): log + for body in bodies { + match client.post(format!("{host}tx")).body(body).send() { + Err(e) => { + error!("Error broadcasting transaction: {}", e); + } + Ok(res) => { + if res.error_for_status_ref().is_err() { + let body = res.text().unwrap_or_default(); + error!("Error broadcasting transaction: {}", body); + } } - } - }; + }; + } }); } } @@ -359,25 +365,24 @@ struct SpentResp { type FeeEstimates = std::collections::HashMap; fn store_estimate_for_target( - fees: &Arc>, + fees: &Arc>, fee_estimates: &FeeEstimates, - target: Target, + target: ConfirmationTarget, ) { - #[allow(clippy::redundant_clone)] - let val = get_estimate_for_target(fee_estimates, &(target.clone() as u16)); + let val = get_estimate_for_target(fee_estimates, &(target as u16)); fees.get(&target) .unwrap() .store(val, std::sync::atomic::Ordering::Relaxed); } -fn poll_for_fee_estimates(fees: Arc>, host: &str) { +fn poll_for_fee_estimates(fees: Arc>, host: &str) { let host = host.to_owned(); std::thread::spawn(move || loop { if let Ok(res) = reqwest::blocking::get(format!("{host}fee-estimates")) { if let Ok(fee_estimates) = res.json::() { - store_estimate_for_target(&fees, &fee_estimates, Target::Background); - store_estimate_for_target(&fees, &fee_estimates, Target::HighPriority); - store_estimate_for_target(&fees, &fee_estimates, Target::Normal); + store_estimate_for_target(&fees, &fee_estimates, ConfirmationTarget::Background); + store_estimate_for_target(&fees, &fee_estimates, ConfirmationTarget::HighPriority); + store_estimate_for_target(&fees, &fee_estimates, ConfirmationTarget::Normal); } } diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 94f20f0c..a9fe6b17 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -10,7 +10,7 @@ cargo-fuzz = true [dependencies] dlc-messages = {path = "../dlc-messages"} honggfuzz = "0.5" -lightning = {version = "0.0.113" } +lightning = {version = "0.0.116"} [workspace] members = ["."] diff --git a/mocks/Cargo.toml b/mocks/Cargo.toml index 723baaf1..9e68dcdc 100644 --- a/mocks/Cargo.toml +++ b/mocks/Cargo.toml @@ -9,6 +9,6 @@ bitcoin = "0.29" dlc = {path = "../dlc"} dlc-manager = {path = "../dlc-manager"} dlc-messages = {path = "../dlc-messages"} -lightning = {version = "0.0.113"} -secp256k1-zkp = {version = "0.7.0", features = ["bitcoin_hashes", "rand", "rand-std"]} +lightning = {version = "0.0.116"} +secp256k1-zkp = {version = "0.7.0", features = ["bitcoin_hashes", "rand", "rand-std", "global-context"]} simple-wallet = {path = "../simple-wallet"} diff --git a/mocks/src/memory_storage_provider.rs b/mocks/src/memory_storage_provider.rs index ec75fa7a..7375123a 100644 --- a/mocks/src/memory_storage_provider.rs +++ b/mocks/src/memory_storage_provider.rs @@ -8,6 +8,8 @@ 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}; use secp256k1_zkp::{PublicKey, SecretKey}; @@ -18,11 +20,14 @@ use std::sync::{Mutex, RwLock}; pub struct MemoryStorage { contracts: RwLock>, channels: RwLock>, + sub_channels: RwLock>, contracts_saved: Mutex>>, channels_saved: Mutex>>, + sub_channels_saved: Mutex>>, addresses: RwLock>, utxos: RwLock>, key_pairs: RwLock>, + actions: RwLock>, } impl MemoryStorage { @@ -30,11 +35,14 @@ impl MemoryStorage { MemoryStorage { contracts: RwLock::new(HashMap::new()), channels: RwLock::new(HashMap::new()), + sub_channels: RwLock::new(HashMap::new()), contracts_saved: Mutex::new(None), channels_saved: Mutex::new(None), + sub_channels_saved: Mutex::new(None), addresses: RwLock::new(HashMap::new()), utxos: RwLock::new(HashMap::new()), key_pairs: RwLock::new(HashMap::new()), + actions: RwLock::new(Vec::new()), } } @@ -54,6 +62,14 @@ impl MemoryStorage { .expect("Could not get read lock") .clone(), ); + + let mut sub_channels_saved = self.sub_channels_saved.lock().unwrap(); + *sub_channels_saved = Some( + self.sub_channels + .read() + .expect("Could not get read lock") + .clone(), + ); } pub fn rollback(&self) { @@ -68,6 +84,12 @@ impl MemoryStorage { let mut tmp = None; std::mem::swap(&mut tmp, &mut *channels_saved); std::mem::swap(&mut *channels, &mut tmp.unwrap()); + + let mut sub_channels = self.sub_channels.write().unwrap(); + let mut sub_channels_saved = self.sub_channels_saved.lock().unwrap(); + let mut tmp = None; + std::mem::swap(&mut tmp, &mut *sub_channels_saved); + std::mem::swap(&mut *sub_channels, &mut tmp.unwrap()); } } @@ -254,6 +276,63 @@ impl Storage for MemoryStorage { fn get_chain_monitor(&self) -> Result, DaemonError> { Ok(None) } + + fn upsert_sub_channel(&self, subchannel: &SubChannel) -> Result<(), DaemonError> { + let mut map = self.sub_channels.write().expect("Could not get write lock"); + map.insert(subchannel.channel_id, subchannel.clone()); + Ok(()) + } + + fn get_sub_channel( + &self, + channel_id: dlc_manager::ChannelId, + ) -> Result, DaemonError> { + 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> { + Ok(self + .sub_channels + .read() + .expect("Could not get read lock") + .values() + .cloned() + .collect()) + } + + fn get_offered_sub_channels(&self) -> Result, DaemonError> { + let map = self.sub_channels.read().expect("Could not get read lock"); + + let mut res: Vec = Vec::new(); + + for (_, val) in map.iter() { + if let SubChannelState::Offered(_) = &val.state { + res.push(val.clone()) + } + } + + 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 { diff --git a/mocks/src/mock_blockchain.rs b/mocks/src/mock_blockchain.rs index 682d2fd6..345cebe6 100644 --- a/mocks/src/mock_blockchain.rs +++ b/mocks/src/mock_blockchain.rs @@ -1,46 +1,126 @@ -use bitcoin::{Block, Transaction, Txid}; -use dlc_manager::{error::Error, Blockchain, Utxo}; -use lightning::chain::chaininterface::FeeEstimator; +use std::{ops::Deref, sync::Mutex}; + +use bitcoin::{Transaction, Txid}; +use dlc_manager::error::Error; +use lightning::chain::chaininterface::BroadcasterInterface; use simple_wallet::WalletBlockchainProvider; -pub struct MockBlockchain {} +pub struct MockBlockchain +where + T::Target: BroadcasterInterface, +{ + inner: T, + discard: Mutex, + discard_ids: Mutex>, + est_fee: Mutex, +} -impl Blockchain for MockBlockchain { - fn send_transaction(&self, _transaction: &Transaction) -> Result<(), Error> { - Ok(()) +impl MockBlockchain +where + T::Target: BroadcasterInterface, +{ + pub fn new(inner: T) -> Self { + Self { + inner, + discard: Mutex::new(false), + discard_ids: Mutex::new(Vec::new()), + est_fee: Mutex::new(500), + } } - fn get_network(&self) -> Result { - Ok(bitcoin::Network::Regtest) + + pub fn start_discard(&self) { + *self.discard.lock().unwrap() = true; } - fn get_blockchain_height(&self) -> Result { - Ok(10) + + pub fn discard_id(&self, txid: Txid) { + self.discard_ids.lock().unwrap().push(txid); } - fn get_block_at_height(&self, _height: u64) -> Result { - unimplemented!(); + + pub fn set_est_fee(&self, est_fee: u32) { + *self.est_fee.lock().unwrap() = est_fee; } - fn get_transaction(&self, _tx_id: &Txid) -> Result { - unimplemented!(); +} + +impl BroadcasterInterface for MockBlockchain +where + T::Target: BroadcasterInterface, +{ + fn broadcast_transactions(&self, txs: &[&Transaction]) { + let to_discard = self.discard_ids.lock().unwrap(); + let to_send = txs + .iter() + .filter(|tx| !to_discard.contains(&tx.txid())) + .cloned() + .collect::>(); + self.inner.broadcast_transactions(&to_send); } - fn get_transaction_confirmations(&self, _tx_id: &Txid) -> Result { - Ok(6) +} + +pub struct MockBroadcaster {} + +impl BroadcasterInterface for MockBroadcaster { + fn broadcast_transactions(&self, _tx: &[&bitcoin::Transaction]) { + unimplemented!(); } } -impl WalletBlockchainProvider for MockBlockchain { - fn get_utxos_for_address(&self, _address: &bitcoin::Address) -> Result, Error> { +impl WalletBlockchainProvider for MockBlockchain +where + T::Target: BroadcasterInterface, +{ + fn get_utxos_for_address( + &self, + _address: &bitcoin::Address, + ) -> Result, dlc_manager::error::Error> { unimplemented!() } - fn is_output_spent(&self, _txid: &Txid, _vout: u32) -> Result { + fn is_output_spent( + &self, + _txid: &bitcoin::Txid, + _vout: u32, + ) -> Result { unimplemented!() } } -impl FeeEstimator for MockBlockchain { +impl lightning::chain::chaininterface::FeeEstimator for MockBlockchain +where + T::Target: BroadcasterInterface, +{ fn get_est_sat_per_1000_weight( &self, _confirmation_target: lightning::chain::chaininterface::ConfirmationTarget, ) -> u32 { - unimplemented!() + *self.est_fee.lock().unwrap() + } +} + +impl dlc_manager::Blockchain for MockBlockchain +where + T::Target: BroadcasterInterface, +{ + fn send_transaction( + &self, + _transaction: &Transaction, + ) -> Result<(), dlc_manager::error::Error> { + Ok(()) + } + fn get_network( + &self, + ) -> Result { + Ok(bitcoin::Network::Regtest) + } + fn get_blockchain_height(&self) -> Result { + Ok(10) + } + fn get_block_at_height(&self, _height: u64) -> Result { + unimplemented!(); + } + fn get_transaction(&self, _tx_id: &Txid) -> Result { + unimplemented!(); + } + fn get_transaction_confirmations(&self, _tx_id: &Txid) -> Result { + Ok(6) } } diff --git a/mocks/src/mock_wallet.rs b/mocks/src/mock_wallet.rs index 0595d686..2bdc7d61 100644 --- a/mocks/src/mock_wallet.rs +++ b/mocks/src/mock_wallet.rs @@ -1,7 +1,8 @@ -use std::rc::Rc; +use std::{ops::Deref, rc::Rc}; use bitcoin::{Address, PackedLockTime, Script, Transaction, TxOut}; use dlc_manager::{error::Error, Blockchain, Signer, Utxo, Wallet}; +use lightning::chain::chaininterface::BroadcasterInterface; use secp256k1_zkp::{rand::seq::SliceRandom, SecretKey}; use crate::mock_blockchain::MockBlockchain; @@ -11,7 +12,10 @@ pub struct MockWallet { } impl MockWallet { - pub fn new(blockchain: &Rc, nb_utxo: u16) -> Self { + pub fn new(blockchain: &Rc>, nb_utxo: u16) -> Self + where + T::Target: BroadcasterInterface, + { let mut utxos = Vec::with_capacity(nb_utxo as usize); for i in 0..nb_utxo { diff --git a/p2pd-oracle-client/src/lib.rs b/p2pd-oracle-client/src/lib.rs index cfeb1dfd..4339ca66 100644 --- a/p2pd-oracle-client/src/lib.rs +++ b/p2pd-oracle-client/src/lib.rs @@ -26,8 +26,10 @@ use secp256k1_zkp::{schnorr::Signature, XOnlyPublicKey}; /// Enables interacting with a DLC oracle. pub struct P2PDOracleClient { - host: String, - public_key: XOnlyPublicKey, + /// Oracle server host. + pub host: String, + /// Oracle public key. + pub public_key: XOnlyPublicKey, } #[derive(serde::Deserialize, serde::Serialize)] @@ -133,7 +135,7 @@ fn parse_event_id(event_id: &str) -> Result<(String, DateTime), DlcManagerE let naive_date_time = NaiveDateTime::from_timestamp_opt(timestamp, 0).ok_or_else(|| { DlcManagerError::InvalidParameters(format!("Invalid timestamp {} in event id", timestamp)) })?; - let date_time = DateTime::from_utc(naive_date_time, Utc); + let date_time = DateTime::from_naive_utc_and_offset(naive_date_time, Utc); Ok((asset_id.to_string(), date_time)) } diff --git a/sample/Cargo.toml b/sample/Cargo.toml index 3a4a906a..3c3b3cac 100644 --- a/sample/Cargo.toml +++ b/sample/Cargo.toml @@ -12,8 +12,8 @@ dlc-manager = {path = "../dlc-manager", features = ["use-serde", "parallel"]} dlc-messages = {path = "../dlc-messages"} dlc-sled-storage-provider = {path = "../dlc-sled-storage-provider"} futures = "0.3" -lightning = {version = "0.0.113"} -lightning-net-tokio = {version = "0.0.113" } +lightning = {version = "0.0.116"} +lightning-net-tokio = {version = "0.0.116"} p2pd-oracle-client = {path = "../p2pd-oracle-client"} serde = "1.0" serde_json = "1.0" diff --git a/sample/src/cli.rs b/sample/src/cli.rs index 5e99ef78..a8eba015 100644 --- a/sample/src/cli.rs +++ b/sample/src/cli.rs @@ -10,7 +10,9 @@ use dlc_manager::channel::signed_channel::SignedChannelStateType; use dlc_manager::contract::contract_input::ContractInput; use dlc_manager::contract::Contract; use dlc_manager::Storage; +use dlc_messages::ChannelMessage; use dlc_messages::Message as DlcMessage; +use dlc_messages::OnChainMessage; use hex_utils::{hex_str, to_slice}; use lightning::ln::msgs::NetAddress; use serde::Deserialize; @@ -194,21 +196,21 @@ pub(crate) async fn poll_for_user_input( let is_contract = o == "offercontract"; let offer = tokio::task::spawn_blocking(move || { if is_contract { - DlcMessage::Offer( + DlcMessage::OnChain(OnChainMessage::Offer( manager_clone .lock() .unwrap() .send_offer(&contract_input, pubkey) .expect("Error sending offer"), - ) + )) } else { - DlcMessage::OfferChannel( + DlcMessage::Channel(ChannelMessage::Offer( manager_clone .lock() .unwrap() .offer_channel(&contract_input, pubkey) .expect("Error sending offer channel"), - ) + )) } }) .await @@ -244,7 +246,8 @@ pub(crate) async fn poll_for_user_input( .unwrap() .accept_contract_offer(&contract_id) .expect("Error accepting contract."); - dlc_message_handler.send_message(node_id, DlcMessage::Accept(msg)); + dlc_message_handler + .send_message(node_id, DlcMessage::OnChain(OnChainMessage::Accept(msg))); peer_manager.process_events(); } "listcontracts" => { @@ -336,7 +339,8 @@ pub(crate) async fn poll_for_user_input( .unwrap() .accept_channel(&channel_id) .expect("Error accepting channel."); - dlc_message_handler.send_message(node_id, DlcMessage::AcceptChannel(msg)); + dlc_message_handler + .send_message(node_id, DlcMessage::Channel(ChannelMessage::Accept(msg))); peer_manager.process_events(); } s @ "offersettlechannel" => { @@ -354,7 +358,10 @@ pub(crate) async fn poll_for_user_input( .unwrap() .settle_offer(&channel_id, counter_payout) .expect("Error getting settle offer message."); - dlc_message_handler.send_message(node_id, DlcMessage::SettleOffer(msg)); + dlc_message_handler.send_message( + node_id, + DlcMessage::Channel(ChannelMessage::SettleOffer(msg)), + ); peer_manager.process_events(); } l @ "acceptsettlechanneloffer" => { @@ -364,7 +371,10 @@ pub(crate) async fn poll_for_user_input( .unwrap() .accept_settle_offer(&channel_id) .expect("Error accepting settle channel offer."); - dlc_message_handler.send_message(node_id, DlcMessage::SettleAccept(msg)); + dlc_message_handler.send_message( + node_id, + DlcMessage::Channel(ChannelMessage::SettleAccept(msg)), + ); peer_manager.process_events(); } l @ "rejectsettlechanneloffer" => { @@ -374,7 +384,8 @@ pub(crate) async fn poll_for_user_input( .unwrap() .reject_settle_offer(&channel_id) .expect("Error rejecting settle channel offer."); - dlc_message_handler.send_message(node_id, DlcMessage::Reject(msg)); + dlc_message_handler + .send_message(node_id, DlcMessage::Channel(ChannelMessage::Reject(msg))); peer_manager.process_events(); } "listsettlechanneloffers" => { @@ -417,7 +428,10 @@ pub(crate) async fn poll_for_user_input( }) .await .unwrap(); - dlc_message_handler.send_message(node_id, DlcMessage::RenewOffer(renew_offer)); + dlc_message_handler.send_message( + node_id, + DlcMessage::Channel(ChannelMessage::RenewOffer(renew_offer)), + ); peer_manager.process_events(); } "listrenewchanneloffers" => { @@ -457,7 +471,10 @@ pub(crate) async fn poll_for_user_input( .unwrap() .accept_renew_offer(&channel_id) .expect("Error accepting channel."); - dlc_message_handler.send_message(node_id, DlcMessage::RenewAccept(msg)); + dlc_message_handler.send_message( + node_id, + DlcMessage::Channel(ChannelMessage::RenewAccept(msg)), + ); peer_manager.process_events(); } l @ "rejectrenewchanneloffer" => { @@ -467,7 +484,8 @@ pub(crate) async fn poll_for_user_input( .unwrap() .reject_renew_offer(&channel_id) .expect("Error rejecting settle channel offer."); - dlc_message_handler.send_message(node_id, DlcMessage::Reject(msg)); + dlc_message_handler + .send_message(node_id, DlcMessage::Channel(ChannelMessage::Reject(msg))); peer_manager.process_events(); } "listsignedchannels" => { @@ -535,7 +553,7 @@ fn help() { fn list_peers(peer_manager: Arc) { println!("\t{{"); - for pubkey in peer_manager.get_peer_node_ids() { + for (pubkey, _) in peer_manager.get_peer_node_ids() { println!("\t\t pubkey: {}", pubkey); } println!("\t}},"); @@ -546,7 +564,7 @@ pub(crate) async fn connect_peer_if_necessary( peer_addr: SocketAddr, peer_manager: Arc, ) -> Result<(), ()> { - for node_pubkey in peer_manager.get_peer_node_ids() { + for (node_pubkey, _) in peer_manager.get_peer_node_ids() { if node_pubkey == pubkey { return Ok(()); } @@ -567,7 +585,7 @@ pub(crate) async fn connect_peer_if_necessary( match peer_manager .get_peer_node_ids() .iter() - .find(|id| **id == pubkey) + .find(|(id, _)| *id == pubkey) { Some(_) => break, None => tokio::time::sleep(Duration::from_millis(10)).await, diff --git a/sample/src/main.rs b/sample/src/main.rs index cfcbc9f9..0d71e410 100644 --- a/sample/src/main.rs +++ b/sample/src/main.rs @@ -12,6 +12,7 @@ use dlc_messages::message_handler::MessageHandler as DlcMessageHandler; use lightning::ln::peer_handler::{ ErroringMessageHandler, IgnoringMessageHandler, MessageHandler, PeerManager as LdkPeerManager, }; +use lightning::sign::KeysManager; use lightning_net_tokio::SocketDescriptor; use p2pd_oracle_client::P2PDOracleClient; use std::collections::hash_map::HashMap; @@ -27,6 +28,7 @@ pub(crate) type PeerManager = LdkPeerManager< Arc, Arc, Arc, + Arc, >; pub(crate) type DlcManager = dlc_manager::manager::Manager< @@ -122,18 +124,24 @@ async fn main() { .duration_since(SystemTime::UNIX_EPOCH) .unwrap(); + let keys_manager = Arc::new(KeysManager::new( + sk.as_ref(), + time.as_secs(), + time.subsec_nanos(), + )); + // The peer manager helps us establish connections and communicate with our peers. let peer_manager: Arc = Arc::new(PeerManager::new( MessageHandler { chan_handler: Arc::new(ErroringMessageHandler::new()), route_handler: Arc::new(IgnoringMessageHandler {}), onion_message_handler: Arc::new(IgnoringMessageHandler {}), + custom_message_handler: dlc_message_handler.clone(), }, - sk, time.as_secs() as u32, &ephemeral_bytes, logger.clone(), - dlc_message_handler.clone(), + Arc::clone(&keys_manager), )); let peer_manager_connection_handler = peer_manager.clone(); diff --git a/scripts/generate_serialized_contract_files.sh b/scripts/generate_serialized_contract_files.sh index ac0b6353..a288d65f 100755 --- a/scripts/generate_serialized_contract_files.sh +++ b/scripts/generate_serialized_contract_files.sh @@ -3,7 +3,6 @@ set -e CONTRACT_TEST_FILES=("Offered" "Accepted" "Confirmed" "Confirmed1" "Signed" "Signed1" "PreClosed" "Closed") - DEST=${PWD}/dlc-sled-storage-provider/test_files/ docker-compose up -d @@ -11,7 +10,7 @@ docker-compose up -d for FILE in ${CONTRACT_TEST_FILES[@]} do - GENERATE_SERIALIZED_CONTRACT=1 cargo test -- single_oracle_numerical_test --ignored --exact + RUST_MIN_STACK=104857600 GENERATE_SERIALIZED_CONTRACT=1 cargo test -- single_oracle_numerical_test --ignored --exact cp ${PWD}/dlc-manager/${FILE//1/} ${DEST}${FILE} done @@ -19,17 +18,25 @@ CHANNEL_TEST_FILES=("OfferedChannel" "AcceptedChannel" "SignedChannelEstablished for FILE in ${CHANNEL_TEST_FILES[@]} do - GENERATE_SERIALIZED_CHANNEL=1 cargo test -- channel_settled_close_test --ignored --exact + RUST_MIN_STACK=104857600 GENERATE_SERIALIZED_CHANNEL=1 cargo test -- channel_settled_close_test --ignored --exact + cp ${PWD}/dlc-manager/${FILE//1/} ${DEST}${FILE} +done + +SUB_CHANNEL_TEST_FILES=("OfferedSubChannel" "OfferedSubChannel1" "AcceptedSubChannel" "SignedSubChannel") + +for FILE in ${SUB_CHANNEL_TEST_FILES[@]} +do + RUST_MIN_STACK=104857600 GENERATE_SERIALIZED_SUB_CHANNEL=1 cargo test -- ln_dlc_established_close --ignored --exact cp ${PWD}/dlc-manager/${FILE//1/} ${DEST}${FILE} done -TEST_FILES=( "${CONTRACT_TEST_FILES[@]}" "${CHANNEL_TEST_FILES[@]}" ) +TEST_FILES=( "${CONTRACT_TEST_FILES[@]}" "${CHANNEL_TEST_FILES[@]}" "${SUB_CHANNEL_TEST_FILES[@]}" ) for FILE in ${TEST_FILES[@]} do rm -f ${PWD}/dlc-manager/${FILE} done -rm ${PWD}/dlc-manager/Signed* +rm -f ${PWD}/dlc-manager/Signed* docker-compose down -v diff --git a/scripts/run_integration_tests.sh b/scripts/run_integration_tests.sh index 682841b3..d5f6f544 100755 --- a/scripts/run_integration_tests.sh +++ b/scripts/run_integration_tests.sh @@ -12,12 +12,15 @@ fi export TEST_BIN=$1 LIST=$(bash ${PWD}/scripts/generate_test_list.sh $TEST_BIN) + for TEST_NAME in $LIST do if [ ! -z $TEST_NAME ] then - bash ${PWD}/scripts/start_node.sh - cargo test -- $TEST_NAME --ignored --exact --nocapture - bash ${PWD}/scripts/stop_node.sh + docker-compose up -d + ./scripts/wait_for_electrs.sh + RUST_MIN_STACK=104857600 cargo test -- $TEST_NAME --ignored --exact --nocapture + docker-compose down -v fi -done \ No newline at end of file +done + diff --git a/simple-wallet/Cargo.toml b/simple-wallet/Cargo.toml index a582b733..cae4eac2 100644 --- a/simple-wallet/Cargo.toml +++ b/simple-wallet/Cargo.toml @@ -9,8 +9,8 @@ version = "0.1.0" bitcoin = "0.29" dlc = {path = "../dlc"} dlc-manager = {path = "../dlc-manager"} -lightning = {version = "0.0.113"} -rust-bitcoin-coin-selection = {version = "0.1.0", git = "https://github.com/p2pderivatives/rust-bitcoin-coin-selection", features = ["rand"]} +lightning = {version = "0.0.116"} +rust-bitcoin-coin-selection = { rev = "23a6bf85", git = "https://github.com/p2pderivatives/rust-bitcoin-coin-selection", features = ["rand"]} secp256k1-zkp = {version = "0.7.0"} [dev-dependencies] diff --git a/simple-wallet/src/lib.rs b/simple-wallet/src/lib.rs index 6044a469..479c404e 100644 --- a/simple-wallet/src/lib.rs +++ b/simple-wallet/src/lib.rs @@ -58,9 +58,8 @@ where } } - /// Refresh the wallet checking and updating the UTXO states. pub fn refresh(&self) -> Result<()> { - let utxos: Vec = self.storage.get_utxos()?; + let utxos = self.storage.get_utxos()?; for utxo in &utxos { let is_spent = self @@ -269,12 +268,20 @@ mod tests { use dlc_manager::{Signer, Wallet}; use mocks::simple_wallet::SimpleWallet; - use mocks::{memory_storage_provider::MemoryStorage, mock_blockchain::MockBlockchain}; + use mocks::{ + memory_storage_provider::MemoryStorage, + mock_blockchain::{MockBlockchain, MockBroadcaster}, + }; use secp256k1_zkp::{PublicKey, SECP256K1}; - fn get_wallet() -> SimpleWallet, Rc> { - let blockchain = Rc::new(MockBlockchain {}); + fn get_wallet() -> mocks::simple_wallet::SimpleWallet< + Rc>>, + Rc, + > { + let broadcaster = Rc::new(MockBroadcaster {}); + let blockchain = Rc::new(MockBlockchain::new(broadcaster)); let storage = Rc::new(MemoryStorage::new()); + SimpleWallet::new(blockchain, storage, bitcoin::Network::Regtest) }