Repository: farcaster-project/farcaster-node
Pull request #800 (2022-12-02 16:52:18)

Config: expose swap finality parameters
This PR introduce a new section`[swap...]` in the config file `farcasterd.toml`. The swap section, for now, contains finality and safety parameters per chain per network. A chain can accept `arbitrating` parameters or `accordant` parameters, currently we only support btc and xmr:
https://github.com/farcaster-project/farcaster-node/blob/981c0d1d93986f7584bd4b32efbbe41e5ae1854e/src/config.rs#L239-L246

The configuration can then return a parsed swap configuration for a given network, arb chain, and acc chain
https://github.com/farcaster-project/farcaster-node/blob/981c0d1d93986f7584bd4b32efbbe41e5ae1854e/src/config.rs#L150-L156

We have default values for mainnent and testnet for Bitcoin and Monero, if no config is found in the file we return those default value (backward compatibility), otherwise an error is returned. The configuration translate the real chain (Btc, Xmr) into its interface (Arb, Acc) as the swap is started with the offer that contains the chains used.

Launching a swap now takes the parsed swap config to 
https://github.com/farcaster-project/farcaster-node/blob/981c0d1d93986f7584bd4b32efbbe41e5ae1854e/src/farcasterd/runtime.rs#L1244-L1260

Cli arguments of swapd have been reworked to accept the new arguments. (Breaking change with other bins that use swapd directly)

The new `chains.rs` file contains enums for listing support arb/acc chains in our node (e.g. btc as acc is in theory possible but probably not supported in the current architecture, but that would be a nice test to see where the current design falls apart). These structs can be reused later elsewhere (e.g. make cli args) to ensure we accept only valid params.

h4sh3d: @Lederstrumpf @TheCharlatan do we want to expose different finality and safety margin per network? (And are those names good?)
TheCharlatan: names lgtm
Lederstrumpf: > @Lederstrumpf @TheCharlatan do we want to expose different finality and safety margin per network? (And are those names good?)

agree it makes sense to have different finality thresholds per network. for safety, it would be more consistent to call this `safety_threshold` rather than `safety_margin`, but it works in the opposite direction (upper vs lower bound) to finality, so having distinct names may avoid someone misinterpreting the direction.
I'll also put suggestions of `finality_min` and `safety_max` on the table to be absolutely clear on direction since they require less thinking / a lower command of English to be understood unambiguously. 

Patch:
diff --git a/farcasterd.toml b/farcasterd.toml
index dc952adc..199d1110 100644
--- a/farcasterd.toml
+++ b/farcasterd.toml
@@ -37,6 +37,24 @@ bitcoin_cookie_path = "~/.bitcoin/testnet3/.cookie"
 # the wallet should have spendable funds
 monero_rpc_wallet = "http://localhost:38084"
 
+# Swap parameter for the Bitcoin blockchain
+[swap.bitcoin.testnet]
+# Avoid broadcasting a transaction if a race can happen in # blocks
+#
+# E.g. if the safety is set to 3 and the the cancel transaction becomes
+# broadcastable in 4 blocks we are safe and we do publish buy, if it becomes
+# broadcastable in 3 blocks or less we do not publish the buy as the safety is
+# not respected.
+safety = 3
+# Number of confirmations required to consider a transaction final. Must be
+# smaller than safety.
+finality = 1
+
+# Swap parameter for the Monero blockchain
+[swap.monero.testnet]
+# Number of confirmations required to consider a transaction final
+finality = 1
+
 # Defines grpc options
 [grpc]
 # Set this to true to enable the grpc daemon
diff --git a/src/bin/swapd.rs b/src/bin/swapd.rs
index 56ad0cb8..abdf6842 100644
--- a/src/bin/swapd.rs
+++ b/src/bin/swapd.rs
@@ -45,23 +45,8 @@ fn main() {
     debug!("MSG RPC socket {}", &service_config.msg_endpoint);
     debug!("CTL RPC socket {}", &service_config.ctl_endpoint);
 
-    /*
-    use self::internal::ResultExt;
-    let (config_from_file, _) =
-        internal::Config::custom_args_and_optional_files(std::iter::empty::<
-            &str,
-        >())
-        .unwrap_or_exit();
-     */
-
     debug!("Starting runtime ...");
-    swapd::run(
-        service_config,
-        opts.swap_id,
-        opts.public_offer,
-        opts.trade_role,
-    )
-    .expect("Error running swapd runtime");
+    swapd::run(service_config, opts).expect("Error running swapd runtime");
 
     unreachable!()
 }
