From b467ed8ae5ac404f64ca28bc6bb321199c27f46c Mon Sep 17 00:00:00 2001 From: Tibo-lg Date: Wed, 1 Mar 2023 11:26:44 +0900 Subject: [PATCH] SubChannelManager implementation --- .github/workflows/rust.yml | 2 +- bitcoin-rpc-provider/Cargo.toml | 2 +- dlc-manager/Cargo.toml | 5 +- dlc-manager/src/chain_monitor.rs | 6 +- dlc-manager/src/channel/ser.rs | 16 +- dlc-manager/src/channel/signed_channel.rs | 16 + dlc-manager/src/channel_updater.rs | 897 ++++++-- dlc-manager/src/contract/contract_input.rs | 6 +- dlc-manager/src/contract_updater.rs | 6 +- dlc-manager/src/lib.rs | 15 +- dlc-manager/src/manager.rs | 486 ++-- dlc-manager/src/sub_channel_manager.rs | 2028 +++++++++++++++++ dlc-manager/src/subchannel/mod.rs | 295 +++ dlc-manager/src/subchannel/ser.rs | 76 + dlc-manager/src/utils.rs | 89 +- dlc-manager/tests/channel_execution_tests.rs | 231 +- dlc-manager/tests/console_logger.rs | 25 + dlc-manager/tests/custom_signer.rs | 360 +++ .../tests/ln_dlc_channel_execution_tests.rs | 1203 ++++++++++ dlc-manager/tests/test_utils.rs | 159 +- dlc-messages/Cargo.toml | 2 +- dlc-messages/src/lib.rs | 2 +- dlc-messages/src/sub_channel.rs | 1 - dlc-sled-storage-provider/Cargo.toml | 2 +- dlc-sled-storage-provider/src/lib.rs | 154 +- dlc-sled-storage-provider/test_files/Accepted | Bin 3430 -> 3430 bytes .../test_files/AcceptedChannel | Bin 908 -> 908 bytes .../test_files/AcceptedSubChannel | Bin 0 -> 3109 bytes dlc-sled-storage-provider/test_files/Closed | Bin 1108 -> 1108 bytes .../test_files/Confirmed | Bin 5983 -> 5983 bytes .../test_files/Confirmed1 | Bin 5983 -> 5983 bytes dlc-sled-storage-provider/test_files/Offered | Bin 1107 -> 1107 bytes .../test_files/OfferedChannel | Bin 235 -> 235 bytes .../test_files/OfferedSubChannel | Bin 0 -> 2424 bytes .../test_files/OfferedSubChannel1 | Bin 0 -> 2424 bytes .../test_files/PreClosed | Bin 6984 -> 7023 bytes dlc-sled-storage-provider/test_files/Signed | Bin 5873 -> 5873 bytes dlc-sled-storage-provider/test_files/Signed1 | Bin 5873 -> 5873 bytes .../test_files/SignedChannelEstablished | Bin 3654 -> 3663 bytes .../test_files/SignedChannelSettled | Bin 3664 -> 3465 bytes .../test_files/SignedSubChannel | Bin 0 -> 3335 bytes dlc/src/channel/mod.rs | 86 +- dlc/src/channel/sub_channel.rs | 4 +- dlc/src/lib.rs | 2 +- dlc/src/util.rs | 15 +- electrs-blockchain-provider/Cargo.toml | 2 +- fuzz/Cargo.toml | 2 +- mocks/Cargo.toml | 2 +- mocks/src/memory_storage_provider.rs | 57 + mocks/src/mock_blockchain.rs | 109 +- sample/Cargo.toml | 4 +- scripts/generate_serialized_contract_files.sh | 13 +- simple-wallet/Cargo.toml | 2 +- simple-wallet/src/lib.rs | 20 +- 54 files changed, 5869 insertions(+), 533 deletions(-) create mode 100644 dlc-manager/src/sub_channel_manager.rs create mode 100644 dlc-manager/src/subchannel/mod.rs create mode 100644 dlc-manager/src/subchannel/ser.rs create mode 100644 dlc-manager/tests/console_logger.rs create mode 100644 dlc-manager/tests/custom_signer.rs create mode 100644 dlc-manager/tests/ln_dlc_channel_execution_tests.rs create mode 100644 dlc-sled-storage-provider/test_files/AcceptedSubChannel create mode 100644 dlc-sled-storage-provider/test_files/OfferedSubChannel create mode 100644 dlc-sled-storage-provider/test_files/OfferedSubChannel1 create mode 100644 dlc-sled-storage-provider/test_files/SignedSubChannel diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index c1b963df..d10f97cf 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 diff --git a/bitcoin-rpc-provider/Cargo.toml b/bitcoin-rpc-provider/Cargo.toml index 24a72356..6ad832c7 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.113", git = "https://github.com/p2pderivatives/rust-lightning/", branch = "split-tx-experiment"} log = "0.4.14" rust-bitcoin-coin-selection = {version = "0.1.0", git = "https://github.com/p2pderivatives/rust-bitcoin-coin-selection", features = ["rand"]} simple-wallet = {path = "../simple-wallet"} diff --git a/dlc-manager/Cargo.toml b/dlc-manager/Cargo.toml index 3bbd99d4..207ea85a 100644 --- a/dlc-manager/Cargo.toml +++ b/dlc-manager/Cargo.toml @@ -19,22 +19,25 @@ 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.113", git = "https://github.com/p2pderivatives/rust-lightning/", branch = "split-tx-experiment"} 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.113", git = "https://github.com/p2pderivatives/rust-lightning/", branch = "split-tx-experiment"} 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..24f445e0 100644 --- a/dlc-manager/src/chain_monitor.rs +++ b/dlc-manager/src/chain_monitor.rs @@ -1,4 +1,5 @@ -//! +//! This module includes the [`ChainMonitor`] struct that helps watching the blockchain for +//! transactions of interest in the context of DLC. use std::collections::HashMap; @@ -60,9 +61,10 @@ impl_dlc_writeable_enum!(TxType,; 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. diff --git a/dlc-manager/src/channel/ser.rs b/dlc-manager/src/channel/ser.rs index 2d8a60bd..a373fcf2 100644 --- a/dlc-manager/src/channel/ser.rs +++ b/dlc-manager/src/channel/ser.rs @@ -45,20 +45,21 @@ 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), + (is_sub_channel, writeable) }); 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)}), + (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), (total_collateral, writeable)}), (9, Closing, {(buffer_transaction, writeable), (signed_cet, writeable), (contract_id, writeable), (attestations, vec)}), (10, ClosedPunished, { (punishment_txid, writeable) }), (11, CollaborativeCloseOffered, { (counter_payout, writeable), (offer_signature, writeable), (close_tx, writeable), (timeout, writeable) }) @@ -67,3 +68,4 @@ impl_dlc_writeable_enum!( 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)}); + diff --git a/dlc-manager/src/channel/signed_channel.rs b/dlc-manager/src/channel/signed_channel.rs index eeee9819..a4a3f98c 100644 --- a/dlc-manager/src/channel/signed_channel.rs +++ b/dlc-manager/src/channel/signed_channel.rs @@ -108,6 +108,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 +128,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 +153,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 +178,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 +193,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. @@ -252,6 +264,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 @@ -364,4 +378,6 @@ 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 is_sub_channel: bool, } diff --git a/dlc-manager/src/channel_updater.rs b/dlc-manager/src/channel_updater.rs index dfd9cf74..797bb32e 100644 --- a/dlc-manager/src/channel_updater.rs +++ b/dlc-manager/src/channel_updater.rs @@ -19,10 +19,11 @@ use crate::{ verify_signed_contract_internal, }, error::Error, + subchannel::{ClosingSubChannel, SubChannel}, utils::get_new_temporary_id, Blockchain, 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, @@ -64,6 +65,32 @@ 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(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, +} + /// Creates an [`OfferedChannel`] and an associated [`OfferedContract`] using /// the given parameter. pub fn offer_channel( @@ -76,6 +103,7 @@ pub fn offer_channel( wallet: &W, blockchain: &B, time: &T, + is_sub_channel: bool, ) -> Result<(OfferedChannel, OfferedContract), Error> where W::Target: Wallet, @@ -88,6 +116,7 @@ where contract.fee_rate, wallet, blockchain, + !is_sub_channel, )?; let party_points = crate::utils::get_party_base_points(secp, wallet)?; @@ -142,16 +171,37 @@ where W::Target: Wallet, B::Target: Blockchain, { - assert_eq!(offered_channel.offered_contract_id, offered_contract.id); + accept_channel_offer_internal( + secp, + offered_channel, + offered_contract, + wallet, + blockchain, + None, + ) +} - let total_collateral = offered_contract.total_collateral; +pub(crate) fn accept_channel_offer_internal( + secp: &Secp256k1, + offered_channel: &OfferedChannel, + offered_contract: &OfferedContract, + wallet: &W, + blockchain: &B, + sub_channel_info: Option, +) -> 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) = crate::utils::get_party_params( secp, - total_collateral - offered_contract.offer_params.collateral, + offered_contract.total_collateral - offered_contract.offer_params.collateral, offered_contract.fee_rate_per_vb, wallet, blockchain, + sub_channel_info.is_none(), )?; let per_update_seed = wallet.get_new_secret_key()?; @@ -181,23 +231,60 @@ 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, + ) = 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, + ) + } 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_spk = txs.dlc_transactions.funding_script_pubkey.clone(); + (txs, accept_fund_sk, funding_output_value, funding_spk) + }; let own_base_secret_key = wallet.get_secret_key_for_pubkey(&accept_points.own_basepoint)?; @@ -209,14 +296,12 @@ where &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, )?; @@ -267,6 +352,29 @@ pub fn verify_and_sign_accepted_channel( cet_nsequence: u32, signer: &S, ) -> 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, + ) +} + +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, +) -> Result<(SignedChannel, SignedContract, SignChannel), Error> where S::Target: Signer, { @@ -299,14 +407,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,23 +421,74 @@ 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, + ) = if let Some(sub_channel_info) = sub_channel_info { + let SubChannelSignVerifyInfo { + funding_info, + own_adaptor_sk, + counter_adaptor_pk, + } = 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, + ) + } 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, + ) + }; let channel_id = crate::utils::compute_id( dlc_transactions.fund.txid(), @@ -364,9 +521,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 +531,18 @@ 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, )?; + let fund_output_index = if is_sub_channel { + 1 + } else { + dlc_transactions.get_fund_output_index() + }; + let signed_channel = SignedChannel { counter_party: signed_contract .accepted_contract @@ -395,14 +558,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 +577,7 @@ where .accepted_contract .offered_contract .fee_rate_per_vb, + is_sub_channel, }; let sign_channel = SignChannel { @@ -436,6 +601,27 @@ pub fn verify_signed_channel( sign_channel: &SignChannel, signer: &S, ) -> Result<(SignedChannel, SignedContract), Error> +where + S::Target: Signer, +{ + verify_signed_channel_internal( + secp, + accepted_channel, + accepted_contract, + sign_channel, + signer, + None, + ) +} + +pub(crate) fn verify_signed_channel_internal( + secp: &Secp256k1, + accepted_channel: &AcceptedChannel, + accepted_contract: &AcceptedContract, + sign_channel: &SignChannel, + signer: &S, + sub_channel_info: Option, +) -> Result<(SignedChannel, SignedContract), Error> where S::Target: Signer, { @@ -446,12 +632,32 @@ where let counter_own_pk = accepted_channel .offer_base_points .get_own_pk(secp, &accepted_channel.offer_per_update_point); + + let is_sub_channel = sub_channel_info.is_some(); + let (buffer_input_spk, buffer_input_value, counter_buffer_adaptor_key) = + 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, + ) + } 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, + ) + }; + 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 +677,12 @@ where Some(accepted_channel.channel_id), )?; + let fund_output_index = if is_sub_channel { + 1 + } else { + accepted_contract.dlc_transactions.get_fund_output_index() + }; + let signed_channel = SignedChannel { counter_party: signed_contract .accepted_contract @@ -482,7 +694,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 +703,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 +718,7 @@ where .accepted_contract .offered_contract .fee_rate_per_vb, + is_sub_channel, }; Ok((signed_channel, signed_contract)) @@ -575,9 +789,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); @@ -602,17 +826,45 @@ 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, + ) +} + +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, +) -> 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 +882,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, @@ -661,6 +921,7 @@ where own_settle_adaptor_signature: settle_adaptor_signature, timeout: time.unix_time_now() + peer_timeout, own_payout, + counter_payout, }; let msg = SettleAccept { @@ -686,6 +947,36 @@ pub fn settle_channel_confirm( signer: &S, time: &T, ) -> 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, + ) +} + +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, +) -> Result where T::Target: Time, S::Target: Signer, @@ -709,19 +1000,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,7 +1031,7 @@ where lock_time, Some(( &settle_channel_accept.settle_adaptor_signature, - channel.counter_params.fund_pubkey, + counter_settle_adaptor_pk, )), channel.fee_rate_per_vb, )?; @@ -751,7 +1051,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 +1076,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 +1097,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 +1132,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 +1172,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 +1205,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 +1214,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 +1223,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 +1255,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 +1300,41 @@ 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 mut offered_contract = OfferedContract::new( contract_input, oracle_announcements, @@ -1077,6 +1445,32 @@ 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, + None, + ) +} + +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, + own_buffer_adaptor_sk: Option, +) -> Result<(AcceptedContract, RenewAccept), Error> where S::Target: Signer, T::Target: Time, @@ -1120,6 +1514,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,14 +1536,24 @@ where offered_contract.fee_rate_per_vb, 0, Sequence(cet_nsequence), + fund_vout, + buffer_nsequence, )?; + 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, - dlc_transactions.get_fund_output().value, + buffer_input_value, &dlc_transactions.funding_script_pubkey, - &own_fund_sk, + own_buffer_adaptor_sk, &offer_revoke_params.publish_pk.inner, )?; @@ -1198,6 +1608,36 @@ 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, + 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, + counter_buffer_own_pk: Option, +) -> Result<(SignedContract, RenewConfirm), Error> where S::Target: Signer, T::Target: Time, @@ -1232,6 +1672,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,6 +1694,8 @@ 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); @@ -1270,12 +1717,22 @@ where Some(signed_channel.channel_id), )?; + 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 counter_buffer_own_pk = counter_buffer_own_pk + .as_ref() + .unwrap_or(&signed_contract.accepted_contract.accept_params.fund_pubkey); + 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, + buffer_input_value, + &signed_channel.fund_script_pubkey, + counter_buffer_own_pk, &offer_revoke_params.publish_pk.inner, &renew_accept.buffer_adaptor_signature, )?; @@ -1283,9 +1740,9 @@ where 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, )?; @@ -1299,6 +1756,7 @@ where 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; @@ -1325,6 +1783,27 @@ pub fn verify_renew_confirm_and_finalize( renew_confirm: &RenewConfirm, signer: &S, ) -> Result<(SignedContract, RenewFinalize), Error> +where + S::Target: Signer, +{ + verify_renew_confirm_and_finalize_internal( + secp, + signed_channel, + accepted_contract, + renew_confirm, + signer, + None, + ) +} + +pub(crate) fn verify_renew_confirm_and_finalize_internal( + secp: &Secp256k1, + signed_channel: &mut SignedChannel, + accepted_contract: &AcceptedContract, + renew_confirm: &RenewConfirm, + signer: &S, + counter_buffer_own_pk: Option, +) -> Result<(SignedContract, RenewFinalize), Error> where S::Target: Signer, { @@ -1350,16 +1829,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, @@ -1382,6 +1865,10 @@ where counter_buffer_adaptor_signature: renew_confirm.buffer_adaptor_signature, buffer_transaction: buffer_transaction.clone(), is_offer: false, + total_collateral: signed_contract + .accepted_contract + .offered_contract + .total_collateral, }; signed_channel.update_idx -= 1; @@ -1419,6 +1906,7 @@ pub fn renew_channel_on_finalize( ) -> Result<(), Error> { let ( contract_id, + total_collateral, offer_per_update_point, accept_per_update_point, offer_buffer_adaptor_signature, @@ -1428,6 +1916,7 @@ pub fn renew_channel_on_finalize( signed_channel, RenewConfirmed, contract_id, + total_collateral, offer_per_update_point, accept_per_update_point, offer_buffer_adaptor_signature, @@ -1440,6 +1929,7 @@ pub fn renew_channel_on_finalize( own_buffer_adaptor_signature: offer_buffer_adaptor_signature, buffer_transaction: buffer_transaction.clone(), is_offer: true, + total_collateral, }; signed_channel @@ -1643,10 +2133,10 @@ where 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 +2154,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 +2167,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 +2182,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 +2199,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, )?; @@ -1768,6 +2248,7 @@ pub fn initiate_unilateral_close_established_channel( attestations: &[(usize, OracleAttestation)], adaptor_info: &AdaptorInfo, signer: &S, + sub_channel: Option<(SubChannel, ClosingSubChannel)>, ) -> Result<(), Error> where S::Target: Signer, @@ -1791,18 +2272,83 @@ 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 (range_info, oracle_sigs) = crate::utils::get_range_info_and_oracle_sigs(contract_info, adaptor_info, attestations)?; @@ -1893,10 +2439,22 @@ where } /// 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 +where + S::Target: Signer, +{ + close_settled_channel_internal(secp, signed_channel, signer, None) +} + +pub(crate) fn close_settled_channel_internal( + secp: &Secp256k1, signed_channel: &mut SignedChannel, signer: &S, + sub_channel: Option<(SubChannel, &ClosingSubChannel)>, ) -> Result where S::Target: Signer, @@ -1920,18 +2478,83 @@ 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, + )?; - 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, - )?; + 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, + )?; + } signed_channel.state = SignedChannelState::Closed; Ok(settle_tx) 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_updater.rs b/dlc-manager/src/contract_updater.rs index 055d7337..15b04e10 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( @@ -85,6 +86,7 @@ where offered_contract.fee_rate_per_vb, wallet, blockchain, + true, )?; let dlc_transactions = dlc::create_dlc_transactions( @@ -422,7 +424,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 +620,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/lib.rs b/dlc-manager/src/lib.rs index d6707ec8..28f2e40f 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,7 @@ use lightning::ln::msgs::DecodeError; use lightning::util::ser::{Readable, Writeable, Writer}; use secp256k1_zkp::XOnlyPublicKey; use secp256k1_zkp::{PublicKey, SecretKey}; +use subchannel::SubChannel; /// Type alias for a contract id. pub type ContractId = [u8; 32]; @@ -164,6 +169,14 @@ 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>; } /// Oracle trait provides access to oracle information. diff --git a/dlc-manager/src/manager.rs b/dlc-manager/src/manager.rs index f3716334..de957b54 100644 --- a/dlc-manager/src/manager.rs +++ b/dlc-manager/src/manager.rs @@ -15,8 +15,10 @@ 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::Address; use bitcoin::Transaction; use dlc_messages::channel::{ @@ -38,6 +40,7 @@ use secp256k1_zkp::{ecdsa::Signature, All, PublicKey, Secp256k1, SecretKey}; 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; @@ -70,43 +73,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, @@ -118,8 +91,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, @@ -131,6 +106,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() { @@ -150,7 +127,19 @@ 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 { + unimplemented!(); + // 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) { Err(e) => error!("Error force closing channel {}", e), _ => {} } @@ -187,7 +176,7 @@ where oracles, time, fee_estimator, - chain_monitor: ChainMonitor::new(init_height), + chain_monitor: Mutex::new(ChainMonitor::new(init_height)), }) } @@ -196,14 +185,29 @@ 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 } /// Function called to pass a DlcMessage to the Manager. pub fn on_dlc_message( - &mut self, + &self, msg: &DlcMessage, counter_party: PublicKey, ) -> Result, Error> { @@ -277,7 +281,7 @@ where /// 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 { @@ -309,7 +313,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 = @@ -339,7 +343,7 @@ where /// 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()?; @@ -349,7 +353,7 @@ where } fn on_offer_message( - &mut self, + &self, offered_message: &OfferDlc, counter_party: PublicKey, ) -> Result<(), Error> { @@ -363,7 +367,7 @@ where } fn on_accept_message( - &mut self, + &self, accept_msg: &AcceptDlc, counter_party: &PublicKey, ) -> Result { @@ -398,11 +402,7 @@ where 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))?; @@ -441,7 +441,7 @@ where } fn sign_fail_on_error( - &mut self, + &self, accepted_contract: AcceptedContract, sign_message: SignDlc, e: Error, @@ -457,7 +457,7 @@ where } fn accept_fail_on_error( - &mut self, + &self, offered_contract: OfferedContract, accept_message: AcceptDlc, e: Error, @@ -472,7 +472,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(), )?; @@ -483,7 +483,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!( @@ -497,7 +497,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() { @@ -551,7 +551,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( @@ -587,7 +587,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!( @@ -601,7 +601,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 @@ -634,7 +634,7 @@ where } fn close_contract( - &mut self, + &self, contract: &SignedContract, signed_cet: Transaction, attestations: Vec, @@ -679,7 +679,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 @@ -720,7 +720,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 { @@ -740,6 +740,7 @@ where &self.wallet, &self.blockchain, &self.time, + false, )?; let msg = offered_channel.get_offer_channel_msg(&offered_contract); @@ -756,7 +757,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 = @@ -802,17 +803,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) } /// 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> { @@ -839,13 +840,26 @@ 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 signed_channel.is_sub_channel { + let (signed_sub_channel, state) = + get_sub_channel_in_state!(self, *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, @@ -853,6 +867,7 @@ where PEER_TIMEOUT, &self.wallet, &self.time, + own_settle_adaptor_sk, )?; let counter_party = signed_channel.counter_party; @@ -867,7 +882,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, @@ -908,7 +923,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 = @@ -924,7 +939,20 @@ where None as Option )?; - let (accepted_contract, msg) = crate::channel_updater::accept_channel_renewal( + let own_buffer_adaptor_sk = if signed_channel.is_sub_channel { + let (signed_sub_channel, state) = + get_sub_channel_in_state!(self, *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 (accepted_contract, msg) = crate::channel_updater::accept_channel_renewal_internal( &self.secp, &mut signed_channel, &offered_contract, @@ -932,6 +960,7 @@ where PEER_TIMEOUT, &self.wallet, &self.time, + own_buffer_adaptor_sk, )?; let counter_party = signed_channel.counter_party; @@ -947,10 +976,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(|| { @@ -982,7 +1008,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 = @@ -1003,7 +1029,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 { @@ -1018,7 +1044,7 @@ where &self.time, )?; - self.chain_monitor.add_tx( + self.chain_monitor.lock().unwrap().add_tx( close_tx.txid(), ChannelInfo { channel_id: *channel_id, @@ -1028,14 +1054,15 @@ 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> { + pub fn accept_collaborative_close(&self, channel_id: &ChannelId) -> Result<(), Error> { let mut signed_channel = get_channel_in_state!(self, channel_id, Signed, None as Option)?; @@ -1094,7 +1121,7 @@ where } fn try_finalize_closing_established_channel( - &mut self, + &self, mut signed_channel: SignedChannel, ) -> Result<(), Error> { let (buffer_tx, signed_cet, contract_id, attestations) = get_signed_channel_state!( @@ -1127,7 +1154,7 @@ where } fn on_offer_channel( - &mut self, + &self, offer_channel: &OfferChannel, counter_party: PublicKey, ) -> Result<(), Error> { @@ -1150,7 +1177,7 @@ where } fn on_accept_channel( - &mut self, + &self, accept_channel: &AcceptChannel, peer_id: &PublicKey, ) -> Result { @@ -1183,7 +1210,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, }; @@ -1206,7 +1233,7 @@ where buffer_transaction, .. } = &signed_channel.state { - self.chain_monitor.add_tx( + self.chain_monitor.lock().unwrap().add_tx( buffer_transaction.txid(), ChannelInfo { channel_id: signed_channel.channel_id, @@ -1222,13 +1249,14 @@ where 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> { @@ -1255,7 +1283,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, }; @@ -1270,7 +1298,7 @@ where buffer_transaction, .. } = &signed_channel.state { - self.chain_monitor.add_tx( + self.chain_monitor.lock().unwrap().add_tx( buffer_transaction.txid(), ChannelInfo { channel_id: signed_channel.channel_id, @@ -1287,13 +1315,14 @@ where 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> { @@ -1315,14 +1344,42 @@ 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 signed_channel.is_sub_channel { + let (signed_sub_channel, state) = get_sub_channel_in_state!( + self, + settle_accept.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, @@ -1331,6 +1388,8 @@ where PEER_TIMEOUT, &self.wallet, &self.time, + own_settle_adaptor_sk, + counter_settle_adaptor_pk, )?; self.store @@ -1340,7 +1399,7 @@ where } fn on_settle_confirm( - &mut self, + &self, settle_confirm: &SettleConfirm, peer_id: &PublicKey, ) -> Result { @@ -1361,14 +1420,35 @@ 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 signed_channel.is_sub_channel { + let (signed_sub_channel, state) = get_sub_channel_in_state!( + self, + settle_confirm.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, @@ -1405,13 +1485,14 @@ where 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> { @@ -1438,7 +1519,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, @@ -1475,13 +1556,14 @@ where 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> { @@ -1508,7 +1590,7 @@ where } fn on_renew_accept( - &mut self, + &self, renew_accept: &RenewAccept, peer_id: &PublicKey, ) -> Result { @@ -1523,16 +1605,47 @@ 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, counter_buffer_adaptor_pk) = if signed_channel.is_sub_channel { + let (signed_sub_channel, state) = get_sub_channel_in_state!( + self, + renew_accept.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 (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, + counter_buffer_adaptor_pk, + )?; // Directly confirmed as we're in a channel the fund tx is already confirmed. self.store.upsert_channel( @@ -1544,7 +1657,7 @@ where } fn on_renew_confirm( - &mut self, + &self, renew_confirm: &RenewConfirm, peer_id: &PublicKey, ) -> Result { @@ -1615,8 +1728,7 @@ 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:?}" ))) } }; @@ -1624,15 +1736,38 @@ where 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 = if signed_channel.is_sub_channel { + let (signed_sub_channel, state) = get_sub_channel_in_state!( + self, + renew_confirm.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 (signed_contract, msg) = + crate::channel_updater::verify_renew_confirm_and_finalize_internal( + &self.secp, + &mut signed_channel, + &accepted_contract, + renew_confirm, + &self.wallet, + counter_buffer_adaptor_pk, + )?; - self.chain_monitor.add_tx( + self.chain_monitor.lock().unwrap().add_tx( prev_tx_id, ChannelInfo { channel_id: signed_channel.channel_id, @@ -1643,7 +1778,7 @@ 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, @@ -1657,7 +1792,8 @@ where 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)?; @@ -1667,7 +1803,7 @@ where } fn on_renew_finalize( - &mut self, + &self, renew_finalize: &RenewFinalize, peer_id: &PublicKey, ) -> Result<(), Error> { @@ -1733,15 +1869,14 @@ 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)?; - self.chain_monitor.add_tx( + self.chain_monitor.lock().unwrap().add_tx( prev_tx_id, ChannelInfo { channel_id: signed_channel.channel_id, @@ -1752,7 +1887,7 @@ 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, @@ -1762,7 +1897,8 @@ 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())?; if let Some(closed_contract) = closed_contract { self.store.update_contract(&closed_contract)?; @@ -1772,7 +1908,7 @@ where } fn on_collaborative_close_offer( - &mut self, + &self, close_offer: &CollaborativeCloseOffer, peer_id: &PublicKey, ) -> Result<(), Error> { @@ -1792,7 +1928,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))?; @@ -1803,7 +1939,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))?; @@ -1820,7 +1956,7 @@ 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); @@ -1831,9 +1967,9 @@ where Ok(()) } - fn check_for_watched_tx(&mut self) -> Result<(), Error> { + fn check_for_watched_tx(&self) -> Result<(), Error> { let cur_height = self.blockchain.get_blockchain_height()?; - let last_height = self.chain_monitor.last_height; + let last_height = self.chain_monitor.lock().unwrap().last_height; if cur_height < last_height { return Err(Error::InvalidState( @@ -1846,7 +1982,11 @@ where 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); + let watch_res = self + .chain_monitor + .lock() + .unwrap() + .process_block(&block, height); for (tx, channel_info) in watch_res { let mut signed_channel = match get_channel_in_state!( @@ -2018,6 +2158,20 @@ 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)?; @@ -2077,18 +2231,34 @@ where } } - self.chain_monitor.increment_height(&block.block_hash()); + self.chain_monitor + .lock() + .unwrap() + .increment_height(&block.block_hash()); } Ok(()) } - fn force_close_channel_internal(&mut self, mut channel: SignedChannel) -> Result<(), Error> { + 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)?; + self.force_close_channel_internal(channel, Some(sub_channel)) + } + + fn force_close_channel_internal( + &self, + mut channel: SignedChannel, + sub_channel: Option<(SubChannel, &ClosingSubChannel)>, + ) -> Result<(), Error> { match channel.state { SignedChannelState::Established { .. } => { self.initiate_unilateral_close_established_channel(channel) } - SignedChannelState::Settled { .. } => self.close_settled_channel(channel), + SignedChannelState::Settled { .. } => self.close_settled_channel(channel, sub_channel), SignedChannelState::SettledOffered { .. } | SignedChannelState::SettledReceived { .. } | SignedChannelState::SettledAccepted { .. } @@ -2101,7 +2271,7 @@ 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) } SignedChannelState::Closing { .. } => Err(Error::InvalidState( "Channel is already closing.".to_string(), @@ -2117,7 +2287,7 @@ where /// Initiate the unilateral closing of a channel that has been established. fn initiate_unilateral_close_established_channel( - &mut self, + &self, mut signed_channel: SignedChannel, ) -> Result<(), Error> { let contract_id = signed_channel.get_contract_id().ok_or_else(|| { @@ -2135,6 +2305,18 @@ where Error::InvalidState("Could not get closable contract info".to_string()) })?; + let sub_channel = if signed_channel.is_sub_channel { + let (sub_channel, state) = get_sub_channel_in_state!( + self, + signed_channel.channel_id, + Closing, + None:: + )?; + Some((sub_channel, state)) + } else { + None + }; + crate::channel_updater::initiate_unilateral_close_established_channel( &self.secp, &mut signed_channel, @@ -2143,6 +2325,7 @@ where &attestations, adaptor_info, &self.wallet, + sub_channel, )?; let buffer_transaction = @@ -2150,22 +2333,31 @@ where 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, + mut signed_channel: SignedChannel, + sub_channel: Option<(SubChannel, &ClosingSubChannel)>, + ) -> Result<(), Error> { + let settle_tx = crate::channel_updater::close_settled_channel_internal( &self.secp, &mut signed_channel, &self.wallet, + sub_channel, )?; self.blockchain.send_transaction(&settle_tx)?; diff --git a/dlc-manager/src/sub_channel_manager.rs b/dlc-manager/src/sub_channel_manager.rs new file mode 100644 index 00000000..091c41cf --- /dev/null +++ b/dlc-manager/src/sub_channel_manager.rs @@ -0,0 +1,2028 @@ +//! # Module containing a manager enabling set up and update of DLC channels embedded within +//! Lightning Network channels. + +use std::{ops::Deref, sync::Mutex}; + +use bitcoin::{OutPoint, PackedLockTime, Script, Sequence}; +use dlc::channel::{get_tx_adaptor_signature, sub_channel::LN_GLUE_TX_WEIGHT}; +use dlc_messages::{ + channel::{AcceptChannel, OfferChannel}, + oracle_msgs::OracleAnnouncement, + sub_channel::{ + SubChannelAccept, SubChannelCloseAccept, SubChannelCloseConfirm, SubChannelCloseFinalize, + SubChannelCloseOffer, SubChannelConfirm, SubChannelFinalize, SubChannelOffer, + }, + FundingSignatures, SubChannelMessage, +}; +use lightning::{ + chain::chaininterface::FeeEstimator, + ln::{ + chan_utils::{ + build_commitment_secret, derive_private_key, derive_private_revocation_key, + CounterpartyCommitmentSecrets, + }, + channelmanager::ChannelDetails, + msgs::RevokeAndACK, + }, +}; +use secp256k1_zkp::{ecdsa::Signature, PublicKey, SecretKey}; + +use crate::{ + chain_monitor::{ChainMonitor, ChannelInfo, RevokedTxType, TxType}, + channel::{offered_channel::OfferedChannel, party_points::PartyBasePoints, Channel}, + channel_updater::{ + self, FundingInfo, SubChannelSignInfo, SubChannelSignVerifyInfo, SubChannelVerifyInfo, + }, + contract::{contract_input::ContractInput, Contract}, + error::Error, + manager::{get_channel_in_state, get_contract_in_state, Manager}, + subchannel::{ + AcceptedSubChannel, CloseAcceptedSubChannel, CloseConfirmedSubChannel, + CloseOfferedSubChannel, ClosingSubChannel, LNChannelManager, OfferedSubChannel, + SignedSubChannel, SubChannel, SubChannelState, + }, + Blockchain, ChannelId, 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) => { + if let Some(p) = $peer_id as Option { + if sub_channel.counter_party != p { + return 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; + +/// Structure enabling management of DLC channels embedded within Lightning Network channels. +pub struct SubChannelManager< + W: Deref, + M: Deref, + S: Deref, + B: Deref, + O: Deref, + T: Deref, + F: Deref, + D: Deref>, +> where + W::Target: Wallet, + M::Target: LNChannelManager, + S::Target: Storage, + B::Target: Blockchain, + O::Target: Oracle, + T::Target: Time, + F::Target: FeeEstimator, +{ + ln_channel_manager: M, + dlc_channel_manager: D, + chain_monitor: Mutex, +} + +impl< + W: Deref, + M: Deref, + S: Deref, + B: Deref, + O: Deref, + T: Deref, + F: Deref, + D: Deref>, + > SubChannelManager +where + W::Target: Wallet, + M::Target: LNChannelManager, + S::Target: Storage, + B::Target: Blockchain, + O::Target: Oracle, + T::Target: Time, + F::Target: FeeEstimator, +{ + /// Creates a new [`SubChannelManager`]. + pub fn new(ln_channel_manager: M, dlc_channel_manager: D, init_height: u64) -> Self { + Self { + ln_channel_manager, + dlc_channel_manager, + chain_monitor: Mutex::new(ChainMonitor::new(init_height)), + } + } + + /// 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) => { + self.on_sub_channel_finalize(f, 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::CloseReject(_) => todo!(), + } + } + + /// 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 party_base_points = crate::utils::get_party_base_points( + self.dlc_channel_manager.get_secp(), + self.dlc_channel_manager.get_wallet(), + )?; + + let (mut 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(), + true, + )?; + + // TODO(tibo): refactor properly. + offered_contract.offer_params.inputs = Vec::new(); + offered_contract.funding_inputs_info = Vec::new(); + + offered_channel.temporary_channel_id = *channel_id; + + let msg = SubChannelOffer { + channel_id: channel_details.channel_id, + next_per_split_point, + revocation_basepoint: party_base_points.revocation_basepoint, + publish_basepoint: party_base_points.publish_basepoint, + own_basepoint: party_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, + }; + + 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 => 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, + }, + }; + + 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> { + let (mut offered_sub_channel, state) = get_sub_channel_in_state!( + self.dlc_channel_manager, + *channel_id, + Offered, + None as Option + )?; + + 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 channel_details = self + .ln_channel_manager + .get_channel_details(channel_id) + .ok_or_else(|| { + Error::InvalidParameters(format!("Unknown LN channel {channel_id:02x?}")) + })?; + + let offered_channel = get_channel_in_state!( + self.dlc_channel_manager, + 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, + )?; + + let ln_output_value = split_tx.transaction.output[0].value; + + let mut split_tx_adaptor_signature = None; + self.ln_channel_manager + .sign_with_fund_key_cb(channel_id, &mut |sk| { + split_tx_adaptor_signature = Some( + get_tx_adaptor_signature( + self.dlc_channel_manager.get_secp(), + &split_tx.transaction, + channel_details.channel_value_satoshis, + &funding_redeemscript, + sk, + &offer_revoke_params.publish_pk.inner, + ) + .unwrap(), + ); + }); + + let split_tx_adaptor_signature = split_tx_adaptor_signature.unwrap(); + + let glue_tx_output_value = ln_output_value + - dlc::util::weight_to_fee(LN_GLUE_TX_WEIGHT, offered_contract.fee_rate_per_vb)?; + + 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_signed = self + .ln_channel_manager + .get_updated_funding_outpoint_commitment_signed( + channel_id, + &offered_sub_channel.counter_party, + &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 (mut 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), + )?; + + 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, + )?; + + // TODO(tibo): refactor properly. + accepted_contract.accept_params.inputs = Vec::new(); + accepted_contract.funding_inputs = Vec::new(); + accepted_channel.channel_id = offered_sub_channel.channel_id; + + let msg = SubChannelAccept { + channel_id: *channel_id, + split_adaptor_signature: split_tx_adaptor_signature, + 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, + 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, + }; + + let accepted_sub_channel = AcceptedSubChannel { + offer_per_split_point: state.per_split_point, + accept_per_split_point: next_per_split_point, + accept_split_adaptor_signature: split_tx_adaptor_signature, + split_tx, + ln_glue_transaction: ln_glue_tx, + }; + + 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)?; + + Ok((offered_sub_channel.counter_party, msg)) + } + + /// Start force closing the sub channel with given [`ChannelId`]. + pub fn initiate_force_close_sub_channel(&self, channel_id: &ChannelId) -> Result<(), Error> { + let (mut signed, state) = get_sub_channel_in_state!( + self.dlc_channel_manager, + *channel_id, + Signed, + None:: + )?; + + let channel_details = self + .ln_channel_manager + .get_channel_details(channel_id) + .unwrap(); + + let publish_base_secret = self + .dlc_channel_manager + .get_wallet() + .get_secret_key_for_pubkey(&signed.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)?; + + let mut split_tx = state.split_tx.transaction.clone(); + + let mut own_sig = None; + + self.ln_channel_manager + .sign_with_fund_key_cb(channel_id, &mut |fund_sk| { + own_sig = Some( + dlc::util::get_raw_sig_for_tx_input( + self.dlc_channel_manager.get_secp(), + &split_tx, + 0, + &signed.original_funding_redeemscript, + signed.fund_value_satoshis, + fund_sk, + ) + .unwrap(), + ); + dlc::util::sign_multi_sig_input( + self.dlc_channel_manager.get_secp(), + &mut split_tx, + &counter_split_signature, + &channel_details.counter_funding_pubkey, + fund_sk, + &signed.original_funding_redeemscript, + signed.fund_value_satoshis, + 0, + ) + .unwrap(); + }); + + dlc::verify_tx_input_sig( + self.dlc_channel_manager.get_secp(), + &own_sig.unwrap(), + &split_tx, + 0, + &signed.original_funding_redeemscript, + signed.fund_value_satoshis, + &channel_details.holder_funding_pubkey, + ) + .unwrap(); + + self.dlc_channel_manager + .get_blockchain() + .send_transaction(&split_tx)?; + + let closing_sub_channel = ClosingSubChannel { + signed_sub_channel: state, + }; + + signed.state = SubChannelState::Closing(closing_sub_channel); + + self.dlc_channel_manager + .get_store() + .upsert_sub_channel(&signed)?; + + Ok(()) + } + + /// Finalize the closing of the sub channel with specified [`ChannelId`]. + pub fn finalize_force_close_sub_channels(&self, channel_id: &ChannelId) -> Result<(), Error> { + let (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(); + + 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)?; + + self.dlc_channel_manager + .force_close_sub_channel(channel_id, (closing, &state))?; + + self.ln_channel_manager + .force_close_channel(channel_id, &counter_party)?; + + 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 = get_channel_in_state!( + self.dlc_channel_manager, + 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, + }; + + 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:: + )?; + + let dlc_channel = get_channel_in_state!( + self.dlc_channel_manager, + channel_id, + Signed, + None:: + )?; + + 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 channel_details = self + .ln_channel_manager + .get_channel_details(channel_id) + .ok_or_else(|| Error::InvalidParameters(format!("Unknown channel {channel_id:?}")))?; + + 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_id, + &sub_channel.counter_party, + &state.signed_subchannel.split_tx.transaction.input[0].previous_output, + fund_value, + ln_own_balance_msats, + )?; + + let close_accept = SubChannelCloseAccept { + channel_id: *channel_id, + commit_signature: commitment_signed.signature, + htlc_signatures: commitment_signed.htlc_signatures, + }; + + let close_accepted_subchannel = CloseAcceptedSubChannel { + signed_subchannel: state.signed_subchannel, + }; + + 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)) + } + + 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 offer_channel = OfferChannel { + protocol_version: 0, //unused + contract_flags: 0, //unused + chain_hash: [0; 32], //unused + temporary_contract_id: channel_details.channel_id, + temporary_channel_id: channel_details.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)?; + + 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: offered_contract.fee_rate_per_vb, + 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, + }, + }; + + 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 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 offered_channel = get_channel_in_state!( + self.dlc_channel_manager, + &channel_details.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; + + dlc::channel::verify_tx_adaptor_signature( + self.dlc_channel_manager.get_secp(), + &split_tx.transaction, + channel_details.channel_value_satoshis, + &funding_redeemscript, + &channel_details.counter_funding_pubkey, + &offer_revoke_params.publish_pk.inner, + &sub_channel_accept.split_adaptor_signature, + )?; + + let channel_id = &channel_details.channel_id; + let mut split_tx_adaptor_signature = None; + self.ln_channel_manager + .sign_with_fund_key_cb(channel_id, &mut |sk| { + split_tx_adaptor_signature = Some( + get_tx_adaptor_signature( + self.dlc_channel_manager.get_secp(), + &split_tx.transaction, + channel_details.channel_value_satoshis, + &funding_redeemscript, + sk, + &accept_revoke_params.publish_pk.inner, + ) + .unwrap(), + ); + }); + + let split_tx_adaptor_signature = split_tx_adaptor_signature.unwrap(); + + 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)?; + + 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_signed = self + .ln_channel_manager + .get_updated_funding_outpoint_commitment_signed( + &sub_channel_accept.channel_id, + counter_party, + &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( + &sub_channel_accept.channel_id, + counter_party, + &sub_channel_accept.commit_signature, + &sub_channel_accept.htlc_signatures, + )?; + + let accept_channel = AcceptChannel { + temporary_channel_id: channel_details.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, + }; + + let (mut 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), + )?; + + // TODO(tibo): consider having separate ids to enable multiple DLC channels. + signed_channel.channel_id = sub_channel_accept.channel_id; + + 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, + 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: split_tx_adaptor_signature, + commit_signature: commitment_signed.signature, + 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, + }; + + let signed_sub_channel = SignedSubChannel { + 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, + counter_split_adaptor_signature: sub_channel_accept.split_adaptor_signature, + split_tx, + counter_glue_signature: sub_channel_accept.ln_glue_signature, + ln_glue_transaction: ln_glue_tx, + }; + + offered_sub_channel.counter_base_points = Some(accept_points); + + offered_sub_channel.state = SubChannelState::Signed(signed_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)?; + + Ok(msg) + } + + fn on_subchannel_confirm( + &self, + sub_channel_confirm: &SubChannelConfirm, + counter_party: &PublicKey, + ) -> Result { + 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 raa = RevokeAndACK { + channel_id: sub_channel_confirm.channel_id, + per_commitment_secret: *sub_channel_confirm.per_commitment_secret.as_ref(), + next_per_commitment_point: sub_channel_confirm.next_per_commitment_point, + }; + + 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 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, + &accept_revoke_params.publish_pk.inner, + &sub_channel_confirm.split_adaptor_signature, + )?; + + self.ln_channel_manager.revoke_and_ack( + &sub_channel_confirm.channel_id, + counter_party, + &raa, + )?; + + let revoke_and_ack = self.ln_channel_manager.on_commitment_signed_get_raa( + &sub_channel_confirm.channel_id, + counter_party, + &sub_channel_confirm.commit_signature, + &sub_channel_confirm.htlc_signatures, + )?; + + let accepted_channel = get_channel_in_state!( + self.dlc_channel_manager, + &sub_channel_confirm.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 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, + }; + + 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), + )?; + + 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: state.accept_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, + }; + + 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, + }; + + accepted_sub_channel.state = SubChannelState::Signed(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)?; + + Ok(msg) + } + + fn on_sub_channel_finalize( + &self, + sub_channel_finalize: &SubChannelFinalize, + counter_party: &PublicKey, + ) -> Result<(), Error> { + let channel = get_channel_in_state!( + self.dlc_channel_manager, + &sub_channel_finalize.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( + &sub_channel_finalize.channel_id, + counter_party, + &raa, + )?; + + self.dlc_channel_manager.get_store().upsert_channel( + Channel::Signed(channel), + Some(Contract::Confirmed(contract)), + )?; + + 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 = get_channel_in_state!( + self.dlc_channel_manager, + &offer.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, + }; + + 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 (mut sub_channel, state) = get_sub_channel_in_state!( + self.dlc_channel_manager, + accept.channel_id, + CloseOffered, + Some(*counter_party) + )?; + + 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 (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 = self + .ln_channel_manager + .get_updated_funding_outpoint_commitment_signed( + &sub_channel.channel_id, + &sub_channel.counter_party, + &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( + &sub_channel.channel_id, + counter_party, + &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, + ))?; + + let close_confirm = SubChannelCloseConfirm { + channel_id: accept.channel_id, + commit_signature: commitment_signed.signature, + 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, + }; + + self.chain_monitor.lock().unwrap().add_tx( + state.signed_subchannel.split_tx.transaction.txid(), + ChannelInfo { + channel_id: sub_channel.channel_id, + tx_type: TxType::Revoked { + update_idx: sub_channel.update_idx, + own_adaptor_signature: state.signed_subchannel.own_split_adaptor_signature, + is_offer: sub_channel.is_offer, + revoked_tx_type: RevokedTxType::Split, + }, + }, + ); + + let updated_channel = CloseConfirmedSubChannel { + signed_subchannel: state.signed_subchannel, + }; + + sub_channel.state = SubChannelState::CloseConfirmed(updated_channel); + + self.dlc_channel_manager + .get_store() + .upsert_sub_channel(&sub_channel)?; + + Ok(close_confirm) + } + + fn on_sub_channel_close_confirm( + &self, + confirm: &SubChannelCloseConfirm, + counter_party: &PublicKey, + ) -> Result { + let (mut sub_channel, state) = get_sub_channel_in_state!( + self.dlc_channel_manager, + confirm.channel_id, + CloseAccepted, + Some(*counter_party) + )?; + + 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()))?; + + debug_assert_eq!( + PublicKey::from_secret_key( + self.dlc_channel_manager.get_secp(), + &confirm.split_revocation_secret + ), + state.signed_subchannel.counter_per_split_point + ); + + 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(&confirm.channel_id, counter_party, &raa)?; + + let own_raa = self.ln_channel_manager.on_commitment_signed_get_raa( + &sub_channel.channel_id, + counter_party, + &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 = derive_private_key( + self.dlc_channel_manager.get_secp(), + &state.signed_subchannel.own_per_split_point, + &per_split_seed, + ); + + let finalize = SubChannelCloseFinalize { + channel_id: confirm.channel_id, + split_revocation_secret: per_split_secret, + commit_revocation_secret: SecretKey::from_slice(&own_raa.per_commitment_secret) + .expect("a valid secret key"), + next_per_commitment_point: own_raa.next_per_commitment_point, + }; + + self.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; + + self.dlc_channel_manager + .get_store() + .upsert_sub_channel(&sub_channel)?; + + Ok(finalize) + } + + fn on_sub_channel_close_finalize( + &self, + finalize: &SubChannelCloseFinalize, + counter_party: &PublicKey, + ) -> Result<(), Error> { + let (mut sub_channel, _) = get_sub_channel_in_state!( + self.dlc_channel_manager, + finalize.channel_id, + CloseConfirmed, + Some(*counter_party) + )?; + + 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 revoke_and_ack = RevokeAndACK { + channel_id: finalize.channel_id, + per_commitment_secret: *finalize.commit_revocation_secret.as_ref(), + next_per_commitment_point: finalize.next_per_commitment_point, + }; + + self.ln_channel_manager.revoke_and_ack( + &finalize.channel_id, + counter_party, + &revoke_and_ack, + )?; + + sub_channel.state = SubChannelState::OffChainClosed; + + self.dlc_channel_manager + .get_store() + .upsert_sub_channel(&sub_channel)?; + + Ok(()) + } + + /// Updtates the view of the blockchain processing transactions and acting upon them if + /// necessary. + pub fn check_for_watched_tx(&self) -> Result<(), Error> { + let cur_height = self + .dlc_channel_manager + .get_blockchain() + .get_blockchain_height()?; + let last_height = self.chain_monitor.lock().unwrap().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 + .dlc_channel_manager + .get_blockchain() + .get_block_at_height(height)?; + + let watch_res = self + .chain_monitor + .lock() + .unwrap() + .process_block(&block, height); + + for (tx, channel_info) in watch_res { + let mut sub_channel = match self + .dlc_channel_manager + .get_store() + .get_sub_channel(channel_info.channel_id)? + { + None => { + log::error!("Unknown channel {:?}", channel_info.channel_id); + continue; + } + Some(s) => s, + }; + + if let TxType::Current = 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. + sub_channel.state = SubChannelState::CounterOnChainClosed; + 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 + { + 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 = match revoked_tx_type { + RevokedTxType::Split => { + 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, + )? + } + _ => panic!("Sub channel manager should only deal with split tx"), + }; + + 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.chain_monitor + .lock() + .unwrap() + .increment_height(&block.block_hash()); + } + + Ok(()) + } +} + +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..d2eab8ee --- /dev/null +++ b/dlc-manager/src/subchannel/mod.rs @@ -0,0 +1,295 @@ +//! # Module containing structures and methods for working with DLC channels embedded in Lightning +//! channels. + +use std::ops::Deref; + +use bitcoin::{OutPoint, Script, Transaction, Txid}; +use dlc::channel::sub_channel::SplitTx; +use lightning::{ + chain::{ + chaininterface::{BroadcasterInterface, FeeEstimator}, + keysinterface::KeysInterface, + }, + ln::{ + chan_utils::CounterpartyCommitmentSecrets, + channelmanager::{ChannelDetails, ChannelManager}, + msgs::{CommitmentSigned, RevokeAndACK}, + }, + util::logger::Logger, +}; +use secp256k1_zkp::{ecdsa::Signature, EcdsaAdaptorSignature, PublicKey, SecretKey}; + +use crate::{channel::party_points::PartyBasePoints, error::Error, ChannelId}; + +pub mod ser; + +#[derive(Clone)] +/// 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, +} + +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() + } +} + +#[derive(Debug, Clone)] +/// 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 transactions have been signed. + 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), +} + +#[derive(Debug, Clone)] +/// 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)] +/// 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, + /// The adaptor signature of the accepting party for the split transaction. + pub accept_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, +} + +#[derive(Debug, Clone)] +/// 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, +} + +#[derive(Debug, Clone)] +/// 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, +} + +#[derive(Debug, Clone)] +/// 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, +} + +#[derive(Debug, Clone)] +/// 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, +} + +/// Information about a sub channel that is in the process of being unilateraly closed. +#[derive(Debug, Clone)] +pub struct ClosingSubChannel { + /// The signed sub channel that is being closed. + pub signed_sub_channel: SignedSubChannel, +} + +/// Provides the ability to access and update Lightning Network channels. +pub trait LNChannelManager { + /// Returns the details of the channel with given `channel_id` if found. + fn get_channel_details(&self, channel_id: &ChannelId) -> Option; + /// Updates the funding output for the channel and returns the [`CommitmentSigned`] message + /// with signatures for the updated commitment transaction and HTLCs. + fn get_updated_funding_outpoint_commitment_signed( + &self, + channel_id: &[u8; 32], + counter_party_node_id: &PublicKey, + funding_outpoint: &OutPoint, + channel_value_satoshis: u64, + value_to_self_msat: u64, + ) -> Result; + /// Provides commitment transaction and HTLCs signatures and returns a [`RevokeAndACK`] + /// message. + fn on_commitment_signed_get_raa( + &self, + channel_id: &[u8; 32], + counter_party_node_id: &PublicKey, + commitment_signature: &Signature, + htlc_signatures: &[Signature], + ) -> Result; + + /// Provides and verify a [`RevokeAndACK`] message. + fn revoke_and_ack( + &self, + channel_id: &[u8; 32], + counter_party_node_id: &PublicKey, + revoke_and_ack: &RevokeAndACK, + ) -> Result<(), Error>; + + /// Gives the ability to access the funding secret key within the provided callback. + fn sign_with_fund_key_cb(&self, channel_id: &[u8; 32], cb: &mut F) + where + F: FnMut(&SecretKey); + + /// 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>; +} + +impl LNChannelManager + for ChannelManager +where + M::Target: lightning::chain::Watch<::Signer>, + T::Target: BroadcasterInterface, + K::Target: KeysInterface, + F::Target: FeeEstimator, + 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_id: &[u8; 32], + counter_party_node_id: &PublicKey, + funding_outpoint: &OutPoint, + channel_value_satoshis: u64, + value_to_self_msat: u64, + ) -> Result { + self.get_updated_funding_outpoint_commitment_signed( + channel_id, + counter_party_node_id, + &lightning::chain::transaction::OutPoint { + txid: funding_outpoint.txid, + index: funding_outpoint.vout as u16, + }, + channel_value_satoshis, + value_to_self_msat, + ) + .map_err(|e| Error::InvalidParameters(format!("{e:?}"))) + } + + fn on_commitment_signed_get_raa( + &self, + channel_id: &[u8; 32], + counter_party_node_id: &PublicKey, + commitment_signature: &Signature, + htlc_signatures: &[Signature], + ) -> Result { + self.on_commitment_signed_get_raa( + channel_id, + counter_party_node_id, + commitment_signature, + htlc_signatures, + ) + .map_err(|e| Error::InvalidParameters(format!("{e:?}"))) + } + + fn revoke_and_ack( + &self, + channel_id: &[u8; 32], + counter_party_node_id: &PublicKey, + revoke_and_ack: &RevokeAndACK, + ) -> Result<(), Error> { + self.revoke_and_ack_commitment(channel_id, counter_party_node_id, revoke_and_ack) + .map_err(|e| Error::InvalidParameters(format!("{e:?}"))) + } + + fn sign_with_fund_key_cb(&self, channel_id: &[u8; 32], cb: &mut SF) + where + SF: FnMut(&SecretKey), + { + self.sign_with_fund_key_callback(channel_id, cb) + .map_err(|e| Error::InvalidParameters(format!("{e:?}"))) + .unwrap(); + } + + 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:?}"))) + } +} diff --git a/dlc-manager/src/subchannel/ser.rs b/dlc-manager/src/subchannel/ser.rs new file mode 100644 index 00000000..35ae49f5 --- /dev/null +++ b/dlc-manager/src/subchannel/ser.rs @@ -0,0 +1,76 @@ +//! 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, 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) +}); + +impl_dlc_writeable_enum!(SubChannelState, + (0, Offered), + (1, Accepted), + (2, Signed), + (3, Closing), + (4, CloseOffered), + (5, CloseAccepted), + (6, CloseConfirmed), + (7, ClosedPunished) + ;;; + (8, OnChainClosed), + (9, CounterOnChainClosed), + (10, OffChainClosed) +); + +impl_dlc_writeable!(OfferedSubChannel, { (per_split_point, writeable) }); + +impl_dlc_writeable_external!(SplitTx, split_tx, {(transaction, writeable), (output_script, writeable)}); + +impl_dlc_writeable!(AcceptedSubChannel, { + (offer_per_split_point, writeable), + (accept_per_split_point, writeable), + (accept_split_adaptor_signature, {cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature}), + (split_tx, {cb_writeable, split_tx::write, split_tx::read}), + (ln_glue_transaction, writeable) +}); + +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) +}); + +impl_dlc_writeable!(CloseOfferedSubChannel, { + (signed_subchannel, writeable), + (offer_balance, writeable), + (accept_balance, writeable) +}); + +impl_dlc_writeable!(CloseAcceptedSubChannel, { (signed_subchannel, writeable) }); + +impl_dlc_writeable!(CloseConfirmedSubChannel, { (signed_subchannel, writeable) }); + +impl_dlc_writeable!(ClosingSubChannel, { (signed_sub_channel, writeable) }); diff --git a/dlc-manager/src/utils.rs b/dlc-manager/src/utils.rs index 9f9d5ff0..2f5ebb6d 100644 --- a/dlc-manager/src/utils.rs +++ b/dlc-manager/src/utils.rs @@ -21,6 +21,40 @@ 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) => { + 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 {} {: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 +105,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 +121,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 616076fb..cbb04174 100644 --- a/dlc-manager/tests/channel_execution_tests.rs +++ b/dlc-manager/tests/channel_execution_tests.rs @@ -26,11 +26,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, }, }; @@ -253,9 +254,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); @@ -453,7 +453,7 @@ fn channel_execution_test(test_params: TestParams, path: TestPath) { 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); @@ -472,7 +472,7 @@ fn channel_execution_test(test_params: TestParams, path: TestPath) { alice_send .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 => { @@ -481,20 +481,20 @@ fn channel_execution_test(test_params: TestParams, path: TestPath) { .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::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); @@ -518,11 +518,26 @@ fn channel_execution_test(test_params: TestParams, path: TestPath) { 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 +549,7 @@ fn channel_execution_test(test_params: TestParams, path: TestPath) { first_send, second, channel_id, - &sync_receive, + &second_receive, &generate_blocks, ); } @@ -544,10 +559,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 +571,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.clone(), second.clone(), second_send, + second_receive.clone(), channel_id, - &sync_receive, ); } @@ -617,10 +650,11 @@ fn channel_execution_test(test_params: TestParams, path: TestPath) { renew_timeout( first, first_send, + first_receive, second, second_send, + second_receive, channel_id, - &sync_receive, &test_params.contract_input, path, ); @@ -629,10 +663,11 @@ fn channel_execution_test(test_params: TestParams, path: TestPath) { renew_reject( first, first_send, + first_receive, second, second_send, + second_receive, channel_id, - &sync_receive, &test_params.contract_input, ); } @@ -640,17 +675,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 +698,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 +722,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 +734,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, ); } _ => (), @@ -760,7 +799,6 @@ fn close_established_channel( .periodic_check() .expect("to be able to do the periodic check"); - // assert_channel_state!(first, channel_id, Signed, Closed); assert_contract_state!(first, contract_id, PreClosed); @@ -790,7 +828,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 @@ -820,27 +858,29 @@ fn cheat_punish ()>( 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."); + println!("A"); first_send .send(Some(Message::Channel(ChannelMessage::SettleOffer( settle_offer, )))) .unwrap(); + println!("B"); - sync_receive.recv().expect("Error synchronizing"); + second_receive.recv().expect("Error synchronizing"); + println!("C"); assert_channel_state!(first, channel_id, Signed, SettledOffered); assert_channel_state!(second, channel_id, Signed, SettledReceived); @@ -857,15 +897,13 @@ fn settle_channel( )))) .unwrap(); + println!("D"); // 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); @@ -875,15 +913,16 @@ 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 @@ -892,7 +931,7 @@ fn settle_reject( )))) .unwrap(); - sync_receive.recv().expect("Error synchronizing"); + second_receive.recv().expect("Error synchronizing"); assert_channel_state!(first, channel_id, Signed, SettledOffered); @@ -910,7 +949,7 @@ fn settle_reject( )))) .unwrap(); - sync_receive.recv().expect("Error synchronizing"); + first_receive.recv().expect("Error synchronizing"); assert_channel_state!(first, channel_id, Signed, Established); @@ -920,21 +959,22 @@ 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 @@ -950,10 +990,18 @@ fn settle_race( .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); @@ -963,10 +1011,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, ) { @@ -979,7 +1028,7 @@ 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 @@ -989,7 +1038,7 @@ fn renew_channel( .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); @@ -1007,12 +1056,12 @@ fn renew_channel( .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"); if let Some(prev_contract_id) = prev_contract_id { assert_contract_state!(first, prev_contract_id, Closed); @@ -1030,16 +1079,17 @@ 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 @@ -1049,7 +1099,7 @@ fn renew_reject( .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); @@ -1065,7 +1115,7 @@ fn 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); } @@ -1073,22 +1123,27 @@ 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 @@ -1104,10 +1159,18 @@ fn renew_race( .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); @@ -1125,7 +1188,7 @@ 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::Channel( @@ -1161,10 +1224,11 @@ fn collaborative_close ()>( 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, ) { @@ -1172,7 +1236,7 @@ fn renew_timeout( 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 @@ -1181,7 +1245,7 @@ fn renew_timeout( )))) .unwrap(); - sync_receive.recv().expect("Error synchronizing"); + second_receive.recv().expect("Error synchronizing"); if let TestPath::RenewOfferTimeout = path { mocks::mock_time::set_time( @@ -1208,7 +1272,7 @@ fn renew_timeout( .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( @@ -1223,7 +1287,7 @@ fn renew_timeout( assert_channel_state!(second, channel_id, Signed, 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, ); @@ -1242,16 +1306,17 @@ 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 @@ -1260,7 +1325,7 @@ fn settle_timeout( )))) .unwrap(); - sync_receive.recv().expect("Error synchronizing"); + second_receive.recv().expect("Error synchronizing"); if let TestPath::SettleOfferTimeout = path { mocks::mock_time::set_time( @@ -1287,7 +1352,7 @@ fn settle_timeout( .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( @@ -1302,7 +1367,7 @@ fn settle_timeout( 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, ); diff --git a/dlc-manager/tests/console_logger.rs b/dlc-manager/tests/console_logger.rs new file mode 100644 index 00000000..52b185b3 --- /dev/null +++ b/dlc-manager/tests/console_logger.rs @@ -0,0 +1,25 @@ +use chrono::Utc; +use lightning::util::logger::{Logger, Record}; + +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..225da346 --- /dev/null +++ b/dlc-manager/tests/custom_signer.rs @@ -0,0 +1,360 @@ +use std::sync::{Arc, Mutex}; + +use bitcoin::{Script, Transaction, TxOut}; +use lightning::{ + chain::keysinterface::{ + BaseSign, ExtraSign, InMemorySigner, KeyMaterial, KeysInterface, KeysManager, Recipient, + Sign, SpendableOutputDescriptor, + }, + ln::{chan_utils::ChannelPublicKeys, msgs::DecodeError, script::ShutdownScript}, + util::ser::Writeable, +}; +use secp256k1_zkp::{ecdsa::RecoverableSignature, 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 BaseSign for CustomSigner { + fn get_per_commitment_point( + &self, + idx: u64, + secp_ctx: &secp256k1_zkp::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) -> &lightning::ln::chan_utils::ChannelPublicKeys { + &self.channel_public_keys + } + + fn channel_keys_id(&self) -> [u8; 32] { + self.in_memory_signer.lock().unwrap().channel_keys_id() + } + + fn sign_counterparty_commitment( + &self, + commitment_tx: &lightning::ln::chan_utils::CommitmentTransaction, + preimages: Vec, + secp_ctx: &secp256k1_zkp::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: &secp256k1_zkp::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_zkp::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: &bitcoin::Transaction, + input: usize, + amount: u64, + per_commitment_key: &secp256k1_zkp::SecretKey, + secp_ctx: &secp256k1_zkp::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: &bitcoin::Transaction, + input: usize, + amount: u64, + per_commitment_key: &secp256k1_zkp::SecretKey, + htlc: &lightning::ln::chan_utils::HTLCOutputInCommitment, + secp_ctx: &secp256k1_zkp::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: &bitcoin::Transaction, + input: usize, + amount: u64, + per_commitment_point: &secp256k1_zkp::PublicKey, + htlc: &lightning::ln::chan_utils::HTLCOutputInCommitment, + secp_ctx: &secp256k1_zkp::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_zkp::Secp256k1, + ) -> Result { + self.in_memory_signer + .lock() + .unwrap() + .sign_closing_transaction(closing_tx, secp_ctx) + } + + fn sign_channel_announcement( + &self, + msg: &lightning::ln::msgs::UnsignedChannelAnnouncement, + secp_ctx: &secp256k1_zkp::Secp256k1, + ) -> Result< + ( + secp256k1_zkp::ecdsa::Signature, + secp256k1_zkp::ecdsa::Signature, + ), + (), + > { + self.in_memory_signer + .lock() + .unwrap() + .sign_channel_announcement(msg, 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 provide_channel_parameters( + &mut self, + channel_parameters: &lightning::ln::chan_utils::ChannelTransactionParameters, + ) { + self.in_memory_signer + .lock() + .unwrap() + .provide_channel_parameters(channel_parameters); + } +} + +impl ExtraSign for CustomSigner { + fn sign_with_fund_key_callback(&self, cb: &mut F) + where + F: FnMut(&secp256k1_zkp::SecretKey), + { + self.in_memory_signer + .lock() + .unwrap() + .sign_with_fund_key_callback(cb) + } + + fn set_channel_value_satoshis(&mut self, value: u64) { + self.in_memory_signer + .lock() + .unwrap() + .set_channel_value_satoshis(value) + } +} + +impl Writeable for CustomSigner { + fn write(&self, writer: &mut W) -> Result<(), std::io::Error> { + self.in_memory_signer.lock().unwrap().write(writer) + } +} + +impl Sign for CustomSigner {} + +pub struct CustomKeysManager { + keys_manager: KeysManager, +} + +impl CustomKeysManager { + pub fn new(keys_manager: KeysManager) -> 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, + secp_ctx, + ) + } +} + +impl KeysInterface for CustomKeysManager { + type Signer = CustomSigner; + + fn get_node_secret(&self, recipient: Recipient) -> Result { + self.keys_manager.get_node_secret(recipient) + } + + fn get_inbound_payment_key_material(&self) -> KeyMaterial { + self.keys_manager.get_inbound_payment_key_material() + } + + fn get_destination_script(&self) -> Script { + self.keys_manager.get_destination_script() + } + + fn get_shutdown_scriptpubkey(&self) -> ShutdownScript { + self.keys_manager.get_shutdown_scriptpubkey() + } + + fn get_secure_random_bytes(&self) -> [u8; 32] { + self.keys_manager.get_secure_random_bytes() + } + + fn read_chan_signer(&self, reader: &[u8]) -> Result { + let in_memory = self.keys_manager.read_chan_signer(reader)?; + Ok(CustomSigner::new(in_memory)) + } + + fn sign_invoice( + &self, + hrp_bytes: &[u8], + invoice_data: &[bitcoin::bech32::u5], + recipient: Recipient, + ) -> Result { + self.keys_manager + .sign_invoice(hrp_bytes, invoice_data, recipient) + } + + fn ecdh( + &self, + recipient: Recipient, + other_key: &secp256k1_zkp::PublicKey, + tweak: Option<&secp256k1_zkp::Scalar>, + ) -> Result { + self.keys_manager.ecdh(recipient, other_key, tweak) + } + + 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)), + } + } +} 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..eacd817a --- /dev/null +++ b/dlc-manager/tests/ln_dlc_channel_execution_tests.rs @@ -0,0 +1,1203 @@ +#[macro_use] +mod test_utils; +mod console_logger; +mod custom_signer; + +use std::{collections::HashMap, convert::TryInto, sync::Arc, 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::RpcApi; +use console_logger::ConsoleLogger; +use custom_signer::{CustomKeysManager, CustomSigner}; +use dlc_manager::{ + manager::Manager, sub_channel_manager::SubChannelManager, subchannel::SubChannelState, + Blockchain, ChannelId, Oracle, Signer, Storage, Utxo, Wallet, +}; +use dlc_messages::{ChannelMessage, Message, SubChannelMessage}; +use electrs_blockchain_provider::{ElectrsBlockchainProvider, OutSpendResp}; +use lightning::{ + chain::{ + chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator}, + keysinterface::{KeysInterface, KeysManager, Recipient}, + BestBlock, Filter, Listen, + }, + ln::{ + channelmanager::{ChainParameters, PaymentId}, + peer_handler::{IgnoringMessageHandler, MessageHandler}, + }, + routing::{ + gossip::{NetworkGraph, NodeId}, + router::{RouteHop, RouteParameters}, + scoring::{ChannelUsage, Score}, + }, + util::{ + config::UserConfig, + events::{Event, EventHandler, EventsProvider, PaymentPurpose}, + }, +}; +use lightning_persister::FilesystemPersister; +use mocks::{ + memory_storage_provider::MemoryStorage, + mock_blockchain::MockBlockchain, + mock_oracle_provider::MockOracle, + mock_time::{self, MockTime}, +}; +use secp256k1_zkp::{ + rand::{thread_rng, RngCore}, + 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, +>; + +pub(crate) type PeerManager = lightning::ln::peer_handler::PeerManager< + MockSocketDescriptor, + 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, +>; + +struct LnDlcParty { + peer_manager: Arc, + channel_manager: Arc, + chain_monitor: Arc, + keys_manager: Arc, + logger: Arc, + network_graph: NetworkGraph>, + chain_height: u64, + sub_channel_manager: DlcSubChannelManager, + dlc_manager: Arc, + blockchain: Arc, + mock_blockchain: Arc>>, + wallet: Arc, Arc>>, + persister: 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(); + } +} + +enum TestPath { + EstablishedClose, + RenewedClose, + SettledClose, + SettledRenewedClose, + CheatPreSplitCommit, + CheatPostSplitCommit, + OffChainClosed, + SplitCheat, +} + +impl LnDlcParty { + fn update_to_chain_tip(&mut self) { + let chain_tip_height = self.blockchain.get_blockchain_height().unwrap(); + for i in self.chain_height + 1..=chain_tip_height { + let block = self.blockchain.get_block_at_height(i).unwrap(); + self.channel_manager.block_connected(&block, i as u32); + for ftxo in self.chain_monitor.list_monitors() { + self.chain_monitor + .get_monitor(ftxo) + .unwrap() + .block_connected( + &block.header, + &block.txdata.iter().enumerate().collect::>(), + i as u32, + self.blockchain.clone(), + self.blockchain.clone(), + self.logger.clone(), + ); + } + } + self.chain_height = chain_tip_height; + self.sub_channel_manager.check_for_watched_tx().unwrap(); + } + + fn process_events(&self) { + self.peer_manager.process_events(); + self.channel_manager.process_pending_events(self); + self.chain_monitor.process_pending_events(self); + } +} + +#[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 { + fn channel_penalty_msat(&self, _: u64, _: &NodeId, _: &NodeId, _: ChannelUsage) -> u64 { + self.penalty_msat + } + + fn payment_path_failed(&mut self, _path: &[&RouteHop], _short_channel_id: u64) {} + + fn payment_path_successful(&mut self, _path: &[&RouteHop]) {} + + fn probe_failed(&mut self, _path: &[&RouteHop], _short_channel_id: u64) {} + + fn probe_successful(&mut self, _path: &[&RouteHop]) {} +} + +impl EventHandler for LnDlcParty { + fn handle_event(&self, event: lightning::util::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_transaction(&spending_tx); + } + _ => { + //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 = KeysManager::new(&key, cur.as_secs(), cur.subsec_nanos()); + let consistent_keys_manager = Arc::new(CustomKeysManager::new(keys_manager)); + 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 chain_monitor: Arc = + Arc::new(lightning::chain::chainmonitor::ChainMonitor::new( + None, + mock_blockchain.clone(), + logger.clone(), + blockchain_provider.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 (blockhash, chain_height, 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), + }; + + let fresh_channel_manager = Arc::new(ChannelManager::new( + blockchain_provider.clone(), + chain_monitor.clone(), + mock_blockchain.clone(), + logger.clone(), + consistent_keys_manager.clone(), + user_config, + chain_params, + )); + (last_block.block_hash(), height, fresh_channel_manager) + }; + + // 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 lightning_msg_handler = MessageHandler { + chan_handler: channel_manager.clone(), + route_handler: Arc::new(IgnoringMessageHandler {}), + onion_message_handler: Arc::new(IgnoringMessageHandler {}), + }; + let peer_manager = PeerManager::new( + lightning_msg_handler, + consistent_keys_manager + .get_node_secret(Recipient::Node) + .unwrap(), + current_time.try_into().unwrap(), + &ephemeral_bytes, + logger.clone(), + Arc::new(IgnoringMessageHandler {}), + ); + + let network_graph = NetworkGraph::new(blockhash, logger.clone()); + + 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 = SubChannelManager::new( + channel_manager.clone(), + dlc_manager.clone(), + blockchain_provider.get_blockchain_height().unwrap(), + ); + + LnDlcParty { + peer_manager: Arc::new(peer_manager), + channel_manager: channel_manager.clone(), + chain_monitor, + keys_manager: consistent_keys_manager, + logger, + network_graph, + chain_height, + sub_channel_manager, + dlc_manager, + blockchain: blockchain_provider.clone(), + mock_blockchain, + wallet, + persister, + } +} + +#[test] +#[ignore] +fn ln_dlc_established_close() { + ln_dlc_test(TestPath::EstablishedClose); +} + +#[test] +#[ignore] +fn ln_dlc_renewed_close() { + ln_dlc_test(TestPath::RenewedClose); +} + +#[test] +#[ignore] +fn ln_dlc_settled_close() { + ln_dlc_test(TestPath::SettledClose); +} + +#[test] +#[ignore] +fn ln_dlc_settled_renewed_close() { + ln_dlc_test(TestPath::SettledRenewedClose); +} + +#[test] +#[ignore] +fn ln_dlc_pre_split_cheat() { + ln_dlc_test(TestPath::CheatPreSplitCommit); +} + +#[test] +#[ignore] +fn ln_dlc_post_split_cheat() { + ln_dlc_test(TestPath::CheatPostSplitCommit); +} + +#[test] +#[ignore] +fn ln_dlc_off_chain_close() { + ln_dlc_test(TestPath::OffChainClosed); +} + +#[test] +#[ignore] +fn ln_dlc_split_cheat() { + ln_dlc_test(TestPath::SplitCheat); +} + +// #[derive(Debug)] +// pub struct TestParams { +// pub oracles: Vec, +// pub contract_input: ContractInput, +// } + +fn ln_dlc_test(test_path: TestPath) { + 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| { + 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(); + } + }; + + 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())); + + let initial_send = alice_node + .peer_manager + .new_outbound_connection( + bob_node.channel_manager.get_our_node_id(), + alice_descriptor, + 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(&mut 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.peer_manager.process_events(); + alice_node.peer_manager.process_events(); + bob_node.peer_manager.process_events(); + + alice_node + .channel_manager + .process_pending_events(&alice_node); + alice_node.peer_manager.process_events(); + bob_node.peer_manager.process_events(); + + let sink_address = sink_rpc.get_new_address(None, None).expect("RPC Error"); + sink_rpc + .generate_to_address(6, &sink_address) + .expect("RPC Error"); + + alice_node.update_to_chain_tip(); + bob_node.update_to_chain_tip(); + + alice_node.peer_manager.process_events(); + bob_node.peer_manager.process_events(); + + assert_eq!(1, alice_node.channel_manager.list_channels().len()); + + while alice_node.channel_manager.list_usable_channels().len() != 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()); + + let payment_params = lightning::routing::router::PaymentParameters::from_node_id( + bob_node.channel_manager.get_our_node_id(), + ); + + 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) + .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: payment_params.clone(), + final_value_msat: 90000000, + final_cltv_expiry_delta: 70, + }; + + 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 payment_id = PaymentId([0u8; 32]); + + alice_node + .channel_manager + .send_spontaneous_payment(&route, Some(payment_preimage), 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(); + + std::thread::sleep(std::time::Duration::from_secs(2)); + + let get_commit_tx_from_node = |node: &LnDlcParty| { + let mut res = node + .persister + .read_channelmonitors(alice_node.keys_manager.clone()) + .unwrap(); + assert!(res.len() == 1); + let (_, channel_monitor) = res.remove(0); + channel_monitor.get_latest_holder_commitment_txn(&alice_node.logger) + }; + + let pre_split_commit_tx = if let TestPath::CheatPreSplitCommit = test_path { + Some(get_commit_tx_from_node(&alice_node)) + } else { + None + }; + + let bob_channel_details = bob_node.channel_manager.list_usable_channels().remove(0); + let channel_id = bob_channel_details.channel_id; + + offer_sub_channel(&test_params, &alice_node, &bob_node, &channel_id); + + if let TestPath::CheatPreSplitCommit = test_path { + let revoked_tx = pre_split_commit_tx.unwrap(); + + ln_cheated_check( + &revoked_tx[0], + &mut bob_node, + electrs.clone(), + &generate_blocks, + ); + + return; + } + + let route_params = RouteParameters { + payment_params, + final_value_msat: 900000, + final_cltv_expiry_delta: 70, + }; + + 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 post_split_commit_tx = if let TestPath::CheatPostSplitCommit = test_path { + alice_node.mock_blockchain.start_discard(); + Some(get_commit_tx_from_node(&alice_node)) + } else { + None + }; + + let mut payment_id = PaymentId([0u8; 32]); + payment_id.0[31] += 1; + + alice_node + .channel_manager + .send_spontaneous_payment(&route, Some(payment_preimage), 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(); + + std::thread::sleep(std::time::Duration::from_secs(1)); + + if let TestPath::RenewedClose = test_path { + renew(&alice_node, &bob_node, channel_id, &test_params); + } else if let TestPath::SettledClose | TestPath::SettledRenewedClose = test_path { + settle(&alice_node, &bob_node, channel_id, &test_params); + + if let TestPath::SettledRenewedClose = test_path { + renew(&alice_node, &bob_node, channel_id, &test_params); + } + } + + mocks::mock_time::set_time(EVENT_MATURITY as u64); + + if let TestPath::OffChainClosed | TestPath::SplitCheat = test_path { + if let TestPath::SplitCheat = test_path { + alice_node.dlc_manager.get_store().save(); + } + + let (close_offer, _) = alice_node + .sub_channel_manager + .offer_subchannel_close(&channel_id, test_params.contract_input.accept_collateral) + .unwrap(); + + bob_node + .sub_channel_manager + .on_sub_channel_message( + &SubChannelMessage::CloseOffer(close_offer), + &alice_node.channel_manager.get_our_node_id(), + ) + .unwrap(); + + let (close_accept, _) = bob_node + .sub_channel_manager + .accept_subchannel_close_offer(&channel_id) + .unwrap(); + + let close_confirm = alice_node + .sub_channel_manager + .on_sub_channel_message( + &SubChannelMessage::CloseAccept(close_accept), + &bob_node.channel_manager.get_our_node_id(), + ) + .unwrap() + .unwrap(); + + let close_finalize = bob_node + .sub_channel_manager + .on_sub_channel_message( + &close_confirm, + &alice_node.channel_manager.get_our_node_id(), + ) + .unwrap() + .unwrap(); + + alice_node + .sub_channel_manager + .on_sub_channel_message(&close_finalize, &bob_node.channel_manager.get_our_node_id()) + .unwrap(); + + offer_sub_channel(&test_params, &alice_node, &bob_node, &channel_id); + + if let TestPath::SplitCheat = test_path { + alice_node.dlc_manager.get_store().rollback(); + let split_tx_id = match alice_node + .dlc_manager + .get_store() + .get_sub_channel(channel_id) + .unwrap() + .unwrap() + .state + { + SubChannelState::Signed(s) => s.split_tx.transaction.txid(), + a => panic!("Unexpected state {:?}", a), + }; + alice_node + .sub_channel_manager + .initiate_force_close_sub_channel(&channel_id) + .unwrap(); + + generate_blocks(1); + + bob_node.update_to_chain_tip(); + + let outspends = 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); + assert_eq!(spent[0].txid, spent[1].txid); + let spending_tx = electrs.get_transaction(&spent[0].txid).unwrap(); + + let receive_addr = + Address::from_script(&spending_tx.output[0].script_pubkey, Network::Regtest) + .unwrap(); + + assert!(bob_node + .dlc_manager + .get_store() + .get_addresses() + .unwrap() + .iter() + .any(|x| *x == receive_addr)); + } else { + alice_node + .channel_manager + .force_close_broadcasting_latest_txn( + &channel_id, + &bob_node.channel_manager.get_our_node_id(), + ) + .unwrap(); + } + + return; + } + + alice_node + .sub_channel_manager + .initiate_force_close_sub_channel(&channel_id) + .unwrap(); + + generate_blocks(500); + + alice_node + .sub_channel_manager + .finalize_force_close_sub_channels(&channel_id) + .unwrap(); + + generate_blocks(1); + + bob_node.update_to_chain_tip(); + + if let TestPath::CheatPostSplitCommit = test_path { + let cheat_tx = post_split_commit_tx.unwrap()[0].clone(); + ln_cheated_check(&cheat_tx, &mut bob_node, electrs.clone(), &generate_blocks); + } +} + +fn settle( + alice_node: &LnDlcParty, + bob_node: &LnDlcParty, + channel_id: ChannelId, + test_params: &TestParams, +) { + let (settle_offer, bob_key) = alice_node + .dlc_manager + .settle_offer(&channel_id, test_params.contract_input.accept_collateral) + .unwrap(); + + bob_node + .dlc_manager + .on_dlc_message( + &Message::Channel(ChannelMessage::SettleOffer(settle_offer)), + alice_node.channel_manager.get_our_node_id(), + ) + .unwrap(); + + let (settle_accept, alice_key) = bob_node + .dlc_manager + .accept_settle_offer(&channel_id) + .unwrap(); + + let msg = alice_node + .dlc_manager + .on_dlc_message( + &Message::Channel(ChannelMessage::SettleAccept(settle_accept)), + bob_key, + ) + .unwrap() + .unwrap(); + + let msg = bob_node + .dlc_manager + .on_dlc_message(&msg, alice_key) + .unwrap() + .unwrap(); + + alice_node + .dlc_manager + .on_dlc_message(&msg, bob_key) + .unwrap(); +} + +fn renew( + alice_node: &LnDlcParty, + bob_node: &LnDlcParty, + channel_id: ChannelId, + test_params: &TestParams, +) { + let (renew_offer, _) = alice_node + .dlc_manager + .renew_offer( + &channel_id, + test_params.contract_input.accept_collateral, + &test_params.contract_input, + ) + .unwrap(); + + bob_node + .dlc_manager + .on_dlc_message( + &Message::Channel(ChannelMessage::RenewOffer(renew_offer)), + alice_node.channel_manager.get_our_node_id(), + ) + .unwrap(); + + let (accept, bob_key) = bob_node + .dlc_manager + .accept_renew_offer(&channel_id) + .unwrap(); + + let msg = alice_node + .dlc_manager + .on_dlc_message( + &Message::Channel(ChannelMessage::RenewAccept(accept)), + bob_node.channel_manager.get_our_node_id(), + ) + .unwrap() + .unwrap(); + + let msg = bob_node + .dlc_manager + .on_dlc_message(&msg, bob_key) + .unwrap() + .unwrap(); + + alice_node + .dlc_manager + .on_dlc_message(&msg, bob_node.channel_manager.get_our_node_id()) + .unwrap(); +} + +fn ln_cheated_check( + cheat_tx: &Transaction, + bob_node: &mut LnDlcParty, + electrs: Arc, + generate_block: &F, +) where + F: Fn(u64), +{ + electrs.broadcast_transaction(cheat_tx); + + // wait for cheat tx to be confirmed + generate_block(6); + + bob_node.update_to_chain_tip(); + + bob_node.process_events(); + + // LDK should have reacted, this should include a punish tx + generate_block(1); + + bob_node.update_to_chain_tip(); + + 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 = 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 = electrs.get_transaction(&outspend_info[vout].txid).unwrap(); + + generate_block(6); + + bob_node.update_to_chain_tip(); + + bob_node.process_events(); + + let mut outspend_info = vec![]; + while outspend_info.is_empty() { + let outspends = 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 = electrs.get_transaction(&outspend_info[0].txid).unwrap(); + + let receive_addr = + Address::from_script(&claim_tx.output[0].script_pubkey, Network::Regtest).unwrap(); + + assert!(bob_node + .dlc_manager + .get_store() + .get_addresses() + .unwrap() + .iter() + .any(|x| *x == receive_addr)); +} + +fn offer_sub_channel( + test_params: &TestParams, + alice_node: &LnDlcParty, + bob_node: &LnDlcParty, + channel_id: &ChannelId, +) { + 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 = alice_node + .sub_channel_manager + .offer_sub_channel( + channel_id, + &test_params.contract_input, + &[oracle_announcements], + ) + .unwrap(); + + assert_sub_channel_state!(alice_node.sub_channel_manager, channel_id, Offered); + + bob_node + .sub_channel_manager + .on_sub_channel_message( + &SubChannelMessage::Offer(offer), + &alice_node.channel_manager.get_our_node_id(), + ) + .unwrap(); + + assert_sub_channel_state!(bob_node.sub_channel_manager, channel_id, Offered); + + let (_, accept) = bob_node + .sub_channel_manager + .accept_sub_channel(channel_id) + .unwrap(); + + assert_sub_channel_state!(bob_node.sub_channel_manager, channel_id, Accepted); + + bob_node.process_events(); + let confirm = alice_node + .sub_channel_manager + .on_sub_channel_message( + &SubChannelMessage::Accept(accept), + &bob_node.channel_manager.get_our_node_id(), + ) + .unwrap() + .unwrap(); + + assert_sub_channel_state!(alice_node.sub_channel_manager, channel_id, Signed); + + alice_node.process_events(); + let finalize = bob_node + .sub_channel_manager + .on_sub_channel_message(&confirm, &alice_node.channel_manager.get_our_node_id()) + .unwrap() + .unwrap(); + + assert_sub_channel_state!(bob_node.sub_channel_manager, channel_id, Signed); + + bob_node.process_events(); + alice_node + .sub_channel_manager + .on_sub_channel_message(&finalize, &bob_node.channel_manager.get_our_node_id()) + .unwrap(); + alice_node.process_events(); +} diff --git a/dlc-manager/tests/test_utils.rs b/dlc-manager/tests/test_utils.rs index cf631084..6063bf6f 100644 --- a/dlc-manager/tests/test_utils.rs +++ b/dlc-manager/tests/test_utils.rs @@ -47,37 +47,44 @@ pub const ROUNDING_MOD: u64 = 1; macro_rules! receive_loop { ($receive:expr, $manager:expr, $send:expr, $expect_err:expr, $sync_send:expr, $rcv_callback: expr, $msg_callback: expr) => { thread::spawn(move || loop { + let m; match $receive.recv() { - Ok(Some(msg)) => 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)) => { + m = format!("{:?}", msg).split_at(6).0.to_string(); + let res = $manager.lock().unwrap().on_dlc_message( + &msg, + "0218845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166" + .parse() + .unwrap(), + ); + println!("SYNCING {} {}!", m, stringify!($manager)); + $sync_send.send(()).expect("Error syncing"); + println!("SYNCED {} {}!", m, stringify!($manager)); + 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 { + $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"); }) }; } @@ -142,6 +149,20 @@ 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)?) => {{ @@ -174,6 +195,35 @@ macro_rules! assert_channel_state { }}; } +#[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 = c.state { + } else { + panic!("Unexpected sub channel state {:?}", c.state); + } + if std::env::var("GENERATE_SERIALIZED_SUB_CHANNEL").is_ok() { + write_sub_channel!(sub_channel, $s_simple); + })? + } else { + panic!("Sub channel not found"); + } + }}; +} + pub fn enum_outcomes() -> Vec { vec![ "a".to_owned(), @@ -184,7 +234,7 @@ pub fn enum_outcomes() -> Vec { } pub fn max_value() -> u32 { - BASE.pow(NB_DIGITS as u32) - 1 + BASE.pow(NB_DIGITS) - 1 } pub fn max_value_from_digits(nb_digits: usize) -> u32 { @@ -218,20 +268,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 +324,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 +353,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 +538,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 +571,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 +591,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,7 +629,7 @@ 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, }; diff --git a/dlc-messages/Cargo.toml b/dlc-messages/Cargo.toml index 6d84a6d9..effe8b2d 100644 --- a/dlc-messages/Cargo.toml +++ b/dlc-messages/Cargo.toml @@ -13,7 +13,7 @@ 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.113", git = "https://github.com/p2pderivatives/rust-lightning/", branch = "split-tx-experiment"} secp256k1-zkp = {version = "0.7.0", features = ["bitcoin_hashes", "rand", "rand-std"]} serde = {version = "1.0", features = ["derive"], optional = true} diff --git a/dlc-messages/src/lib.rs b/dlc-messages/src/lib.rs index 57738a2c..5f809de3 100644 --- a/dlc-messages/src/lib.rs +++ b/dlc-messages/src/lib.rs @@ -735,7 +735,7 @@ mod tests { let mut single_contract_input = offer.clone(); single_contract_input.contract_info = if let ContractInfo::DisjointContractInfo(d) = offer.contract_info { - let mut single = d.clone(); + let mut single = d; single.contract_infos.remove(1); ContractInfo::DisjointContractInfo(single) } else { diff --git a/dlc-messages/src/sub_channel.rs b/dlc-messages/src/sub_channel.rs index 169dbb96..81b3be04 100644 --- a/dlc-messages/src/sub_channel.rs +++ b/dlc-messages/src/sub_channel.rs @@ -1,5 +1,4 @@ //! Module containing messages related to DLC on Lightning channels. -//! use bitcoin::Script; use secp256k1_zkp::{ecdsa::Signature, EcdsaAdaptorSignature, PublicKey, SecretKey}; diff --git a/dlc-sled-storage-provider/Cargo.toml b/dlc-sled-storage-provider/Cargo.toml index 5f45a8ef..4ef586f6 100644 --- a/dlc-sled-storage-provider/Cargo.toml +++ b/dlc-sled-storage-provider/Cargo.toml @@ -14,7 +14,7 @@ wallet = ["bitcoin", "secp256k1-zkp", "simple-wallet", "lightning"] [dependencies] bitcoin = {version = "0.29", optional = true} dlc-manager = {path = "../dlc-manager"} -lightning = {version = "0.0.113", optional = true} +lightning = {version = "0.0.113", optional = true, git = "https://github.com/p2pderivatives/rust-lightning/", branch = "split-tx-experiment"} secp256k1-zkp = {version = "0.7", optional = true} simple-wallet = {path = "../simple-wallet", optional = true} sled = "0.34" diff --git a/dlc-sled-storage-provider/src/lib.rs b/dlc-sled-storage-provider/src/lib.rs index 2c1e092b..3d45438c 100644 --- a/dlc-sled-storage-provider/src/lib.rs +++ b/dlc-sled-storage-provider/src/lib.rs @@ -28,6 +28,7 @@ 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}; @@ -40,7 +41,7 @@ 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; @@ -50,6 +51,7 @@ const CHAIN_MONITOR_KEY: u8 = 4; const UTXO_TREE: u8 = 6; #[cfg(feature = "wallet")] const KEY_PAIR_TREE: u8 = 7; +const SUB_CHANNEL_TREE: u8 = 3; #[cfg(feature = "wallet")] const ADDRESS_TREE: u8 = 8; @@ -60,7 +62,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 +84,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))), } } } @@ -119,7 +121,7 @@ convertible_enum!( enum ChannelPrefix { Offered = 100, Accepted, - Signed, + Signed; SignedChannelPrefix, state, FailedAccept, FailedSign,; }, @@ -147,6 +149,23 @@ convertible_enum!( SignedChannelStateType ); +convertible_enum!( + enum SubChannelPrefix {; + Offered = 500, + Accepted, + Signed, + Closing, + OnChainClosed, + CounterOnChainClosed, + CloseOffered, + CloseAccepted, + CloseConfirmed, + OffChainClosed, + ClosedPunished, + }, + SubChannelState +); + fn to_storage_error(e: T) -> Error where T: std::fmt::Display, @@ -190,7 +209,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 +219,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")] @@ -386,14 +409,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 +426,44 @@ 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)?; + 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> { + self.sub_channel_tree()? + .iter() + .values() + .map(|x| deserialize_sub_channel(&x.unwrap())) + .collect::, Error>>() + } + + fn get_offered_sub_channels(&self) -> Result, Error> { + self.get_data_with_prefix( + &self.sub_channel_tree()?, + &[SubChannelPrefix::Offered.into()], + None, + ) + } } #[cfg(feature = "wallet")] @@ -428,7 +489,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 +559,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 +571,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; @@ -656,6 +712,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::*; @@ -812,6 +885,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 +999,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 +1073,28 @@ 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()); + } + ); } diff --git a/dlc-sled-storage-provider/test_files/Accepted b/dlc-sled-storage-provider/test_files/Accepted index bcb851ca0177918d1c6efe1d7e64e650c2ba2064..69a46fdee3632d70deb8ad95a697043e240a3597 100644 GIT binary patch literal 3430 zcmZ2Ib5lv!lXraEzIer3oBJJjZ|bvgI$vf%_3jvkC9$0I7#JCu7?>Gw0k8~ftXBhE z0V{~b#;WW9q?kZ#AV3xa3o!o&Q4m#(U=aiX=QF`rT#S0DoZH>4mqfoVZocz;<$=!F z;5;@~DXDFVjPk-l^)zy=I{9NyP#{CRizui}PcfdtAqKGf8Kl14MQ$c;b z2AaDnX1lpec440NxBJ*EmN%`B`a|*$FPs=AcVYA28%lo}6B)QToD*mB?%m@0E%Zr+ z$LYGN2`bG?e(##}=xw2pBG3IxS3~j^sVBcGKe?H2-nGQr32!P_*y$EYPmN>BS$+AZ zex2BSu^0EIp2&?q;1^yvHg4YJJ;hktYG&hcePWA|=JV`5lO$)X zS^3DgmH*ZB($<>!I-2{UcV9j`T~}bORjc3p>;EJc?meP)$8+P~E1S-4FWBZ#%d74n zWwczZdy2Q*<`vyXu!Q~G8~)zl5(36NM{#0Fv3_xCGB{?rSVB^ZOQ4a=$P^*YAp0}q zv(&QIuo+2bZi$7mwldE7pXs;byL{eX6~XBYVhke8`&{SyusbB|n6zActK!Ovywb{7 zxlW+?g(2RtoHKz^15rkL}WVL-0LMj&2i#adTo1@@wy7f#;#|*x*OKc ziv4O--`0K5f*^n5}jH->BmhMU~lwg+}fg9W)?9|F^`L=8%$j4%cF zz$u!M@x+WdC+c?}NOJB?PMh#s>w#+F!9@vG;^{>%x9$GFdG2+v3IG28|Ifs5=ruQp z2XwH*w{sgimi<}#S4i~iyYlv)2~C;Ie^jL8K*<~^J!jggDL&G_&T+pKKO(Y=t)q5z zMk15DfeVv@%$2ouqNPbu=jJ%o&Jz zT1l-|@o!N%{~m(f%)kJ2^nXy8FnkUPdzo1)Sqc?r1jg<9gcEKCOcE{Ojo%E9^vg`= zd}J_*t(L91-e48ag%Gb3?f)j;y_m+_=y>bf)*llC?A%{kf2{YowyG{>S+C>s>BkiK zLiIkz90U0_{9oKYj;uW)JJ{9EhlMp2gdQnjXFlTm^z~HZaFG0J&Lx~PWRl|+YR|pD z$6~|RW9{d4kAA$J9hvl{i}Az$xdjKji>}Kpe*4btgMK0BV~00ieKH>QtWd8nn6Z5d z)X5+U=#wJWBBiYNp7tOvEOr@|-#EYslYqtQmA0wLc_1A43)-L<)~qI}8WY4?@Ys}5?jXr(0m`_kpa294FKZD-sInwqnZ z-<6YdHQuv?Z?PXABvyI)*?u&2e)MI2`o(AYAeLdq!8}L z>)H87`olgx^@F{Rn>W4mRP(&P-0FqVO8&RTYv1i=RAicbFp>SZ^^{4XTc>(I(+a#N zmM_2btf0kK&nrn%jc07RCV}0Ih*d^V*kW@sGb4jEBdoxMYGPnO2*BlFLdXIzF$P9> znGM&<2v-QJ4G?OOEI{alOS8ds!s;KOBOw4t!=8~xJR(-6oTm##?Z+Plftf31STztp_2zDgW+JLI0~O0e zRcauG%(*X|VYLpnf_80L9~;PPz_I{aE&EED6)dg9pm61|ScuKkCqi72rsus^n5S%! zWv}??>=Gs9kj*ub;Wih@6gH4A7~eczJ&WUlv%2?1uE}eP=SN?2(^=fJKRoA2TJQ_* zyk$P%U<4JOj1Exaag_4wx%VfKW+GF=wWZ)90*8qQeB!~j1EsMw8pyDaLHG_hIpDC+ zK|m8+@KDdf12>j~f*M!N*x(flF0ZI)p*kmC3xEHnu7${{f=ZD@AW#Sdse#*x1P1pY OMS^WrMS|^}pmhLdyboId literal 3430 zcmeyzRZ;umfu=_Q*M=Ok{?F-*eCrQbZr3tucavIoY>B)w10y3712Y3I0G45m^=g1C zU&N$m&|vydCq*Q zo8V!pVAWc4(k6LXLFu#2F9c3zeENR-!ol{vlTLR2y`l7%F_D3*YsqV^@@swHMa$dv z?z$1mqWIQXOC!MSe(CEO_J#btXLe4xlv3t-=I4dS8Qm?tvBh^-m9+wIvU#ieF(pa8qYG!BhPjb_ltduYHT>4q6>9=QoV6wUW;so28X#e#SRDWKQ z>b7~a{LS3IJ0#CDPMvdv_4euuH`A-0Y1dFPU~mXQ>%#~;c(NigO;gBXK|=bbqZEOu`BRV?9ka>|Cim6flb zI$d|UKI^vhu^5p2!ej4OM*VMQOghrodd2YCgg57xZ8L(piKqNPYYzBr&EZc7Wt#V`miE_U_1eSy)YEU9& zgekZOPSK2v6BUxbc$Zvr__b@<1imekH8GAc4zy6a5c{x`{Q=C)mb=LU4h3!K+G zhV!k~5TRddpF{$Qv$6DV#IPPiE`NwkDFeltAM zFEgF^N6b%-YAc_Vw%j4%mUtbUO*yJ+pn8EO~SVsDz+%^)b~ zwx{FsoQ2QDBCoV=u+;{K1UOc|bUCp>qb2QXG=P7qE?{TCDU#_On4 z$@TXAf6T&NqGi3(Z|9juZW5Uz4vketSgbOF!WNsGm>C(Q8DW72_9%!z2*BlFLdXIz zF$P9>nGM&<2v-QJ4G?OOEI{alOS8ds!s;KOBOw4t!h6>S2K?NA?s# zCtR8bt`l5m0adfYbRyY_6f-$%;bixfq(urgz2TUg~ zhEBK?cAb1Mo%|R&Ibl-Rbqc_w1u=BOJ%U}Q5KN~qhE8sn6n32=FlkW?ojfop>^j9@ z(&898d0|o@8W9^3Fkwj;LkbjiKmbd&FmXf#GB7CJc&M;+soIT+x^lr$-`?iim;dt; zi#hqM?W&8%v^|w8LqEl8$6f0#{I=WUkIwUB^0k`HNo&j)H$RkKWt)4&dMv|XHpmh-kS`e39cOu@TrLm%-h98oWATmi-bY{Fy?3>T zsdLo~ZoBi_AsG==crrRDLmY>rl;6O;KY=t8nGX2GgJTJ3BDPjR!?mU05`=mdItXZj zJ%qzT2H`v4xS*be4PLR}0tttO2W~6}#RRU3lZ+s3`28ENh3cHtwGcT~V2eX?yg*Ty W8MvKDU~msoB-ol!B-ok_S_c3ic{8N| diff --git a/dlc-sled-storage-provider/test_files/AcceptedChannel b/dlc-sled-storage-provider/test_files/AcceptedChannel index cc924b8b61ebfe1c48eab582ac75e5006ee4f270..8e8bdcc5f27f8fb6fd842da60392de4d3db1b2a1 100644 GIT binary patch delta 904 zcmV;319$w42aE@Q!mUv`FBM8>t91U_3>@m=w6L2<6Q6O^^ljNMDZnx>18K@jV_9jR zZ9TyiLgbfFdrh{N*n<<75BtuhIwDUtYOBzDbx zi#(l_lTicI6Ou-51OFux?!-^*X*CBrLB@1M3kWXwwtOFd38J$&0^f5bQY_vg$x%S~ zg03dcsm1Xjew{#t46h9+@~j>xE&@x-T*bTVWPh?2=nx`#n1>3P&*0bOf3!5tUF1jL zDM$jJhm^{DO!J&~n>Do~zIBtS2EZ6Y^IZ}|GDA!m|mVb z<;-&b@#BAg^lgTU>baMDZvzqmDv@AC2~l3r$c&8hFujqfnrNtvvD~p<#C4o-Ltg>_ z0004<8>R8&BJRmg-BDC-#KP7HIBW&9&70dfN!?tN+b8J)00000{{R2~0qP?K1^@s6 z03rY&crEEEqO9 ztAo%odAUb(l@1Hy)uKO)zUczQg6^cWx~yblcBvE|XBn4zl8K~(0{$GO(-88B;E(W# zt#+vtpV6&cOXa%cC7i|f1D5IePV0rKh^=<16hLe}*qD_fBi3eqZYzoGcPgi%_lRe9 zsT93`La6BDQI%0U&Q<#SwrOUrPNA!at#+vt-5fbzE>|HHec+%`<0rM;>3pXNh^=<1 z6j=+)2uSVfe`j965>Qz$D e^{hki>#z8}1Oga@SAq61#E%@83F0u5c>@#29=(77 delta 904 zcmV;319$w42aE@Q8|#>&4XfXhsN-KzT%b4aT-!rv_O2IeV^ZG6H2tz60z62s>{NiF z#$jFrBMyAs&1CY-!6f(~0e))@R zRd&j=0rf@SFmv@rw3|(4ZK#IY`2(`drmcS+DZcmJiuj~IC1zb=Q@36fDuDML!T5k? z2sQ(ebjCj1}?UY=O9CKu~X=4iJ3h1NVRbn=3xNhRP+C6>Fa(My( z0003w^c(rzxS!yoRqkqV@#eFOh+e}enLHv^OM>Jd`ENi000000{{R2~0hA;K1^@s6 z03rY&vRBIh{4|eCg;qHBe~7oKsquB;injdeSEH|g8nR`Ok+2KDU;yAD0`wRX74$_; z(ejW`(m#(oZiFh+Y-KdyjFFtRw$khX4yf;bbh^=<1 z6xFCsL9TH5uP?Z7LrgJG0Ro~XH;8CwAp-T@QVF$oC3jlR)))s(H9|pJEv=?Vmhxm( zqj!A8X=f*Pdew+ubDXI~$%V><2kKsbG`Y%oR2np}{!r&LqbUg9X`WZZ2~nUh zZ3^2$53~%gMAIZJ_xh^{BdPqyn*uC=fNSfSl;9W5n%Z_yyi@IijZ2Y#La^R8g+YD|iXOPnin>Im>A0XGNeMk^9o;zeX8< z@nhN`qq;!t%7ARVzx4s3UnMHW_A;=qm&Q9>VYjTeva$MsQWBcw*&xh7zvdV{HHf;} z8rT@!W@U3Y8 diff --git a/dlc-sled-storage-provider/test_files/AcceptedSubChannel b/dlc-sled-storage-provider/test_files/AcceptedSubChannel new file mode 100644 index 0000000000000000000000000000000000000000..762a3c0aedc640f32a4848585ba2019409992a64 GIT binary patch literal 3109 zcmezQIpEsCTp>FyzS%du*nQ@6E1p+p{HfcR#?I6orn$$CS=!yhpLOH1gCD!Sg!+A> zx1P$~x-r$Kv9Ci!wjo|(vjzjhe=uNVp10}3j;BG{-obw=BjmCOFxjz32hYO4Z#*YMGy}c_tlG(3!pY zMxveU=R0<%=c^mtYMrued++4ysehZencoYq^}bPktJ`>CrFYS4kJ*c-RTWKQ3X49x zH}TqWhN|L}O%D`zZ@=xu9qch(YLniw2(K;rPP;CuDaT%)yz#2hZ4LFynGc$eJH|fB zO4BlapCHhjp=E1z)qvGy>r!*hC4W9gl-<#vYiXG6!?>e4oX53v%d7C*Tf%RwZ1yC1 ze!uR>#K6G7_>(}80o6bN(=Ss-FiVL+;l!;6nNE}bE$D2!uE-xO)Y$AM^3toxZQHJE zf>t}LkM^c^5Umt!|q za@k6eh~L{b_iyj6uQj(9%niF>|9ANsosP9Y35SXwuf*abRw%A{x&7OssMrejDUZPt z(KWj7x>-MOK5^vX$)n8X?%!q~o7a&J)bU@%Y|`bb2CI;%=NEB*Q40N88D>sd9w<9B6k=gUnsW&D< z84I6GlzX{B`^;tY2a!c7KkGOxCAV)dH=O$*V+}Z{A1lWAzuMy3Tqj?Z8G)~Px*#_RbzuS1I`ZTdg!%afx;T><+<-ZwCD%1hPQ_!+(Xv+w>d&v~8GPMmLQ zcUGDHc;&8KD~qTllbPG*2X|z)6h>Pa zO#2lW+=CRE)P2gP{bPE{+gHf7CfInNPuypNYfHLk@LitLaZOp5n@^ExX8+Fl*Oysv zc`xb8f3Dn|YCcVEi@m3a3zOFM_$|{Etb^7u5L83Z8fXyXrXX1Yw>(T{R2?iNQ0dWd nK@ATWZ!}zBA%RMdh6`$Vz<8tK0t*RLdNf>6!vn?}Zs7s|8N>qv literal 0 HcmV?d00001 diff --git a/dlc-sled-storage-provider/test_files/Closed b/dlc-sled-storage-provider/test_files/Closed index eca0e8e625e9192aff42494d55d91a1e9efb28de..4c59dd2ef14703d8404803a48dce5129fc92f851 100644 GIT binary patch delta 1093 zcmV-L1iJgw2-FCF0RcqKP$o2ri;K+q>6=EkG}UFW z1cw7Z;-LN3swVRhL>nGq?rgYX)=*wpPWr9RY|}|Orc611aC!H%=VAKNNB$8u~p0wW;)4 z9k$GLEiOJt&xQ=D=~%MfMY6QFDKO$_X|!9Vfn~gn1q!OrTlR2f*ij5x$wP5KtO1MK zy4FyCA3J1!FdB7cl{DqgTTMUPEHj@YQXVi-#F7#xUi1C-;ZFYrO?HuBNy}u17c{U@ zE!h2VO_F;)#d&C1Y(Ro`^x9AeT;dj-KFQfUwXfWVlAYj+J zOze8Piy8)+abLAH*5lQM%Jznan{d5(MSgh+Zy#-ET>~k&|KHM^h&ER|ewNoYs;tB{ zJYivip{TU&moq_qHu_!V4f3wP-#09FCefErySI?LzKLJx7j>HoGuJKqXcww)rbDA- zixzZ$v2bjX#%`o9@8M<>m1P|D%V>ev^o2N3@B`XW+T@l3eFCQw*`ABzwmvz3Kyw!1 zww0kgc|!xzl#npxWR$n8_g$fp9+e~egl%DNEKL&Sm%z{v(vo&L2Pgm=P{ z*EqZ^$7>x*|0$_`=yX*+ebfwXHN`l3WqS;NJ4>(h4&nz;Lq z7ceHsCFng4xS=d+4sog0pa2R1Faa)d)qCkHw2CEA4sJ*0Y@-I0w4}?|8Fn^ z>9$MCs53sOrR|OKinca-!d~>dDJOM{iAp&A4Ado!HK25-4AQPy*Ey2#|kO#lqmBy8Htw{OA#L^)H*5IZP z9}v8ID@iUV^Tj{}r4wu~+Pa6341?iYnlbapQm(HHzhKCoTT+xeJ4JtM%xzjTa0KnRkReS;p^I)$sV`e-PkvvzVBLG~@n%gehqFSb|N^ofU_)xBlGX zk2rtH4e)BgFk^kCDs0#@d4R`C{(sRbO31 zq%7HZ&*Z;HOD;$S9pN}sqpVhb$VHFWlsLQQEskE}i3DOXD}&I4*4ZvgW6>69KnCOeh6a)l-S?v7tIenp+#j}ub69U1;#o;66%K=)sF+IT)89< z(OD<2`vA3R?9dB9hc{%&3TQ&sx9ck}>8PqYn!R6MYR{G9qzWYed4GhzpA*XK{xoAkVae6&aC3c}3ad77=84OrO_8AIuVeBKMAdZE*evJ_*-2QfW6`b-TlY zijgrkeTB4zqS>jJ>-qL(cq69&qV&J#(~a@+ii2!(zGZ6^Oh!F=(pX`Tt6aP)5SbJ? znxd6ElpaGUw@DuZb)}5+?pH4(c+B2nffhIPh|RTcwx>1bm=4`KqA0OA*h3$)n=>Js zM7>FWq~rRUt#IS3Y3nmN2keJdE?aiRQMmaTD<^l!VjKTp*6xlsZRM@mr!86z$-V1P zC6IALKuqiTRGSx()RO=;MPiK+-zrVow4L7jyC)mnR*j5}tZpdeZ%07)KR&~r3bm3R z|C>c|TGi9XB3YC}t?>a{A3n!i@#mrl7#L`O%z2ys0``p8aM2J%-6`!mupopS;4`Wq zl-YkEgqeaU*`H1@-m{23q0>QEY>PEN<2F&|o0Y@-I0wDnW*=$r@ zQY+r!c6jwf6ON=SJ`}2Ig8K94%B>cE*K)ag0v`qCRx3*3r%&KaQ_ImURJuARUqI>? z5SyeM?KjI*F9AnVApk(owLzJLIh!mPJ*$9X6}oToHv_X;c5`KG^Rccvi&ikIS@ z=jy%B+3B$GTE+gOD>n$g*pvq=Xc4{p79o3pQm(KIzhLl!!3g!}$`fda!(Om6c%WV@ zs5GBQzk9sUf{S#uI*HGlRk>?FmQ&d6G#{;(Atq9op@9p_-ac-mOf4UrHB`8hD+D13 L000000H@f%u%Q9o diff --git a/dlc-sled-storage-provider/test_files/Confirmed b/dlc-sled-storage-provider/test_files/Confirmed index e237b301e2772769d9dc055975ec7f6a27e11ba8..ba0ad56e7186e2e316a8e5bd43399f822c4fa9e0 100644 GIT binary patch literal 5983 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 CMf|G( diff --git a/dlc-sled-storage-provider/test_files/Confirmed1 b/dlc-sled-storage-provider/test_files/Confirmed1 index 8a4307d1b051392df31d310e65703375e7036262..cfd9d2bc9756dd6f21905dd2abe0d662a27bebf5 100644 GIT binary patch literal 5983 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 J{QrEW{|k5=8u0)C literal 5983 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 CT>F>+ diff --git a/dlc-sled-storage-provider/test_files/Offered b/dlc-sled-storage-provider/test_files/Offered index ec98c66281824945be24151b6a55c3d64f21a8ed..82a4b544dbecbf85a85f414220e1dc80757f8f95 100644 GIT binary patch delta 900 zcmV-~1AF|_2-654&TD90%Z}cp|JX}7Gic4o5tSeD$ljU|mpDrlc|22RksyYD2|TH# z1oVPySJem4SlhiX8{tZnSqz~_?P1f-Qc%vMN}hm)5f5*XrAIX)4J2o1vrfF@$f$O^ zZ<~H|;B#o|oa^>S#?`6z4Kj9ZU}76DE=-o^jt1i_>vJv`kyd2UaCyEU@z-7qd#tHU*fD^=vSKw3K9R)R^i>fC%cA>SO?fouA zfzu`*K#{p$i$rx*K0LsV5>lXUWQ|wLm=#$?mDDJF!@l*YA69}f(>gng$lboyqT5ee z*Ze5F*OLjIo7E0NtO1QH88!FzmUXICpu602TlgQiveX zZDM(|wID1Lc&YQzHDutXuS+;;NL8FL02TlgF^6|WojsSZIbp;wL|j}v;K+uMpAo4T$Ag5ClE000010e7JpTn1y3&`+YygS~F(jfe?M-eD;j zkns;#QlD`BjR61v0092~|NjE`S1u9(0000M02G2HxkQHky02F5(X$pbB){}@!cqXj z>k9w?000&M6dYU$K95(;9nt1M)Y_93f`sas69PvtL;@fSe|8vBj5oJwhqRXy%DPS9%ncF!%PT(Nf`VkADy$}kMJ7@p^000000ssI1{{U+M000Nm a;1;&;^Tz-H3IG5A0004CzYDLEE(9gxC6)jH delta 900 zcmV-~1AF|_2-654HjdjTCEE*inv0tPLna#08g>(Gqd{Fy2usj?p__KGksyYD=diCf zKgUn(L|{c*jgkXy9&-aiPa%z*W0IewP32dMqo9Pn-9U; zewV*5A)8>s!*0)6hkM{c168a#EJ&}VJnWhgM|rl(+Nv!~ZnZKzwt71Sn`GNRb9*6a zvCat2n++NzXrVd4h#=))gFba#q*<142}fRlD$8hDH=)kWT|AuxjHq*ejwx38w_Wu) zY;L4Ih)#~+*gxU}lW_mOyXC@{rc}+L3gzsCuWsPr^_IyCC;My3qiTQKLov8X_-49V z?-PhPjgPC?m}Je-4Yn)2FG_F^+x>w#;Hqau69iTP&V9_lp(M)zR; zU%%F^VW(_YPi;PyiwkbV7s0rVa=(K|^ARX$dPKQ(EK01**JyomFhq30G^)P=AZJu3hJ00961000000BZmM z*9cO-Eh+mlu`8c7kV`*2Y&dPJ=v0Lw7v zheyF@-`Hc9=m7u#0092~|NjE`S1u9(0000M02Ffiyh}%8cwKnqVEQ#%BFKCDie3Q1 z>k9w?000&M6oZTQ0s~3II|X4%TutOpepzsGFak#~L;@fye{ik-o`gUrZcJKwd+Poy zU=)UlOnes`25Enh-rfgR0w4#k^qJ8h!MBmk`pvNkWJg0X0$QQ_qe>S=>%aoL%ZmXa z1AYxXF7;)VPX*F5raS0N<4ym diff --git a/dlc-sled-storage-provider/test_files/OfferedChannel b/dlc-sled-storage-provider/test_files/OfferedChannel index dbbe53f90e187c86b48da234e876a40401cc1672..5edb4eb625269167f7af3508996d545bdaa35cd0 100644 GIT binary patch delta 205 zcmV;;05bpU0qX&OGtxeL*WL?#h9E_bo59G8oP^eBe7nZ#t1L;w+YhA8o&$9T9QCQa zc9LiM@7@6h*Yhy?^q1ItU$@K^0%bz=0TDJA0ScPpgt#o{#68aD5*6UNB#bjeOJQ{g_T zYf43#v47|d+06(65y7Z4Xs*Qo18jErSXDq%+hgC4{2REef|KaUpYEB diff --git a/dlc-sled-storage-provider/test_files/OfferedSubChannel b/dlc-sled-storage-provider/test_files/OfferedSubChannel new file mode 100644 index 0000000000000000000000000000000000000000..3340a20e7adfef508df8576d517bb9849583e062 GIT binary patch literal 2424 zcmdnNzOhxZi79N~(sZZNe0Duu^_(}F8a4MG#EKlM$`X0Tyzr8P>gk+qAr1BuPk3ua z%-uIXZ28w4p*j8GlV-$BeO}7I@E;5qm@a-_v5|GIV3*nR^>*>9=_&pu8-p|ar@H)~ z;&(eR^SUYn6fiPhs4mxco6#ee)YrGh$#%`Qzlv3R?Q+w8)SYeCk#f&cXO>ve)jr*t z<@w^P4)yD|&V14qExO5tS&Mbm0ys`z`qLnaBIS|MYwu^-=eN{n8Z;51%}YFlF{n4-GMKywT#^7*&TF9x&c$xWGaJl^zWj)bN1uM#BXb5~%cOxS)myj5iuCu#iBdhg-M+0O=Cb AbN~PV literal 0 HcmV?d00001 diff --git a/dlc-sled-storage-provider/test_files/OfferedSubChannel1 b/dlc-sled-storage-provider/test_files/OfferedSubChannel1 new file mode 100644 index 0000000000000000000000000000000000000000..5f82593d685aa7090fa2d01f0016b2e1e8b165ba GIT binary patch literal 2424 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 T1e4I4SWYAGzjfR?IV}Dc=@0%# literal 6984 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