depolymerization

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

commit 88556bdbfa2140e8d11367cf87601df0c70f0604
parent 187f435336d86935a92b4d55a5a17cd5e24b9aab
Author: Antoine A <>
Date:   Tue, 15 Feb 2022 16:05:04 +0100

Replace argh CLI parser with clap, merge wire binary with its bootstrap and improve config error msg

Diffstat:
MCargo.lock | 150++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
MREADME.md | 17+++++++++++++++--
Mbtc-wire/Cargo.toml | 4++--
Mbtc-wire/README.md | 12++++++------
Dbtc-wire/src/bin/btc-wire-cli.rs | 113-------------------------------------------------------------------------------
Mbtc-wire/src/bin/btc-wire-utils.rs | 194+++++++++++++++++++++++++++++++++----------------------------------------------
Mbtc-wire/src/main.rs | 122+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mcommon/src/config.rs | 71+++++++++++++++++++++++++++++++++++++++++++++--------------------------
Mcommon/src/log.rs | 2++
Meth-wire/Cargo.toml | 4++--
Aeth-wire/README.md | 38++++++++++++++++++++++++++++++++++++++
Deth-wire/src/bin/eth-wire-cli.rs | 102-------------------------------------------------------------------------------
Meth-wire/src/bin/eth-wire-utils.rs | 200++++++++++++++++++++++++++++++-------------------------------------------------
Meth-wire/src/main.rs | 112+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mmakefile | 16++++++++++------
Mtest/common.sh | 28++++++++++++++--------------
Muri-pack/README.md | 2+-
Mwire-gateway/src/main.rs | 10+++++++---
18 files changed, 625 insertions(+), 572 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -24,35 +24,6 @@ dependencies = [ ] [[package]] -name = "argh" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb41d85d92dfab96cb95ab023c265c5e4261bb956c0fb49ca06d90c570f1958" -dependencies = [ - "argh_derive", - "argh_shared", -] - -[[package]] -name = "argh_derive" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be69f70ef5497dd6ab331a50bd95c6ac6b8f7f17a7967838332743fbd58dc3b5" -dependencies = [ - "argh_shared", - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "argh_shared" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6f8c380fa28aa1b36107cd97f0196474bb7241bb95a453c5c01a15ac74b2eac" - -[[package]] name = "async-trait" version = "0.1.52" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -150,10 +121,10 @@ dependencies = [ name = "btc-wire" version = "0.1.0" dependencies = [ - "argh", "base64", "bech32", "bitcoin", + "clap 3.0.14", "common", "criterion", "hex", @@ -223,11 +194,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "bitflags", - "textwrap", + "textwrap 0.11.0", "unicode-width", ] [[package]] +name = "clap" +version = "3.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63edc3f163b3c71ec8aa23f9bd6070f77edbf3d1d198b164afa90ff00e4ec62" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "indexmap", + "lazy_static", + "os_str_bytes", + "strsim", + "termcolor", + "textwrap 0.14.2", +] + +[[package]] +name = "clap_derive" +version = "3.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a1132dc3944b31c20dd8b906b3a9f0a5d0243e092d59171414969657ac6aa85" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "common" version = "0.1.0" dependencies = [ @@ -262,7 +263,7 @@ checksum = "1604dafd25fba2fe2d5895a9da139f8dc9b319a5fe5354ca137cbbce4e178d10" dependencies = [ "atty", "cast", - "clap", + "clap 2.34.0", "criterion-plot", "csv", "itertools", @@ -479,7 +480,7 @@ dependencies = [ name = "eth-wire" version = "0.1.0" dependencies = [ - "argh", + "clap 3.0.14", "common", "ethereum-types", "hex", @@ -704,13 +705,16 @@ dependencies = [ ] [[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] name = "heck" -version = "0.3.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" [[package]] name = "hermit-abi" @@ -833,6 +837,16 @@ dependencies = [ ] [[package]] +name = "indexmap" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +dependencies = [ + "autocfg", + "hashbrown 0.11.2", +] + +[[package]] name = "instant" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -879,9 +893,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.117" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c" +checksum = "06e509672465a0504304aa87f9f176f2b2b716ed8fb105ebe5c02dc6dce96a94" [[package]] name = "listenfd" @@ -1033,7 +1047,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c672c7ad9ec066e428c00eb917124a06f08db19e2584de982cc34b1f4c12485" dependencies = [ "dlv-list", - "hashbrown", + "hashbrown 0.9.1", +] + +[[package]] +name = "os_str_bytes" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" +dependencies = [ + "memchr", ] [[package]] @@ -1206,6 +1229,30 @@ dependencies = [ ] [[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] name = "proc-macro2" version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1575,6 +1622,15 @@ dependencies = [ ] [[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] name = "textwrap" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1584,6 +1640,12 @@ dependencies = [ ] [[package]] +name = "textwrap" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" + +[[package]] name = "thiserror" version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1785,12 +1847,6 @@ dependencies = [ ] [[package]] -name = "unicode-segmentation" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" - -[[package]] name = "unicode-width" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/README.md b/README.md @@ -8,12 +8,26 @@ - **docs**: Documentation files - **test**: Test scripts +## Install + +``` +make install +``` + +Read implementation specific documentation to run depolymerizer: + +- [BTC](btc-wire/README.md) +- [ETH](eth-wire/README.md) + + ## Configuration The configuration is based on [taler.conf](https://docs.taler.net/manpages/taler.conf.5.html). +This is the minimal required config for initialization. + ``` ini -# taler.conf - common config +# taler.conf - required config (fill all ___) [taler] CURRENCY = ___ @@ -22,7 +36,6 @@ BASE_URL = https://___ [depolymerizer-___] DB_URL = postgres://___ -PAYTO = payto://___ ``` ### Process lifetime diff --git a/btc-wire/Cargo.toml b/btc-wire/Cargo.toml @@ -15,8 +15,8 @@ bitcoin = { version = "0.27.1", features = [ "std", "use-serde", ], default-features = false } -# Cli args -argh = "0.1.7" +# Cli args parser +clap = { version = "3.0.14", features = ["derive"] } # Bech32 encoding and decoding bech32 = "0.8.1" # Serialization library diff --git a/btc-wire/README.md b/btc-wire/README.md @@ -4,7 +4,7 @@ btc-wire is taler wire implementation for [bitcoincore](https://bitcoincore.org/ ## Bitcoincore compatibility -Bitcoind version >= 22.0 is required +Bitcoind version 22.* is required ## Install @@ -17,11 +17,11 @@ Bitcoind version >= 22.0 is required The configuration is based on [taler.conf](https://docs.taler.net/manpages/taler.conf.5.html) ``` ini -# taler.conf - btc-wire config +# taler.conf - btc-wire default config [depolymerizer-bitcoin] DATA_DIR = CONFIRMATION = 6 -BOUNCE_FEE = 1000 +BOUNCE_FEE = BTC:0.00001 ``` ### bitcoin.conf @@ -34,9 +34,9 @@ the RPC server, `txindex=1` and `maxtxfee` are mandatory. 1. Write configuration files 2. Start bitcoind 3. Start PostgreSQL -4. Initialize database `btc-wire-cli initdb` -5. Initialize wallet `btc-wire-cli initwallet` -6. Update taler.conf with wallet info +4. Initialize database `btc-wire initdb` +5. Initialize wallet `btc-wire initwallet` +6. Update configuration files with wallet info 7. Run wire-gateway `wire-gateway` 8. Run btc-wire `btc-wire` diff --git a/btc-wire/src/bin/btc-wire-cli.rs b/btc-wire/src/bin/btc-wire-cli.rs @@ -1,113 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2022 Taler Systems SA - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU Affero General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. - - 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 btc_wire::{ - config::{BitcoinConfig, WIRE_WALLET_NAME}, - rpc::{Error, ErrorCode, Rpc}, - rpc_utils::default_data_dir, -}; -use common::{ - config::{Config, CoreConfig}, - log::init, - postgres::{Client, NoTls}, -}; - -fn main() { - init(); - let args: Vec<_> = std::env::args().collect(); - // Parse taler config - let config = CoreConfig::load_taler_config(Some(&args[2])); - assert_eq!(config.currency, "BTC"); - // Connect to database - let mut db = Client::connect(&config.db_url, NoTls).expect("Failed to connect to database"); - - match args[1].as_str() { - "initdb" => { - // Load schema - db.batch_execute(include_str!("../../../db/btc.sql")) - .expect("Failed to load database schema"); - // Init status to true - db - .execute( - "INSERT INTO state (name, value) VALUES ('status', $1) ON CONFLICT (name) DO NOTHING", - &[&[1u8].as_ref()], - ) - .expect("Failed to initialise database state"); - println!("Database initialised"); - } - "initwallet" => { - // Parse bitcoin config - let btc_conf = BitcoinConfig::load(config.data_dir.unwrap_or_else(default_data_dir)) - .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"); - - // Skip previous blocks - let info = rpc - .get_blockchain_info() - .expect("Failed to get blockchain info"); - let nb_row = db - .execute( - "INSERT INTO state (name, value) VALUES ('last_hash', $1) ON CONFLICT (name) DO NOTHING", - &[&info.best_block_hash.as_ref()], - ) - .expect("Failed to update database state"); - if nb_row > 0 { - println!("Skip previous block until now"); - } - - // Create wallet - let created = match rpc.create_wallet(WIRE_WALLET_NAME) { - Err(Error::RPC { code, .. }) if code == ErrorCode::RpcWalletError => false, - Err(e) => panic!("{}", e), - Ok(_) => true, - }; - - rpc.load_wallet(WIRE_WALLET_NAME).ok(); - - // Load previous address - let prev_addr = db - .query_opt("SELECT value FROM state WHERE name = 'addr'", &[]) - .expect("Failed to query database state"); - let addr = if let Some(row) = prev_addr { - String::from_utf8(row.get(0)).expect("Stored address is not a valid string") - } else { - // Or generate a new one - let new = Rpc::wallet(&btc_conf, WIRE_WALLET_NAME) - .expect("Failed to connect to wallet bitcoin RPC server") - .get_new_address() - .expect("Failed to generate new address") - .to_string(); - db.execute( - "INSERT INTO state (name, value) VALUES ('addr', $1)", - &[&new.as_bytes()], - ) - .expect("Failed to update database state"); - new - }; - - if created { - println!("Created new wallet"); - } else { - println!("Found already existing wallet") - } - println!("Address is {}", &addr); - println!("Add the following line into taler.conf:"); - println!("[depolymerizer-bitcoin]"); - println!("PAYTO = payto://bitcoin/{}", addr); - } - cmd => panic!("Unknown command {}", cmd), - } -} diff --git a/btc-wire/src/bin/btc-wire-utils.rs b/btc-wire/src/bin/btc-wire-utils.rs @@ -21,140 +21,109 @@ use btc_wire::{ rpc::{Category, Error, ErrorCode, Rpc}, rpc_utils::default_data_dir, }; +use clap::StructOpt; use common::{ config::{Config, CoreConfig}, postgres::{Client, NoTls}, - rand_slice, log::init, + rand_slice, }; -#[derive(argh::FromArgs)] -/// Bitcoin wire test client +/// btc-wire test utils +#[derive(clap::Parser, Debug)] +#[clap(name = "btc-wire-utils")] struct Args { - #[argh(option, short = 'd')] - /// specify data directory + /// Override default configuration file path + #[clap(global = true, short, long)] + config: Option<PathBuf>, + /// Override default data directory path + #[clap(global = true, short, long)] datadir: Option<PathBuf>, - #[argh(subcommand)] + #[clap(subcommand)] cmd: Cmd, } -#[derive(argh::FromArgs)] -#[argh(subcommand)] +#[derive(clap::Subcommand, Debug)] enum Cmd { - Transfer(TransferCmd), - NextBlock(NextBlockCmd), - Abandon(AbandonCmd), - ResetDB(ResetCmd), + /// Wait or mine the next block + Transfer { + #[clap(short, long, default_value_t = String::from("client"))] + /// sender wallet + from: String, + #[clap(short, long, default_value_t = String::from("wire"))] + /// receiver wallet + to: String, + /// amount to send in btc + amount: f64, + }, + /// Wait or mine the next block + Nblock { + #[clap(default_value_t = String::from("wire"))] + /// receiver wallet + to: String, + }, + /// Abandon all unconfirmed transaction + Abandon { + #[clap(default_value_t = String::from("wire"))] + /// sender wallet + from: String, + }, + /// Clear database + Resetdb, } -#[derive(argh::FromArgs)] -#[argh(subcommand, name = "transfer")] -/// Wait or mine the next block -struct TransferCmd { - #[argh(option, short = 'f', default = "String::from(\"client\")")] - /// sender wallet - from: String, - - #[argh(option, short = 't', default = "String::from(\"wire\")")] - /// receiver wallet - to: String, - - #[argh(positional)] - /// amount to send in btc - amount: f64, -} - -#[derive(argh::FromArgs)] -#[argh(subcommand, name = "nblock")] -/// Wait or mine the next block -struct NextBlockCmd { - #[argh(positional, default = "String::from(\"wire\")")] - /// receiver wallet - to: String, -} - -#[derive(argh::FromArgs)] -#[argh(subcommand, name = "abandon")] -/// Abandon all unconfirmed transaction -struct AbandonCmd { - #[argh(positional, default = "String::from(\"wire\")")] - /// sender wallet - from: String, -} - -#[derive(argh::FromArgs)] -#[argh(subcommand, name = "resetdb")] -/// Clear database -struct ResetCmd { - #[argh(positional)] - /// taler config - config: String, -} - -struct App { - config: BitcoinConfig, - rpc: Rpc, -} - -impl App { - pub fn start(data_dir: Option<PathBuf>) -> Self { - let data_dir = data_dir.unwrap_or_else(default_data_dir); - let config = BitcoinConfig::load(data_dir).unwrap(); - let client = Rpc::common(&config).unwrap(); - - Self { - config, - rpc: client, - } - } - - pub fn auto_wallet(&mut self, name: &str) -> (Rpc, Address) { - // Auto load - if let Err(err) = self.rpc.load_wallet(name) { - match err { - Error::RPC { code, .. } if code == ErrorCode::RpcWalletAlreadyLoaded => {} - e => Err(e).unwrap(), - } - } - let mut wallet = Rpc::wallet(&self.config, name).unwrap(); - let addr = wallet - .get_new_address() - .unwrap_or_else(|_| panic!("Failed to get wallet address {}", name)); - (wallet, addr) - } - - pub fn next_block(&mut self, wallet: &str) { - match self.config.network { - Network::Regtest => { - // Manually mine a block - let (_, addr) = self.auto_wallet(wallet); - self.rpc.generate(1, &addr).unwrap(); - } - _ => { - // Wait for next network block - self.rpc.wait_for_new_block(0).ok(); - } +pub fn auto_wallet(rpc: &mut Rpc, config: &BitcoinConfig, name: &str) -> (Rpc, Address) { + // Auto load + if let Err(err) = rpc.load_wallet(name) { + match err { + Error::RPC { code, .. } if code == ErrorCode::RpcWalletAlreadyLoaded => {} + e => Err(e).unwrap(), } } + let mut wallet = Rpc::wallet(config, name).unwrap(); + let addr = wallet + .get_new_address() + .unwrap_or_else(|_| panic!("Failed to get wallet address {}", name)); + (wallet, addr) } fn main() { - init(); - let args: Args = argh::from_env(); - let mut app = App::start(args.datadir); + 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 mut rpc = Rpc::common(&btc_config).unwrap(); + match args.cmd { - Cmd::Transfer(TransferCmd { from, to, amount }) => { - let (mut client, _) = app.auto_wallet(&from); - let (_, to) = app.auto_wallet(&to); + Cmd::Transfer { from, to, amount } => { + let (mut client, _) = auto_wallet(&mut rpc, &btc_config, &from); + let (_, to) = auto_wallet(&mut rpc, &btc_config, &to); let tx = client .send_segwit_key(&to, &Amount::from_btc(amount).unwrap(), &rand_slice()) .unwrap(); println!("{}", tx); } - Cmd::NextBlock(NextBlockCmd { to }) => { - app.next_block(&to); + Cmd::Nblock { to } => { + match btc_config.network { + Network::Regtest => { + // Manually mine a block + let (_, addr) = auto_wallet(&mut rpc, &btc_config, &to); + rpc.generate(1, &addr).unwrap(); + } + _ => { + // Wait for next network block + rpc.wait_for_new_block(0).ok(); + } + } } - Cmd::Abandon(AbandonCmd { from }) => { - let (mut wire, _) = app.auto_wallet(&from); + Cmd::Abandon { from } => { + let (mut wire, _) = auto_wallet(&mut rpc, &btc_config, &from); let list = wire.list_since_block(None, 1, false).unwrap(); for tx in list.transactions { if tx.category == Category::Send && tx.confirmations == 0 { @@ -162,10 +131,9 @@ fn main() { } } } - Cmd::ResetDB(ResetCmd { config }) => { - let config = CoreConfig::load_taler_config(Some(&config)); - let hash: BlockHash = app.rpc.get_block_hash(0).unwrap(); - let mut db = Client::connect(&config.db_url, NoTls).unwrap(); + Cmd::Resetdb => { + let hash: BlockHash = rpc.get_block_hash(0).unwrap(); + let mut db = Client::connect(&config.unwrap().db_url, 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/main.rs b/btc-wire/src/main.rs @@ -16,17 +16,19 @@ use bitcoin::{Amount as BtcAmount, Network}; use btc_wire::{ config::{BitcoinConfig, WIRE_WALLET_NAME}, - rpc::{auto_reconnect_rpc, Rpc}, + rpc::{self, auto_reconnect_rpc, ErrorCode, Rpc}, rpc_utils::default_data_dir, }; +use clap::StructOpt; use common::{ api_common::Amount, - config::{load_btc_config, BtcConfig}, + config::{load_btc_config, BtcConfig, Config, CoreConfig}, log::log::info, named_spawn, + postgres::{Client, NoTls}, reconnect::auto_reconnect_db, }; -use std::{str::FromStr, sync::atomic::AtomicU16}; +use std::{path::PathBuf, str::FromStr, sync::atomic::AtomicU16}; use taler_util::taler_to_btc; use crate::loops::{analysis::analysis, watcher::watcher, worker::worker}; @@ -55,12 +57,120 @@ fn config_bounce_fee(config: &BtcConfig) -> BtcAmount { .expect("config value BOUNCE_FEE is no a valid bitcoin amount") } +/// Taler wire for bitcoincore +#[derive(clap::Parser, Debug)] +struct Args { + /// Override default configuration file path + #[clap(global = true, short, long)] + config: Option<PathBuf>, + + #[clap(subcommand)] + init: Option<Init>, +} + +#[derive(clap::Subcommand, Debug)] +enum Init { + /// Initialize database schema and state + Initdb, + /// Generate bitcoin wallet and initialize state + Initwallet, +} + fn main() { common::log::init(); + let args = Args::parse(); + + match args.init { + Some(cmd) => init(args.config, cmd), + None => run(args.config), + } +} + +fn init(config: Option<PathBuf>, init: Init) { + // Parse taler config + let config = CoreConfig::load_taler_config(config.as_deref(), Some("BTC")); + // Connect to database + let mut db = Client::connect(&config.db_url, NoTls).expect("Failed to connect to database"); + match init { + Init::Initdb => { + // Load schema + db.batch_execute(include_str!("../../db/btc.sql")) + .expect("Failed to load database schema"); + // Init status to true + db + .execute( + "INSERT INTO state (name, value) VALUES ('status', $1) ON CONFLICT (name) DO NOTHING", + &[&[1u8].as_ref()], + ) + .expect("Failed to initialise database state"); + println!("Database initialised"); + } + Init::Initwallet => { + // Parse bitcoin config + let btc_conf = BitcoinConfig::load(config.data_dir.unwrap_or_else(default_data_dir)) + .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"); + + // Skip previous blocks + let info = rpc + .get_blockchain_info() + .expect("Failed to get blockchain info"); + let nb_row = db + .execute( + "INSERT INTO state (name, value) VALUES ('last_hash', $1) ON CONFLICT (name) DO NOTHING", + &[&info.best_block_hash.as_ref()], + ) + .expect("Failed to update database state"); + if nb_row > 0 { + println!("Skip previous block until now"); + } + + // Create wallet + let created = match rpc.create_wallet(WIRE_WALLET_NAME) { + Err(rpc::Error::RPC { code, .. }) if code == ErrorCode::RpcWalletError => false, + Err(e) => panic!("{}", e), + Ok(_) => true, + }; + + rpc.load_wallet(WIRE_WALLET_NAME).ok(); + + // Load previous address + let prev_addr = db + .query_opt("SELECT value FROM state WHERE name = 'addr'", &[]) + .expect("Failed to query database state"); + let addr = if let Some(row) = prev_addr { + String::from_utf8(row.get(0)).expect("Stored address is not a valid string") + } else { + // Or generate a new one + let new = Rpc::wallet(&btc_conf, WIRE_WALLET_NAME) + .expect("Failed to connect to wallet bitcoin RPC server") + .get_new_address() + .expect("Failed to generate new address") + .to_string(); + db.execute( + "INSERT INTO state (name, value) VALUES ('addr', $1)", + &[&new.as_bytes()], + ) + .expect("Failed to update database state"); + new + }; + + if created { + println!("Created new wallet"); + } else { + println!("Found already existing wallet") + } + println!("Address is {}", &addr); + println!("Add the following line into taler.conf:"); + println!("[depolymerizer-bitcoin]"); + println!("PAYTO = payto://bitcoin/{}", addr); + } + } +} - let config = load_btc_config(Some( - &std::env::args().nth(1).expect("Missing conf path arg"), - )); +fn run(config: Option<PathBuf>) { + let config = load_btc_config(config.as_deref()); let data_dir = config .core .data_dir diff --git a/common/src/config.rs b/common/src/config.rs @@ -14,18 +14,26 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ use ini::{Ini, Properties}; -use std::{path::PathBuf, process::Command, str::FromStr}; +use std::{ + fmt::Display, + path::{Path, PathBuf}, + process::{exit, Command}, + str::FromStr, +}; use url::Url; pub trait Config: Sized { /// Load using taler-config - fn load_taler_config(file: Option<&str>) -> Self { + fn load_taler_config(file: Option<&Path>, currency: Option<&'static str>) -> Self { let mut cmd = Command::new("taler-config"); cmd.arg("-d"); if let Some(path) = file { - cmd.args(["-c", path]); + cmd.arg("-c"); + cmd.arg(path); } - let output = cmd.output().expect("Failed to execute taler-config"); + let output = cmd + .output() + .unwrap_or_else(|_| err("Failed to execute taler-config")); if !output.status.success() { panic!( "taler-config failure:\n{}", @@ -35,15 +43,23 @@ pub trait Config: Sized { let conf = ini::Ini::load_from_str(&String::from_utf8_lossy(&output.stdout)) .expect("Failed to parse config"); let taler = section(&conf, "taler"); - let currency = require(taler, "CURRENCY", string); - let section_name = match currency.as_str() { + let curr = require(taler, "CURRENCY", string); + if let Some(currency) = currency { + if currency != curr { + err(format_args!( + "expected config CURRENCY = {} got {}", + curr, currency + )) + } + } + let section_name = match curr.as_str() { "BTC" => "depolymerizer-bitcoin", "ETH" => "depolymerizer-ethereum", - currency => unimplemented!("Unsupported currency {}", currency), + currency => err(format_args!("Unsupported currency {}", currency)), }; let dep = section(&conf, section_name); - return Self::load_from_ini(&conf, &currency, dep); + return Self::load_from_ini(&conf, &curr, dep); } /// Load from loaded ini file fn load_from_ini(ini: &Ini, currency: &str, dep: &Properties) -> Self; @@ -105,7 +121,7 @@ pub struct WireConfig { impl Config for WireConfig { fn load_from_ini(ini: &Ini, currency: &str, dep: &Properties) -> Self { let ex = section(ini, "exchange"); - let config = Self { + Self { base_url: require(ex, "BASE_URL", url), payto: require(dep, "PAYTO", url), confirmation: nb(dep, "CONFIRMATION"), @@ -117,33 +133,27 @@ impl Config for WireConfig { .and_then(|nb| (nb != 0).then(|| Some(nb))) .unwrap_or(None), core: CoreConfig::load_from_ini(ini, currency, dep), - }; - assert_eq!(config.core.currency, currency); - return config; + } } } pub type BtcConfig = WireConfig; -pub fn load_btc_config(path: Option<&str>) -> BtcConfig { - let config = WireConfig::load_taler_config(path); - assert_eq!(config.core.currency, "BTC"); - return config; +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<&str>) -> EthConfig { - let config = WireConfig::load_taler_config(path); - assert_eq!(config.core.currency, "ETH"); - return config; +pub fn load_eth_config(path: Option<&Path>) -> EthConfig { + WireConfig::load_taler_config(path, Some("ETH")) } -/* ----- Helper functions ----- */ +/* ----- Helper parsing functions ----- */ fn section<'a>(ini: &'a Ini, name: &str) -> &'a Properties { ini.section(Some(name)) - .unwrap_or_else(|| panic!("missing config section {}", name)) + .unwrap_or_else(|| err(format_args!("missing config section {}", name))) } fn require<T>( @@ -152,7 +162,7 @@ fn require<T>( lambda: fn(properties: &Properties, name: &str) -> Option<T>, ) -> T { let result = lambda(properties, name); - result.unwrap_or_else(|| panic!("missing config {}", name)) + result.unwrap_or_else(|| err(format_args!("missing config {}", name))) } fn string(properties: &Properties, name: &str) -> Option<String> { @@ -161,19 +171,28 @@ fn string(properties: &Properties, name: &str) -> Option<String> { fn path(properties: &Properties, name: &str) -> Option<PathBuf> { properties.get(name).map(|s| { - PathBuf::from_str(s).unwrap_or_else(|_| panic!("config value {} is not a valid path", name)) + PathBuf::from_str(s) + .unwrap_or_else(|_| err(format_args!("config value {} is not a valid path", name))) }) } fn nb<T: FromStr>(properties: &Properties, name: &str) -> Option<T> { properties.get(name).map(|s| { s.parse() - .unwrap_or_else(|_| panic!("config value {} is not a number", name)) + .unwrap_or_else(|_| err(format_args!("config value {} is not a number", name))) }) } fn url(properties: &Properties, name: &str) -> Option<Url> { properties.get(name).map(|s| { - Url::parse(s).unwrap_or_else(|_| panic!("config value {} is not a valid url", name)) + Url::parse(s) + .unwrap_or_else(|_| err(format_args!("config value {} is not a valid url", name))) }) } + +/* ----- Helper report functions ----- */ + +fn err(msg: impl Display) -> ! { + eprintln!("error: {}", msg); + exit(1); +} diff --git a/common/src/log.rs b/common/src/log.rs @@ -44,3 +44,5 @@ pub fn init() { .start() .unwrap(); } + + diff --git a/eth-wire/Cargo.toml b/eth-wire/Cargo.toml @@ -10,8 +10,8 @@ rust-version = "1.56.1" fail = [] [dependencies] -# Cli args -argh = "0.1.7" +# Cli args +clap = { version = "3.0.14", features = ["derive"] } # Serialization library serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0.78" diff --git a/eth-wire/README.md b/eth-wire/README.md @@ -0,0 +1,38 @@ +# eth-wire + +eth-wire is taler wire implementation for [geth](https://geth.ethereum.org/) node + +## Geth compatibility + +Geth version 1.10.* is required + +## Install + +`cargo install --path eth-wire` + +## Configuration + +### taler.conf + +The configuration is based on [taler.conf](https://docs.taler.net/manpages/taler.conf.5.html) + +``` ini +# taler.conf - eth-wire default config +[depolymerizer-ethereum] +DATA_DIR = +CONFIRMATION = 24 +BOUNCE_FEE = ETH:0.00001 +``` + +## Getting Started + +1. Write configuration files +2. Start geth +3. Start PostgreSQL +4. Initialize database `eth-wire initdb` +5. Initialize wallet `eth-wire initwallet` +6. Update configuration files with wallet info +7. Run wire-gateway `wire-gateway` +8. Run eth-wire `eth-wire` + +## Implementation details diff --git a/eth-wire/src/bin/eth-wire-cli.rs b/eth-wire/src/bin/eth-wire-cli.rs @@ -1,102 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2022 Taler Systems SA - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU Affero General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. - - 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 common::{ - config::{Config, CoreConfig}, - log::init, - postgres::{Client, NoTls}, -}; -use eth_wire::{rpc::Rpc, SyncState}; -use ethereum_types::H160; - -fn main() { - init(); - let args: Vec<_> = std::env::args().collect(); - let config = CoreConfig::load_taler_config(Some(&args[2])); - assert_eq!(config.currency, "ETH"); - let mut db = Client::connect(&config.db_url, NoTls).expect("Failed to connect to database"); - - // TODO user defined password - - match args[1].as_str() { - "initdb" => { - // Load schema - db.batch_execute(include_str!("../../../db/eth.sql")) - .expect("Failed to load database schema"); - // Init status to true - db - .execute( - "INSERT INTO state (name, value) VALUES ('status', $1) ON CONFLICT (name) DO NOTHING", - &[&[1u8].as_ref()], - ) - .expect("Failed to initialise database state"); - println!("Database initialised"); - } - "initwallet" => { - // Connect to ethereum node - let mut rpc = Rpc::new(config.data_dir.unwrap().join("geth.ipc")) - .expect("Failed to connect to ethereum RPC server"); - - // Skip previous blocks - let block = rpc.latest_block().expect("Failed to get current block"); - let state = SyncState { - tip_hash: block.hash.unwrap(), - tip_height: block.number.unwrap(), - conf_height: block.number.unwrap(), - }; - let nb_row = db - .execute( - "INSERT INTO state (name, value) VALUES ('sync', $1) ON CONFLICT (name) DO NOTHING", - &[&state.to_bytes().as_ref()], - ) - .expect("Failed to update database state"); - if nb_row > 0 { - println!("Skipped {} previous block", state.conf_height); - } - - let prev_addr = db - .query_opt("SELECT value FROM state WHERE name = 'addr'", &[]) - .expect("Failed to query database state"); - let (addr, created) = if let Some(row) = prev_addr { - (H160::from_slice(row.get(0)), false) - } else { - // Or generate a new one - let new = rpc - .new_account("password") - .expect("Failed creating account"); - db.execute( - "INSERT INTO state (name, value) VALUES ('addr', $1)", - &[&new.as_bytes()], - ) - .expect("Failed to update database state"); - (new, true) - }; - - if created { - println!("Created new wallet"); - } else { - println!("Found already existing wallet") - }; - - let addr = hex::encode(addr.as_bytes()); - println!("Address is {}", &addr); - println!("Add the following line into taler.conf:"); - println!("[depolymerizer-ethereum]"); - println!("PAYTO = payto://ethereum/{}", addr); - } - cmd => panic!("Unknown command {}", cmd), - } -} diff --git a/eth-wire/src/bin/eth-wire-utils.rs b/eth-wire/src/bin/eth-wire-utils.rs @@ -13,8 +13,12 @@ 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 std::{path::PathBuf, str::FromStr}; +use std::{ + path::{Path, PathBuf}, + str::FromStr, +}; +use clap::StructOpt; use common::{ api_common::Amount, config::{Config, CoreConfig}, @@ -29,138 +33,87 @@ use eth_wire::{ }; use ethereum_types::{H160, U256}; -#[derive(argh::FromArgs)] -/// Euthereum wire test client +#[derive(clap::Parser, Debug)] +#[clap(name = "eth-wire-utils")] +/// eth-wire test utils struct Args { - #[argh(option, short = 'd')] - /// specify data directory + /// Override default configuration file path + #[clap(global = true, short, long)] + config: Option<PathBuf>, + /// Override default data directory path + #[clap(global = true, short, long)] datadir: Option<PathBuf>, - #[argh(subcommand)] + #[clap(subcommand)] cmd: Cmd, } -#[derive(argh::FromArgs)] -#[argh(subcommand)] -enum Cmd { - Send(SendCmd), - Deposit(DepositCmd), - Mine(MineCmd), - ResetDb(ResetCmd), - Balance(BalanceCmd), - Connect(ConnectCmd), - Disconnect(DisconnectCmd), - Abandon(AbandonCmd), -} - -#[derive(argh::FromArgs)] -#[argh(subcommand, name = "send")] -/// Send a transaction -struct SendCmd { - #[argh(positional)] - /// sender wallet - from: String, - - #[argh(positional)] - /// receiver wallet - to: String, - - #[argh(positional)] - /// sender wallet - fmt: String, - - #[argh(positional)] - /// amounts to send in eth - amounts: Vec<u32>, -} - -#[derive(argh::FromArgs)] -#[argh(subcommand, name = "deposit")] -/// Perform a deposit transaction -struct DepositCmd { - #[argh(positional)] +#[derive(clap::Parser, Debug)] +struct TransactionCmd { /// sender wallet from: String, - - #[argh(positional)] /// receiver wallet to: String, - - #[argh(positional)] /// sender wallet fmt: String, - - #[argh(positional)] /// amounts to send in eth amounts: Vec<u32>, } -#[derive(argh::FromArgs)] -#[argh(subcommand, name = "mine")] -/// Wait or mine the next block -struct MineCmd { - #[argh(positional)] - /// receiver wallet - to: String, - #[argh(positional, default = "0")] - /// amount to mine in eth - amount: u64, -} - -#[derive(argh::FromArgs)] -#[argh(subcommand, name = "resetdb")] -/// Clear database -struct ResetCmd { - #[argh(positional)] - /// taler config - config: String, -} - -#[derive(argh::FromArgs)] -#[argh(subcommand, name = "balance")] -/// Get eth balance -struct BalanceCmd { - #[argh(positional)] - /// account address - addr: String, -} - -#[derive(argh::FromArgs)] -#[argh(subcommand, name = "connect")] -/// Add a peer -struct ConnectCmd { - #[argh(positional)] - /// peer datadir - datadir: PathBuf, -} - -#[derive(argh::FromArgs)] -#[argh(subcommand, name = "disconnect")] -/// Remove a peer -struct DisconnectCmd { - #[argh(positional)] - /// peer datadir - datadir: PathBuf, -} - -#[derive(argh::FromArgs)] -#[argh(subcommand, name = "abandon")] -/// Abandon all unconfirmed transaction -struct AbandonCmd { - #[argh(positional)] - /// sender address - from: String, +#[derive(clap::Subcommand, Debug)] +enum Cmd { + /// Send common ethereum transactions + Send(TransactionCmd), + /// Send taler deposit transactions + Deposit(TransactionCmd), + /// Mine pending transactions and more blocks + Mine { + /// receiver wallet + to: String, + #[clap(default_value_t = 0)] + /// amount to mine in eth + amount: u64, + }, + /// Clear database + Resetdb, + /// Get eth balance + Balance { + /// account address + addr: String, + }, + /// Add a peer + Connect { + /// peer datadir + datadir: PathBuf, + }, + /// Remove a peer + Disconnect { + /// peer datadir + datadir: PathBuf, + }, + /// Abandon all unconfirmed transaction + Abandon { + /// sender address + from: String, + }, } fn main() { init(); - let args: Args = argh::from_env(); - let mut rpc = Rpc::new(args.datadir.unwrap().join("geth.ipc")).unwrap(); + 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().join("geth.ipc")).unwrap(); match args.cmd { - Cmd::Deposit(DepositCmd { + Cmd::Deposit(TransactionCmd { from, to, - amounts, fmt, + amounts, }) => { let from = H160::from_str(&from).unwrap(); let to = H160::from_str(&to).unwrap(); @@ -171,7 +124,7 @@ fn main() { rpc.deposit(from, to, value, rand_slice()).unwrap(); } } - Cmd::Send(SendCmd { + Cmd::Send(TransactionCmd { from, to, fmt, @@ -195,7 +148,7 @@ fn main() { .unwrap(); } } - Cmd::Mine(MineCmd { to, mut amount }) => { + Cmd::Mine { to, mut amount } => { let to = H160::from_str(&to).unwrap(); rpc.unlock_account(&to, "password").ok(); let mut notifier = rpc.subscribe_new_head().unwrap(); @@ -210,15 +163,9 @@ fn main() { } rpc.miner_stop().unwrap(); } - Cmd::Balance(BalanceCmd { addr }) => { - let addr = H160::from_str(&addr).unwrap(); - let balance = rpc.balance(&addr).unwrap(); - println!("{}", (balance / 10_000_000_000u64).as_u64()); - } - Cmd::ResetDb(ResetCmd { config }) => { - let config = CoreConfig::load_taler_config(Some(&config)); + Cmd::Resetdb => { let block = rpc.earliest_block().unwrap(); - let mut db = Client::connect(&config.db_url, NoTls).unwrap(); + let mut db = Client::connect(&config.unwrap().db_url, NoTls).unwrap(); let mut tx = db.transaction().unwrap(); // Clear transaction tables and reset state tx.execute("DELETE FROM tx_in", &[]).unwrap(); @@ -237,21 +184,26 @@ fn main() { .unwrap(); tx.commit().unwrap(); } - Cmd::Connect(ConnectCmd { datadir }) => { + Cmd::Balance { addr } => { + let addr = H160::from_str(&addr).unwrap(); + let balance = rpc.balance(&addr).unwrap(); + println!("{}", (balance / 10_000_000_000u64).as_u64()); + } + Cmd::Connect { datadir } => { let mut peer = Rpc::new(datadir.join("geth.ipc")).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(); assert!(rpc.add_peer(&enode).unwrap()); } - Cmd::Disconnect(DisconnectCmd { datadir }) => { + Cmd::Disconnect { datadir } => { let mut peer = Rpc::new(datadir.join("geth.ipc")).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(); assert!(rpc.remove_peer(&enode).unwrap()); } - Cmd::Abandon(AbandonCmd { from }) => { + Cmd::Abandon { from } => { let from = H160::from_str(&from).unwrap(); rpc.unlock_account(&from, "password").ok(); let pending = rpc.pending_transactions().unwrap(); @@ -262,7 +214,7 @@ fn main() { to: tx.to.unwrap(), value: U256::zero(), gas: None, - gas_price: Some(U256::from(1)), // Bigger gas price to replace fee + gas_price: Some(U256::from(1u8)), // Bigger gas price to replace fee data: Hex(vec![]), nonce: Some(tx.nonce), }) diff --git a/eth-wire/src/main.rs b/eth-wire/src/main.rs @@ -14,17 +14,20 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -use std::{str::FromStr, sync::atomic::AtomicU16}; +use std::{path::PathBuf, str::FromStr, sync::atomic::AtomicU16}; +use clap::StructOpt; use common::{ api_common::Amount, - config::{load_eth_config, EthConfig}, + config::{load_eth_config, Config, CoreConfig, EthConfig}, named_spawn, + postgres::{Client, NoTls}, reconnect::auto_reconnect_db, }; use eth_wire::{ - rpc::auto_reconnect_rpc, + rpc::{auto_reconnect_rpc, Rpc}, taler_util::{eth_payto_addr, taler_to_eth}, + SyncState, }; use ethereum_types::{H160, U256}; use loops::{analysis::analysis, watcher::watcher, worker::worker}; @@ -52,12 +55,111 @@ fn config_bounce_fee(config: &EthConfig) -> U256 { .expect("config value BOUNCE_FEE is no a valid ethereum amount") } +/// Taler wire for geth +#[derive(clap::Parser, Debug)] +struct Args { + /// Override default configuration file path + #[clap(global = true, short, long)] + config: Option<PathBuf>, + + #[clap(subcommand)] + init: Option<Init>, +} + +#[derive(clap::Subcommand, Debug)] +enum Init { + /// Initialize database schema and state + Initdb, + /// Generate ethereum wallet and initialize state + Initwallet, +} + fn main() { common::log::init(); - let path = std::env::args().nth(1).unwrap(); - let config = load_eth_config(Some(&path)); + let args = Args::parse(); + + match args.init { + Some(cmd) => init(args.config, cmd), + None => run(args.config), + } +} + +fn init(config: Option<PathBuf>, init: Init) { + let config = CoreConfig::load_taler_config(config.as_deref(), Some("ETH")); + let mut db = Client::connect(&config.db_url, NoTls).expect("Failed to connect to database"); + + match init { + Init::Initdb => { + // Load schema + db.batch_execute(include_str!("../../db/eth.sql")) + .expect("Failed to load database schema"); + // Init status to true + db + .execute( + "INSERT INTO state (name, value) VALUES ('status', $1) ON CONFLICT (name) DO NOTHING", + &[&[1u8].as_ref()], + ) + .expect("Failed to initialise database state"); + println!("Database initialised"); + } + Init::Initwallet => { + // Connect to ethereum node + let mut rpc = Rpc::new(config.data_dir.unwrap().join("geth.ipc")) + .expect("Failed to connect to ethereum RPC server"); + + // Skip previous blocks + let block = rpc.latest_block().expect("Failed to get current block"); + let state = SyncState { + tip_hash: block.hash.unwrap(), + tip_height: block.number.unwrap(), + conf_height: block.number.unwrap(), + }; + let nb_row = db + .execute( + "INSERT INTO state (name, value) VALUES ('sync', $1) ON CONFLICT (name) DO NOTHING", + &[&state.to_bytes().as_ref()], + ) + .expect("Failed to update database state"); + if nb_row > 0 { + println!("Skipped {} previous block", state.conf_height); + } + + let prev_addr = db + .query_opt("SELECT value FROM state WHERE name = 'addr'", &[]) + .expect("Failed to query database state"); + let (addr, created) = if let Some(row) = prev_addr { + (H160::from_slice(row.get(0)), false) + } else { + // Or generate a new one + let new = rpc + .new_account("password") + .expect("Failed creating account"); + db.execute( + "INSERT INTO state (name, value) VALUES ('addr', $1)", + &[&new.as_bytes()], + ) + .expect("Failed to update database state"); + (new, true) + }; + + if created { + println!("Created new wallet"); + } else { + println!("Found already existing wallet") + }; + + let addr = hex::encode(addr.as_bytes()); + println!("Address is {}", &addr); + println!("Add the following line into taler.conf:"); + println!("[depolymerizer-ethereum]"); + println!("PAYTO = payto://ethereum/{}", addr); + } + } +} +fn run(config: Option<PathBuf>) { + let config = load_eth_config(config.as_deref()); let init_confirmation = config.confirmation.unwrap_or(DEFAULT_CONFIRMATION); let state: &'static WireState = Box::leak(Box::new(WireState { confirmation: AtomicU16::new(init_confirmation), diff --git a/makefile b/makefile @@ -1,12 +1,16 @@ install: - cargo install --path btc-wire --bin btc-wire-cli --bin btc-wire-utils --bin btc-wire - cargo install --path eth-wire --bin eth-wire-cli --bin eth-wire-utils --bin eth-wire + cargo install --path btc-wire --bin btc-wire + cargo install --path eth-wire --bin eth-wire cargo install --path wire-gateway -test_gateway: install +install_test: install + cargo install --path btc-wire --bin btc-wire-utils + cargo install --path eth-wire --bin eth-wire-utils + +test_gateway: install_test test/gateway/api.sh -test_btc: install +test_btc: install_test test/btc/wire.sh test/btc/lifetime.sh test/btc/reconnect.sh @@ -19,7 +23,7 @@ test_btc: install test/btc/maxfee.sh test/btc/config.sh -test_eth: install +test_eth: install_test test/eth/wire.sh test/eth/lifetime.sh test/eth/reconnect.sh @@ -28,7 +32,7 @@ test_eth: install test/eth/hell.sh test/eth/analysis.sh -test: install test_gateway test_eth test_btc +test: test_gateway test_eth test_btc segwit_demo: cargo run --release --bin segwit-demo \ No newline at end of file diff --git a/test/common.sh b/test/common.sh @@ -47,12 +47,12 @@ function load_config() { 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-cli" - WIRE_UTILS="btc-wire-utils -d $WIRE_DIR" + WIRE_CLI="btc-wire -c $CONF" + WIRE_UTILS="btc-wire-utils -c $CONF" WIRE_UTILS2="btc-wire-utils -d $WIRE_DIR2" else - WIRE_CLI="eth-wire-cli" - WIRE_UTILS="eth-wire-utils -d $WIRE_DIR" + WIRE_CLI="eth-wire -c $CONF" + WIRE_UTILS="eth-wire-utils -c $CONF" WIRE_UTILS2="eth-wire-utils -d $WIRE_DIR2" fi } @@ -88,13 +88,13 @@ function setup_db() { echo "port=5454" >> $DB_DIR/postgresql.conf pg_ctl start -D $DB_DIR >> log/postgres.log echo "CREATE ROLE postgres LOGIN SUPERUSER PASSWORD 'password'" | psql -p 5454 postgres > /dev/null - $WIRE_CLI initdb $CONF > /dev/null + $WIRE_CLI initdb > /dev/null } # Erase database function reset_db() { sleep 5 # Wait for loop to stop - $WIRE_UTILS resetdb $CONF + $WIRE_UTILS resetdb } # Stop database @@ -117,7 +117,7 @@ function init_btc() { # Wait for RPC server to be online $BTC_CLI -rpcwait getnetworkinfo > /dev/null # Create wire wallet - btc-wire-cli initwallet $CONF > /dev/null + $WIRE_CLI initwallet > /dev/null # Create other wallets for wallet in client reserve; do $BTC_CLI createwallet $wallet > /dev/null @@ -210,15 +210,15 @@ function check_balance() { # Start btc-wire function btc_wire() { cargo build --bin btc-wire --release &>> log/cargo.log - target/release/btc-wire $CONF &>> log/wire.log & + target/release/btc-wire -c $CONF &>> log/wire.log & WIRE_PID="$!" } # Start multiple btc-wire with random failures in parallel function stress_btc_wire() { cargo build --bin btc-wire --release --features fail &>> log/cargo.log - target/release/btc-wire $CONF &>> log/wire.log & - target/release/btc-wire $CONF &>> log/wire1.log & + target/release/btc-wire -c $CONF &>> log/wire.log & + target/release/btc-wire -c $CONF &>> log/wire1.log & } # ----- Ethereum node ----- # @@ -261,7 +261,7 @@ function init_eth() { NODE_PID="$!" sleep 1 # Create wire address - WIRE=`eth-wire-cli initwallet $CONF | grep -oP '(?<=is ).*'` + WIRE=`$WIRE_CLI initwallet | grep -oP '(?<=is ).*'` echo -e "PAYTO = payto://ethereum/$WIRE" >> $CONF } @@ -325,15 +325,15 @@ function check_balance_eth() { # Start eth-wire function eth_wire() { cargo build --bin eth-wire --release &>> log/cargo.log - target/release/eth-wire $CONF &>> log/wire.log & + target/release/eth-wire -c $CONF &>> log/wire.log & WIRE_PID="$!" } # Start multiple eth-wire with random failures in parallel function stress_eth_wire() { cargo build --bin eth-wire --release --features fail &>> log/cargo.log - target/release/eth-wire $CONF &>> log/wire.log & - target/release/eth-wire $CONF &>> log/wire1.log & + target/release/eth-wire -c $CONF &>> log/wire.log & + target/release/eth-wire -c $CONF &>> log/wire1.log & } # Mine ethereum blocks diff --git a/uri-pack/README.md b/uri-pack/README.md @@ -10,7 +10,7 @@ uri are encoded with 5b, the encoded size is smaller than a simple ascii format. On the majestic_million database, 98.77% of the domain name where smaller, -going from an average of 14b to an average of 10b. +going from an average encoded size of 14B in ASCII to 10B using our format ## Usage diff --git a/wire-gateway/src/main.rs b/wire-gateway/src/main.rs @@ -37,6 +37,7 @@ use json::{encode_body, parse_body}; use listenfd::ListenFd; use std::{ convert::Infallible, + path::PathBuf, str::FromStr, sync::atomic::{AtomicBool, AtomicU32, Ordering}, time::{Duration, Instant}, @@ -88,9 +89,12 @@ impl ServerState { async fn main() { common::log::init(); - let conf = GatewayConfig::load_taler_config(Some( - &std::env::args().nth(1).expect("Missing conf path arg"), - )); + let conf = GatewayConfig::load_taler_config( + Some(&PathBuf::from( + std::env::args_os().nth(1).expect("Missing conf path arg"), + )), + None, + ); #[cfg(feature = "test")] common::log::log::warn!("Running with test admin endpoint unsuitable for production");