diff --git a/src/chains.rs b/src/chains.rs
new file mode 100644
index 00000000..0ac6bc49
--- /dev/null
+++ b/src/chains.rs
@@ -0,0 +1,113 @@
+//! This module defines the currently supported blockchains in the node for the arbitrating and
+//! the accordant roles. The types defined here are convertible with core `Blockchain` type.
+
+use std::convert::TryFrom;
+use std::str::FromStr;
+
+use farcaster_core::{blockchain::Blockchain, consensus};
+use strict_encoding::{StrictDecode, StrictEncode};
+
+/// A list of supported arbitrating blockchain
+#[derive(
+    Debug,
+    Clone,
+    Copy,
+    Hash,
+    PartialEq,
+    Eq,
+    Parser,
+    Display,
+    Serialize,
+    Deserialize,
+    StrictEncode,
+    StrictDecode,
+)]
+#[serde(crate = "serde_crate")]
+#[display(Debug)]
+pub enum ArbitratingBlockchain {
+    /// The Bitcoin (BTC) blockchain.
+    Bitcoin,
+}
+
+impl FromStr for ArbitratingBlockchain {
+    type Err = consensus::Error;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        match s {
+            "Bitcoin" | "bitcoin" | "btc" | "BTC" => Ok(ArbitratingBlockchain::Bitcoin),
+            _ => Err(consensus::Error::UnknownType),
+        }
+    }
+}
+
+impl Into<Blockchain> for ArbitratingBlockchain {
+    fn into(self) -> Blockchain {
+        match self {
+            ArbitratingBlockchain::Bitcoin => Blockchain::Bitcoin,
+        }
+    }
+}
+
+impl TryFrom<Blockchain> for ArbitratingBlockchain {
+    type Error = consensus::Error;
+
+    fn try_from(blockchain: Blockchain) -> Result<Self, Self::Error> {
+        match blockchain {
+            Blockchain::Bitcoin => Ok(ArbitratingBlockchain::Bitcoin),
+            _ => Err(consensus::Error::TypeMismatch),
+        }
+    }
+}
+
+/// A list of supported accordant blockchain
+#[derive(
+    Debug,
+    Clone,
+    Copy,
+    Hash,
+    PartialEq,
+    Eq,
+    Parser,
+    Display,
+    Serialize,
+    Deserialize,
+    StrictEncode,
+    StrictDecode,
+)]
+#[serde(crate = "serde_crate")]
+#[display(Debug)]
+pub enum AccordantBlockchain {
+    /// The Monero (XMR) blockchain.
+    Monero,
+    // NOTE: we could in theory add Bitcoin here, but currently the node does not support it
+}
+
+impl FromStr for AccordantBlockchain {
+    type Err = consensus::Error;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        match s {
+            "Monero" | "monero" | "xmr" | "XMR" => Ok(AccordantBlockchain::Monero),
+            _ => Err(consensus::Error::UnknownType),
+        }
+    }
+}
+
+impl Into<Blockchain> for AccordantBlockchain {
+    fn into(self) -> Blockchain {
+        match self {
+            AccordantBlockchain::Monero => Blockchain::Monero,
+        }
+    }
+}
+
+impl TryFrom<Blockchain> for AccordantBlockchain {
+    type Error = consensus::Error;
+
+    fn try_from(blockchain: Blockchain) -> Result<Self, Self::Error> {
+        match blockchain {
+            Blockchain::Monero => Ok(AccordantBlockchain::Monero),
+            _ => Err(consensus::Error::TypeMismatch),
+        }
+    }
+}
diff --git a/src/config.rs b/src/config.rs
index 190c7e64..0425e710 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -12,7 +12,7 @@
 // along with this software.
 // If not, see <https://opensource.org/licenses/MIT>.
 
-use crate::Error;
+use crate::{AccordantBlockchain, ArbitratingBlockchain, Error};
 use farcaster_core::blockchain::Network;
 use internet2::addr::InetSocketAddr;
 use std::fs::File;
@@ -35,15 +35,25 @@ pub const FARCASTER_BIND_IP: &str = "0.0.0.0";
 
 pub const GRPC_BIND_IP_ADDRESS: &str = "127.0.0.1";
 
