diff --git a/crates/interledger-settlement-engines/Cargo.toml b/crates/interledger-settlement-engines/Cargo.toml index 8ba255f16..90fa66240 100644 --- a/crates/interledger-settlement-engines/Cargo.toml +++ b/crates/interledger-settlement-engines/Cargo.toml @@ -37,6 +37,8 @@ sha3 = "0.8.2" num-bigint = "0.2.2" num-traits = "0.2.8" lazy_static = "1.3.0" +config = "0.9.3" +structopt = "0.2.18" [dev-dependencies] lazy_static = "1.3" diff --git a/crates/interledger-settlement-engines/src/main.rs b/crates/interledger-settlement-engines/src/main.rs index 061ed8258..d9b2cfc72 100644 --- a/crates/interledger-settlement-engines/src/main.rs +++ b/crates/interledger-settlement-engines/src/main.rs @@ -1,106 +1,209 @@ -use clap::{value_t, App, Arg, SubCommand}; +use config::{Source, Value, *}; +use std::collections::{hash_map::RandomState, HashMap}; use std::str::FromStr; +use structopt::StructOpt; use tokio; use url::Url; use interledger_settlement_engines::engines::ethereum_ledger::{run_ethereum_engine, EthAddress}; -#[allow(clippy::cognitive_complexity)] pub fn main() { env_logger::init(); + let opt = CliOpt::from_args(); + opt.execute(); +} + +// The commands structure +// interledger-settlement-engines +// - ethereum-ledger + +impl CliOpt { + fn execute(&self) { + match &self.sub_command { + TopSubCommands::EthereumLedger(opt) => opt.execute(), + }; + } +} + +impl EthereumLedgerOpt { + fn execute(&self) { + let mut config = config::Config::new(); + config.merge(self.clone()).unwrap(); + config + .merge(config::Environment::with_prefix("ILP")) + .unwrap(); - let mut app = App::new("interledger-settlement-engines") - .about("Interledger Settlement Engines CLI") - .subcommands(vec![ - SubCommand::with_name("ethereum-ledger") - .about("Ethereum settlement engine which performs ledger (layer 1) transactions") - .args(&[ - Arg::with_name("port") - .long("port") - .help("Port to listen for settlement requests on") - .default_value("3000"), - Arg::with_name("key") - .long("key") - .help("private key for settlement account") - .takes_value(true) - .required(true), - Arg::with_name("ethereum_endpoint") - .long("ethereum_endpoint") - .help("Ethereum node endpoint") - .default_value("http://127.0.0.1:8545"), - Arg::with_name("token_address") - .long("token_address") - .help("The address of the ERC20 token to be used for settlement (defaults to sending ETH if no token address is provided)") - .default_value(""), - Arg::with_name("connector_url") - .long("connector_url") - .help("Connector Settlement API endpoint") - .default_value("http://127.0.0.1:7771"), - Arg::with_name("redis_uri") - .long("redis_uri") - .help("Redis database to add the account to") - .default_value("redis://127.0.0.1:6379"), - Arg::with_name("chain_id") - .long("chain_id") - .help("The chain id so that the signer calculates the v value of the sig appropriately") - .default_value("1"), - Arg::with_name("confirmations") - .long("confirmations") - .help("The number of confirmations the engine will wait for a transaction's inclusion before it notifies the node of its success") - .default_value("6"), - Arg::with_name("asset_scale") - .long("asset_scale") - .help("The asset scale you want to use for your payments (default: 18)") - .default_value("18"), - Arg::with_name("poll_frequency") - .long("poll_frequency") - .help("The frequency in milliseconds at which the engine will check the blockchain about the confirmation status of a tx") - .default_value("5000"), - Arg::with_name("watch_incoming") - .long("watch_incoming") - .help("Launch a blockchain watcher that listens for incoming transactions and notifies the connector upon sufficient confirmations") - .default_value("true"), - ]) - ] - ); + let settlement_port = config.get("port").unwrap(); + // TODO make compatible with + // https://github.com/tendermint/signatory to have HSM sigs + let private_key: String = config.get("key").unwrap(); + let ethereum_endpoint: String = config.get("ethereum_endpoint").unwrap(); + let token_address: String = config.get("token_address").unwrap(); + let token_address = if token_address.len() == 20 { + Some(EthAddress::from_str(&token_address).unwrap()) + } else { + None + }; + let connector_url: String = config.get("connector_url").unwrap(); + let redis_uri: String = config.get("redis_uri").unwrap(); + let redis_uri = Url::parse(&redis_uri).expect("redis_uri is not a valid URI"); + let chain_id = config.get("chain_id").unwrap(); + let confirmations = config.get("confirmations").unwrap(); + let asset_scale = config.get("asset_scale").unwrap(); + let poll_frequency = config.get("poll_frequency").unwrap(); + let watch_incoming = !config.get::("without_incoming_watcher").unwrap(); + + tokio::run(run_ethereum_engine( + redis_uri, + ethereum_endpoint, + settlement_port, + private_key, + chain_id, + confirmations, + asset_scale, + poll_frequency, + connector_url, + token_address, + watch_incoming, + )); + } +} - match app.clone().get_matches().subcommand() { - ("ethereum-ledger", Some(matches)) => { - let settlement_port = - value_t!(matches, "port", u16).expect("port for settlement engine required"); - // TODO make compatible with - // https://github.com/tendermint/signatory to have HSM sigs - let private_key: String = value_t!(matches, "key", String).unwrap(); - let ethereum_endpoint: String = value_t!(matches, "ethereum_endpoint", String).unwrap(); - let token_address = value_t!(matches, "token_address", String).unwrap(); - let token_address = if token_address.len() == 20 { - Some(EthAddress::from_str(&token_address).unwrap()) - } else { - None - }; - let connector_url: String = value_t!(matches, "connector_url", String).unwrap(); - let redis_uri = value_t!(matches, "redis_uri", String).expect("redis_uri is required"); - let redis_uri = Url::parse(&redis_uri).expect("redis_uri is not a valid URI"); - let chain_id = value_t!(matches, "chain_id", u8).unwrap(); - let confirmations = value_t!(matches, "confirmations", u8).unwrap(); - let asset_scale = value_t!(matches, "asset_scale", u8).unwrap(); - let poll_frequency = value_t!(matches, "poll_frequency", u64).unwrap(); - let watch_incoming = value_t!(matches, "watch_incoming", bool).unwrap(); +impl Source for EthereumLedgerOpt { + fn clone_into_box(&self) -> Box { + Box::new(self.clone()) + } - tokio::run(run_ethereum_engine( - redis_uri, - ethereum_endpoint, - settlement_port, - private_key, - chain_id, - confirmations, - asset_scale, - poll_frequency, - connector_url, - token_address, - watch_incoming, - )); - } - _ => app.print_help().unwrap(), + fn collect(&self) -> Result, ConfigError> { + let mut hash_map = HashMap::new(); + hash_map.insert("port".to_string(), Value::new(None, self.port as i64)); + hash_map.insert( + "key".to_string(), + Value::new(None, self.key.clone().unwrap_or(String::new())), + ); + hash_map.insert( + "ethereum_endpoint".to_string(), + Value::new(None, self.ethereum_endpoint.to_string()), + ); + hash_map.insert( + "token_address".to_string(), + Value::new(None, self.token_address.to_string()), + ); + hash_map.insert( + "connector_url".to_string(), + Value::new(None, self.connector_url.to_string()), + ); + hash_map.insert( + "redis_uri".to_string(), + Value::new(None, self.redis_uri.to_string()), + ); + hash_map.insert( + "chain_id".to_string(), + Value::new(None, self.chain_id as i64), + ); + hash_map.insert( + "confirmations".to_string(), + Value::new(None, self.confirmations as i64), + ); + hash_map.insert( + "asset_scale".to_string(), + Value::new(None, self.asset_scale as i64), + ); + hash_map.insert( + "poll_frequency".to_string(), + Value::new(None, self.poll_frequency as i64), + ); + hash_map.insert( + "without_incoming_watcher".to_string(), + Value::new(None, self.without_incoming_watcher), + ); + Ok(hash_map) } } + +#[derive(Debug, StructOpt)] +#[structopt( + name = "interledger-settlement-engines", + about = "Interledger Settlement Engines CLI" +)] +struct CliOpt { + #[structopt(subcommand)] + sub_command: TopSubCommands, +} + +#[derive(Debug, StructOpt)] +enum TopSubCommands { + #[structopt( + name = "ethereum-ledger", + about = "Ethereum settlement engine which performs ledger (layer 1) transactions" + )] + EthereumLedger(EthereumLedgerOpt), +} + +#[derive(Debug, StructOpt, Clone)] +struct EthereumLedgerOpt { + #[structopt( + short = "p", + long = "port", + default_value = "3000", + help = "Port to listen for settlement requests on" + )] + port: u16, + #[structopt(long = "key", help = "private key for settlement account")] + key: Option, + #[structopt( + long = "ethereum_endpoint", + default_value = "http://127.0.0.1:8545", + help = "Ethereum node endpoint" + )] + ethereum_endpoint: String, + #[structopt( + long = "token_address", + default_value = "", + help = "The address of the ERC20 token to be used for settlement (defaults to sending ETH if no token address is provided)" + )] + token_address: String, + #[structopt( + long = "connector_url", + default_value = "http://127.0.0.1:7771", + help = "Connector Settlement API endpoint" + )] + connector_url: String, + #[structopt( + long = "redis_uri", + default_value = "redis://127.0.0.1:6379", + help = "Redis database to add the account to" + )] + redis_uri: String, + // Although the length of `chain_id` seems to be not limited on its specs, + // u8 seems sufficient at this point. + #[structopt( + long = "chain_id", + default_value = "1", + help = "The chain id so that the signer calculates the v value of the sig appropriately" + )] + chain_id: u8, + #[structopt( + long = "confirmations", + default_value = "6", + help = "The number of confirmations the engine will wait for a transaction's inclusion before it notifies the node of its success" + )] + confirmations: u8, + #[structopt( + long = "asset_scale", + default_value = "18", + help = "The asset scale you want to use for your payments (default: 18)" + )] + asset_scale: u8, + #[structopt( + long = "poll_frequency", + default_value = "5000", + help = "The frequency in milliseconds at which the engine will check the blockchain about the confirmation status of a tx" + )] + poll_frequency: u64, + #[structopt( + long = "without_incoming_watcher", + help = "Not launch a blockchain watcher that listens for incoming transactions and notifies the connector upon sufficient confirmations" + )] + without_incoming_watcher: bool, +} diff --git a/examples/eth-settlement/README.md b/examples/eth-settlement/README.md index efdc95f50..cd3d98d03 100644 --- a/examples/eth-settlement/README.md +++ b/examples/eth-settlement/README.md @@ -131,7 +131,6 @@ cargo run --package interledger-settlement-engines -- ethereum-ledger \ --ethereum_endpoint http://127.0.0.1:8545 \ --connector_url http://127.0.0.1:7771 \ --redis_uri redis://127.0.0.1:6379/0 \ ---watch_incoming true \ --port 3000 \ &> logs/node-alice-settlement-engine.log & @@ -143,7 +142,6 @@ cargo run --package interledger-settlement-engines -- ethereum-ledger \ --ethereum_endpoint http://127.0.0.1:8545 \ --connector_url http://127.0.0.1:8771 \ --redis_uri redis://127.0.0.1:6379/1 \ ---watch_incoming true \ --port 3001 \ &> logs/node-bob-settlement-engine.log & ``` diff --git a/examples/eth_xrp_three_nodes/README.md b/examples/eth_xrp_three_nodes/README.md index 580e4f077..634d40c41 100755 --- a/examples/eth_xrp_three_nodes/README.md +++ b/examples/eth_xrp_three_nodes/README.md @@ -193,7 +193,6 @@ cargo run --package interledger-settlement-engines -- ethereum-ledger \ --connector_url http://127.0.0.1:7771 \ --redis_uri redis://127.0.0.1:6379/0 \ --asset_scale 6 \ ---watch_incoming true \ --port 3000 \ &> logs/node-alice-settlement-engine-eth.log & @@ -206,7 +205,6 @@ cargo run --package interledger-settlement-engines -- ethereum-ledger \ --connector_url http://127.0.0.1:8771 \ --redis_uri redis://127.0.0.1:6380/0 \ --asset_scale 6 \ ---watch_incoming true \ --port 3001 \ &> logs/node-bob-settlement-engine-eth.log &