depolymerization

wire gateway for Bitcoin/Ethereum
Log | Files | Refs | Submodules | README | LICENSE

commit a02daa453da90f15ac1033e44ba3aaa54d38d1c3
parent 1e93728aebe96095dde1848e303f07fe9a22daf6
Author: Antoine A <>
Date:   Fri,  4 Mar 2022 21:22:32 +0100

Improve config parsing

Diffstat:
MCargo.lock | 42+++++++++++++++++++-----------------------
MREADME.md | 8++++----
Mbtc-wire/Cargo.toml | 6+++---
Mbtc-wire/src/bin/btc-wire-utils.rs | 25+++++++++----------------
Mbtc-wire/src/config.rs | 81+++++++++++++++++++++++++++++++++++++------------------------------------------
Mbtc-wire/src/lib.rs | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Mbtc-wire/src/loops/worker.rs | 6+++---
Mbtc-wire/src/main.rs | 25+++++++++++--------------
Mbtc-wire/src/rpc.rs | 5+----
Mbtc-wire/src/rpc_utils.rs | 23++++++++++++++++++++---
Mcommon/Cargo.toml | 13+++++++------
Mcommon/src/config.rs | 159++++++++++++++++++++++++++++++-------------------------------------------------
Mdocs/taler-btc.conf | 2+-
Mdocs/taler-eth.conf | 2+-
Meth-wire/Cargo.toml | 6+++---
Meth-wire/src/bin/eth-wire-utils.rs | 34++++++++++++----------------------
Meth-wire/src/lib.rs | 59++++++++++++++++++++++++++++++++++++++++++++++-------------
Meth-wire/src/loops/worker.rs | 8++++----
Meth-wire/src/main.rs | 63+++++++++++++++------------------------------------------------
Meth-wire/src/rpc.rs | 18++++++++++++------
Aeth-wire/src/rpc_utils.rs | 20++++++++++++++++++++
Minstrumentation/Cargo.toml | 4++--
Minstrumentation/src/btc.rs | 9++++-----
Minstrumentation/src/eth.rs | 24++++++------------------
Minstrumentation/src/main.rs | 8++++----
Mtest/btc/config.sh | 4++--
Mtest/btc/wire.sh | 2+-
Mtest/common.sh | 7++++---
Rtest/conf/bitcoin_auth.conf -> test/conf/bitcoin_auth0.conf | 0
Mwire-gateway/Cargo.toml | 12++++++------
Mwire-gateway/src/main.rs | 35++++++++++++++++++++---------------
31 files changed, 393 insertions(+), 394 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -10,9 +10,14 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.4.7" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] [[package]] name = "aho-corasick" @@ -480,12 +485,9 @@ dependencies = [ [[package]] name = "dlv-list" -version = "0.2.3" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68df3f2b690c1b86e65ef7830956aededf3cb0a16f898f79b9a6f421a7b6211b" -dependencies = [ - "rand", -] +checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" [[package]] name = "either" @@ -736,20 +738,14 @@ checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "hashbrown" -version = "0.9.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" dependencies = [ "ahash", ] [[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" - -[[package]] name = "heck" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -882,7 +878,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" dependencies = [ "autocfg", - "hashbrown 0.11.2", + "hashbrown", ] [[package]] @@ -1100,9 +1096,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" +checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" [[package]] name = "oorandom" @@ -1112,12 +1108,12 @@ checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] name = "ordered-multimap" -version = "0.3.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c672c7ad9ec066e428c00eb917124a06f08db19e2584de982cc34b1f4c12485" +checksum = "7b476c5fc0aad16f8b8d74e7df9da1813731fae300f7a923713c4c591905ff50" dependencies = [ "dlv-list", - "hashbrown 0.9.1", + "hashbrown", ] [[package]] @@ -1472,9 +1468,9 @@ checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" [[package]] name = "rust-ini" -version = "0.17.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63471c4aa97a1cf8332a5f97709a79a4234698de6a1f5087faf66f2dae810e22" +checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" dependencies = [ "cfg-if", "ordered-multimap", diff --git a/README.md b/README.md @@ -72,8 +72,8 @@ the RPC server. Two flags are mandatory: ```ini [depolymerizer-bitcoin] -# TODO -DATA_DIR = TODO +# Datadir or configuration file path +CONF_PATH = ~/.bitcoin # Number of blocks to consider a transactions durable CONFIRMATION = 6 # Amount to keep when bouncing malformed credit @@ -84,8 +84,8 @@ BOUNCE_FEE = BTC:0.00001 ```ini [depolymerizer-bitcoin] -# TODO -DATA_DIR = TODO +# Datadir or ipc file path +IPC_PATH = ~/.ethereum/geth/geth.ipc # Number of blocks to consider a transactions durable CONFIRMATION = 24 # Amount to keep when bouncing malformed credit diff --git a/btc-wire/Cargo.toml b/btc-wire/Cargo.toml @@ -16,12 +16,12 @@ bitcoin = { version = "0.27.1", features = [ "use-serde", ], default-features = false } # Cli args parser -clap = { version = "3.1.0", features = ["derive"] } +clap = { version = "3.1.5", features = ["derive"] } # Bech32 encoding and decoding bech32 = "0.8.1" # Serialization library serde = { version = "1.0.136", features = ["derive"] } -serde_json = "1.0.78" +serde_json = "1.0.79" serde_repr = "0.1.7" # Error macros thiserror = "1.0.30" @@ -31,7 +31,7 @@ base64 = "0.13.0" # Common lib common = { path = "../common" } # Ini parser -rust-ini = "0.17.0" +rust-ini = "0.18.0" # Hexadecimal encoding hex = "0.4.3" diff --git a/btc-wire/src/bin/btc-wire-utils.rs b/btc-wire/src/bin/btc-wire-utils.rs @@ -18,15 +18,11 @@ use std::path::PathBuf; use bitcoin::{Address, Amount, BlockHash, Network}; use btc_wire::{ config::BitcoinConfig, + load_taler_config, rpc::{Category, Rpc}, - rpc_utils::default_data_dir, }; use clap::StructOpt; -use common::{ - config::{Config, CoreConfig}, - postgres::NoTls, - rand_slice, -}; +use common::{postgres::NoTls, rand_slice}; /// btc-wire test utils #[derive(clap::Parser, Debug)] @@ -84,15 +80,12 @@ pub fn auto_wallet(rpc: &mut Rpc, config: &BitcoinConfig, name: &str) -> (Rpc, A fn main() { common::log::init(); let args = Args::parse(); - let config = args - .config - .map(|path| CoreConfig::load_taler_config(Some(&path), Some("BTC"))); - let data_dir: PathBuf = config - .as_ref() - .and_then(|it| it.data_dir.clone()) - .or(args.datadir) - .unwrap_or_else(default_data_dir); - let btc_config = BitcoinConfig::load(data_dir).unwrap(); + let taler_config = load_taler_config(args.config.as_deref()); + let btc_config = BitcoinConfig::load( + args.datadir.unwrap_or(taler_config.custom), + &taler_config.currency, + ) + .unwrap(); let mut rpc = Rpc::common(&btc_config).unwrap(); match args.cmd { @@ -128,7 +121,7 @@ fn main() { } Cmd::Resetdb => { let hash: BlockHash = rpc.get_genesis().unwrap(); - let mut db = config.unwrap().db_config.connect(NoTls).unwrap(); + let mut db = taler_config.db_config.connect(NoTls).unwrap(); let mut tx = db.transaction().unwrap(); // Clear transaction tables and reset state tx.execute("DELETE FROM tx_in", &[]).unwrap(); diff --git a/btc-wire/src/config.rs b/btc-wire/src/config.rs @@ -22,48 +22,36 @@ use std::{ use bitcoin::Network; use common::log::{fail, OrFail}; -pub const WIRE_WALLET_NAME: &str = "wire"; - -/// Default chain dir <https://github.com/bitcoin/bitcoin/blob/master/doc/files.md#data-directory-location> -fn chain_dir(network: Network) -> &'static str { - match network { - Network::Bitcoin => "main", - Network::Testnet => "testnet3", - Network::Regtest => "regtest", - Network::Signet => "signet", - } -} +use crate::{ + check_network_currency, + rpc_utils::{chain_dir, rpc_port}, +}; -/// Default rpc port <https://github.com/bitcoin/bitcoin/blob/master/share/examples/bitcoin.conf> -fn rpc_port(network: Network) -> u16 { - match network { - Network::Bitcoin => 8332, - Network::Testnet => 18332, - Network::Regtest => 18443, - Network::Signet => 38333, - } -} +pub const WIRE_WALLET_NAME: &str = "wire"; -#[derive(Clone)] +#[derive(Debug, Clone)] pub enum BtcAuth { - Cookie, + Cookie(PathBuf), Auth(String), } /// Bitcoin config relevant for btc-wire -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct BitcoinConfig { pub network: Network, - pub dir: PathBuf, pub addr: SocketAddr, pub auth: BtcAuth, - pub cookie_path: String, } impl BitcoinConfig { - /// Load from bitcoin data_dir - pub fn load(data_dir: impl AsRef<Path>) -> Result<Self, ini::Error> { - let conf = ini::Ini::load_from_file(data_dir.as_ref().join("bitcoin.conf"))?; + /// Load from bitcoin path + pub fn load(path: impl AsRef<Path>, currency: &str) -> Result<Self, ini::Error> { + let path = path.as_ref(); + let conf = if path.is_dir() { + ini::Ini::load_from_file(path.join("bitcoin.conf")) + } else { + ini::Ini::load_from_file(path) + }?; let main = conf.general_section(); @@ -85,6 +73,8 @@ impl BitcoinConfig { Network::Bitcoin }; + check_network_currency(network, currency); + let section = match network { Network::Bitcoin => Some(main), Network::Testnet => conf.section(Some("test")), @@ -100,9 +90,8 @@ impl BitcoinConfig { }; let addr = if let Some(addr) = section.and_then(|s| s.get("rpcbind")) { - SocketAddr::from_str(addr).or_fail(|_| { - "bitcoin config value 'rpcbind' is not a valid socket address".into() - }) + SocketAddr::from_str(addr) + .or_fail(|_| "bitcoin config value 'rpcbind' is not a valid socket address".into()) } else { ([127, 0, 0, 1], port).into() }; @@ -115,24 +104,30 @@ impl BitcoinConfig { } else if let (Some(login), Some(passwd)) = (main.get("rpcuser"), main.get("rpcpassword")) { BtcAuth::Auth(format!("{}:{}", login, passwd)) } else { - BtcAuth::Cookie + let cookie_file = if let Some(path) = section.and_then(|s| s.get("rpccookiefile")) { + path + } else if let Some(path) = main.get("rpccookiefile") { + path + } else { + ".cookie" + } + .to_string(); + let data_dir = if let Some(path) = section.and_then(|s| s.get("datadir")) { + PathBuf::from(path) + } else if let Some(path) = main.get("datadir") { + PathBuf::from(path) + } else if path.is_file() { + path.parent().unwrap().to_path_buf() + } else { + path.to_path_buf() + }; + BtcAuth::Cookie(data_dir.join(chain_dir(network)).join(cookie_file)) }; - let cookie_path = if let Some(path) = section.and_then(|s| s.get("rpccookiefile")) { - path - } else if let Some(path) = main.get("rpccookiefile") { - path - } else { - ".cookie" - } - .to_string(); - Ok(Self { network, addr, - dir: data_dir.as_ref().join(chain_dir(network)), auth, - cookie_path, }) } } diff --git a/btc-wire/src/lib.rs b/btc-wire/src/lib.rs @@ -1,3 +1,4 @@ +use std::path::{Path, PathBuf}; /* This file is part of TALER Copyright (C) 2022 Taler Systems SA @@ -17,7 +18,10 @@ use std::{str::FromStr, sync::atomic::AtomicU32}; use bitcoin::{hashes::hex::FromHex, Address, Amount, Network, Txid}; use common::api_common::Amount as TalerAmount; -use common::config::BtcConfig; +use common::config::TalerConfig; +use common::log::{fail, OrFail}; +use common::postgres; +use common::url::Url; use config::BitcoinConfig; use rpc::{Category, Rpc, Transaction}; use rpc_utils::{default_data_dir, segwit_min_amount, sender_address}; @@ -143,39 +147,68 @@ impl Rpc { const DEFAULT_BOUNCE_FEE: &str = "BTC:0.00001"; const DEFAULT_CONFIRMATION: u16 = 6; -fn config_bounce_fee(config: &BtcConfig) -> Amount { - let config = config.bounce_fee.as_deref().unwrap_or(DEFAULT_BOUNCE_FEE); - TalerAmount::from_str(config) - .ok() - .and_then(|a| taler_to_btc(&a).ok()) - .expect("config value BOUNCE_FEE is no a valid bitcoin amount") -} - pub struct WireState { pub confirmation: AtomicU32, pub max_confirmation: u32, - pub config: BtcConfig, pub btc_config: BitcoinConfig, pub bounce_fee: Amount, + pub lifetime: Option<u32>, + pub bump_delay: Option<u32>, + pub base_url: Url, + pub db_config: postgres::Config, } impl WireState { - pub fn from_taler_config(config: BtcConfig) -> Self { - let data_dir = config - .core - .data_dir - .as_ref() - .cloned() - .unwrap_or_else(default_data_dir); - let btc_config = - BitcoinConfig::load(&data_dir).expect("Failed to read bitcoin configuration file"); - let init_confirmation = config.confirmation.unwrap_or(DEFAULT_CONFIRMATION) as u32; + pub fn load_taler_config(file: Option<&Path>) -> Self { + let taler_config = load_taler_config(file); + let btc_config = BitcoinConfig::load(taler_config.custom, &taler_config.currency) + .expect("Failed to read bitcoin configuration file"); + let init_confirmation = taler_config.confirmation.unwrap_or(DEFAULT_CONFIRMATION) as u32; Self { confirmation: AtomicU32::new(init_confirmation), max_confirmation: init_confirmation * 2, - bounce_fee: config_bounce_fee(&config), - config, btc_config, + bounce_fee: config_bounce_fee(&taler_config.bounce_fee), + lifetime: taler_config.wire_lifetime, + bump_delay: taler_config.bump_delay, + base_url: taler_config.base_url, + db_config: taler_config.db_config, + } + } +} + +// Load taler config with btc-wire specific config +pub fn load_taler_config(file: Option<&Path>) -> TalerConfig<PathBuf> { + TalerConfig::load_with_custom(file, |dep| { + common::config::path(dep, "CONF_PATH").unwrap_or_else(default_data_dir) + }) +} + +// Parse bitcoin amount from config bounce fee +fn config_bounce_fee(bounce_fee: &Option<String>) -> Amount { + let config = bounce_fee.as_deref().unwrap_or(DEFAULT_BOUNCE_FEE); + TalerAmount::from_str(&config) + .map_err(|s| s.to_string()) + .and_then(|a| taler_to_btc(&a)) + .or_fail(|a| { + format!( + "config value BOUNCE_FEE={} is not a valid bitcoin amount: {}", + config, a + ) + }) +} + +// Check network match config currency +fn check_network_currency(network: Network, currency: &str) { + match network { + Network::Bitcoin | Network::Regtest => { + if currency != "BTC" { + fail(format_args!( + "wrong config CURRENCY = {} expected {}", + currency, "BTC" + )) + } } + Network::Testnet | Network::Signet => unimplemented!(), } } diff --git a/btc-wire/src/loops/worker.rs b/btc-wire/src/loops/worker.rs @@ -47,7 +47,7 @@ use crate::{ use super::{LoopError, LoopResult}; pub fn worker(mut rpc: AutoRpcWallet, mut db: AutoReconnectDb, state: &WireState) { - let mut lifetime = state.config.wire_lifetime; + let mut lifetime = state.lifetime; let mut status = true; let mut skip_notification = false; @@ -432,7 +432,7 @@ fn sync_chain_withdraw( let date = SystemTime::UNIX_EPOCH + Duration::from_secs(full.time); let nb = db.execute( "INSERT INTO tx_out (_date, amount, wtid, debit_acc, credit_acc, exchange_url, status, txid, request_uid) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) ON CONFLICT (wtid) DO NOTHING", - &[&date, &amount.to_string(), &wtid.as_ref(), &btc_payto_url(&debit_addr).as_ref(), &btc_payto_url(credit_addr).as_ref(), &state.config.base_url.as_ref(), &(WithdrawStatus::Sent as i16), &id.as_ref(), &None::<&[u8]>], + &[&date, &amount.to_string(), &wtid.as_ref(), &btc_payto_url(&debit_addr).as_ref(), &btc_payto_url(credit_addr).as_ref(), &state.base_url.as_ref(), &(WithdrawStatus::Sent as i16), &id.as_ref(), &None::<&[u8]>], )?; if nb > 0 { warn!( @@ -446,7 +446,7 @@ fn sync_chain_withdraw( } // Check if stuck - if let Some(delay) = state.config.bump_delay { + if let Some(delay) = state.bump_delay { if confirmations == 0 && full.replaced_by_txid.is_none() { let now = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) diff --git a/btc-wire/src/main.rs b/btc-wire/src/main.rs @@ -16,17 +16,13 @@ use bitcoin::Network; use btc_wire::{ config::{BitcoinConfig, WIRE_WALLET_NAME}, + load_taler_config, rpc::{self, auto_rpc_common, auto_rpc_wallet, ErrorCode, Rpc}, - rpc_utils::default_data_dir, WireState, }; use clap::StructOpt; use common::{ - config::{load_btc_config, Config, CoreConfig}, - log::log::info, - named_spawn, password, - postgres::NoTls, - reconnect::auto_reconnect_db, + log::log::info, named_spawn, password, postgres::NoTls, reconnect::auto_reconnect_db, }; use std::path::PathBuf; @@ -68,14 +64,15 @@ fn main() { fn init(config: Option<PathBuf>, init: Init) { // Parse taler config - let config = CoreConfig::load_taler_config(config.as_deref(), Some("BTC")); + let taler_config = load_taler_config(config.as_deref()); + // Connect to database - let mut db = config + let mut db = taler_config .db_config .connect(NoTls) .expect("Failed to connect to database"); // Parse bitcoin config - let btc_conf = BitcoinConfig::load(config.data_dir.unwrap_or_else(default_data_dir)) + let btc_conf = BitcoinConfig::load(taler_config.custom, &taler_config.currency) .expect("Failed to load bitcoin configuration"); // Connect to bitcoin node let mut rpc = Rpc::common(&btc_conf).expect("Failed to connect to bitcoin RPC server"); @@ -148,8 +145,8 @@ fn init(config: Option<PathBuf>, init: Init) { } fn run(config: Option<PathBuf>) { - let config = load_btc_config(config.as_deref()); - let state: &'static _ = Box::leak(Box::new(WireState::from_taler_config(config))); + let state = WireState::load_taler_config(config.as_deref()); + let state: &'static _ = Box::leak(Box::new(state)); #[cfg(feature = "fail")] if state.btc_config.network == Network::Regtest { @@ -170,9 +167,9 @@ fn run(config: Option<PathBuf>) { let rpc_analysis = auto_rpc_common(state.btc_config.clone()); let rpc_worker = auto_rpc_wallet(state.btc_config.clone(), WIRE_WALLET_NAME); - let db_watcher = auto_reconnect_db(state.config.core.db_config.clone()); - let db_analysis = auto_reconnect_db(state.config.core.db_config.clone()); - let db_worker = auto_reconnect_db(state.config.core.db_config.clone()); + let db_watcher = auto_reconnect_db(state.db_config.clone()); + let db_analysis = auto_reconnect_db(state.db_config.clone()); + let db_worker = auto_reconnect_db(state.db_config.clone()); named_spawn("watcher", move || watcher(rpc_watcher, db_watcher)); named_spawn("analysis", move || { analysis(rpc_analysis, db_analysis, state) diff --git a/btc-wire/src/rpc.rs b/btc-wire/src/rpc.rs @@ -141,10 +141,7 @@ impl Rpc { String::from("/") }; let token = match &config.auth { - BtcAuth::Cookie => { - let cookie_path = config.dir.join(&config.cookie_path); - std::fs::read(cookie_path)? - } + BtcAuth::Cookie(path) => std::fs::read(path)?, BtcAuth::Auth(s) => s.as_bytes().to_vec(), }; // Open connection diff --git a/btc-wire/src/rpc_utils.rs b/btc-wire/src/rpc_utils.rs @@ -15,12 +15,29 @@ */ use std::{path::PathBuf, str::FromStr}; -use bitcoin::{Address, Amount}; +use bitcoin::{Address, Amount, Network}; use crate::rpc::{self, Rpc, Transaction}; -pub const CLIENT: &str = "client"; -pub const WIRE: &str = "wire"; +/// Default chain dir <https://github.com/bitcoin/bitcoin/blob/master/doc/files.md#data-directory-location> +pub fn chain_dir(network: Network) -> &'static str { + match network { + Network::Bitcoin => "main", + Network::Testnet => "testnet3", + Network::Regtest => "regtest", + Network::Signet => "signet", + } +} + +/// Default rpc port <https://github.com/bitcoin/bitcoin/blob/master/share/examples/bitcoin.conf> +pub fn rpc_port(network: Network) -> u16 { + match network { + Network::Bitcoin => 8332, + Network::Testnet => 18332, + Network::Regtest => 18443, + Network::Signet => 38333, + } +} /// Default bitcoin data_dir <https://github.com/bitcoin/bitcoin/blob/master/doc/bitcoin-conf.md> pub fn default_data_dir() -> PathBuf { diff --git a/common/Cargo.toml b/common/Cargo.toml @@ -11,9 +11,9 @@ rust-version = "1.56.1" # Serialization framework serde = { version = "1.0.136", features = ["derive"] } # Serialization helper -serde_with = "1.11.0" +serde_with = "1.12.0" # JSON serialization -serde_json = "1.0.78" +serde_json = "1.0.79" # Url format url = { version = "2.2.2", features = ["serde"] } # Crockford’s base32 @@ -21,17 +21,18 @@ base32 = "0.4.0" # Error macros thiserror = "1.0.30" # Ini files -rust-ini = "0.17.0" +rust-ini = "0.18.0" # Logging log = "0.4.14" flexi_logger = { version = "0.22.3", default-features = false, features = [ "use_chrono_for_offset", # Temporary hack for multithreaded code https://rustsec.org/advisories/RUSTSEC-2020-0159 ] } -# Local timz +# Localized time time = { version = "0.3.7", features = ["formatting", "macros"] } # Postgres client postgres = "0.19.2" # Secure random -rand = { version = "0.8.4", features = ["getrandom"] } +rand = { version = "0.8.5", features = ["getrandom"] } # Securely zero memory -zeroize = "1.5.2" +zeroize = "1.5.3" + diff --git a/common/src/config.rs b/common/src/config.rs @@ -22,10 +22,37 @@ use std::{ use url::Url; use crate::log::{fail, OrFail}; +pub use ini; -pub trait Config: Sized { - /// Load using taler-config - fn load_taler_config(file: Option<&Path>, currency: Option<&'static str>) -> Self { +// Depolymerizer taler config +pub struct TalerConfig<T> { + // common + pub db_config: postgres::Config, + pub currency: String, + pub base_url: Url, + // wire-gateway + pub http_lifetime: Option<u32>, + pub port: u16, + pub unix_path: Option<PathBuf>, + // wire common + pub confirmation: Option<u16>, + pub bounce_fee: Option<String>, + pub wire_lifetime: Option<u32>, + pub bump_delay: Option<u32>, + pub payto: Option<Url>, + // custom config + pub custom: T, +} + +impl TalerConfig<()> { + pub fn load(file: Option<&Path>) -> Self { + Self::load_with_custom(file, |_| {}) + } +} + +impl<T> TalerConfig<T> { + pub fn load_with_custom(file: Option<&Path>, lambda: fn(&Properties) -> T) -> Self { + // Load config using taler-config let mut cmd = Command::new("taler-config"); cmd.arg("-d"); if let Some(path) = file { @@ -41,88 +68,22 @@ pub trait Config: Sized { String::from_utf8_lossy(&output.stderr) ); } + // Parse ini config let conf = ini::Ini::load_from_str(&String::from_utf8_lossy(&output.stdout)) .expect("Failed to parse config"); let taler = section(&conf, "taler"); - let curr = require(taler, "CURRENCY", string); - if let Some(currency) = currency { - if currency != curr { - fail(format_args!( - "expected config CURRENCY = {} got {}", - currency, curr - )) - } - } - let section_name = match curr.as_str() { + let currency = required(taler, "CURRENCY", string); + let section_name = match currency.as_str() { "BTC" => "depolymerizer-bitcoin", "ETH" => "depolymerizer-ethereum", currency => fail(format_args!("Unsupported currency {}", currency)), }; let dep = section(&conf, section_name); - return Self::load_from_ini(&conf, &curr, dep); - } - /// Load from loaded ini file - fn load_from_ini(ini: &Ini, currency: &str, dep: &Properties) -> Self; -} - -#[derive(Debug, Clone)] -pub struct CoreConfig { - pub db_config: postgres::Config, - pub data_dir: Option<PathBuf>, - pub currency: String, -} - -impl Config for CoreConfig { - fn load_from_ini(_: &Ini, currency: &str, dep: &Properties) -> Self { Self { - db_config: require(dep, "DB_URL", postgres), - data_dir: path(dep, "DATA_DIR"), + db_config: required(dep, "DB_URL", postgres), currency: currency.to_string(), - } - } -} - -#[derive(Debug, Clone)] -pub struct GatewayConfig { - pub http_lifetime: Option<u32>, - pub port: u16, - pub unix_path: Option<PathBuf>, - pub payto: Url, - pub core: CoreConfig, -} - -impl Config for GatewayConfig { - fn load_from_ini(ini: &Ini, currency: &str, dep: &Properties) -> Self { - Self { - port: nb(dep, "PORT").unwrap_or(8080), - unix_path: path(dep, "UNIXPATH"), - payto: require(dep, "PAYTO", url), - http_lifetime: nb(dep, "HTTP_LIFETIME") - .and_then(|nb| (nb != 0).then(|| Some(nb))) - .unwrap_or(None), - core: CoreConfig::load_from_ini(ini, currency, dep), - } - } -} - -#[derive(Debug, Clone)] -pub struct WireConfig { - pub base_url: Url, - pub confirmation: Option<u16>, - pub bounce_fee: Option<String>, - pub wire_lifetime: Option<u32>, - pub bump_delay: Option<u32>, - pub payto: Url, - pub core: CoreConfig, -} - -impl Config for WireConfig { - fn load_from_ini(ini: &Ini, currency: &str, dep: &Properties) -> Self { - let ex = section(ini, "exchange"); - Self { - base_url: require(ex, "BASE_URL", url), - payto: require(dep, "PAYTO", url), + base_url: required(section(&conf, "exchange"), "BASE_URL", url), confirmation: nb(dep, "CONFIRMATION"), bounce_fee: string(dep, "BOUNCE_FEE"), wire_lifetime: nb(dep, "WIRE_LIFETIME") @@ -131,65 +92,65 @@ impl Config for WireConfig { bump_delay: nb(dep, "BUMP_DELAY") .and_then(|nb| (nb != 0).then(|| Some(nb))) .unwrap_or(None), - core: CoreConfig::load_from_ini(ini, currency, dep), + port: nb(dep, "PORT").unwrap_or(8080), + unix_path: path(dep, "UNIXPATH"), + payto: url(dep, "PAYTO"), + http_lifetime: nb(dep, "HTTP_LIFETIME") + .and_then(|nb| (nb != 0).then(|| Some(nb))) + .unwrap_or(None), + custom: lambda(dep), } } -} -pub type BtcConfig = WireConfig; - -pub fn load_btc_config(path: Option<&Path>) -> BtcConfig { - WireConfig::load_taler_config(path, Some("BTC")) -} - -pub type EthConfig = WireConfig; - -pub fn load_eth_config(path: Option<&Path>) -> EthConfig { - WireConfig::load_taler_config(path, Some("ETH")) + // Enforce payto requirement + pub fn require_payto(&self) -> Url { + expect_config(self.payto.clone(), "PAYTO") + } } /* ----- Helper parsing functions ----- */ -fn section<'a>(ini: &'a Ini, name: &str) -> &'a Properties { +pub fn section<'a>(ini: &'a Ini, name: &str) -> &'a Properties { ini.section(Some(name)) .or_fail(|_| format!("missing config section {}", name)) } -fn require<T>( +pub fn required<T>( properties: &Properties, name: &str, lambda: fn(properties: &Properties, name: &str) -> Option<T>, ) -> T { - let result = lambda(properties, name); - result.or_fail(|_| format!("missing config value {}", name)) + expect_config(lambda(properties, name), name) +} + +pub fn expect_config<T>(value: Option<T>, name: &str) -> T { + value.or_fail(|_| format!("missing config value {}", name)) } -fn string(properties: &Properties, name: &str) -> Option<String> { +pub fn string(properties: &Properties, name: &str) -> Option<String> { properties.get(name).map(|s| s.to_string()) } -fn path(properties: &Properties, name: &str) -> Option<PathBuf> { +pub fn path(properties: &Properties, name: &str) -> Option<PathBuf> { properties.get(name).map(|s| { - PathBuf::from_str(s) - .or_fail(|_| format!("config value {}={} is not a valid path", name, s)) + PathBuf::from_str(s).or_fail(|_| format!("config value {}={} is not a valid path", name, s)) }) } -fn nb<T: FromStr>(properties: &Properties, name: &str) -> Option<T> { +pub fn nb<T: FromStr>(properties: &Properties, name: &str) -> Option<T> { properties.get(name).map(|s| { s.parse() .or_fail(|_| format!("config value {}={} is not a number", name, s)) }) } -fn url(properties: &Properties, name: &str) -> Option<Url> { +pub fn url(properties: &Properties, name: &str) -> Option<Url> { properties.get(name).map(|s| { - Url::parse(s) - .or_fail(|e| format!("config value {}={} is not a valid url: {}", name, s, e)) + Url::parse(s).or_fail(|e| format!("config value {}={} is not a valid url: {}", name, s, e)) }) } -fn postgres(properties: &Properties, name: &str) -> Option<postgres::Config> { +pub fn postgres(properties: &Properties, name: &str) -> Option<postgres::Config> { properties.get(name).map(|s| { postgres::Config::from_str(s).or_fail(|e| { format!( diff --git a/docs/taler-btc.conf b/docs/taler-btc.conf @@ -9,7 +9,7 @@ BASE_URL = http://test.com DB_URL = postgres://%2Fvar%2Frun%2Fpostgresql/btc-wire PAYTO = payto://bitcoin/bc1qcr40fzphnh4mcwlv65kvdam4dxq977t2rn54qx PORT = 8080 -DATA_DIR = ~/.bitcoin +CONF_PATH = ~/.bitcoin CONFIRMATION = 6 BOUNCE_FEE = BTC:0.00001 HTTP_LIFETIME = 0 diff --git a/docs/taler-eth.conf b/docs/taler-eth.conf @@ -9,7 +9,7 @@ BASE_URL = http://test.com DB_URL = postgres://%2Fvar%2Frun%2Fpostgresql/eth-wire PAYTO = payto://ethereum/425870d7b77ad5665ca982ff85c398222a70fe7c PORT = 8080 -DATA_DIR = ~/.ethereum/get/geth.ipc +IPC_PATH = ~/.ethereum/geth/geth.ipc CONFIRMATION = 24 BOUNCE_FEE = ETH:0.00001 HTTP_LIFETIME = 0 diff --git a/eth-wire/Cargo.toml b/eth-wire/Cargo.toml @@ -11,14 +11,14 @@ fail = [] [dependencies] # Cli args -clap = { version = "3.1.1", features = ["derive"] } +clap = { version = "3.1.5", features = ["derive"] } # Serialization library serde = { version = "1.0.136", features = ["derive"] } -serde_json = "1.0.78" +serde_json = "1.0.79" serde_repr = "0.1.7" hex = "0.4.3" # Ethereum serializable types -ethereum-types = { version = "0.13.0", default-features = false, features = [ +ethereum-types = { version = "0.13.1", default-features = false, features = [ "serialize", ] } # Error macros diff --git a/eth-wire/src/bin/eth-wire-utils.rs b/eth-wire/src/bin/eth-wire-utils.rs @@ -14,25 +14,19 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ use std::{ - path::{Path, PathBuf}, + path::PathBuf, process::exit, str::FromStr, time::{Duration, Instant}, }; use clap::StructOpt; -use common::{ - api_common::Amount, - config::{Config, CoreConfig}, - log::init, - password, - postgres::NoTls, - rand_slice, -}; +use common::{api_common::Amount, log::init, password, postgres::NoTls, rand_slice}; use eth_wire::{ - rpc::{hex::Hex, Rpc, TransactionRequest, RpcClient}, + load_taler_config, + rpc::{hex::Hex, Rpc, RpcClient, TransactionRequest}, taler_util::{taler_to_eth, TRUNC}, - SyncState, RpcExtended, + RpcExtended, SyncState, }; use ethereum_types::{H160, U256}; @@ -110,14 +104,10 @@ enum Cmd { fn main() { init(); let args: Args = Args::parse(); - let config = args - .config - .map(|path| CoreConfig::load_taler_config(Some(&path), Some("ETH"))); - let data_dir: Option<&Path> = config - .as_ref() - .and_then(|it| it.data_dir.as_deref()) - .or(args.datadir.as_deref()); - let mut rpc = Rpc::new(data_dir.unwrap_or(&PathBuf::from("/tmp/")).join("geth.ipc")).unwrap(); + let taler_config = load_taler_config(args.config.as_deref()); + + let ipc_path = args.datadir.unwrap_or(taler_config.custom); + let mut rpc = Rpc::new(ipc_path).unwrap(); let passwd = password(); match args.cmd { Cmd::Deposit(TransactionCmd { @@ -175,7 +165,7 @@ fn main() { } Cmd::Resetdb => { let block = rpc.earliest_block().unwrap(); - let mut db = config.unwrap().db_config.connect(NoTls).unwrap(); + let mut db = taler_config.db_config.connect(NoTls).unwrap(); let mut tx = db.transaction().unwrap(); // Clear transaction tables and reset state tx.execute("DELETE FROM tx_in", &[]).unwrap(); @@ -225,7 +215,7 @@ fn main() { } } Cmd::Connect { datadir } => { - let mut peer = Rpc::new(datadir.join("geth.ipc")).unwrap(); + let mut peer = Rpc::new(datadir).unwrap(); let mut enode = peer.node_info().unwrap().enode; // Replace ip with localhost because it is broken enode.set_host(Some("127.0.0.1")).unwrap(); @@ -239,7 +229,7 @@ fn main() { } } Cmd::Disconnect { datadir } => { - let mut peer = Rpc::new(datadir.join("geth.ipc")).unwrap(); + let mut peer = Rpc::new(datadir).unwrap(); let mut enode = peer.node_info().unwrap().enode; // Replace ip with localhost because it is broken enode.set_host(Some("127.0.0.1")).unwrap(); diff --git a/eth-wire/src/lib.rs b/eth-wire/src/lib.rs @@ -14,17 +14,24 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -use std::{fmt::Debug, str::FromStr, sync::atomic::AtomicU32}; +use std::{ + fmt::Debug, + path::{Path, PathBuf}, + str::FromStr, + sync::atomic::AtomicU32, +}; -use common::{api_common::Amount, config::EthConfig, url::Url}; +use common::{api_common::Amount, config::TalerConfig, log::OrFail, postgres, url::Url}; use ethereum_types::{Address, H160, H256, U256, U64}; use metadata::{InMetadata, OutMetadata}; use rpc::{hex::Hex, Rpc, RpcClient, RpcStream, Transaction}; +use rpc_utils::default_data_dir; use serde::de::DeserializeOwned; use taler_util::{eth_payto_addr, taler_to_eth}; pub mod metadata; pub mod rpc; +mod rpc_utils; pub mod taler_util; pub trait RpcExtended: RpcClient { @@ -202,33 +209,59 @@ impl SyncState { const DEFAULT_CONFIRMATION: u16 = 24; const DEFAULT_BOUNCE_FEE: &str = "ETH:0.00001"; + pub struct WireState { pub confirmation: AtomicU32, pub max_confirmations: u32, pub address: H160, - pub config: EthConfig, pub bounce_fee: U256, + pub ipc_path: PathBuf, + pub lifetime: Option<u32>, + pub bump_delay: Option<u32>, + pub base_url: Url, + pub payto: Url, + pub db_config: postgres::Config, } impl WireState { - pub fn from_taler_config(config: EthConfig) -> Self { - let init_confirmation = config.confirmation.unwrap_or(DEFAULT_CONFIRMATION) as u32; + pub fn load_taler_config(file: Option<&Path>) -> Self { + let taler_config = load_taler_config(file); + let init_confirmation = taler_config.confirmation.unwrap_or(DEFAULT_CONFIRMATION) as u32; + let payto = taler_config.require_payto(); Self { confirmation: AtomicU32::new(init_confirmation), max_confirmations: init_confirmation * 2, - address: eth_payto_addr(&config.payto).unwrap(), - bounce_fee: config_bounce_fee(&config), - config, + address: eth_payto_addr(&payto).unwrap(), + ipc_path: taler_config.custom, + bounce_fee: config_bounce_fee(&taler_config.bounce_fee), + lifetime: taler_config.wire_lifetime, + bump_delay: taler_config.bump_delay, + base_url: taler_config.base_url, + db_config: taler_config.db_config, + payto, } } } -fn config_bounce_fee(config: &EthConfig) -> U256 { - let config = config.bounce_fee.as_deref().unwrap_or(DEFAULT_BOUNCE_FEE); +// Load taler config with eth-wire specific config +pub fn load_taler_config(file: Option<&Path>) -> TalerConfig<PathBuf> { + TalerConfig::load_with_custom(file, |dep| { + common::config::path(dep, "IPC_PATH").unwrap_or_else(default_data_dir) + }) +} + +// Parse ethereum value from config bounce fee +fn config_bounce_fee(bounce_fee: &Option<String>) -> U256 { + let config = bounce_fee.as_deref().unwrap_or(DEFAULT_BOUNCE_FEE); Amount::from_str(config) - .ok() - .and_then(|a| taler_to_eth(&a).ok()) - .expect("config value BOUNCE_FEE is no a valid ethereum amount") + .map_err(|s| s.to_string()) + .and_then(|a| taler_to_eth(&a)) + .or_fail(|a| { + format!( + "config value BOUNCE_FEE={} is not a valid ethereum amount: {}", + config, a + ) + }) } #[cfg(test)] diff --git a/eth-wire/src/loops/worker.rs b/eth-wire/src/loops/worker.rs @@ -41,7 +41,7 @@ use crate::{ use super::LoopResult; pub fn worker(mut rpc: AutoRpcWallet, mut db: AutoReconnectDb, state: &WireState) { - let mut lifetime = state.config.wire_lifetime; + let mut lifetime = state.lifetime; let mut status = true; let mut skip_notification = false; @@ -276,7 +276,7 @@ fn sync_chain_incoming_confirmed( let amount = eth_to_taler(&tx.value); let credit_addr = tx.from.expect("Not coinbase"); let nb = db.execute("INSERT INTO tx_in (_date, amount, reserve_pub, debit_acc, credit_acc) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (reserve_pub) DO NOTHING ", &[ - &date, &amount.to_string(), &reserve_pub.as_ref(), &eth_payto_url(&credit_addr).as_ref(), &state.config.payto.as_ref() + &date, &amount.to_string(), &reserve_pub.as_ref(), &eth_payto_url(&credit_addr).as_ref(), &state.payto.as_ref() ])?; if nb > 0 { info!( @@ -351,7 +351,7 @@ fn sync_chain_outgoing(tx: &SyncTransaction, db: &mut Client, state: &WireState) let date = SystemTime::now(); let nb = db.execute( "INSERT INTO tx_out (_date, amount, wtid, debit_acc, credit_acc, exchange_url, status, txid, request_uid) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) ON CONFLICT (wtid) DO NOTHING", - &[&date, &amount.to_string(), &wtid.as_ref(), &eth_payto_url(&state.address).as_ref(), &eth_payto_url(&credit_addr).as_ref(), &state.config.base_url.as_ref(), &(WithdrawStatus::Sent as i16), &tx.hash.as_ref(), &None::<&[u8]>], + &[&date, &amount.to_string(), &wtid.as_ref(), &eth_payto_url(&state.address).as_ref(), &eth_payto_url(&credit_addr).as_ref(), &state.base_url.as_ref(), &(WithdrawStatus::Sent as i16), &tx.hash.as_ref(), &None::<&[u8]>], )?; if nb > 0 { warn!( @@ -446,7 +446,7 @@ fn withdraw(db: &mut Client, rpc: &mut Rpc, state: &WireState) -> LoopResult<boo /// Bump a stuck transaction, return false if no more stuck transactions are found fn bump(db: &mut Client, rpc: &mut Rpc, state: &WireState) -> LoopResult<bool> { - if let Some(delay) = state.config.bump_delay { + if let Some(delay) = state.bump_delay { // We rely on the advisory lock to ensure we are the only one sending transactions let row = db.query_opt( "SELECT id, txid FROM tx_out WHERE status=$1 AND EXTRACT(EPOCH FROM (now() - sent)) > $2 ORDER BY _date LIMIT 1", diff --git a/eth-wire/src/main.rs b/eth-wire/src/main.rs @@ -17,15 +17,10 @@ use std::path::PathBuf; use clap::StructOpt; -use common::{ - config::{load_eth_config, Config, CoreConfig}, - named_spawn, password, - postgres::NoTls, - reconnect::auto_reconnect_db, -}; +use common::{named_spawn, password, postgres::NoTls, reconnect::auto_reconnect_db}; use eth_wire::{ rpc::{auto_rpc_common, auto_rpc_wallet, Rpc, RpcClient}, - SyncState, WireState, + SyncState, WireState, load_taler_config, }; use ethereum_types::H160; use loops::{analysis::analysis, watcher::watcher, worker::worker}; @@ -64,20 +59,14 @@ fn main() { fn init(config: Option<PathBuf>, init: Init) { // Parse taler config - let config = CoreConfig::load_taler_config(config.as_deref(), Some("ETH")); + let taler_config = load_taler_config(config.as_deref()); // Connect to database - let mut db = config + let mut db = taler_config .db_config .connect(NoTls) .expect("Failed to connect to database"); // Connect to ethereum node - let mut rpc = Rpc::new( - config - .data_dir - .unwrap_or(PathBuf::from("/tmp/")) - .join("geth.ipc"), - ) - .expect("Failed to connect to ethereum RPC server"); + let mut rpc = Rpc::new(taler_config.custom).expect("Failed to connect to ethereum RPC server"); match init { Init::Initdb => { @@ -156,38 +145,16 @@ fn init(config: Option<PathBuf>, init: Init) { } fn run(config: Option<PathBuf>) { - let config = load_eth_config(config.as_deref()); - let state: &'static _ = Box::leak(Box::new(WireState::from_taler_config(config))); - - let rpc_worker = auto_rpc_wallet( - state - .config - .core - .data_dir - .clone() - .unwrap_or(PathBuf::from("/tmp/")), - state.address, - ); - let rpc_analysis = auto_rpc_common( - state - .config - .core - .data_dir - .clone() - .unwrap_or(PathBuf::from("/tmp/")), - ); - let rpc_watcher = auto_rpc_common( - state - .config - .core - .data_dir - .clone() - .unwrap_or(PathBuf::from("/tmp/")), - ); - - let db_watcher = auto_reconnect_db(state.config.core.db_config.clone()); - let db_analysis = auto_reconnect_db(state.config.core.db_config.clone()); - let db_worker = auto_reconnect_db(state.config.core.db_config.clone()); + let state = WireState::load_taler_config(config.as_deref()); + let state: &'static _ = Box::leak(Box::new(state)); + + let rpc_worker = auto_rpc_wallet(state.ipc_path.clone(), state.address); + let rpc_analysis = auto_rpc_common(state.ipc_path.clone()); + let rpc_watcher = auto_rpc_common(state.ipc_path.clone()); + + let db_watcher = auto_reconnect_db(state.db_config.clone()); + let db_analysis = auto_reconnect_db(state.db_config.clone()); + let db_worker = auto_reconnect_db(state.db_config.clone()); named_spawn("watcher", move || watcher(rpc_watcher, db_watcher)); named_spawn("analysis", move || { diff --git a/eth-wire/src/rpc.rs b/eth-wire/src/rpc.rs @@ -34,9 +34,9 @@ use self::hex::Hex; pub type AutoRpcWallet = AutoReconnect<(PathBuf, Address), Rpc>; -pub fn auto_rpc_wallet(data_dir: PathBuf, address: Address) -> AutoRpcWallet { +pub fn auto_rpc_wallet(ipc_path: PathBuf, address: Address) -> AutoRpcWallet { AutoReconnect::new( - (data_dir.join("geth.ipc"), address), + (ipc_path, address), |(path, address)| { let mut rpc = Rpc::new(path) .map_err(|err| error!("connect RPC: {}", err)) @@ -52,9 +52,9 @@ pub fn auto_rpc_wallet(data_dir: PathBuf, address: Address) -> AutoRpcWallet { pub type AutoRpcCommon = AutoReconnect<PathBuf, Rpc>; -pub fn auto_rpc_common(data_dir: PathBuf) -> AutoRpcCommon { +pub fn auto_rpc_common(ipc_path: PathBuf) -> AutoRpcCommon { AutoReconnect::new( - data_dir.join("geth.ipc"), + ipc_path, |path| { Rpc::new(path) .map_err(|err| error!("connect RPC: {}", err)) @@ -111,7 +111,13 @@ pub struct Rpc { impl Rpc { pub fn new(path: impl AsRef<Path>) -> io::Result<Self> { - let conn = UnixStream::connect(&path)?; + let path = path.as_ref(); + + let conn = if path.is_dir() { + UnixStream::connect(path.join("geth.ipc")) + } else { + UnixStream::connect(path) + }?; Ok(Self { id: 0, @@ -232,7 +238,7 @@ enum NotificationOrResponse<T, N> { Response(RpcResponse<T>), } -/// A notification stream wrapping an rpc client +/// A notification stream wrapping an rpc client pub struct RpcStream<'a, N: Debug + DeserializeOwned> { rpc: &'a mut Rpc, id: String, diff --git a/eth-wire/src/rpc_utils.rs b/eth-wire/src/rpc_utils.rs @@ -0,0 +1,20 @@ +use std::{path::PathBuf, str::FromStr}; + +/// Default geth data_dir <https://geth.ethereum.org/docs/install-and-build/backup-restore#data-directory> +pub fn default_data_dir() -> PathBuf { + if cfg!(target_os = "windows") { + PathBuf::from_str(&std::env::var("APPDATA").unwrap()) + .unwrap() + .join("Ethereum") + } else if cfg!(target_os = "linux") { + PathBuf::from_str(&std::env::var("HOME").unwrap()) + .unwrap() + .join(".ethereum") + } else if cfg!(target_os = "macos") { + PathBuf::from_str(&std::env::var("HOME").unwrap()) + .unwrap() + .join("Library/Ethereum") + } else { + unimplemented!("Only windows, linux or macos") + } +} diff --git a/instrumentation/Cargo.toml b/instrumentation/Cargo.toml @@ -7,14 +7,14 @@ rust-version = "1.56.1" [dependencies] # Cli args parser -clap = { version = "3.1.1", features = ["derive"] } +clap = { version = "3.1.5", features = ["derive"] } common = { path = "../common" } # Bitcoin btc-wire = { path = "../btc-wire" } bitcoin = { version = "0.27.1", default-features = false } # Ethereum eth-wire = { path = "../eth-wire" } -ethereum-types = { version = "0.13.0", default-features = false } +ethereum-types = { version = "0.13.1", default-features = false } hex = "0.4.3" # Wire Gateway ureq = { version = "2.4.0", features = ["json"] } diff --git a/instrumentation/src/btc.rs b/instrumentation/src/btc.rs @@ -24,7 +24,7 @@ use btc_wire::{ taler_utils::{btc_payto_url, btc_to_taler}, WireState, }; -use common::{config::load_btc_config, rand_slice}; +use common::rand_slice; use crate::{check_incoming, check_outgoing, print_now, transfer}; @@ -59,8 +59,7 @@ fn wait_for_pending(since: &mut BlockHash, client_rpc: &mut Rpc, wire_rpc: &mut } pub fn btc_test(config: Option<&Path>, base_url: &str) { - let config = load_btc_config(config); - let state = WireState::from_taler_config(config); + let state = WireState::load_taler_config(config); if state.btc_config.network == Network::Bitcoin { panic!("You should never run this test on a real bitcoin network"); @@ -189,7 +188,7 @@ pub fn btc_test(config: Option<&Path>, base_url: &str) { transfer( base_url, &wtid, - &state.config.base_url, + &state.base_url, btc_payto_url(&client_addr), &taler_test_amount, ); @@ -199,5 +198,5 @@ pub fn btc_test(config: Option<&Path>, base_url: &str) { let last_client_balance = client_rpc.get_balance().unwrap(); assert_eq!(new_client_balance + test_amount, last_client_balance); - check_outgoing(base_url, &wtid, &state.config.base_url, &taler_test_amount); + check_outgoing(base_url, &wtid, &state.base_url, &taler_test_amount); } diff --git a/instrumentation/src/eth.rs b/instrumentation/src/eth.rs @@ -1,9 +1,6 @@ -use std::{ - path::{Path, PathBuf}, - time::Duration, -}; +use std::{path::Path, time::Duration}; -use common::{config::load_eth_config, rand_slice}; +use common::rand_slice; use eth_wire::{ metadata::OutMetadata, rpc::{hex::Hex, Rpc, RpcClient, TransactionRequest}, @@ -27,22 +24,13 @@ fn wait_for_pending(rpc: &mut Rpc) { } pub fn eth_test(config: Option<&Path>, base_url: &str) { - let config = load_eth_config(config); - let state = WireState::from_taler_config(config); + let state = WireState::load_taler_config(config); // TODO eth network check let min_fund = U256::from(100_000 * TRUNC); let test_amount = U256::from(20_000 * TRUNC); let taler_test_amount = eth_to_taler(&test_amount); - let mut rpc = Rpc::new( - state - .config - .core - .data_dir - .unwrap_or_else(|| PathBuf::from("/tmp/")) - .join("geth.ipc"), - ) - .unwrap(); + let mut rpc = Rpc::new(state.ipc_path).unwrap(); // Load client let client_addr = rpc @@ -164,7 +152,7 @@ pub fn eth_test(config: Option<&Path>, base_url: &str) { transfer( base_url, &wtid, - &state.config.base_url, + &state.base_url, eth_payto_url(&client_addr), &taler_test_amount, ); @@ -174,5 +162,5 @@ pub fn eth_test(config: Option<&Path>, base_url: &str) { let last_client_balance = rpc.get_balance(&client_addr).unwrap(); assert_eq!(new_client_balance + test_amount, last_client_balance); - check_outgoing(base_url, &wtid, &state.config.base_url, &taler_test_amount); + check_outgoing(base_url, &wtid, &state.base_url, &taler_test_amount); } diff --git a/instrumentation/src/main.rs b/instrumentation/src/main.rs @@ -21,7 +21,7 @@ use clap::StructOpt; use common::{ api_common::{Amount, Base32}, api_wire::{IncomingBankTransaction, IncomingHistory, OutgoingHistory, TransferRequest}, - config::{Config, GatewayConfig}, + config::TalerConfig, rand_slice, url::Url, }; @@ -92,10 +92,10 @@ struct Args { pub fn main() { common::log::init(); let args = Args::parse(); - let gateway_conf = GatewayConfig::load_taler_config(args.config.as_deref(), None); - let base_url = format!("http://localhost:{}", gateway_conf.port); + let taler_config = TalerConfig::load(args.config.as_deref()); + let base_url = format!("http://localhost:{}", taler_config.port); - match gateway_conf.core.currency.as_str() { + match taler_config.currency.as_str() { "BTC" => btc_test(args.config.as_deref(), &base_url), "ETH" => eth_test(args.config.as_deref(), &base_url), _ => unimplemented!(), diff --git a/test/btc/config.sh b/test/btc/config.sh @@ -20,8 +20,8 @@ function test() { cleanup } -for config in bitcoin_auth.conf bitcoin_auth1.conf bitcoin_auth2.conf bitcoin_auth3.conf bitcoin_auth4.conf bitcoin_auth5.conf; do - test $config +for n in `seq 0 5`; do + test "bitcoin_auth$n.conf" done echo "All tests passed!" \ No newline at end of file diff --git a/test/btc/wire.sh b/test/btc/wire.sh @@ -44,7 +44,7 @@ for n in `$SEQ`; do -C payto://bitcoin/$CLIENT \ -a BTC:0.0000$n > /dev/null done -sleep 10 +sleep 15 mine_btc # Mine transactions check_balance 9.95514310 "" echo " OK" diff --git a/test/common.sh b/test/common.sh @@ -47,17 +47,18 @@ ETH_CLI2="geth -datadir=$WIRE_DIR2" # Load test.conf as bash variables function load_config() { cp ${BASH_SOURCE%/*}/conf/$CONFIG $CONF - echo -e "\nDATA_DIR = ${WIRE_DIR}" >> $CONF + echo -e "\nCONF_PATH = ${WIRE_DIR}" >> $CONF + echo -e "IPC_PATH = ${WIRE_DIR}" >> $CONF source <(grep = $CONF | sed 's/ *= */=/' | sed 's/=\(.*\)/="\1"/g1') BANK_ENDPOINT=http://127.0.0.1:$PORT/ if [ "$CURRENCY" == "BTC" ]; then WIRE_CLI="btc-wire -c $CONF" WIRE_UTILS="btc-wire-utils -c $CONF" - WIRE_UTILS2="btc-wire-utils -d $WIRE_DIR2" + WIRE_UTILS2="btc-wire-utils -c $CONF -d $WIRE_DIR2" else WIRE_CLI="eth-wire -c $CONF" WIRE_UTILS="eth-wire-utils -c $CONF" - WIRE_UTILS2="eth-wire-utils -d $WIRE_DIR2" + WIRE_UTILS2="eth-wire-utils -c $CONF -d $WIRE_DIR2" fi } diff --git a/test/conf/bitcoin_auth.conf b/test/conf/bitcoin_auth0.conf diff --git a/wire-gateway/Cargo.toml b/wire-gateway/Cargo.toml @@ -7,17 +7,17 @@ rust-version = "1.56.1" [dependencies] # Http library -hyper = { version = "0.14.16", features = ["http1", "server", "runtime"] } +hyper = { version = "0.14.17", features = ["http1", "server", "runtime"] } # Hyper compat lib for unix domain socket hyperlocal = "0.8.0" # Async runtime -tokio = { version = "1.16.1", features = ["net", "macros", "rt-multi-thread"] } +tokio = { version = "1.17.0", features = ["net", "macros", "rt-multi-thread"] } # Serialization framework serde = { version = "1.0.136", features = ["derive"] } # Serialization helper -serde_with = "1.11.0" +serde_with = "1.12.0" # JSON serialization -serde_json = "1.0.78" +serde_json = "1.0.79" # Url query serialization serde_urlencoded = "0.7.1" # Error macros @@ -34,9 +34,9 @@ common = { path = "../common" } # Bitcoin types bitcoin = { version = "0.27.1", optional = true } # Euthereum types -ethereum-types = { version = "0.13.0", default-features = false, optional = true } +ethereum-types = { version = "0.13.1", default-features = false, optional = true } # Cli args parser -clap = { version = "3.1.1", features = ["derive"] } +clap = { version = "3.1.5", features = ["derive"] } [features] default = ["btc", "eth"] diff --git a/wire-gateway/src/main.rs b/wire-gateway/src/main.rs @@ -1,4 +1,3 @@ -use clap::StructOpt; /* This file is part of TALER Copyright (C) 2022 Taler Systems SA @@ -14,16 +13,17 @@ use clap::StructOpt; You should have received a copy of the GNU Affero General Public License along with TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ +use clap::StructOpt; use common::{ api_common::{ShortHashCode, Timestamp}, api_wire::{ HistoryParams, IncomingBankTransaction, IncomingHistory, OutgoingBankTransaction, OutgoingHistory, TransferRequest, TransferResponse, }, - config::{Config, GatewayConfig}, + config::TalerConfig, error_codes::ErrorCode, log::log::{error, info, log, Level}, - postgres::fallible_iterator::FallibleIterator, + postgres::{self, fallible_iterator::FallibleIterator}, sql::{sql_amount, sql_array, sql_safe_u64, sql_url}, url::Url, }; @@ -51,7 +51,9 @@ mod json; struct ServerState { pool: Pool, - config: GatewayConfig, + db_config: postgres::Config, + payto: Url, + currency: String, payto_check: fn(&Url) -> bool, notify: Notify, lifetime: Option<AtomicU32>, @@ -98,13 +100,13 @@ struct Args { async fn main() { common::log::init(); let args = Args::parse(); - let conf = GatewayConfig::load_taler_config(args.config.as_deref(), None); + let taler_config = TalerConfig::load(args.config.as_deref()); #[cfg(feature = "test")] common::log::log::warn!("Running with test admin endpoint unsuitable for production"); // Parse postgres url - let config = &conf.core.db_config; + let config = &taler_config.db_config; // TODO find a way to clean this ugly mess let mut cfg = deadpool_postgres::Config::new(); cfg.user = config.get_user().map(|it| it.to_string()); @@ -129,19 +131,22 @@ async fn main() { cfg.connect_timeout = config.get_connect_timeout().cloned(); let pool = cfg.create_pool(Some(Runtime::Tokio1), NoTls).unwrap(); + let payto = taler_config.require_payto(); let state = ServerState { pool, - payto_check: match conf.core.currency.as_str() { + payto_check: match taler_config.currency.as_str() { #[cfg(feature = "btc")] "BTC" => check_pay_to_btc, #[cfg(feature = "eth")] "ETH" => check_pay_to_eth, currency => unimplemented!("Unsupported currency {}", currency), }, - config: conf.clone(), notify: Notify::new(), - lifetime: conf.http_lifetime.map(AtomicU32::new), + lifetime: taler_config.http_lifetime.map(AtomicU32::new), status: AtomicBool::new(true), + db_config: taler_config.db_config, + payto, + currency: taler_config.currency, }; let state: &'static ServerState = Box::leak(Box::new(state)); std::thread::spawn(move || status_watcher(state)); @@ -189,7 +194,7 @@ async fn main() { if let Err(e) = server.await { error!("server: {}", e); } - } else if let Some(path) = conf.unix_path { + } else if let Some(path) = taler_config.unix_path { use hyperlocal::UnixServerExt; info!("Server listening on unix domain socket {:?}", path); if let Err(err) = std::fs::remove_file(&path) { @@ -205,7 +210,7 @@ async fn main() { error!("server: {}", e); } } else { - let addr = ([0, 0, 0, 0], state.config.port).into(); + let addr = ([0, 0, 0, 0], taler_config.port).into(); info!("Server listening on http://{}", &addr); let server = Server::bind(&addr) .serve(make_service) @@ -307,7 +312,7 @@ async fn router( ErrorCode::GENERIC_PAYTO_URI_MALFORMED, )); } - if request.amount.currency != state.config.core.currency { + if request.amount.currency != state.currency { return Err(ServerError::code( StatusCode::BAD_REQUEST, ErrorCode::GENERIC_PARAMETER_MALFORMED, @@ -348,7 +353,7 @@ async fn router( let timestamp = Timestamp::now(); let tx = db.transaction().await?; let row = tx.query_one("INSERT INTO tx_out (amount, wtid, debit_acc, credit_acc, exchange_url, request_uid) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id", &[ - &request.amount.to_string(), &request.wtid.as_ref(), &state.config.payto.as_ref(), &request.credit_account.as_ref(), &request.exchange_base_url.as_ref(), &request.request_uid.as_ref() + &request.amount.to_string(), &request.wtid.as_ref(), &state.payto.as_ref(), &request.credit_account.as_ref(), &request.exchange_base_url.as_ref(), &request.request_uid.as_ref() ]).await?; tx.execute("NOTIFY new_tx", &[]).await?; tx.commit().await?; @@ -480,7 +485,7 @@ async fn router( /// Listen to backend status change fn status_watcher(state: &'static ServerState) { fn inner(state: &'static ServerState) -> Result<(), Box<dyn std::error::Error>> { - let mut db = state.config.core.db_config.connect(NoTls)?; + let mut db = state.db_config.connect(NoTls)?; // Register as listener db.batch_execute("LISTEN status")?; loop { @@ -497,7 +502,7 @@ fn status_watcher(state: &'static ServerState) { loop { if let Err(err) = inner(state) { error!("status-watcher: {}", err); + std::thread::sleep(Duration::from_secs(5)); } - std::thread::sleep(Duration::from_secs(5)); } }