Skip to content

Commit

Permalink
feat(main.rs, etc): fixed not to use struct opt, etc
Browse files Browse the repository at this point in the history
Signed-off-by: Taiga Nakayama <dora@dora-gt.jp>

interledger#113, interledger#206, interledger#171, interledger#194
  • Loading branch information
dora-gt committed Aug 28, 2019
1 parent 5eb7b79 commit 40778d4
Show file tree
Hide file tree
Showing 10 changed files with 580 additions and 573 deletions.
1 change: 0 additions & 1 deletion crates/interledger-settlement-engines/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ 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"
Expand Down
323 changes: 156 additions & 167 deletions crates/interledger-settlement-engines/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,209 +1,198 @@
use config::{Source, Value, *};
use std::collections::{hash_map::RandomState, HashMap};
use config::{Source, Value, Config, ConfigError};
use std::str::FromStr;
use structopt::StructOpt;
use tokio;
use url::Url;
use clap::{App, Arg, ArgMatches, AppSettings, SubCommand, crate_version};
use serde::Deserialize;

use interledger_settlement_engines::engines::ethereum_ledger::{run_ethereum_engine, EthAddress};

pub fn main() {
env_logger::init();
let opt = CliOpt::from_args();
opt.execute();
}

// The commands structure
// interledger-settlement-engines
// - ethereum-ledger
let mut app = App::new("interledger-settlement-engines")
.about("Interledger Settlement Engines CLI")
.version(crate_version!())
.setting(AppSettings::SubcommandsNegateReqs)
.setting(AppSettings::ArgsNegateSubcommands)
.subcommands(vec![
SubCommand::with_name("ethereum-ledger")
.about("Ethereum settlement engine which performs ledger (layer 1) transactions")
.args(&[
Arg::with_name("config")
.long("config")
.short("c")
.takes_value(true)
.help("Name of config file (in JSON, TOML, YAML, or INI format)")
.index(1),
Arg::with_name("port")
.long("port")
.short("p")
.takes_value(true)
.default_value("3000")
.help("Port to listen for settlement requests on"),
Arg::with_name("key")
.long("key")
.takes_value(true)
.help("private key for settlement account (REQUIRED)"),
Arg::with_name("ethereum_endpoint")
.long("ethereum_endpoint")
.takes_value(true)
.default_value("http://127.0.0.1:8545")
.help("Ethereum node endpoint"),
Arg::with_name("token_address")
.long("token_address")
.takes_value(true)
.default_value("")
.help("The address of the ERC20 token to be used for settlement (defaults to sending ETH if no token address is provided)"),
Arg::with_name("connector_url")
.long("connector_url")
.takes_value(true)
.help("Connector Settlement API endpoint")
.default_value("http://127.0.0.1:7771"),
Arg::with_name("redis_uri")
.long("redis_uri")
.takes_value(true)
.default_value("redis://127.0.0.1:6379")
.help("Redis database to add the account to"),
Arg::with_name("chain_id")
.long("chain_id")
.takes_value(true)
.default_value("1")
.help("The chain id so that the signer calculates the v value of the sig appropriately"),
Arg::with_name("confirmations")
.long("confirmations")
.takes_value(true)
.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"),
Arg::with_name("asset_scale")
.long("asset_scale")
.takes_value(true)
.default_value("18")
.help("The asset scale you want to use for your payments"),
Arg::with_name("poll_frequency")
.long("poll_frequency")
.takes_value(true)
.default_value("5000")
.help("The frequency in milliseconds at which the engine will check the blockchain about the confirmation status of a tx"),
Arg::with_name("watch_incoming")
.long("watch_incoming")
.default_value("true")
.help("Launch a blockchain watcher that listens for incoming transactions and notifies the connector upon sufficient confirmations"),
])
]);

