forked from interledger/interledger-rs
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(main.rs, etc): fixed not to use struct opt, etc
Signed-off-by: Taiga Nakayama <dora@dora-gt.jp> interledger#113, interledger#206, interledger#171, interledger#194
- Loading branch information
Showing
10 changed files
with
580 additions
and
573 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.