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 bcb851ca..69a46fde 100644 Binary files a/dlc-sled-storage-provider/test_files/Accepted and b/dlc-sled-storage-provider/test_files/Accepted differ diff --git a/dlc-sled-storage-provider/test_files/AcceptedChannel b/dlc-sled-storage-provider/test_files/AcceptedChannel index cc924b8b..8e8bdcc5 100644 Binary files a/dlc-sled-storage-provider/test_files/AcceptedChannel and b/dlc-sled-storage-provider/test_files/AcceptedChannel differ diff --git a/dlc-sled-storage-provider/test_files/AcceptedSubChannel b/dlc-sled-storage-provider/test_files/AcceptedSubChannel new file mode 100644 index 00000000..762a3c0a Binary files /dev/null and b/dlc-sled-storage-provider/test_files/AcceptedSubChannel differ diff --git a/dlc-sled-storage-provider/test_files/Closed b/dlc-sled-storage-provider/test_files/Closed index eca0e8e6..4c59dd2e 100644 Binary files a/dlc-sled-storage-provider/test_files/Closed and b/dlc-sled-storage-provider/test_files/Closed differ diff --git a/dlc-sled-storage-provider/test_files/Confirmed b/dlc-sled-storage-provider/test_files/Confirmed index e237b301..ba0ad56e 100644 Binary files a/dlc-sled-storage-provider/test_files/Confirmed and b/dlc-sled-storage-provider/test_files/Confirmed differ diff --git a/dlc-sled-storage-provider/test_files/Confirmed1 b/dlc-sled-storage-provider/test_files/Confirmed1 index 8a4307d1..cfd9d2bc 100644 Binary files a/dlc-sled-storage-provider/test_files/Confirmed1 and b/dlc-sled-storage-provider/test_files/Confirmed1 differ diff --git a/dlc-sled-storage-provider/test_files/Offered b/dlc-sled-storage-provider/test_files/Offered index ec98c662..82a4b544 100644 Binary files a/dlc-sled-storage-provider/test_files/Offered and b/dlc-sled-storage-provider/test_files/Offered differ diff --git a/dlc-sled-storage-provider/test_files/OfferedChannel b/dlc-sled-storage-provider/test_files/OfferedChannel index dbbe53f9..5edb4eb6 100644 Binary files a/dlc-sled-storage-provider/test_files/OfferedChannel and b/dlc-sled-storage-provider/test_files/OfferedChannel differ diff --git a/dlc-sled-storage-provider/test_files/OfferedSubChannel b/dlc-sled-storage-provider/test_files/OfferedSubChannel new file mode 100644 index 00000000..3340a20e Binary files /dev/null and b/dlc-sled-storage-provider/test_files/OfferedSubChannel differ diff --git a/dlc-sled-storage-provider/test_files/OfferedSubChannel1 b/dlc-sled-storage-provider/test_files/OfferedSubChannel1 new file mode 100644 index 00000000..5f82593d Binary files /dev/null and b/dlc-sled-storage-provider/test_files/OfferedSubChannel1 differ diff --git a/dlc-sled-storage-provider/test_files/PreClosed b/dlc-sled-storage-provider/test_files/PreClosed index b3442e9f..8a37c3ff 100644 Binary files a/dlc-sled-storage-provider/test_files/PreClosed and b/dlc-sled-storage-provider/test_files/PreClosed differ diff --git a/dlc-sled-storage-provider/test_files/Signed b/dlc-sled-storage-provider/test_files/Signed index 273dfad2..67f8ceeb 100644 Binary files a/dlc-sled-storage-provider/test_files/Signed and b/dlc-sled-storage-provider/test_files/Signed differ diff --git a/dlc-sled-storage-provider/test_files/Signed1 b/dlc-sled-storage-provider/test_files/Signed1 index bfc34a1d..159cf42f 100644 Binary files a/dlc-sled-storage-provider/test_files/Signed1 and b/dlc-sled-storage-provider/test_files/Signed1 differ diff --git a/dlc-sled-storage-provider/test_files/SignedChannelEstablished b/dlc-sled-storage-provider/test_files/SignedChannelEstablished index 8c7b2348..88099cdb 100644 Binary files a/dlc-sled-storage-provider/test_files/SignedChannelEstablished and b/dlc-sled-storage-provider/test_files/SignedChannelEstablished differ diff --git a/dlc-sled-storage-provider/test_files/SignedChannelSettled b/dlc-sled-storage-provider/test_files/SignedChannelSettled index 5449383b..c15f3019 100644 Binary files a/dlc-sled-storage-provider/test_files/SignedChannelSettled and b/dlc-sled-storage-provider/test_files/SignedChannelSettled differ diff --git a/dlc-sled-storage-provider/test_files/SignedSubChannel b/dlc-sled-storage-provider/test_files/SignedSubChannel new file mode 100644 index 00000000..86800faa Binary files /dev/null and b/dlc-sled-storage-provider/test_files/SignedSubChannel differ diff --git a/dlc/src/channel/mod.rs b/dlc/src/channel/mod.rs index 359830a1..31261ca0 100644 --- a/dlc/src/channel/mod.rs +++ b/dlc/src/channel/mod.rs @@ -11,8 +11,8 @@ use bitcoin::{ }; use miniscript::Descriptor; use secp256k1_zkp::{ - schnorr::Signature as SchnorrSignature, EcdsaAdaptorSignature, PublicKey as SecpPublicKey, - Secp256k1, SecretKey, Signing, Verification, + ecdsa::Signature, schnorr::Signature as SchnorrSignature, EcdsaAdaptorSignature, + PublicKey as SecpPublicKey, Secp256k1, SecretKey, Signing, Verification, }; pub mod sub_channel; @@ -26,14 +26,14 @@ pub mod sub_channel; * scriptSig -> 0 * nSequence -> 4 * 4 * Witness item count -> 1 - * Witness -> 220 + * Witness -> 366 * OUTPUT: * nValue -> 8 * 4 * scriptPubkeyLen -> 1 * 4 * scriptPubkey -> 34 * 4 * TOTAL: 599 */ -const BUFFER_TX_WEIGHT: usize = 599; +pub const BUFFER_TX_WEIGHT: usize = 748; /** * Due to the buffer output script being more complex than the funding output @@ -49,7 +49,7 @@ const BUFFER_TX_WEIGHT: usize = 599; * Regular CET witness size: 220 * Extra size => 330 */ -const CET_EXTRA_WEIGHT: usize = 330; +pub const CET_EXTRA_WEIGHT: usize = 330; // Same as buffer input const SETTLE_INPUT_WEIGHT: usize = 428; @@ -165,7 +165,7 @@ pub fn verify_tx_adaptor_signature( /// Returns a settle transaction. pub fn create_settle_transaction( - fund_tx_in: &TxIn, + prev_outpoint: &OutPoint, offer_revoke_params: &RevokeParams, accept_revoke_params: &RevokeParams, offer_payout: u64, @@ -206,7 +206,7 @@ pub fn create_settle_transaction( - offer_payout - accept_payout - crate::util::weight_to_fee( - SETTLE_INPUT_WEIGHT + output.len() * SETTLE_OUTPUT_WEIGHT, + SETTLE_INPUT_WEIGHT + output.len() * SETTLE_OUTPUT_WEIGHT + 148, fee_rate_per_vb, )?) / (output.len() as u64); @@ -215,10 +215,17 @@ pub fn create_settle_transaction( o.value += remaining_fee; } + let input = TxIn { + previous_output: *prev_outpoint, + script_sig: Script::default(), + sequence: crate::util::get_sequence(lock_time), + witness: Witness::default(), + }; + Ok(Transaction { version: super::TX_VERSION, lock_time: PackedLockTime(lock_time), - input: vec![fund_tx_in.clone()], + input: vec![input], output, }) } @@ -260,6 +267,8 @@ pub fn create_channel_transactions( fee_rate_per_vb, cet_lock_time, cet_nsequence, + None, + None, ) } @@ -277,13 +286,24 @@ pub fn create_renewal_channel_transactions( fee_rate_per_vb: u64, cet_lock_time: u32, cet_nsequence: Sequence, + fund_vout: Option, + buffer_nsequence: Option, ) -> Result { let extra_fee = super::util::weight_to_fee(BUFFER_TX_WEIGHT + CET_EXTRA_WEIGHT, fee_rate_per_vb)?; - let (fund_vout, fund_output) = - super::util::get_output_for_script_pubkey(fund_tx, &funding_script_pubkey.to_v0_p2wsh()) - .expect("to find the funding script pubkey"); + let (fund_vout, fund_output) = { + if let Some(fund_vout) = fund_vout { + (fund_vout, &fund_tx.output[fund_vout]) + } else { + super::util::get_output_for_script_pubkey(fund_tx, &funding_script_pubkey.to_v0_p2wsh()) + .expect("to find the funding script pubkey") + } + }; + + if fund_output.value <= extra_fee + super::DUST_LIMIT { + return Err(Error::InvalidArgument); + } let outpoint = OutPoint { txid: fund_tx.txid(), @@ -292,7 +312,7 @@ pub fn create_renewal_channel_transactions( let tx_in = TxIn { previous_output: outpoint, - sequence: super::util::get_sequence(cet_lock_time), + sequence: buffer_nsequence.unwrap_or_else(|| crate::util::get_sequence(cet_lock_time)), script_sig: Script::default(), witness: Witness::default(), }; @@ -377,6 +397,36 @@ pub fn sign_cet( Ok(()) } +/// Use the given parameters to build the descriptor of the given buffer transaction and inserts +/// the signatures in the transaction witness. +pub fn satisfy_buffer_descriptor( + tx: &mut Transaction, + offer_params: &RevokeParams, + accept_params: &RevokeParams, + own_pubkey: &SecpPublicKey, + own_signature: &Signature, + counter_pubkey: &PublicKey, + counter_signature: &Signature, +) -> Result<(), Error> { + let descriptor = buffer_descriptor(offer_params, accept_params); + let sigs = HashMap::from([ + ( + PublicKey { + inner: *own_pubkey, + compressed: true, + }, + EcdsaSig::sighash_all(*own_signature), + ), + (*counter_pubkey, EcdsaSig::sighash_all(*counter_signature)), + ]); + + descriptor + .satisfy(&mut tx.input[0], sigs) + .map_err(|_| Error::InvalidArgument)?; + + Ok(()) +} + /// Returns a signed transaction to punish the publication of a revoked buffer /// transaction. pub fn create_and_sign_punish_buffer_transaction( @@ -595,15 +645,7 @@ pub fn buffer_descriptor( }; // heavily inspired by: https://github.com/comit-network/maia/blob/main/src/protocol.rs#L283 // policy: or(and(pk(offer_pk),pk(accept_pk)),or(and(pk(offer_pk),and(pk(accept_publish_pk), pk(accept_rev_pk))),and(pk(accept_pk),and(pk(offer_publish_pk),pk(offer_rev_pk))))) - let script = format!("wsh(c:andor(pk({first_pk}),pk_k({second_pk}),or_i(and_v(v:pkh({offer_pk_hash}),and_v(v:pkh({accept_publish_pk_hash}),pk_h({accept_revoke_pk_hash}))),and_v(v:pkh({accept_pk_hash}),and_v(v:pkh({offer_publish_pk_hash}),pk_h({offer_revoke_pk_hash}))))))", - first_pk = first_pk, - second_pk = second_pk, - offer_pk_hash = offer_pk, - accept_pk_hash = accept_pk, - accept_publish_pk_hash = accept_publish_pk, - accept_revoke_pk_hash = accept_revoke_pk, - offer_publish_pk_hash = offer_publish_pk, - offer_revoke_pk_hash = offer_revoke_pk); + let script = format!("wsh(c:andor(pk({first_pk}),pk_k({second_pk}),or_i(and_v(v:pkh({offer_pk}),and_v(v:pkh({accept_publish_pk}),pk_h({accept_revoke_pk}))),and_v(v:pkh({accept_pk}),and_v(v:pkh({offer_publish_pk}),pk_h({offer_revoke_pk}))))))"); script.parse().expect("a valid miniscript") } @@ -797,7 +839,7 @@ mod tests { let payout = 100000000; let csv_timelock = 100; let settle_tx = create_settle_transaction( - &TxIn::default(), + &OutPoint::default(), &offer_params, &accept_params, payout, diff --git a/dlc/src/channel/sub_channel.rs b/dlc/src/channel/sub_channel.rs index 17f1ee33..b46ed149 100644 --- a/dlc/src/channel/sub_channel.rs +++ b/dlc/src/channel/sub_channel.rs @@ -59,9 +59,9 @@ pub fn dlc_channel_and_split_fee(fee_rate_per_vb: u64) -> Result { #[derive(Clone, Debug)] /// Structure containing a split transaction and its associated output script. pub struct SplitTx { - /// + /// The actual Bitcoin transaction representation. pub transaction: Transaction, - /// + /// The script used to lock the outputs. pub output_script: Script, } diff --git a/dlc/src/lib.rs b/dlc/src/lib.rs index bf42c324..1ce35af1 100644 --- a/dlc/src/lib.rs +++ b/dlc/src/lib.rs @@ -45,7 +45,7 @@ pub mod util; /// Minimum value that can be included in a transaction output. Under this value, /// outputs are discarded /// See: https://github.com/discreetlogcontracts/dlcspecs/blob/master/Transactions.md#change-outputs -const DUST_LIMIT: u64 = 1000; +pub const DUST_LIMIT: u64 = 1000; /// The transaction version /// See: https://github.com/discreetlogcontracts/dlcspecs/blob/master/Transactions.md#funding-transaction diff --git a/dlc/src/util.rs b/dlc/src/util.rs index f15e70a4..a07ed9eb 100644 --- a/dlc/src/util.rs +++ b/dlc/src/util.rs @@ -17,6 +17,8 @@ pub(crate) const DISABLE_LOCKTIME: Sequence = Sequence(0xffffffff); // RBF but enables nLockTime usage. pub(crate) const ENABLE_LOCKTIME: Sequence = Sequence(0xfffffffe); +const MIN_FEE: u64 = 153; + /// Get a BIP143 (https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki) /// signature hash with sighash all flag for a segwit transaction input as /// a Message instance @@ -94,10 +96,15 @@ pub fn get_sig_for_p2wpkh_input( ) } -pub(crate) fn weight_to_fee(weight: usize, fee_rate: u64) -> Result { - (f64::ceil((weight as f64) / 4.0) as u64) - .checked_mul(fee_rate) - .ok_or(Error::InvalidArgument) +/// Computes the required fee for a transaction based on the given weight and fee +/// rate per vbyte. +pub fn weight_to_fee(weight: usize, fee_rate: u64) -> Result { + Ok(u64::max( + (f64::ceil((weight as f64) / 4.0) as u64) + .checked_mul(fee_rate) + .ok_or(Error::InvalidArgument)?, + MIN_FEE, + )) } fn get_pkh_script_pubkey_from_sk(secp: &Secp256k1, sk: &SecretKey) -> Script { diff --git a/electrs-blockchain-provider/Cargo.toml b/electrs-blockchain-provider/Cargo.toml index 84d5c069..a4979079 100644 --- a/electrs-blockchain-provider/Cargo.toml +++ b/electrs-blockchain-provider/Cargo.toml @@ -9,7 +9,7 @@ version = "0.1.0" bitcoin = {version = "0.29"} bitcoin-test-utils = {path = "../bitcoin-test-utils"} dlc-manager = {path = "../dlc-manager"} -lightning = {version = "0.0.113"} +lightning = {version = "0.0.113", git = "https://github.com/p2pderivatives/rust-lightning/", branch = "split-tx-experiment"} lightning-block-sync = {version = "0.0.113"} reqwest = {version = "0.11", features = ["blocking", "json"]} rust-bitcoin-coin-selection = {version = "0.1.0", git = "https://github.com/p2pderivatives/rust-bitcoin-coin-selection", features = ["rand"]} diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 94f20f0c..69f3d294 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -10,7 +10,7 @@ cargo-fuzz = true [dependencies] dlc-messages = {path = "../dlc-messages"} honggfuzz = "0.5" -lightning = {version = "0.0.113" } +lightning = {version = "0.0.113", git = "https://github.com/p2pderivatives/rust-lightning/", branch = "split-tx-experiment"} [workspace] members = ["."] diff --git a/mocks/Cargo.toml b/mocks/Cargo.toml index 723baaf1..8a1b6c29 100644 --- a/mocks/Cargo.toml +++ b/mocks/Cargo.toml @@ -9,6 +9,6 @@ bitcoin = "0.29" dlc = {path = "../dlc"} dlc-manager = {path = "../dlc-manager"} dlc-messages = {path = "../dlc-messages"} -lightning = {version = "0.0.113"} +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"]} simple-wallet = {path = "../simple-wallet"} diff --git a/mocks/src/memory_storage_provider.rs b/mocks/src/memory_storage_provider.rs index ec75fa7a..0163c3f0 100644 --- a/mocks/src/memory_storage_provider.rs +++ b/mocks/src/memory_storage_provider.rs @@ -8,6 +8,7 @@ use dlc_manager::channel::{ use dlc_manager::contract::{ offered_contract::OfferedContract, signed_contract::SignedContract, Contract, PreClosedContract, }; +use dlc_manager::subchannel::{SubChannel, SubChannelState}; use dlc_manager::Storage; use dlc_manager::{error::Error as DaemonError, ChannelId, ContractId, Utxo}; use secp256k1_zkp::{PublicKey, SecretKey}; @@ -18,8 +19,10 @@ use std::sync::{Mutex, RwLock}; pub struct MemoryStorage { contracts: RwLock>, channels: RwLock>, + sub_channels: RwLock>, contracts_saved: Mutex>>, channels_saved: Mutex>>, + sub_channels_saved: Mutex>>, addresses: RwLock>, utxos: RwLock>, key_pairs: RwLock>, @@ -30,8 +33,10 @@ impl MemoryStorage { MemoryStorage { contracts: RwLock::new(HashMap::new()), channels: RwLock::new(HashMap::new()), + sub_channels: RwLock::new(HashMap::new()), contracts_saved: Mutex::new(None), channels_saved: Mutex::new(None), + sub_channels_saved: Mutex::new(None), addresses: RwLock::new(HashMap::new()), utxos: RwLock::new(HashMap::new()), key_pairs: RwLock::new(HashMap::new()), @@ -54,6 +59,14 @@ impl MemoryStorage { .expect("Could not get read lock") .clone(), ); + + let mut sub_channels_saved = self.sub_channels_saved.lock().unwrap(); + *sub_channels_saved = Some( + self.sub_channels + .read() + .expect("Could not get read lock") + .clone(), + ); } pub fn rollback(&self) { @@ -68,6 +81,12 @@ impl MemoryStorage { let mut tmp = None; std::mem::swap(&mut tmp, &mut *channels_saved); std::mem::swap(&mut *channels, &mut tmp.unwrap()); + + let mut sub_channels = self.sub_channels.write().unwrap(); + let mut sub_channels_saved = self.sub_channels_saved.lock().unwrap(); + let mut tmp = None; + std::mem::swap(&mut tmp, &mut *sub_channels_saved); + std::mem::swap(&mut *sub_channels, &mut tmp.unwrap()); } } @@ -254,6 +273,44 @@ impl Storage for MemoryStorage { fn get_chain_monitor(&self) -> Result, DaemonError> { Ok(None) } + + fn upsert_sub_channel(&self, subchannel: &SubChannel) -> Result<(), DaemonError> { + let mut map = self.sub_channels.write().expect("Could not get write lock"); + map.insert(subchannel.channel_id, subchannel.clone()); + Ok(()) + } + + fn get_sub_channel( + &self, + channel_id: dlc_manager::ChannelId, + ) -> Result, DaemonError> { + let map = self.sub_channels.read().expect("could not get read lock"); + Ok(map.get(&channel_id).cloned()) + } + + fn get_sub_channels(&self) -> Result, DaemonError> { + Ok(self + .sub_channels + .read() + .expect("Could not get read lock") + .values() + .cloned() + .collect()) + } + + fn get_offered_sub_channels(&self) -> Result, DaemonError> { + let map = self.sub_channels.read().expect("Could not get read lock"); + + let mut res: Vec = Vec::new(); + + for (_, val) in map.iter() { + if let SubChannelState::Offered(_) = &val.state { + res.push(val.clone()) + } + } + + Ok(res) + } } impl WalletStorage for MemoryStorage { diff --git a/mocks/src/mock_blockchain.rs b/mocks/src/mock_blockchain.rs index 682d2fd6..107501bb 100644 --- a/mocks/src/mock_blockchain.rs +++ b/mocks/src/mock_blockchain.rs @@ -1,42 +1,77 @@ -use bitcoin::{Block, Transaction, Txid}; -use dlc_manager::{error::Error, Blockchain, Utxo}; -use lightning::chain::chaininterface::FeeEstimator; +use std::{ops::Deref, sync::Mutex}; + +use bitcoin::{Transaction, Txid}; +use dlc_manager::error::Error; +use lightning::chain::chaininterface::BroadcasterInterface; use simple_wallet::WalletBlockchainProvider; -pub struct MockBlockchain {} +pub struct MockBlockchain +where + T::Target: BroadcasterInterface, +{ + inner: T, + discard: Mutex, +} -impl Blockchain for MockBlockchain { - fn send_transaction(&self, _transaction: &Transaction) -> Result<(), Error> { - Ok(()) - } - fn get_network(&self) -> Result { - Ok(bitcoin::Network::Regtest) +impl MockBlockchain +where + T::Target: BroadcasterInterface, +{ + pub fn new(inner: T) -> Self { + Self { + inner, + discard: Mutex::new(false), + } } - fn get_blockchain_height(&self) -> Result { - Ok(10) + + pub fn start_discard(&self) { + *self.discard.lock().unwrap() = true; } - fn get_block_at_height(&self, _height: u64) -> Result { - unimplemented!(); +} + +impl BroadcasterInterface for MockBlockchain +where + T::Target: BroadcasterInterface, +{ + fn broadcast_transaction(&self, tx: &bitcoin::Transaction) { + if !*self.discard.lock().unwrap() { + self.inner.broadcast_transaction(tx); + } } - fn get_transaction(&self, _tx_id: &Txid) -> Result { +} + +pub struct MockBroadcaster {} + +impl BroadcasterInterface for MockBroadcaster { + fn broadcast_transaction(&self, _tx: &bitcoin::Transaction) { unimplemented!(); } - fn get_transaction_confirmations(&self, _tx_id: &Txid) -> Result { - Ok(6) - } } -impl WalletBlockchainProvider for MockBlockchain { - fn get_utxos_for_address(&self, _address: &bitcoin::Address) -> Result, Error> { +impl WalletBlockchainProvider for MockBlockchain +where + T::Target: BroadcasterInterface, +{ + fn get_utxos_for_address( + &self, + _address: &bitcoin::Address, + ) -> Result, dlc_manager::error::Error> { unimplemented!() } - fn is_output_spent(&self, _txid: &Txid, _vout: u32) -> Result { + fn is_output_spent( + &self, + _txid: &bitcoin::Txid, + _vout: u32, + ) -> Result { unimplemented!() } } -impl FeeEstimator for MockBlockchain { +impl lightning::chain::chaininterface::FeeEstimator for MockBlockchain +where + T::Target: BroadcasterInterface, +{ fn get_est_sat_per_1000_weight( &self, _confirmation_target: lightning::chain::chaininterface::ConfirmationTarget, @@ -44,3 +79,33 @@ impl FeeEstimator for MockBlockchain { unimplemented!() } } + +impl dlc_manager::Blockchain for MockBlockchain +where + T::Target: BroadcasterInterface, +{ + fn send_transaction( + &self, + _transaction: &Transaction, + ) -> Result<(), dlc_manager::error::Error> { + Ok(()) + } + fn get_network( + &self, + ) -> Result { + Ok(bitcoin::Network::Regtest) + } + fn get_blockchain_height(&self) -> Result { + Ok(10) + } + fn get_block_at_height(&self, _height: u64) -> Result { + unimplemented!(); + } + fn get_transaction(&self, _tx_id: &Txid) -> Result { + unimplemented!(); + } + fn get_transaction_confirmations(&self, _tx_id: &Txid) -> Result { + Ok(6) + } +} + diff --git a/sample/Cargo.toml b/sample/Cargo.toml index 3a4a906a..34c868fc 100644 --- a/sample/Cargo.toml +++ b/sample/Cargo.toml @@ -12,8 +12,8 @@ dlc-manager = {path = "../dlc-manager", features = ["use-serde", "parallel"]} dlc-messages = {path = "../dlc-messages"} dlc-sled-storage-provider = {path = "../dlc-sled-storage-provider"} futures = "0.3" -lightning = {version = "0.0.113"} -lightning-net-tokio = {version = "0.0.113" } +lightning = {version = "0.0.113", git = "https://github.com/p2pderivatives/rust-lightning/", branch = "split-tx-experiment"} +lightning-net-tokio = {version = "0.0.113", git = "https://github.com/p2pderivatives/rust-lightning/", branch = "split-tx-experiment"} p2pd-oracle-client = {path = "../p2pd-oracle-client"} serde = "1.0" serde_json = "1.0" diff --git a/scripts/generate_serialized_contract_files.sh b/scripts/generate_serialized_contract_files.sh index ac0b6353..821f0021 100755 --- a/scripts/generate_serialized_contract_files.sh +++ b/scripts/generate_serialized_contract_files.sh @@ -3,7 +3,6 @@ set -e CONTRACT_TEST_FILES=("Offered" "Accepted" "Confirmed" "Confirmed1" "Signed" "Signed1" "PreClosed" "Closed") - DEST=${PWD}/dlc-sled-storage-provider/test_files/ docker-compose up -d @@ -23,13 +22,21 @@ do cp ${PWD}/dlc-manager/${FILE//1/} ${DEST}${FILE} done -TEST_FILES=( "${CONTRACT_TEST_FILES[@]}" "${CHANNEL_TEST_FILES[@]}" ) +SUB_CHANNEL_TEST_FILES=("OfferedSubChannel" "OfferedSubChannel1" "AcceptedSubChannel" "SignedSubChannel") + +for FILE in ${SUB_CHANNEL_TEST_FILES[@]} +do + GENERATE_SERIALIZED_SUB_CHANNEL=1 cargo test -- ln_dlc_established_close --ignored --exact + cp ${PWD}/dlc-manager/${FILE//1/} ${DEST}${FILE} +done + +TEST_FILES=( "${CONTRACT_TEST_FILES[@]}" "${CHANNEL_TEST_FILES[@]}" "${SUB_CHANNEL_TEST_FILES[@]}" ) for FILE in ${TEST_FILES[@]} do rm -f ${PWD}/dlc-manager/${FILE} done -rm ${PWD}/dlc-manager/Signed* +rm -f ${PWD}/dlc-manager/Signed* docker-compose down -v diff --git a/simple-wallet/Cargo.toml b/simple-wallet/Cargo.toml index a582b733..4f214375 100644 --- a/simple-wallet/Cargo.toml +++ b/simple-wallet/Cargo.toml @@ -9,7 +9,7 @@ version = "0.1.0" bitcoin = "0.29" dlc = {path = "../dlc"} 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"} rust-bitcoin-coin-selection = {version = "0.1.0", git = "https://github.com/p2pderivatives/rust-bitcoin-coin-selection", features = ["rand"]} secp256k1-zkp = {version = "0.7.0"} diff --git a/simple-wallet/src/lib.rs b/simple-wallet/src/lib.rs index a6a679ea..479c404e 100644 --- a/simple-wallet/src/lib.rs +++ b/simple-wallet/src/lib.rs @@ -58,9 +58,8 @@ where } } - /// Refresh the wallet checking and updating the UTXO states. pub fn refresh(&self) -> Result<()> { - let utxos: Vec = self.storage.get_utxos()?; + let utxos = self.storage.get_utxos()?; for utxo in &utxos { let is_spent = self @@ -269,14 +268,21 @@ mod tests { use dlc_manager::{Signer, Wallet}; use mocks::simple_wallet::SimpleWallet; - use mocks::{memory_storage_provider::MemoryStorage, mock_blockchain::MockBlockchain}; + use mocks::{ + memory_storage_provider::MemoryStorage, + mock_blockchain::{MockBlockchain, MockBroadcaster}, + }; use secp256k1_zkp::{PublicKey, SECP256K1}; - fn get_wallet() -> SimpleWallet, Rc> { - let blockchain = Rc::new(MockBlockchain {}); + fn get_wallet() -> mocks::simple_wallet::SimpleWallet< + Rc>>, + Rc, + > { + let broadcaster = Rc::new(MockBroadcaster {}); + let blockchain = Rc::new(MockBlockchain::new(broadcaster)); let storage = Rc::new(MemoryStorage::new()); - let wallet = SimpleWallet::new(blockchain, storage, bitcoin::Network::Regtest); - wallet + + SimpleWallet::new(blockchain, storage, bitcoin::Network::Regtest) } #[test]