impl CliOpt {
fn execute(&self) {
match &self.sub_command {
TopSubCommands::EthereumLedger(opt) => opt.execute(),
};
let matches = app.clone().get_matches();
match matches.subcommand() {
("ethereum-ledger", Some(ethereum_ledger_matches)) => {
let config = get_config(ethereum_ledger_matches, "ILP");
get_or_error(config.try_into::<EthereumLedgerOpt>()).execute();
},
("", None) => {
app.print_help().unwrap()
},
_ => unreachable!(),
}
}

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 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: String = self.token_address.clone();
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::<bool>("without_incoming_watcher").unwrap();
let redis_uri = Url::parse(&self.redis_uri).expect("redis_uri is not a valid URI");

// TODO make key compatible with
// https://github.com/tendermint/signatory to have HSM sigs

tokio::run(run_ethereum_engine(
redis_uri,
ethereum_endpoint,
settlement_port,
private_key,
chain_id,
confirmations,
asset_scale,
poll_frequency,
connector_url,
self.ethereum_endpoint.clone(),
self.port,
self.key.clone(),
self.chain_id,
self.confirmations,
self.asset_scale,
self.poll_frequency,
self.connector_url.clone(),
token_address,
watch_incoming,
self.watch_incoming,
));
}
}

impl Source for EthereumLedgerOpt {
fn clone_into_box(&self) -> Box<Source + Send + Sync> {
Box::new(self.clone())
}

fn collect(&self) -> Result<HashMap<String, Value, RandomState>, ConfigError> {
let mut hash_map = HashMap::new();
hash_map.insert("port".to_string(), Value::new(None, i64::from(self.port)));
hash_map.insert(
"key".to_string(),
Value::new(None, self.key.clone()),
);
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, i64::from(self.chain_id)),
);
hash_map.insert(
"confirmations".to_string(),
Value::new(None, i64::from(self.confirmations)),
);
hash_map.insert(
"asset_scale".to_string(),
Value::new(None, i64::from(self.asset_scale)),
);
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)]
#[derive(Deserialize, 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: String,
#[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,
watch_incoming: bool,
}

fn get_config(matches: &ArgMatches, env_prefix: &str) -> Config {
// priority:
// (low) args -> config file -> env vars (high)
let mut config = config::Config::new();

for (key, value) in &matches.args {
if value.vals.is_empty() {
// flag
config.set(key, Value::new(None, true)).unwrap();
}
else {
// value
config.set(key, Value::new(None, value.vals[0].to_str().unwrap())).unwrap();
}
}

// because `merge` doesn't override, `set` manually
if let Some(config_path) = matches.value_of("config") {
let file_config = config::File::with_name(config_path);
let file_config = file_config.collect().unwrap();
for (k,v) in file_config {
config.set(&k, v).unwrap();
}
}

// HACK parameter named "address" from env is assumed that it was originally "ILP_ADDRESS"
// so we amend it to be "ilp_address" after removing prefixes.
// if we don't do this, `ilp_address` has to be `address` which looks a bit confusing.
// even if we set an alias to `address`, it could cause a duplicated key error
// when deserializing because there are 2 pointers to `ilp_address`: `ilp_address`, `address`.
let env = config::Environment::with_prefix(env_prefix);
let env = env.collect().unwrap();
for (k,v) in env {
if k == "address" {
config.set("ilp_address", v).unwrap();
} else {
config.set(&k, v).unwrap();
}
}

config
}

fn get_or_error<T>(item: Result<T, ConfigError>) -> T {
match item {
Ok(item) => item,
Err(error) => {
match error {
ConfigError::Message(message) => eprintln!("Configuration error. You might lack a parameter or its format is invalid: {:?}", message),
_ => eprintln!("{:?}", error),
};
std::process::exit(1);
},
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ fn eth_ledger_settlement() {

let node1_secret = cli::random_secret();
let node1 = InterledgerNode {
address: Address::from_str("example.alice").unwrap(),
ilp_address: Address::from_str("example.alice").unwrap(),
default_spsp_account: None,
admin_auth_token: "hi_alice".to_string(),
redis_connection: connection_info1.clone(),
Expand Down Expand Up @@ -129,7 +129,7 @@ fn eth_ledger_settlement() {

let node2_secret = cli::random_secret();
let node2 = InterledgerNode {
address: Address::from_str("example.bob").unwrap(),
ilp_address: Address::from_str("example.bob").unwrap(),
default_spsp_account: None,
admin_auth_token: "admin".to_string(),
redis_connection: connection_info2.clone(),
Expand Down
Loading

0 comments on commit 40778d4

Please sign in to comment.