+pub const SWAP_MAINNET_BITCOIN_SAFETY: u8 = 7;
+pub const SWAP_MAINNET_BITCOIN_FINALITY: u8 = 6;
+pub const SWAP_MAINNET_MONERO_FINALITY: u8 = 20;
+
+pub const SWAP_TESTNET_BITCOIN_SAFETY: u8 = 3;
+pub const SWAP_TESTNET_BITCOIN_FINALITY: u8 = 1;
+pub const SWAP_TESTNET_MONERO_FINALITY: u8 = 1;
+
 #[derive(Deserialize, Serialize, Debug, Clone)]
 #[serde(crate = "serde_crate")]
 pub struct Config {
     /// Farcasterd configuration
     pub farcasterd: Option<FarcasterdConfig>,
+    /// Swap configuration, applies to all swaps launched by this node
+    pub swap: Option<SwapConfig>,
     /// Sets the grpc server port, if none is given, no grpc server is run
     pub grpc: Option<GrpcConfig>,
     /// Syncer configuration
-    pub syncers: Option<SyncersConfig>,
+    pub syncers: Option<Networked<Option<SyncerServers>>>,
 }
 
 impl Config {
@@ -128,6 +138,7 @@ impl Config {
         Ok(InetSocketAddr::from_str(&addr).map_err(|e| Error::Farcaster(e.to_string()))?)
     }
 
+    /// Returns a syncer configuration, if found in config, for the specified network
     pub fn get_syncer_servers(&self, network: Network) -> Option<SyncerServers> {
         match network {
             Network::Mainnet => self.syncers.as_ref()?.mainnet.clone(),
@@ -135,6 +146,53 @@ impl Config {
             Network::Local => self.syncers.as_ref()?.local.clone(),
         }
     }
+
+    /// Returns the swap config for the specified network and arbitrating/accordant blockchains
+    pub fn get_swap_config(
+        &self,
+        arb: ArbitratingBlockchain,
+        acc: AccordantBlockchain,
+        network: Network,
+    ) -> Result<ParsedSwapConfig, config::ConfigError> {
+        match &self.swap {
+            Some(swap) => {
+                let arbitrating = match arb {
+                    ArbitratingBlockchain::Bitcoin => swap
+                        .bitcoin
+                        .get_for_network(network)
+                        .or_else(|| ArbConfig::get(arb, network))
+                        .ok_or(config::ConfigError::Message(
+                            "No configuration nor defaults founds!".to_string(),
+                        ))?,
+                };
+                let accordant = match acc {
+                    AccordantBlockchain::Monero => swap
+                        .monero
+                        .get_for_network(network)
+                        .or_else(|| AccConfig::get(acc, network))
+                        .ok_or(config::ConfigError::Message(
+                            "No configuration nor defaults founds!".to_string(),
+                        ))?,
+                };
+                Ok(ParsedSwapConfig {
+                    arbitrating,
+                    accordant,
+                })
+            }
+            None => {
+                let arbitrating = ArbConfig::get(arb, network).ok_or(
+                    config::ConfigError::Message("No defaults founds!".to_string()),
+                )?;
+                let accordant = AccConfig::get(acc, network).ok_or(
+                    config::ConfigError::Message("No defaults founds!".to_string()),
+                )?;
+                Ok(ParsedSwapConfig {
+                    arbitrating,
+                    accordant,
+                })
+            }
+        }
+    }
 }
 
 // Default implementation is used to generate the config file on disk if not found
@@ -142,8 +200,25 @@ impl Default for Config {
     fn default() -> Self {
         Config {
             farcasterd: Some(FarcasterdConfig::default()),
+            swap: Some(SwapConfig::default()),
             grpc: None,
-            syncers: Some(SyncersConfig::default()),
+            syncers: Some(Networked {
+                mainnet: Some(SyncerServers {
+                    electrum_server: FARCASTER_MAINNET_ELECTRUM_SERVER.into(),
+                    monero_daemon: FARCASTER_MAINNET_MONERO_DAEMON.into(),
+                    monero_rpc_wallet: FARCASTER_MAINNET_MONERO_RPC_WALLET.into(),
+                    monero_lws: None,
+                    monero_wallet_dir: None,
+                }),
+                testnet: Some(SyncerServers {
+                    electrum_server: FARCASTER_TESTNET_ELECTRUM_SERVER.into(),
+                    monero_daemon: FARCASTER_TESTNET_MONERO_DAEMON.into(),
+                    monero_rpc_wallet: FARCASTER_TESTNET_MONERO_RPC_WALLET.into(),
+                    monero_lws: None,
+                    monero_wallet_dir: None,
+                }),
+                local: None,
+            }),
         }
     }
 }
@@ -161,6 +236,100 @@ pub struct FarcasterdConfig {
     pub auto_restore: Option<bool>,
 }
 
+#[derive(Deserialize, Serialize, Debug, Clone)]
+#[serde(crate = "serde_crate")]
+pub struct SwapConfig {
+    /// Swap parameters for the Bitcoin blockchain per network
+    pub bitcoin: Networked<Option<ArbConfig>>,
+    /// Swap parameters for the Monero blockchain per network
+    pub monero: Networked<Option<AccConfig>>,
+}
+
+#[derive(Deserialize, Serialize, Debug, Clone)]
+#[serde(crate = "serde_crate")]
+pub struct ParsedSwapConfig {
+    /// Swap parameters for an arbitrating blockchain
+    pub arbitrating: ArbConfig,
+    /// Swap parameters for an accordant blockchain
+    pub accordant: AccConfig,
+}
+
+#[derive(Deserialize, Serialize, Debug, Clone)]
+#[serde(crate = "serde_crate")]
+pub struct ArbConfig {
+    /// Avoid broadcasting a transaction if a race can happen with the next available execution
+    /// fork in # blocks
+    pub safety: u8,
+    /// Number of confirmations required to consider a transaction final
+    pub finality: u8,
+}
+
+impl ArbConfig {
+    fn get(blockchain: ArbitratingBlockchain, network: Network) -> Option<Self> {
+        match blockchain {
+            ArbitratingBlockchain::Bitcoin => match network {
+                Network::Mainnet => Some(Self::btc_mainnet_default()),
+                Network::Testnet => Some(Self::btc_testnet_default()),
+                Network::Local => None,
+            },
+        }
+    }
+
+    fn btc_mainnet_default() -> Self {
+        ArbConfig {
+            safety: SWAP_MAINNET_BITCOIN_SAFETY,
+            finality: SWAP_MAINNET_BITCOIN_FINALITY,
+        }
+    }
+
+    fn btc_testnet_default() -> Self {
+        ArbConfig {
+            safety: SWAP_TESTNET_BITCOIN_SAFETY,
+            finality: SWAP_TESTNET_BITCOIN_FINALITY,
+        }
+    }
+}
+
+#[derive(Deserialize, Serialize, Debug, Clone)]
+#[serde(crate = "serde_crate")]
+pub struct AccConfig {
+    /// Number of confirmations required to consider a transaction final
+    pub finality: u8,
+}
+
+impl AccConfig {
+    fn get(blockchain: AccordantBlockchain, network: Network) -> Option<Self> {
+        match blockchain {
+            AccordantBlockchain::Monero => match network {
+                Network::Mainnet => Some(Self::xmr_mainnet_default()),
+                Network::Testnet => Some(Self::xmr_testnet_default()),
+                Network::Local => None,
+            },
+        }
+    }
+
+    fn xmr_mainnet_default() -> Self {
+        AccConfig {
+            finality: SWAP_MAINNET_MONERO_FINALITY,
+        }
+    }
+
+    fn xmr_testnet_default() -> Self {
+        AccConfig {
+            finality: SWAP_TESTNET_MONERO_FINALITY,
+        }
+    }
+}
+
+// Arbitrating blockchains are a superset of accordant ones
+impl From<ArbConfig> for AccConfig {
+    fn from(arb: ArbConfig) -> Self {
+        Self {
+            finality: arb.finality,
+        }
+    }
+}
+
 #[derive(Deserialize, Serialize, Debug, Clone)]
 #[serde(crate = "serde_crate")]
 pub struct GrpcConfig {
@@ -200,17 +369,6 @@ pub struct AutoFundingServers {
     pub monero_rpc_wallet: String,
 }
 
-#[derive(Deserialize, Serialize, Debug, Clone)]
-#[serde(crate = "serde_crate")]
-pub struct SyncersConfig {
-    /// Mainnet syncer configuration
-    pub mainnet: Option<SyncerServers>,
-    /// Testnet syncer configuration
-    pub testnet: Option<SyncerServers>,
-    /// Local syncer configuration
-    pub local: Option<SyncerServers>,
-}
-
 #[derive(Deserialize, Serialize, Default, Debug, Clone)]
 #[serde(crate = "serde_crate")]
 pub struct SyncerServers {
@@ -226,25 +384,44 @@ pub struct SyncerServers {
     pub monero_wallet_dir: Option<String>,
 }
 
+#[derive(Deserialize, Serialize, Default, Debug, Clone)]
+#[serde(crate = "serde_crate")]
+pub struct Networked<T> {
+    /// A configuration section applied to the mainnet network
+    pub mainnet: T,
+    /// A configuration section applied to the live testnet network
+    pub testnet: T,
+    /// A configuration section applied to the local network (regtest)
+    pub local: T,
+}
+
+impl<T> Networked<T>
+where
+    T: Clone,
+{
+    fn get_for_network(&self, network: Network) -> T {
+        match network {
+            Network::Mainnet => self.mainnet.clone(),
+            Network::Testnet => self.testnet.clone(),
+            Network::Local => self.local.clone(),
+        }
+    }
+}
+
 // Default implementation is used to generate the config file on disk if not found
-impl Default for SyncersConfig {
+impl Default for SwapConfig {
     fn default() -> Self {
-        SyncersConfig {
-            mainnet: Some(SyncerServers {
-                electrum_server: FARCASTER_MAINNET_ELECTRUM_SERVER.into(),
-                monero_daemon: FARCASTER_MAINNET_MONERO_DAEMON.into(),
-                monero_rpc_wallet: FARCASTER_MAINNET_MONERO_RPC_WALLET.into(),
-                monero_lws: None,
-                monero_wallet_dir: None,
-            }),
-            testnet: Some(SyncerServers {
-                electrum_server: FARCASTER_TESTNET_ELECTRUM_SERVER.into(),
-                monero_daemon: FARCASTER_TESTNET_MONERO_DAEMON.into(),
-                monero_rpc_wallet: FARCASTER_TESTNET_MONERO_RPC_WALLET.into(),
-                monero_lws: None,
-                monero_wallet_dir: None,
-            }),
-            local: None,
+        SwapConfig {
+            bitcoin: Networked {
+                mainnet: Some(ArbConfig::btc_mainnet_default()),
+                testnet: Some(ArbConfig::btc_testnet_default()),
+                local: None,
+            },
+            monero: Networked {
+                mainnet: Some(AccConfig::xmr_mainnet_default()),
+                testnet: Some(AccConfig::xmr_testnet_default()),
+                local: None,
+            },
         }
     }
 }
@@ -263,6 +440,7 @@ impl Default for FarcasterdConfig {
     }
 }
 
+/// Parse a configuration file and return a [`Config`]
 pub fn parse_config(path: &str) -> Result<Config, Error> {
     if Path::new(path).exists() {
         let config_file = path;
@@ -278,3 +456,14 @@ pub fn parse_config(path: &str) -> Result<Config, Error> {
         Ok(config)
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::parse_config;
+
+    #[test]
+    fn config_example_parse() {
+        let config = parse_config("./farcasterd.toml").expect("correct config example");
+        dbg!(config);
+    }
+}
diff --git a/src/farcasterd/runtime.rs b/src/farcasterd/runtime.rs
index 4ac59896..07229a4c 100644
--- a/src/farcasterd/runtime.rs
+++ b/src/farcasterd/runtime.rs
@@ -29,6 +29,7 @@ use crate::{
     bus::info::{InfoMsg, NodeInfo, OfferInfo, OfferStatusSelector, ProgressEvent, SwapProgress},
     bus::{Failure, FailureCode, Progress},
     clap::Parser,
+    config::ParsedSwapConfig,
     error::SyncerError,
     service::Endpoints,
 };
@@ -121,7 +122,7 @@ pub struct Runtime {
     progress: HashMap<ServiceId, VecDeque<ProgressStack>>, // A mapping from Swap ServiceId to its sent and received progress messages (Progress, Success, Failure)
     progress_subscriptions: HashMap<ServiceId, HashSet<ServiceId>>, // A mapping from a Client ServiceId to its subsribed swap progresses
     pub stats: Stats,             // Some stats about offers and swaps
-    pub config: Config,           // Configuration for syncers, auto-funding, and grpc
+    pub config: Config,           // The complete node configuration
     pub syncer_task_counter: u32, // A strictly incrementing counter of issued syncer tasks
     pub trade_state_machines: Vec<TradeStateMachine>, // New trade state machines are inserted on creation and destroyed upon state machine end transitions
     syncer_state_machines: HashMap<TaskId, SyncerStateMachine>, // New syncer state machines are inserted by their syncer task id when sending a syncer request and destroyed upon matching syncer request receival
@@ -1229,25 +1230,35 @@ pub fn syncer_up(
     }
 }
 
-#[allow(clippy::too_many_arguments)]
+/// Launch a swapd instance with all the necessary paramters for: swap id, offer to use, trade role
+/// to execute, temporal safety arguments.
 pub fn launch_swapd(
     local_trade_role: TradeRole,
     public_offer: PublicOffer,
     swap_id: SwapId,
-) -> Result<String, Error> {
+    swap_config: ParsedSwapConfig,
+) -> Result<(), Error> {
     debug!("Instantiating swapd...");
     let child = launch(
         "swapd",
         &[
+            "--arb-finality".to_string(),
+            swap_config.arbitrating.finality.to_string(),
+            "--arb-safety".to_string(),
+            swap_config.arbitrating.safety.to_string(),
+            "--acc-finality".to_string(),
+            swap_config.accordant.finality.to_string(),
+            "--id".to_string(),
             swap_id.to_hex(),
+            "--public-offer".to_string(),
             public_offer.to_string(),
+            "--trade-role".to_string(),
             local_trade_role.to_string(),
         ],
     )?;
-    let msg = format!("New instance of swapd launched with PID {}", child.id());
-    debug!("{}", msg);
+    debug!("New instance of swapd launched with PID {}", child.id());
     debug!("Awaiting for swapd to connect...");
-    Ok(msg)
+    Ok(())
 }
 
 /// Return the list of needed arguments for a syncer given a config and a network.
diff --git a/src/farcasterd/trade_state_machine.rs b/src/farcasterd/trade_state_machine.rs
index d35deef4..25e366a5 100644
--- a/src/farcasterd/trade_state_machine.rs
+++ b/src/farcasterd/trade_state_machine.rs
@@ -5,7 +5,7 @@ use crate::bus::ctl::{
 use crate::bus::info::{InfoMsg, MadeOffer, OfferInfo, TookOffer};
 use crate::bus::p2p::{Commit, PeerMsg};
 use crate::bus::{CheckpointEntry, Failure, FailureCode, OfferStatus, OfferStatusPair};
-use crate::farcasterd::runtime::{launch, launch_swapd, syncer_up, Runtime};
+use crate::farcasterd::runtime::{launch_swapd, syncer_up, Runtime};
 use crate::LogStyle;
 use crate::{
     bus::{BusMsg, Outcome},
@@ -13,12 +13,12 @@ use crate::{
     event::{Event, StateMachine, StateMachineExecutor},
     ServiceId,
 };
-use bitcoin::hashes::hex::ToHex;
 use farcaster_core::blockchain::Blockchain;
 use farcaster_core::role::TradeRole;
 use farcaster_core::swap::{btcxmr::PublicOffer, SwapId};
 use internet2::addr::{NodeAddr, NodeId};
 use microservices::esb::Handler;
+use std::convert::TryInto;
 use std::str::FromStr;
 
 /// State machine for launching a swap and cleaning up once done.
@@ -619,30 +619,27 @@ fn attempt_transition_to_restoring_swapd(
                 false
             };
 
+            let swap_config = runtime.config.get_swap_config(
+                public_offer.offer.arbitrating_blockchain.try_into()?,
+                public_offer.offer.accordant_blockchain.try_into()?,
+                public_offer.offer.network,
+            )?;
             let arbitrating_syncer_up = syncer_up(
                 &mut runtime.spawning_services,
                 &mut runtime.registered_services,
-                Blockchain::Bitcoin,
+                public_offer.offer.arbitrating_blockchain,
                 public_offer.offer.network,
                 &runtime.config,
             )?;
             let accordant_syncer_up = syncer_up(
                 &mut runtime.spawning_services,
                 &mut runtime.registered_services,
-                Blockchain::Monero,
+                public_offer.offer.accordant_blockchain,
                 public_offer.offer.network,
                 &runtime.config,
             )?;
 
-            let _child = launch(
-                "swapd",
-                &[
-                    swap_id.to_hex(),
-                    public_offer.to_string(),
-                    trade_role.to_string(),
-                ],
-            )?;
-
+            launch_swapd(trade_role, public_offer.clone(), swap_id, swap_config)?;
             event.complete_client_info(InfoMsg::String("Restoring checkpoint.".to_string()))?;
 
             Ok(Some(TradeStateMachine::RestoringSwapd(RestoringSwapd {
@@ -944,20 +941,24 @@ fn transition_to_swapd_launched_tsm(
     target_monero_address: monero::Address,
     swap_id: SwapId,
 ) -> Result<TradeStateMachine, Error> {
+    let swap_config = runtime.config.get_swap_config(
+        public_offer.offer.arbitrating_blockchain.try_into()?,
+        public_offer.offer.accordant_blockchain.try_into()?,
+        public_offer.offer.network,
+    )?;
     let SwapKeys { key_manager, .. } = swap_keys;
-    let network = public_offer.offer.network;
     let arbitrating_syncer_up = syncer_up(
         &mut runtime.spawning_services,
         &mut runtime.registered_services,
-        Blockchain::Bitcoin,
-        network,
+        public_offer.offer.arbitrating_blockchain,
+        public_offer.offer.network,
         &runtime.config,
     )?;
     let accordant_syncer_up = syncer_up(
         &mut runtime.spawning_services,
         &mut runtime.registered_services,
-        Blockchain::Monero,
-        network,
+        public_offer.offer.accordant_blockchain,
+        public_offer.offer.network,
         &runtime.config,
     )?;
     trace!(
@@ -970,6 +971,7 @@ fn transition_to_swapd_launched_tsm(
         consumed_offer_role.clone().into(),
         public_offer.clone(),
         swap_id,
+        swap_config,
     )?;
 
     Ok(TradeStateMachine::SwapdLaunched(SwapdLaunched {
diff --git a/src/lib.rs b/src/lib.rs
index 47d8e9cd..b2a50c8a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -44,6 +44,7 @@ extern crate serde_with;
 
 #[cfg(feature = "_rpc")]
 pub mod bus;
+mod chains;
 #[cfg(feature = "cli")]
 pub mod cli;
 pub mod client;
@@ -75,6 +76,7 @@ pub mod walletd;
 pub use crate::config::Config;
 #[cfg(feature = "_rpc")]
 pub use crate::service::ServiceConfig;
+pub use chains::{AccordantBlockchain, ArbitratingBlockchain};
 pub use error::Error;
 #[cfg(feature = "_rpc")]
 pub use service::{CtlServer, Endpoints, LogStyle, Service, ServiceId, TryToServiceId};
diff --git a/src/swapd/opts.rs b/src/swapd/opts.rs
index 90c599a6..994eac2f 100644
--- a/src/swapd/opts.rs
+++ b/src/swapd/opts.rs
@@ -12,8 +12,10 @@
 // along with this software.
 // If not, see <https://opensource.org/licenses/MIT>.
 
-use farcaster_core::swap::SwapId;
-use farcaster_core::{role::TradeRole, swap::btcxmr::PublicOffer};
+use farcaster_core::{
+    role::TradeRole,
+    swap::{btcxmr::PublicOffer, SwapId},
+};
 use std::str::FromStr;
 
 /// Swap executor daemon; part of Farcaster Node
@@ -23,18 +25,37 @@ use std::str::FromStr;
 #[derive(Parser, Clone, PartialEq, Eq, Debug)]
 #[clap(name = "swapd", bin_name = "swapd", author, version)]
 pub struct Opts {
-    /// Swap id
-    #[clap(parse(try_from_str = SwapId::from_str))]
+    /// The unique swap identifier; used between nodes to reference this unique swap
+    #[clap(long = "id", parse(try_from_str = SwapId::from_str))]
     pub swap_id: SwapId,
 
-    /// Public offer to initiate swapd runtime
-    #[clap(parse(try_from_str = FromStr::from_str))]
+    /// Public offer to initiate the swap runtime
+    #[clap(long, parse(try_from_str = FromStr::from_str))]
     pub public_offer: PublicOffer,
 
-    /// Trade role of participant (Maker or Taker)
-    #[clap(parse(try_from_str = FromStr::from_str))]
+    /// Trade role to execute; maker propose the trade, taker accept the trade. Swap role (Alice or
+    /// Bob) is defined by the executed trade role and the public offer, if we are the maker we
+    /// execute the `maker role` defined in the public offer, otherwise we execute the
+    /// complementary role
+    #[clap(long, parse(try_from_str = FromStr::from_str), possible_values = &["maker", "Maker", "taker", "Taker"])]
     pub trade_role: TradeRole,
 
+    /// Finality argument used for the arbitrating blockchain; defines when transactions are
+    /// considered final
+    #[clap(long = "arb-finality")]
+    pub arbitrating_finality: u8,
+
+    /// Safety argument used for the arbitrating blockchain to avoid races; a transaction will not
+    /// be broadcasted if the safety number of blocks unmined before unlocking another transaction
+    /// is not respected
+    #[clap(long = "arb-safety")]
+    pub arbitrating_safety: u8,
+
+    /// Finality argument used for the accordant blockchain; defines when transactions are
+    /// considered final
+    #[clap(long = "acc-finality")]
+    pub accordant_finality: u8,
+
     /// These params can be read also from the configuration file, not just
     /// Command-line args or environment variables
     #[clap(flatten)]
diff --git a/src/swapd/runtime.rs b/src/swapd/runtime.rs
index 45cd007a..997b7c17 100644
--- a/src/swapd/runtime.rs
+++ b/src/swapd/runtime.rs
@@ -25,7 +25,7 @@ use crate::swapd::wallet::{
     HandleBuyProcedureSignatureRes, HandleCoreArbitratingSetupRes,
     HandleRefundProcedureSignaturesRes,
 };
-use crate::swapd::StateReport;
+use crate::swapd::{Opts, StateReport};
 use crate::syncerd::bitcoin_syncer::p2wpkh_signed_tx_fee;
 use crate::syncerd::types::{AddressTransaction, Boolean, Event, Task, TransactionConfirmations};
 use crate::syncerd::{FeeEstimation, FeeEstimations};
@@ -41,7 +41,6 @@ use crate::{
         Abort, HeightChanged, SweepSuccess, TaskTarget, TransactionRetrieved, XmrAddressAddendum,
     },
 };
-
 use crate::{CtlServer, Error, LogStyle, Service, ServiceConfig, ServiceId};
 
 use std::collections::HashMap;
@@ -69,12 +68,17 @@ use internet2::{
 use microservices::esb::{self, Handler};
 use strict_encoding::{StrictDecode, StrictEncode};
 
-pub fn run(
-    config: ServiceConfig,
-    swap_id: SwapId,
-    public_offer: PublicOffer,
-    local_trade_role: TradeRole,
-) -> Result<(), Error> {
+pub fn run(config: ServiceConfig, opts: Opts) -> Result<(), Error> {
+    let Opts {
+        swap_id,
+        public_offer,
+        trade_role: local_trade_role,
+        arbitrating_finality,
+        arbitrating_safety,
+        accordant_finality,
+        ..
+    } = opts;
+
     let Offer {
         cancel_timelock,
         punish_timelock,
@@ -84,6 +88,7 @@ pub fn run(
         arbitrating_amount: bitcoin_amount,
         ..
     } = public_offer.offer;
+
     // alice or bob
     let local_swap_role = match local_trade_role {
         TradeRole::Maker => maker_role,
@@ -94,7 +99,6 @@ pub fn run(
         SwapRole::Alice => State::Alice(AliceState::StartA { local_trade_role }),
         SwapRole::Bob => State::Bob(BobState::StartB { local_trade_role }),
     };
-    let sweep_monero_thr = 10;
     info!(
         "{}: {}",
         "Starting swap".to_string().bright_green_bold(),
@@ -109,10 +113,10 @@ pub fn run(
     let temporal_safety = TemporalSafety {
         cancel_timelock: cancel_timelock.as_u32(),
         punish_timelock: punish_timelock.as_u32(),
-        btc_finality_thr: 1,
-        race_thr: 3,
-        xmr_finality_thr: 1,
-        sweep_monero_thr,
+        btc_finality_thr: arbitrating_finality.into(),
+        race_thr: arbitrating_safety.into(),
+        xmr_finality_thr: accordant_finality.into(),
+        sweep_monero_thr: crate::swapd::temporal_safety::SWEEP_MONERO_THRESHOLD,
     };
 
     temporal_safety.valid_params()?;
diff --git a/src/swapd/temporal_safety.rs b/src/swapd/temporal_safety.rs
index 7bfdee0d..e53e79ce 100644
--- a/src/swapd/temporal_safety.rs
+++ b/src/swapd/temporal_safety.rs
@@ -2,23 +2,40 @@ use crate::Error;
 use farcaster_core::blockchain::Blockchain;
 use strict_encoding::{StrictDecode, StrictEncode};
 
+/// Represent a blockchain height
 pub type BlockHeight = u32;
 
+/// Represent a block length or a block number
+pub type BlockSpan = u32;
+
+/// The minimum number of block confirmations required before sweeping
+pub const SWEEP_MONERO_THRESHOLD: u32 = 10;
+
+/// List of parameters used to determined if a transaction should be considered final or not and if
+/// it is safe to broadcast a transaction given the timelocks and confirmations of other
+/// transactions.
 #[derive(Debug, Clone, StrictEncode, StrictDecode)]
 pub struct TemporalSafety {
+    /// First timelock in the protocol, used to abort the swap instead of buying tx
     pub cancel_timelock: BlockHeight,
+    /// Second timelock in the protocol, if this timelock is reached Bob miss-behaved and will lose
+    /// money
     pub punish_timelock: BlockHeight,
-    pub race_thr: BlockHeight,
-    pub btc_finality_thr: BlockHeight,
-    pub xmr_finality_thr: BlockHeight,
-    pub sweep_monero_thr: BlockHeight,
+    /// The minimum number of blocks that should remain unmined before the next transaction to be
+    /// considered safe
+    pub race_thr: BlockSpan,
+    /// Number of confirmation required for the arbitrating blockchain to consider a tx final
+    pub btc_finality_thr: BlockSpan,
+    /// Number of confirmation required for the accordant blockchain to consider a tx final
+    pub xmr_finality_thr: BlockSpan,
+    // FIXME: this should be removed, used only const instead
+    pub sweep_monero_thr: BlockSpan,
 }
 
 impl TemporalSafety {
-    /// check if temporal params are in correct order
+    /// Validate if temporal parameters are coherent
     pub fn valid_params(&self) -> Result<(), Error> {
         let btc_finality = self.btc_finality_thr;
-        // let xmr_finality = self.xmr_finality_thr;
         let cancel = self.cancel_timelock;
         let punish = self.punish_timelock;
         let race = self.race_thr;
@@ -27,16 +44,16 @@ impl TemporalSafety {
             && btc_finality < race
             && punish > race
             && cancel > race
-        // && btc_finality < xmr_finality
         {
             Ok(())
         } else {
             Err(Error::Farcaster(s!(
-                "unsafe and invalid temporal parameters, timelocks, race and tx finality params"
+                "Unsafe or invalid temporal parameters between: timelocks, finality, and safety"
             )))
         }
     }
-    /// returns whether tx is final given the finality threshold set for the chain
+
+    /// Returns whether tx is final given the finality threshold set for the chain
     pub fn final_tx(&self, confs: u32, blockchain: Blockchain) -> bool {
         let finality_thr = match blockchain {
             Blockchain::Bitcoin => self.btc_finality_thr,
@@ -44,40 +61,49 @@ impl TemporalSafety {
         };
         confs >= finality_thr
     }
-    /// lock must be final, cancel cannot be raced, add + 1 to offset initial lock confirmation
+
+    /// Lock must be final, cancel cannot be raced, add + 1 to offset initial lock confirmation
     pub fn stop_funding_before_cancel(&self, lock_confirmations: u32) -> bool {
         self.final_tx(lock_confirmations, Blockchain::Bitcoin)
             && lock_confirmations > (self.cancel_timelock - self.race_thr + 1)
     }
-    // blocks remaining until funding will be stopped for safety, because it is too close to cancel. Adds the same +1 offset as in stop_funding_before_cancel
+
+    // Blocks remaining until funding will be stopped for safety, because it is too close to
+    // cancel. Adds the same +1 offset as in stop_funding_before_cancel
     pub fn blocks_until_stop_funding(&self, lock_confirmations: u32) -> i64 {
         self.cancel_timelock as i64 - (self.race_thr as i64 + 1 + lock_confirmations as i64)
     }
-    /// lock must be final, valid after lock_minedblock + cancel_timelock
+
+    /// Lock must be final, valid after lock_minedblock + cancel_timelock
     pub fn valid_cancel(&self, lock_confirmations: u32) -> bool {
         self.final_tx(lock_confirmations, Blockchain::Bitcoin)
             && lock_confirmations >= self.cancel_timelock
     }
-    /// blocks remaining until cancel, copies logic from valid_cancel
+
+    /// Blocks remaining until cancel, copies logic from valid_cancel
     pub fn blocks_until_cancel(&self, lock_confirmations: u32) -> i64 {
         self.cancel_timelock as i64 - lock_confirmations as i64
     }
-    /// lock must be final, but buy shall not be raced with cancel
+
+    /// Lock must be final, but buy shall not be raced with cancel
     pub fn safe_buy(&self, lock_confirmations: u32) -> bool {
         self.final_tx(lock_confirmations, Blockchain::Bitcoin)
             && lock_confirmations <= (self.cancel_timelock - self.race_thr)
     }
-    /// cancel must be final, but refund shall not be raced with punish
+
+    /// Cancel must be final, but refund shall not be raced with punish
     pub fn safe_refund(&self, cancel_confirmations: u32) -> bool {
         self.final_tx(cancel_confirmations, Blockchain::Bitcoin)
             && cancel_confirmations <= (self.punish_timelock - self.race_thr)
     }
-    /// cancel must be final, valid after cancel_confirmations > punish_timelock
+
+    /// Cancel must be final, valid after cancel_confirmations > punish_timelock
     pub fn valid_punish(&self, cancel_confirmations: u32) -> bool {
         self.final_tx(cancel_confirmations, Blockchain::Bitcoin)
             && cancel_confirmations >= self.punish_timelock
     }
-    /// blocks remaning until punish, copies logic from valid_punish
+
+    /// Blocks remaning until punish, copies logic from valid_punish
     pub fn blocks_until_punish_after_cancel(&self, cancel_confirmations: u32) -> i64 {
         self.punish_timelock as i64 - cancel_confirmations as i64
     }


Test patch:
diff --git a/tests/cfg/fc1.ci.toml b/tests/cfg/fc1.ci.toml
index 316f678a..347df8d1 100644
--- a/tests/cfg/fc1.ci.toml
+++ b/tests/cfg/fc1.ci.toml
@@ -8,6 +8,13 @@ enable = true
 bind_port = 23432
 bind_ip = "0.0.0.0"
 
+[swap.bitcoin.local]
+safety = 3
+finality = 1
+
+[swap.monero.local]
+finality = 1
+
 [syncers.local]
 electrum_server = "tcp://electrs:60401"
 monero_daemon = "http://monerod:18081"
diff --git a/tests/cfg/fc1.toml b/tests/cfg/fc1.toml
index ab402777..70181a1e 100644
--- a/tests/cfg/fc1.toml
+++ b/tests/cfg/fc1.toml
@@ -8,6 +8,13 @@ enable = true
 bind_port = 23432
 bind_ip = "0.0.0.0"
 
+[swap.bitcoin.local]
+safety = 3
+finality = 1
+
+[swap.monero.local]
+finality = 1
+
 [syncers.local]
 electrum_server = "tcp://localhost:60401"
 monero_daemon = "http://localhost:18081"
diff --git a/tests/cfg/fc2.ci.toml b/tests/cfg/fc2.ci.toml
index ab2a56d7..3a23b866 100644
--- a/tests/cfg/fc2.ci.toml
+++ b/tests/cfg/fc2.ci.toml
@@ -8,6 +8,13 @@ enable = true
 bind_port = 23433
 bind_ip = "0.0.0.0"
 
+[swap.bitcoin.local]
+safety = 3
+finality = 1
+
+[swap.monero.local]
+finality = 1
+
 [syncers.local]
 electrum_server = "tcp://electrs:60401"
 monero_daemon = "http://monerod:18081"
diff --git a/tests/cfg/fc2.toml b/tests/cfg/fc2.toml
index 96f1fee2..70368f00 100644
--- a/tests/cfg/fc2.toml
+++ b/tests/cfg/fc2.toml
@@ -8,6 +8,13 @@ enable = true
 bind_port = 23433
 bind_ip = "0.0.0.0"
 
+[swap.bitcoin.local]
+safety = 3
+finality = 1
+
+[swap.monero.local]
+finality = 1
+
 [syncers.local]
 electrum_server = "tcp://localhost:60401"
 monero_daemon = "http://localhost:18081"