diff --git a/dlc-manager/benches/benchmarks.rs b/dlc-manager/benches/benchmarks.rs index bfd6641d..5426f8d4 100644 --- a/dlc-manager/benches/benchmarks.rs +++ b/dlc-manager/benches/benchmarks.rs @@ -203,7 +203,7 @@ fn create_transactions(payouts: &[Payout]) -> DlcTransactions { input_amount: 300000000, collateral: 100000000, }; - create_dlc_transactions(&offer_params, &accept_params, payouts, 1000, 2, 0, 1000, 3).unwrap() + create_dlc_transactions(&offer_params, &accept_params, payouts, 1000, 2, 0, 1000, 3, dlc::FeeConfig::EvenSplit).unwrap() } fn accept_seckey() -> SecretKey { diff --git a/dlc-manager/src/channel_updater.rs b/dlc-manager/src/channel_updater.rs index f906e512..9d894945 100644 --- a/dlc-manager/src/channel_updater.rs +++ b/dlc-manager/src/channel_updater.rs @@ -309,6 +309,7 @@ where offered_contract.cet_locktime, offered_contract.fund_output_serial_id, Sequence(offered_channel.cet_nsequence), + fee_config, )?; 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; @@ -393,6 +394,7 @@ pub fn verify_and_sign_accepted_channel( cet_nsequence: u32, signer: &S, chain_monitor: &Mutex, + fee_config: FeeConfig, ) -> Result<(SignedChannel, SignedContract, SignChannel), Error> where S::Target: Signer, @@ -406,6 +408,7 @@ where signer, None, chain_monitor, + fee_config, ) } @@ -418,6 +421,7 @@ pub(crate) fn verify_and_sign_accepted_channel_internal( signer: &S, sub_channel_info: Option, chain_monitor: &Mutex, + fee_config: FeeConfig, ) -> Result<(SignedChannel, SignedContract, SignChannel), Error> where S::Target: Signer, @@ -521,6 +525,7 @@ where offered_contract.cet_locktime, offered_contract.fund_output_serial_id, Sequence(cet_nsequence), + fee_config, )?; let offer_fund_sk = signer.get_secret_key_for_pubkey(&offered_contract.offer_params.fund_pubkey)?; diff --git a/dlc-manager/src/contract_updater.rs b/dlc-manager/src/contract_updater.rs index 008b81fd..01dd6c8a 100644 --- a/dlc-manager/src/contract_updater.rs +++ b/dlc-manager/src/contract_updater.rs @@ -106,6 +106,7 @@ where 0, offered_contract.cet_locktime, offered_contract.fund_output_serial_id, + FeeConfig::EvenSplit, )?; let fund_output_value = dlc_transactions.get_fund_output().value; @@ -265,6 +266,7 @@ where 0, offered_contract.cet_locktime, offered_contract.fund_output_serial_id, + FeeConfig::EvenSplit, )?; let fund_output_value = dlc_transactions.get_fund_output().value; let fund_privkey = diff --git a/dlc-manager/src/manager.rs b/dlc-manager/src/manager.rs index 57feb994..d77742dd 100644 --- a/dlc-manager/src/manager.rs +++ b/dlc-manager/src/manager.rs @@ -23,6 +23,7 @@ use bitcoin::consensus::encode::serialize_hex; use bitcoin::{Address, OutPoint, Txid}; use bitcoin::Transaction; use bitcoin::hashes::hex::ToHex; +use dlc::FeeConfig; use dlc_messages::channel::{ AcceptChannel, CollaborativeCloseOffer, OfferChannel, Reject, RenewAccept, RenewConfirm, RenewFinalize, RenewOffer, RenewRevoke, SettleAccept, SettleConfirm, SettleFinalize, @@ -237,6 +238,7 @@ where &self, msg: &DlcMessage, counter_party: PublicKey, + fee_config: FeeConfig, ) -> Result, Error> { match msg { DlcMessage::OnChain(on_chain) => match on_chain { @@ -256,7 +258,7 @@ where Ok(None) } ChannelMessage::Accept(a) => Ok(Some(DlcMessage::Channel(ChannelMessage::Sign( - self.on_accept_channel(a, &counter_party)?, + self.on_accept_channel(a, &counter_party, fee_config)?, )))), ChannelMessage::Sign(s) => { self.on_sign_channel(s, &counter_party)?; @@ -1438,6 +1440,7 @@ where &self, accept_channel: &AcceptChannel, peer_id: &PublicKey, + fee_config: dlc::FeeConfig, ) -> Result { let offered_channel = get_channel_in_state!( self, @@ -1462,6 +1465,7 @@ where CET_NSEQUENCE, &self.wallet, &self.chain_monitor, + fee_config, ); match res { @@ -2938,11 +2942,11 @@ mod test { let manager = get_manager(); manager - .on_dlc_message(&offer_message, pubkey()) + .on_dlc_message(&offer_message, pubkey(), dlc::FeeConfig::EvenSplit) .expect("To accept the first offer message"); manager - .on_dlc_message(&offer_message, pubkey()) + .on_dlc_message(&offer_message, pubkey(), dlc::FeeConfig::EvenSplit) .expect_err("To reject the second offer message"); } @@ -2955,11 +2959,11 @@ mod test { let manager = get_manager(); manager - .on_dlc_message(&offer_message, pubkey()) + .on_dlc_message(&offer_message, pubkey(), dlc::FeeConfig::EvenSplit) .expect("To accept the first offer message"); manager - .on_dlc_message(&offer_message, pubkey()) + .on_dlc_message(&offer_message, pubkey(), dlc::FeeConfig::EvenSplit) .expect_err("To reject the second offer message"); } } diff --git a/dlc-manager/src/sub_channel_manager.rs b/dlc-manager/src/sub_channel_manager.rs index ac495dad..67006061 100644 --- a/dlc-manager/src/sub_channel_manager.rs +++ b/dlc-manager/src/sub_channel_manager.rs @@ -1920,6 +1920,7 @@ where self.dlc_channel_manager.get_wallet(), Some(sub_channel_info), self.dlc_channel_manager.get_chain_monitor(), + FeeConfig::EvenSplit, )?; dlc::verify_tx_input_sig( diff --git a/dlc-manager/tests/channel_execution_tests.rs b/dlc-manager/tests/channel_execution_tests.rs index 3ee6f1be..f83e5063 100644 --- a/dlc-manager/tests/channel_execution_tests.rs +++ b/dlc-manager/tests/channel_execution_tests.rs @@ -4,8 +4,9 @@ mod test_utils; use bitcoin::Amount; use bitcoin_test_utils::rpc_helpers::init_clients; use bitcoincore_rpc::RpcApi; +use dlc::FeeConfig; use dlc_manager::contract::contract_input::ContractInput; -use dlc_manager::manager::{FeeConfig, Manager}; +use dlc_manager::manager::Manager; use dlc_manager::{channel::Channel, contract::Contract, Blockchain, Oracle, Storage, Wallet}; use dlc_manager::{ContractId, DlcChannelId}; use dlc_messages::{ChannelMessage, Message}; diff --git a/dlc-manager/tests/ln_dlc_channel_execution_tests.rs b/dlc-manager/tests/ln_dlc_channel_execution_tests.rs index 6ba8c9c0..70900217 100644 --- a/dlc-manager/tests/ln_dlc_channel_execution_tests.rs +++ b/dlc-manager/tests/ln_dlc_channel_execution_tests.rs @@ -2233,6 +2233,7 @@ fn settle(test_params: &LnDlcTestParams, channel_id: &DlcChannelId) { .on_dlc_message( &Message::Channel(ChannelMessage::SettleOffer(settle_offer)), test_params.alice_node.channel_manager.get_our_node_id(), + dlc::FeeConfig::EvenSplit, ) .unwrap(); @@ -2248,6 +2249,7 @@ fn settle(test_params: &LnDlcTestParams, channel_id: &DlcChannelId) { .on_dlc_message( &Message::Channel(ChannelMessage::SettleAccept(settle_accept)), bob_key, + dlc::FeeConfig::EvenSplit, ) .unwrap() .unwrap(); @@ -2255,14 +2257,14 @@ fn settle(test_params: &LnDlcTestParams, channel_id: &DlcChannelId) { let msg = test_params .bob_node .dlc_manager - .on_dlc_message(&msg, alice_key) + .on_dlc_message(&msg, alice_key, dlc::FeeConfig::EvenSplit) .unwrap() .unwrap(); test_params .alice_node .dlc_manager - .on_dlc_message(&msg, bob_key) + .on_dlc_message(&msg, bob_key, dlc::FeeConfig::EvenSplit) .unwrap(); } @@ -2284,6 +2286,7 @@ fn renew(test_params: &LnDlcTestParams, dlc_channel_id: &DlcChannelId) { .on_dlc_message( &Message::Channel(ChannelMessage::RenewOffer(renew_offer)), test_params.alice_node.channel_manager.get_our_node_id(), + dlc::FeeConfig::EvenSplit, ) .unwrap(); @@ -2299,6 +2302,7 @@ fn renew(test_params: &LnDlcTestParams, dlc_channel_id: &DlcChannelId) { .on_dlc_message( &Message::Channel(ChannelMessage::RenewAccept(accept)), test_params.bob_node_id, + dlc::FeeConfig::EvenSplit, ) .unwrap() .unwrap(); @@ -2306,21 +2310,21 @@ fn renew(test_params: &LnDlcTestParams, dlc_channel_id: &DlcChannelId) { let msg = test_params .bob_node .dlc_manager - .on_dlc_message(&msg, test_params.alice_node_id) + .on_dlc_message(&msg, test_params.alice_node_id, dlc::FeeConfig::EvenSplit) .unwrap() .unwrap(); let msg = test_params .alice_node .dlc_manager - .on_dlc_message(&msg, test_params.bob_node_id) + .on_dlc_message(&msg, test_params.bob_node_id, dlc::FeeConfig::EvenSplit) .unwrap() .unwrap(); test_params .bob_node .dlc_manager - .on_dlc_message(&msg, test_params.alice_node_id) + .on_dlc_message(&msg, test_params.alice_node_id, dlc::FeeConfig::EvenSplit) .unwrap(); } diff --git a/dlc-manager/tests/test_utils.rs b/dlc-manager/tests/test_utils.rs index ae0f6b66..4c8c9588 100644 --- a/dlc-manager/tests/test_utils.rs +++ b/dlc-manager/tests/test_utils.rs @@ -54,6 +54,7 @@ macro_rules! receive_loop { "0218845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166" .parse() .unwrap(), + dlc::FeeConfig::EvenSplit, ); $sync_send.send(()).expect("Error syncing"); match res { diff --git a/dlc/src/channel/mod.rs b/dlc/src/channel/mod.rs index 386a5f8c..3e9fe5e0 100644 --- a/dlc/src/channel/mod.rs +++ b/dlc/src/channel/mod.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; -use crate::{signatures_to_secret, util::get_sig_hash_msg, DlcTransactions, PartyParams, Payout}; +use crate::{signatures_to_secret, util::get_sig_hash_msg, DlcTransactions, FeeConfig, PartyParams, Payout}; use super::Error; use bitcoin::{ @@ -245,6 +245,7 @@ pub fn create_channel_transactions( cet_lock_time: u32, fund_output_serial_id: u64, cet_nsequence: Sequence, + fee_config: FeeConfig, ) -> Result { let extra_fee = super::util::dlc_channel_extra_fee(fee_rate_per_vb)?; let (fund, funding_script_pubkey) = super::create_fund_transaction_with_fees( @@ -254,6 +255,7 @@ pub fn create_channel_transactions( fund_lock_time, fund_output_serial_id, extra_fee, + fee_config )?; create_renewal_channel_transactions( diff --git a/dlc/src/lib.rs b/dlc/src/lib.rs index 4df75b6c..de20a6d7 100644 --- a/dlc/src/lib.rs +++ b/dlc/src/lib.rs @@ -273,7 +273,15 @@ impl PartyParams { &self, fee_rate_per_vb: u64, extra_fee: u64, + fee_config: FeeConfig, + is_offer: bool, ) -> Result<(TxOut, u64, u64), Error> { + let fee_multiplier = match (fee_config, is_offer) { + (FeeConfig::AllOffer, true) | (FeeConfig::AllAccept, false) => 1.0, + (FeeConfig::EvenSplit, _) => 0.5, + (FeeConfig::AllOffer, false) | (FeeConfig::AllAccept, true) => 0.0, + }; + let mut inputs_weight: usize = 0; for w in &self.inputs { @@ -291,7 +299,7 @@ impl PartyParams { // Base weight (nLocktime, nVersion, ...) is distributed among parties // independently of inputs contributed - let this_party_fund_base_weight = FUND_TX_BASE_WEIGHT / 2; + let this_party_fund_base_weight = (FUND_TX_BASE_WEIGHT as f32 * fee_multiplier) as usize; let fund_weight_without_change = checked_add!( this_party_fund_base_weight, @@ -301,14 +309,23 @@ impl PartyParams { // Base weight (nLocktime, nVersion, funding input ...) is distributed // among parties independently of output types - let this_party_cet_base_weight = CET_BASE_WEIGHT / 2; + let this_party_cet_base_weight = (CET_BASE_WEIGHT as f32 * fee_multiplier) as usize; let output_spk_fee = dlc_payout_spk_fee( &self.payout_script_pubkey, fee_rate_per_vb, ); + + let output_spk_fee = match (fee_config, is_offer) { + (FeeConfig::AllOffer, true) | (FeeConfig::AllAccept, false) => output_spk_fee * 2, + (FeeConfig::EvenSplit, _) => output_spk_fee, + (FeeConfig::AllOffer, false) | (FeeConfig::AllAccept, true) => 0, + }; let cet_or_refund_base_fee = util::weight_to_fee(this_party_cet_base_weight, fee_rate_per_vb)?; let cet_or_refund_fee = checked_add!(cet_or_refund_base_fee, output_spk_fee)?; + + let extra_fee = (extra_fee as f32 * fee_multiplier) as u64; + let required_input_funds = checked_add!(self.collateral, fund_fee_without_change, cet_or_refund_fee, extra_fee)?; @@ -332,26 +349,30 @@ impl PartyParams { let (change_amount, change_fee) = { let leftover = self.input_amount - required_input_funds; - // Value size + script length var_int + ouput script pubkey size - let change_script_size = self.change_script_pubkey.len(); - // Change size is scaled by 4 from vBytes to weight units - let change_script_weight = - change_script_size + if leftover == 0 { + (0, 0) + } else { + // Value size + script length var_int + ouput script pubkey size + let change_script_size = self.change_script_pubkey.len(); + // Change size is scaled by 4 from vBytes to weight units + let change_script_weight = + change_script_size .checked_mul(4) .ok_or(Error::InvalidArgument( "failed to multiply 4 to change size".to_string(), ))?; - let change_weight = 36 + change_script_weight; + let change_weight = 36 + change_script_weight; - let change_fee = util::weight_to_fee(change_weight, fee_rate_per_vb)?; + let change_fee = util::weight_to_fee(change_weight, fee_rate_per_vb)?; - let change_amount = leftover - .checked_sub(change_fee) - .ok_or_else(|| { - Error::InvalidArgument("Change output value is lower than cost".to_string()) - })?; + let change_amount = leftover + .checked_sub(change_fee) + .ok_or_else(|| { + Error::InvalidArgument("Change output value is lower than cost".to_string()) + })?; - (change_amount, change_fee) + (change_amount, change_fee) + } }; let fund_fee = fund_fee_without_change + change_fee; @@ -393,6 +414,7 @@ pub fn create_dlc_transactions( fund_lock_time: u32, cet_lock_time: u32, fund_output_serial_id: u64, + fee_config: FeeConfig, ) -> Result { let (fund_tx, funding_script_pubkey) = create_fund_transaction_with_fees( offer_params, @@ -401,6 +423,7 @@ pub fn create_dlc_transactions( fund_lock_time, fund_output_serial_id, 0, + fee_config, )?; let fund_outpoint = OutPoint { txid: fund_tx.txid(), @@ -433,6 +456,7 @@ pub(crate) fn create_fund_transaction_with_fees( fund_lock_time: u32, fund_output_serial_id: u64, extra_fee: u64, + fee_config: FeeConfig, ) -> Result<(Transaction, Script), Error> { let total_collateral = checked_add!(offer_params.collateral, accept_params.collateral)?; @@ -445,10 +469,10 @@ pub(crate) fn create_fund_transaction_with_fees( }; let (offer_change_output, offer_fund_fee, offer_cet_fee) = - offer_params.get_change_output_and_fees(fee_rate_per_vb, offer_extra_fee)?; + offer_params.get_change_output_and_fees(fee_rate_per_vb, offer_extra_fee, fee_config, true)?; let (accept_change_output, accept_fund_fee, accept_cet_fee) = - accept_params.get_change_output_and_fees(fee_rate_per_vb, accept_extra_fee)?; + accept_params.get_change_output_and_fees(fee_rate_per_vb, accept_extra_fee, fee_config, false)?; let fund_output_value = checked_add!(offer_params.input_amount, accept_params.input_amount)? - offer_change_output.value @@ -946,6 +970,12 @@ pub fn verify_tx_input_sig( } /// Configure who pays for the transaction fees involved in a DLC channel. +#[derive(Clone, Copy, Debug)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] pub enum FeeConfig { /// The transaction fees are split evenly between the offer party and the accept party. EvenSplit, @@ -1294,7 +1324,7 @@ mod tests { // Act let (change_out, fund_fee, cet_fee) = - party_params.get_change_output_and_fees(4, 0).unwrap(); + party_params.get_change_output_and_fees(4, 0, FeeConfig::EvenSplit, true).unwrap(); // Assert assert!(change_out.value > 0 && fund_fee > 0 && cet_fee > 0); @@ -1306,7 +1336,7 @@ mod tests { let (party_params, _) = get_party_params(100000, 100000, None); // Act - let res = party_params.get_change_output_and_fees(4, 0); + let res = party_params.get_change_output_and_fees(4, 0, FeeConfig::EvenSplit, true); // Assert assert!(res.is_err()); @@ -1328,6 +1358,7 @@ mod tests { 10, 10, 0, + FeeConfig::EvenSplit, ) .unwrap(); @@ -1354,6 +1385,7 @@ mod tests { 10, 10, 0, + FeeConfig::EvenSplit, ) .unwrap(); @@ -1524,6 +1556,7 @@ mod tests { 10, 10, case.serials[0], + FeeConfig::EvenSplit, ) .unwrap(); diff --git a/sample/src/cli.rs b/sample/src/cli.rs index e8d0a11c..cee2b018 100644 --- a/sample/src/cli.rs +++ b/sample/src/cli.rs @@ -648,7 +648,7 @@ fn process_incoming_messages( let resp = dlc_manager .lock() .unwrap() - .on_dlc_message(&message, node_id) + .on_dlc_message(&message, node_id, dlc::FeeConfig::EvenSplit) .expect("Error processing message"); if let Some(msg) = resp { println!("Sending message to {}", node_id);