Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix the Maker's Fees #312

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 47 additions & 5 deletions src/maker/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,51 @@ use crate::{

use super::{config::MakerConfig, error::MakerError};

use crate::maker::server::{
HEART_BEAT_INTERVAL_SECS, MIN_CONTRACT_REACTION_TIME, REQUIRED_CONFIRMS,
};
/// The core server process interval. many of the maker server's internal threads "beats" at this frequency.
pub const HEART_BEAT_INTERVAL_SECS: u64 = 3;

/// RPC Backend health check interval.
pub const RPC_PING_INTERVAL_SECS: u64 = 60;

// Currently we don't refresh address at DNS. The Maker only post it once at startup.
// If the address record gets deleted, or the DNS gets blasted, the Maker won't know.
// TODO: Make the maker repost their address to DNS once a day in spawned thread.
// pub const DIRECTORY_SERVERS_REFRESH_INTERVAL_SECS: u64 = 60 * 60 * 24; // Once a day.

/// Maker triggers the recovery mechanism, if Taker is idle for more than 300 secs.
pub const IDLE_CONNECTION_TIMEOUT: Duration = Duration::from_secs(300);

/// Number of confirmation required funding transaction.
pub const REQUIRED_CONFIRMS: u64 = 1;

/// The minimum locktime difference between the incoming and outgoing swaps.
/// This is the reaction time in blocks a Maker has to claim his refund transaction, in case of recovery.
/// Bolt2 has an estimate of minimum `cltv_expiry_delta` as 18 blocks. https://github.com/lightning/bolts/blob/aa5207aeaa32d841353dd2df3ce725a4046d528d/02-peer-protocol.md?plain=1#L1798
/// To be a bit more conservative we use 20 as the default value.
pub const MIN_CONTRACT_REACTION_TIME: u16 = 20;

/// Fee Parameters
///
/// abs_fee = Constant fee for all swaps.
/// amount_relative_fee = Percentage fee relative to the swap_amount.
/// time_relative_fee = Percentage fee applied to the refund_locktime, i.e, how long the maker needs to wait for their refund time-lock.
///
/// Increasing the swap amount and refund timelock, increases the coinswap fee, claimed by the maker.
/// Check [REFUND_LOCKTIME] and [REFUND_LOCKTIME_STEP] of taker::api.rs.
///
/// So Total fee on swap is calculated as
/// `total_fee = abs_fee + (swap_amount * relative_fee)/100 + (swap_amount * refund_locktime * time_relative_fee)/100`;
///
/// # Example for default values:
/// For swap_amount = 100,000 sats, refund_locktime = 20 Blocks;
/// abs_fee = 1000 sats;
/// amount_relative_fee = (100,000 * 2.5)/100 = 2500 sats;
/// time_relative_fee = (100,000 * 20 * 0.1)/100 = 2000 sats;
/// total_fee = 5500 sats, i.e. 5.5%;
/// The fee rates are set such a way, that the total % fees reaches 5% asymptotically with increase in swap amount.
pub const ABSOLUTE_FEE: u64 = 1000;
pub const AMOUNT_RELATIVE_FEE: f64 = 2.50;
pub const TIME_RELATIVE_FEE: f64 = 0.10;

/// Used to configure the maker for testing purposes.
#[derive(Debug, Clone, Copy)]
Expand Down Expand Up @@ -285,7 +327,7 @@ impl Maker {
// check that the new locktime is sufficently short enough compared to the
// locktime in the provided funding tx
let locktime = read_contract_locktime(&funding_info.contract_redeemscript)?;
if locktime - message.next_locktime < MIN_CONTRACT_REACTION_TIME {
if locktime - message.refund_locktime < MIN_CONTRACT_REACTION_TIME {
return Err(MakerError::General(
"Next hop locktime too close to current hop locktime",
));
Expand Down Expand Up @@ -560,7 +602,7 @@ pub fn check_for_idle_states(maker: Arc<Maker>) -> Result<(), MakerError> {
ip,
no_response_since
);
if no_response_since > std::time::Duration::from_secs(60) {
if no_response_since > IDLE_CONNECTION_TIMEOUT {
log::error!(
"[{}] Potential Dropped Connection from {}",
maker.config.port,
Expand Down
51 changes: 15 additions & 36 deletions src/maker/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
use crate::utill::parse_toml;
use std::{io, path::PathBuf};

use bitcoin::Amount;
use std::io::Write;

use crate::utill::{get_maker_dir, parse_field, ConnectionType};
Expand All @@ -15,13 +14,8 @@ pub struct MakerConfig {
pub port: u16,
/// RPC listening port
pub rpc_port: u16,
/// Absolute coinswap fee
pub absolute_fee_sats: Amount,
/// Fee rate for timelocked contract in parts per billion (PPB).
/// Similar to `DEFAULT_AMOUNT_RELATIVE_FEE_PPB`, calculated as (amount * fee_ppb) / 1_000_000_000.
pub time_relative_fee_ppb: Amount,
/// Minimum timelock difference between contract transaction of two hops
pub min_size: u64,
/// Minimum swap size.
pub min_swap_amount: u64,
/// Socks port
pub socks_port: u16,
/// Directory server address (can be clearnet or onion)
Expand All @@ -39,9 +33,7 @@ impl Default for MakerConfig {
Self {
port: 6102,
rpc_port: 6103,
absolute_fee_sats: Amount::from_sat(1000),
time_relative_fee_ppb: Amount::from_sat(100_000),
min_size: 10_000,
min_swap_amount: 100_000, // A swap amount lower than this will not be economical.
socks_port: 19050,
directory_server_address: "directoryhiddenserviceaddress.onion:8080".to_string(),
fidelity_value: 5_000_000, // 5 million sats
Expand Down Expand Up @@ -88,21 +80,15 @@ impl MakerConfig {
Ok(MakerConfig {
port: parse_field(config_map.get("port"), default_config.port),
rpc_port: parse_field(config_map.get("rpc_port"), default_config.rpc_port),
absolute_fee_sats: parse_field(
config_map.get("absolute_fee_sats"),
default_config.absolute_fee_sats,
min_swap_amount: parse_field(
config_map.get("min_swap_amount"),
default_config.min_swap_amount,
),
time_relative_fee_ppb: parse_field(
config_map.get("time_relative_fee_ppb"),
default_config.time_relative_fee_ppb,
),
min_size: parse_field(config_map.get("min_size"), default_config.min_size),
socks_port: parse_field(config_map.get("socks_port"), default_config.socks_port),
directory_server_address: parse_field(
config_map.get("directory_server_address"),
default_config.directory_server_address,
),

fidelity_value: parse_field(
config_map.get("fidelity_value"),
default_config.fidelity_value,
Expand All @@ -122,20 +108,16 @@ impl MakerConfig {
pub fn write_to_file(&self, path: &PathBuf) -> std::io::Result<()> {
let toml_data = format!(
"port = {}
rpc_port = {}
absolute_fee_sats = {}
time_relative_fee_ppb = {}
min_size = {}
socks_port = {}
directory_server_address = {}
fidelity_value = {}
fidelity_timelock = {}
connection_type = {:?}",
rpc_port = {}
min_swap_amount = {}
socks_port = {}
directory_server_address = {}
fidelity_value = {}
fidelity_timelock = {}
connection_type = {:?}",
self.port,
self.rpc_port,
self.absolute_fee_sats,
self.time_relative_fee_ppb,
self.min_size,
self.min_swap_amount,
self.socks_port,
self.directory_server_address,
self.fidelity_value,
Expand Down Expand Up @@ -177,12 +159,9 @@ mod tests {
[maker_config]
port = 6102
rpc_port = 6103
absolute_fee_sats = 1000
amount_relative_fee_ppb = 10000000
time_relative_fee_ppb = 100000
required_confirms = 1
min_contract_reaction_time = 48
min_size = 10000
min_swap_amount = 10000
socks_port = 19050
"#;
let config_path = create_temp_config(contents, "valid_maker_config.toml");
Expand Down
77 changes: 40 additions & 37 deletions src/maker/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ use bitcoin::{
use bitcoind::bitcoincore_rpc::RpcApi;

use crate::{
maker::api::recover_from_swap,
maker::api::{recover_from_swap, ABSOLUTE_FEE, AMOUNT_RELATIVE_FEE, TIME_RELATIVE_FEE},
protocol::{
contract::calculate_coinswap_fee,
error::ProtocolError,
messages::{MakerHello, MultisigPrivkey, PrivKeyHandover},
Hash160,
Expand All @@ -33,8 +34,7 @@ use crate::{
},
protocol::{
contract::{
calculate_coinswap_fee, create_receivers_contract_tx, find_funding_output_index,
read_contract_locktime, read_hashvalue_from_contract,
create_receivers_contract_tx, find_funding_output_index, read_hashvalue_from_contract,
read_pubkeys_from_multisig_redeemscript, FUNDING_TX_VBYTE_SIZE,
},
messages::{
Expand All @@ -47,9 +47,8 @@ use crate::{
wallet::{IncomingSwapCoin, SwapCoin},
};

use crate::maker::server::{
AMOUNT_RELATIVE_FEE_PPB, MIN_CONTRACT_REACTION_TIME, REQUIRED_CONFIRMS,
};
use super::api::{MIN_CONTRACT_REACTION_TIME, REQUIRED_CONFIRMS};

/// The Global Handle Message function. Takes in a [`Arc<Maker>`] and handle messages
/// according to a [ConnectionState].
pub fn handle_message(
Expand Down Expand Up @@ -96,13 +95,13 @@ pub fn handle_message(
let fidelity = maker.highest_fidelity_proof.read()?;
let fidelity = fidelity.as_ref().expect("proof expected");
Some(MakerToTakerMessage::RespOffer(Box::new(Offer {
absolute_fee_sat: maker.config.absolute_fee_sats,
amount_relative_fee_ppb: AMOUNT_RELATIVE_FEE_PPB,
time_relative_fee_ppb: maker.config.time_relative_fee_ppb,
absolute_fee: ABSOLUTE_FEE,
amount_relative_fee: AMOUNT_RELATIVE_FEE,
time_relative_fee: TIME_RELATIVE_FEE,
required_confirms: REQUIRED_CONFIRMS,
minimum_locktime: MIN_CONTRACT_REACTION_TIME,
max_size,
min_size: maker.config.min_size,
min_size: maker.config.min_swap_amount,
tweakable_point,
fidelity: fidelity.clone(),
})))
Expand Down Expand Up @@ -241,7 +240,7 @@ impl Maker {
acc + txinfo.funding_input_value.to_sat()
});

if total_funding_amount >= self.config.min_size
if total_funding_amount >= self.config.min_swap_amount
&& total_funding_amount < self.wallet.read()?.store.offer_maxsize
{
log::info!(
Expand Down Expand Up @@ -300,7 +299,7 @@ impl Maker {
},
funding_output.value,
&funding_info.contract_redeemscript,
Amount::from_sat(message.next_fee_rate),
Amount::from_sat(message.contract_feerate),
)?;

let (tweakable_privkey, _) = self.wallet.read()?.get_tweakable_keypair()?;
Expand Down Expand Up @@ -359,19 +358,30 @@ impl Maker {
})?;

let calc_coinswap_fees = calculate_coinswap_fee(
self.config.absolute_fee_sats,
AMOUNT_RELATIVE_FEE_PPB,
self.config.time_relative_fee_ppb,
Amount::from_sat(incoming_amount),
REQUIRED_CONFIRMS, //time_in_blocks just 1 for now
incoming_amount,
message.refund_locktime,
ABSOLUTE_FEE,
AMOUNT_RELATIVE_FEE,
TIME_RELATIVE_FEE,
);

let calc_funding_tx_fees = (FUNDING_TX_VBYTE_SIZE
* message.next_fee_rate
* message.contract_feerate
* (message.next_coinswap_info.len() as u64))
/ 1000;

let outgoing_amount = incoming_amount - calc_coinswap_fees - calc_funding_tx_fees;
// Check for overflow. If happens hard error.
// This can happen if the fee_rate for funding tx is very high and incoming_amount is very low.
// TODO: Ensure at Taker protocol that this never happens.
let outgoing_amount = if let Some(a) =
incoming_amount.checked_sub(calc_coinswap_fees + calc_funding_tx_fees)
{
a
} else {
return Err(MakerError::General(
"Fatal Error! Total swap fee is more than the swap amount. Failing the swap.",
));
};

// Create outgoing coinswap of the next hop
let (my_funding_txes, outgoing_swapcoins, act_funding_txs_fees) = {
Expand All @@ -388,12 +398,14 @@ impl Maker {
.map(|next_hop| next_hop.next_hashlock_pubkey)
.collect::<Vec<PublicKey>>(),
hashvalue,
message.next_locktime,
Amount::from_sat(message.next_fee_rate),
message.refund_locktime,
Amount::from_sat(message.contract_feerate),
)?
};

let act_coinswap_fees = incoming_amount - outgoing_amount - act_funding_txs_fees.to_sat();
let act_coinswap_fees = incoming_amount
.checked_sub(outgoing_amount + act_funding_txs_fees.to_sat())
.expect("This should not overflow as we just above.");

log::info!(
"[{}] Outgoing Funding Txids: {:?}.",
Expand All @@ -403,27 +415,18 @@ impl Maker {
.map(|tx| tx.compute_txid())
.collect::<Vec<_>>()
);
log::debug!(
"[{}] Outgoing Swapcoins: {:?}.",
self.config.port,
outgoing_swapcoins
);

log::info!(
"incoming_amount = {} | incoming_locktime = {} | outgoing_amount = {} | outgoing_locktime = {}",
"Incoming Swap Amount = {} | Outgoing Swap Amount = {} | Swap Revenue = {}",
Amount::from_sat(incoming_amount),
read_contract_locktime(
&message.confirmed_funding_txes[0].contract_redeemscript
)?,
Amount::from_sat(outgoing_amount),
message.next_locktime
Amount::from_sat(act_coinswap_fees)
);

log::info!(
"Calculated Funding Txs Fees = {} | Actual Funding Txs Fees = {} | Calculated Swap Revenue = {} | Actual Swap Revenue = {}",
Amount::from_sat(calc_funding_tx_fees),
act_funding_txs_fees,
Amount::from_sat(calc_coinswap_fees),
Amount::from_sat(act_coinswap_fees)
"Our Refund Tx locktime (blocks) = {} | Total Funding Tx Mining Fees = {} | ",
message.refund_locktime,
act_funding_txs_fees
);

connection_state.pending_funding_txes = my_funding_txes;
Expand Down
17 changes: 2 additions & 15 deletions src/maker/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use bitcoind::bitcoincore_rpc::RpcApi;

use socks::Socks5Stream;

use super::api::{HEART_BEAT_INTERVAL_SECS, RPC_PING_INTERVAL_SECS};
pub use super::Maker;

use crate::{
Expand All @@ -38,18 +39,6 @@ use crate::{

use crate::maker::error::MakerError;

// Default values for Maker configurations
pub const HEART_BEAT_INTERVAL_SECS: u64 = 3;
pub const RPC_PING_INTERVAL_SECS: u64 = 60;
pub const _DIRECTORY_SERVERS_REFRESH_INTERVAL_SECS: u64 = 60 * 60 * 12; // 12 Hours
pub const _IDLE_CONNECTION_TIMEOUT: u64 = 300;
pub const REQUIRED_CONFIRMS: u64 = 1;
pub const MIN_CONTRACT_REACTION_TIME: u16 = 48;

/// Fee rate per swap amount in parts per billion (PPB).
/// E.g., for 1 billion sats (0.01 BTC), a value of 10_000 would result in a 0.1% fee.
pub const AMOUNT_RELATIVE_FEE_PPB: Amount = Amount::from_sat(10_000_000);

/// Fetches the Maker and DNS address, and sends maker address to the DNS server.
/// Depending upon ConnectionType and test/prod environment, different maker address and DNS addresses are returned.
/// Return the Maker address and an optional tor thread handle.
Expand Down Expand Up @@ -419,8 +408,6 @@ pub fn start_maker_server(maker: Arc<Maker>) -> Result<(), MakerError> {
maker_address
);

let heart_beat_interval = HEART_BEAT_INTERVAL_SECS; // All maker internal threads loops at this frequency.

// Setup the wallet with fidelity bond.
let network = maker.get_wallet().read()?.store.network;
let balance = maker.get_wallet().read()?.balance()?;
Expand Down Expand Up @@ -502,7 +489,7 @@ pub fn start_maker_server(maker: Arc<Maker>) -> Result<(), MakerError> {

maker.thread_pool.add_thread(rpc_thread);

sleep(Duration::from_secs(heart_beat_interval)); // wait for 1 beat, to complete spawns of all the threads.
sleep(Duration::from_secs(HEART_BEAT_INTERVAL_SECS)); // wait for 1 beat, to complete spawns of all the threads.
maker.is_setup_complete.store(true, Relaxed);
log::info!("[{}] Maker setup is ready", maker.config.port);
}
Expand Down
Loading
Loading