commit a02daa453da90f15ac1033e44ba3aaa54d38d1c3
parent 1e93728aebe96095dde1848e303f07fe9a22daf6
Author: Antoine A <>
Date: Fri, 4 Mar 2022 21:22:32 +0100
Improve config parsing
Diffstat:
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(), ð_payto_url(&credit_addr).as_ref(), &state.config.payto.as_ref()
+ &date, &amount.to_string(), &reserve_pub.as_ref(), ð_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(), ð_payto_url(&state.address).as_ref(), ð_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(), ð_payto_url(&state.address).as_ref(), ð_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));
}
}