depolymerization

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

commit 0467435320f42f1e7245554e194f3f2cd73d92c1
parent e21dbc6255dad5c30de11a9622bb4c02af68a4b0
Author: Antoine A <>
Date:   Wed, 15 Dec 2021 17:01:28 +0100

Move taler api to another crate and other improvements

Diffstat:
MCargo.lock | 35++++++++++++++++++++++++-----------
MCargo.toml | 9++++++++-
Mbtc-wire/Cargo.toml | 8+++-----
Mbtc-wire/src/bin/test.rs | 16++++++++--------
Mbtc-wire/src/main.rs | 53++++++++++++++---------------------------------------
Mscript/setup.sh | 2++
Mscript/test_btc_stress.sh | 9+++------
Mscript/test_btc_wire.sh | 3++-
Mscript/test_gateway.sh | 2++
Mscript/test_recover_db.sh | 3+++
Ataler-api/Cargo.toml | 21+++++++++++++++++++++
Rwire-gateway/src/api_common.rs -> taler-api/src/api_common.rs | 0
Rwire-gateway/src/api_wire.rs -> taler-api/src/api_wire.rs | 0
Rwire-gateway/src/error_codes.rs -> taler-api/src/error_codes.rs | 0
Ataler-api/src/lib.rs | 5+++++
Ataler-config/Cargo.toml | 13+++++++++++++
Ataler-config/src/lib.rs | 27+++++++++++++++++++++++++++
Mwire-gateway/Cargo.toml | 12++++--------
Mwire-gateway/src/error.rs | 2+-
Dwire-gateway/src/lib.rs | 4----
Mwire-gateway/src/main.rs | 35+++++++++++++----------------------
21 files changed, 153 insertions(+), 106 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -191,13 +191,13 @@ dependencies = [ "owo-colors", "postgres", "rand", - "rust-ini", "serde", + "taler-api", + "taler-config", "taler-log", "thiserror", "uri-pack", "url", - "wire-gateway", ] [[package]] @@ -251,12 +251,6 @@ dependencies = [ ] [[package]] -name = "configparser" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06821ea598337a8412cf47c5b71c3bc694a7f0aed188ac28b836fab164a2c202" - -[[package]] name = "cpufeatures" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1473,6 +1467,26 @@ dependencies = [ ] [[package]] +name = "taler-api" +version = "0.1.0" +dependencies = [ + "base32", + "serde", + "serde_json", + "serde_with", + "thiserror", + "url", +] + +[[package]] +name = "taler-config" +version = "0.1.0" +dependencies = [ + "rust-ini", + "url", +] + +[[package]] name = "taler-log" version = "0.1.0" dependencies = [ @@ -1853,9 +1867,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" name = "wire-gateway" version = "0.1.0" dependencies = [ - "base32", "bitcoin", - "configparser", "deadpool-postgres", "hyper", "listenfd", @@ -1865,9 +1877,10 @@ dependencies = [ "serde_json", "serde_urlencoded", "serde_with", + "taler-api", + "taler-config", "taler-log", "thiserror", "tokio", "tokio-postgres", - "url", ] diff --git a/Cargo.toml b/Cargo.toml @@ -1,2 +1,9 @@ [workspace] -members = ["wire-gateway", "btc-wire", "uri-pack", "taler-log"] +members = [ + "wire-gateway", + "btc-wire", + "uri-pack", + "taler-log", + "taler-api", + "taler-config", +] diff --git a/btc-wire/Cargo.toml b/btc-wire/Cargo.toml @@ -28,13 +28,11 @@ postgres = "0.19.2" uri-pack = { path = "../uri-pack" } # Url format url = { version = "2.2.2", features = ["serde"] } -# Wire gateway api -wire-gateway = { path = "../wire-gateway" } # Ansi color owo-colors = "3.1.1" -# Ini files -rust-ini = "0.17.0" -# Taler logging +# Taler libs +taler-api = { path = "../taler-api" } +taler-config = { path = "../taler-config" } taler-log = { path = "../taler-log" } [dev-dependencies] diff --git a/btc-wire/src/bin/test.rs b/btc-wire/src/bin/test.rs @@ -13,7 +13,7 @@ use bitcoincore_rpc::{ use btc_wire::{ rpc_patch::RpcErrorCode, rpc_utils::{ - common_rpc, dirty_guess_network, network_dir_path, wallet_rpc, Network, CLIENT, WIRE, + common_rpc, default_data_dir, dirty_guess_network, wallet_rpc, Network, CLIENT, WIRE, }, test::rand_key, BounceErr, ClientExtended, @@ -25,9 +25,9 @@ const RESERVE: &str = "reserve"; /// Instrumentation test pub fn main() { let test_amount = Amount::from_sat(1500); - + let data_dir = default_data_dir(); // Network check - let network = dirty_guess_network(); + let network = dirty_guess_network(&data_dir); match network { Network::MainNet => { panic!("Do not run tests on the mainnet, you are going to loose money") @@ -39,13 +39,13 @@ pub fn main() { // Wallet check { let existing_wallets: HashSet<String> = - std::fs::read_dir(network_dir_path(network).join("wallets")) + std::fs::read_dir(data_dir.join(network.dir()).join("wallets")) .unwrap() .filter_map(|it| it.ok()) .map(|it| it.file_name().to_string_lossy().to_string()) .collect(); - let rpc = common_rpc(network).expect("Failed to open common client"); + let rpc = common_rpc(&data_dir, network).expect("Failed to open common client"); if !existing_wallets.contains(CLIENT) || !existing_wallets.contains(WIRE) || !existing_wallets.contains(RESERVE) @@ -65,9 +65,9 @@ pub fn main() { } // Client initialization - let client_rpc = wallet_rpc(network, CLIENT); - let wire_rpc = wallet_rpc(network, WIRE); - let reserve_rpc = wallet_rpc(network, RESERVE); + let client_rpc = wallet_rpc(&data_dir, network, CLIENT); + let wire_rpc = wallet_rpc(&data_dir, network, WIRE); + let reserve_rpc = wallet_rpc(&data_dir, network, RESERVE); let client_addr = client_rpc.get_new_address(None, None).unwrap(); let wire_addr = wire_rpc.get_new_address(None, None).unwrap(); let reserve_addr = reserve_rpc.get_new_address(None, None).unwrap(); diff --git a/btc-wire/src/main.rs b/btc-wire/src/main.rs @@ -14,13 +14,14 @@ use postgres::{fallible_iterator::FallibleIterator, Client, NoTls, Transaction}; use rand::{rngs::OsRng, RngCore}; use std::{ collections::HashMap, - path::{Path, PathBuf}, + path::PathBuf, str::FromStr, time::{Duration, SystemTime}, }; +use taler_api::api_common::{crockford_base32_encode, Amount}; +use taler_config::Config; use taler_log::log::{error, info, warn}; use url::Url; -use wire_gateway::api_common::{crockford_base32_encode, Amount}; use crate::fail_point::fail_point; @@ -84,22 +85,20 @@ fn taler_amount_to_btc_amount(amount: &Amount) -> Result<BtcAmount, String> { } fn encode_info(wtid: &[u8; 32], url: &Url) -> Vec<u8> { - let mut buffer = Vec::new(); - buffer.extend_from_slice(wtid); + let mut buffer = wtid.to_vec(); + buffer.push(if url.scheme() == "http" { 1 } else { 0 }); let parts = format!("{}{}", url.domain().unwrap_or(""), url.path()); let packed = uri_pack::pack_uri(&parts).unwrap(); - buffer.push((url.scheme() == "http:") as u8); buffer.extend_from_slice(&packed); return buffer; } fn decode_info(bytes: &[u8]) -> ([u8; 32], Url) { - let mut packed = uri_pack::unpack_uri(&bytes[33..]).unwrap(); - packed.insert_str(0, "://"); - if bytes[32] != 0 { - packed.insert(0, 's'); - } - packed.insert_str(0, "http"); + let packed = format!( + "http{}://{}", + if bytes[32] == 0 { "s" } else { "" }, + uri_pack::unpack_uri(&bytes[33..]).unwrap(), + ); let url = Url::parse(&packed).unwrap(); return (bytes[..32].try_into().unwrap(), url); } @@ -113,13 +112,14 @@ mod test { #[test] fn decode_encode_info() { - let key = rand_key(); let urls = [ "https://git.taler.net/", "https://git.taler.net/depolymerization.git/", + "http://git.taler.net/", + "http://git.taler.net/depolymerization.git/", ]; - for url in urls { + let key = rand_key(); let url = Url::parse(url).unwrap(); let encode = encode_info(&key, &url); let decode = decode_info(&encode); @@ -416,31 +416,6 @@ fn watcher(rpc: RPC, mut db: AutoReloadDb, config: &Config) { } } -#[derive(Debug, Clone)] -struct Config { - base_url: Url, - db_url: String, - port: u16, - payto: Url, - address: String, - confirmation: u8, -} - -impl Config { - pub fn from_path(path: impl AsRef<Path>) -> Self { - let conf = ini::Ini::load_from_file(path).unwrap(); - let conf = conf.section(Some("main")).unwrap(); - Self { - base_url: Url::parse(&conf.get("BASE_URL").unwrap()).unwrap(), - db_url: conf.get("DB_URL").unwrap().to_string(), - port: conf.get("PORT").unwrap().parse().unwrap(), - payto: Url::parse(&conf.get("PAYTO").unwrap()).unwrap(), - address: conf.get("ADDRESS").unwrap().to_string(), - confirmation: conf.get("CONFIRMATION").unwrap().parse().unwrap(), - } - } -} - fn main() { taler_log::init(); @@ -453,7 +428,7 @@ fn main() { .next() .map(|str| PathBuf::from_str(&str).unwrap()) .unwrap_or(default_data_dir()); - let config = Config::from_path("test.conf"); + let config = taler_config::Config::from_path("test.conf"); let network = dirty_guess_network(&data_dir); let rpc = common_rpc(&data_dir, network).unwrap(); rpc.load_wallet(&WIRE).ok(); diff --git a/script/setup.sh b/script/setup.sh @@ -1,5 +1,7 @@ #!/bin/bash +## Test utils + # Load test.conf as bash variables function load_config() { source <(grep = test.conf | sed 's/ *= */=/' | sed 's/=\(.*\)/="\1"/g1') diff --git a/script/test_btc_stress.sh b/script/test_btc_stress.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Test btc_wire behavior +## Test btc_wire behavior when ran and stressed concurrently set -eu @@ -65,12 +65,9 @@ for n in `$SEQ`; do -a BTC:0.0000$n > /dev/null done next_btc # Mine transactions -sleep 10 -next_btc # Trigger watcher twice, never sure -sleep 10 -next_btc # Trigger watcher twice, never sure -sleep 10 +sleep 5 next_btc # Trigger watcher twice, never sure +sleep 3 echo " OK" echo -n "Requesting exchange outgoing transaction list:" diff --git a/script/test_btc_wire.sh b/script/test_btc_wire.sh @@ -1,7 +1,8 @@ #!/bin/bash -set -eu +## Test btc_wire correctly receive and sens transactions on the blockchain +set -eu # Cleanup to run whenever we exit function cleanup() { diff --git a/script/test_gateway.sh b/script/test_gateway.sh @@ -1,5 +1,7 @@ #!/bin/bash +## Test wire_gateway conformance to documentation and its security + set -eu # Create temp file diff --git a/script/test_recover_db.sh b/script/test_recover_db.sh @@ -1,5 +1,7 @@ #!/bin/bash +## Check the capacity of wire_gateway and btc_wire to recover from database loss + set -eu # Cleanup to run whenever we exit @@ -62,6 +64,7 @@ taler-exchange-wire-gateway-client \ -b $BANK_ENDPOINT \ -C payto://bitcoin/$CLIENT \ -a BTC:0.00002 > /dev/null +mine_btc next_btc check_balance 9.99992218 1.00006001 echo " OK" diff --git a/taler-api/Cargo.toml b/taler-api/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "taler-api" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +# Serialization framework +serde = { version = "1.0.130", features = ["derive"] } +# Serialization helper +serde_with = "1.11.0" +# JSON serialization +serde_json = "1.0.72" +# Url format +url = { version = "2.2.2", features = ["serde"] } +# Crockford’s base32 +base32 = "0.4.0" +# Error macros +thiserror = "1.0.30" +\ No newline at end of file diff --git a/wire-gateway/src/api_common.rs b/taler-api/src/api_common.rs diff --git a/wire-gateway/src/api_wire.rs b/taler-api/src/api_wire.rs diff --git a/wire-gateway/src/error_codes.rs b/taler-api/src/error_codes.rs diff --git a/taler-api/src/lib.rs b/taler-api/src/lib.rs @@ -0,0 +1,5 @@ +pub use url; + +pub mod api_common; +pub mod api_wire; +pub mod error_codes; diff --git a/taler-config/Cargo.toml b/taler-config/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "taler-config" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +# Url format +url = { version = "2.2.2", features = ["serde"] } +# Ini files +rust-ini = "0.17.0" +\ No newline at end of file diff --git a/taler-config/src/lib.rs b/taler-config/src/lib.rs @@ -0,0 +1,27 @@ +use std::path::Path; +use url::Url; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Config { + pub base_url: Url, + pub db_url: String, + pub port: u16, + pub payto: Url, + pub address: String, + pub confirmation: u8, +} + +impl Config { + pub fn from_path(path: impl AsRef<Path>) -> Self { + let conf = ini::Ini::load_from_file(path).unwrap(); + let conf = conf.section(Some("main")).unwrap(); + Self { + base_url: Url::parse(&conf.get("BASE_URL").unwrap()).unwrap(), + db_url: conf.get("DB_URL").unwrap().to_string(), + port: conf.get("PORT").unwrap().parse().unwrap(), + payto: Url::parse(&conf.get("PAYTO").unwrap()).unwrap(), + address: conf.get("ADDRESS").unwrap().to_string(), + confirmation: conf.get("CONFIRMATION").unwrap().parse().unwrap(), + } + } +} diff --git a/wire-gateway/Cargo.toml b/wire-gateway/Cargo.toml @@ -20,25 +20,21 @@ serde_with = "1.11.0" serde_json = "1.0.72" # Url query serialization serde_urlencoded = "0.7.0" -# Crockford’s base32 -base32 = "0.4.0" # Error macros thiserror = "1.0.30" # Deflate compression miniz_oxide = "0.5.1" # Rng rand = { version = "0.8.4", features = ["getrandom"] } -# Url format -url = { version = "2.2.2", features = ["serde"] } # Async postgres client tokio-postgres = { version = "0.7.5" } deadpool-postgres = "0.10.1" -# Logging -taler-log = { path = "../taler-log" } # Socket activation listenfd = "0.3.5" -# Ini files -configparser = "3.0.0" +# Taler libs +taler-api = { path = "../taler-api" } +taler-config = { path = "../taler-config" } +taler-log = { path = "../taler-log" } # TODO Put this behind a feature # Bitcoin data structure diff --git a/wire-gateway/src/error.rs b/wire-gateway/src/error.rs @@ -1,5 +1,5 @@ use hyper::{header, Body, Response, StatusCode}; -use wire_gateway::{api_common::ErrorDetail, error_codes::ErrorCode}; +use taler_api::{api_common::ErrorDetail, error_codes::ErrorCode}; /// Generic http error #[derive(Debug)] diff --git a/wire-gateway/src/lib.rs b/wire-gateway/src/lib.rs @@ -1,3 +0,0 @@ -pub mod api_common; -pub mod api_wire; -pub mod error_codes; -\ No newline at end of file diff --git a/wire-gateway/src/main.rs b/wire-gateway/src/main.rs @@ -1,5 +1,4 @@ -use configparser::ini::Ini; -use deadpool_postgres::{Config, Pool, Runtime}; +use deadpool_postgres::{Pool, Runtime}; use error::{CatchResult, ServerError}; use hyper::{ http::request::Parts, @@ -9,47 +8,39 @@ use hyper::{ use json::{encode_body, parse_body}; use listenfd::ListenFd; use std::{convert::Infallible, str::FromStr, time::Instant}; -use taler_log::log::{error, info, log, Level}; -use tokio_postgres::{config::Host, NoTls}; -use url::Url; -use wire_gateway::{ +use taler_api::{ api_common::{Amount, SafeUint64, ShortHashCode, Timestamp}, api_wire::{ HistoryParams, IncomingBankTransaction, IncomingHistory, OutgoingBankTransaction, OutgoingHistory, TransferRequest, TransferResponse, }, error_codes::ErrorCode, + url::Url, }; +use taler_log::log::{error, info, log, Level}; +use tokio_postgres::{config::Host, NoTls}; mod error; mod json; struct ServerState { pool: Pool, - pay_to: String, + config: taler_config::Config, } #[tokio::main] async fn main() { taler_log::init(); - let mut conf = Ini::new(); - conf.read(std::fs::read_to_string("test.conf").unwrap()) - .unwrap(); - let db_url = conf.get("main", "DB_URL").expect("Missing BD_URL"); - let port = conf - .getuint("main", "PORT") - .expect("Missing PORT") - .unwrap_or(8080); - let pay_to = conf.get("main", "PAYTO").expect("Missing PAYTO"); + let conf = taler_config::Config::from_path("test.conf"); #[cfg(feature = "test")] taler_log::log::warn!("Running with test admin endpoint unsuitable for production"); // Parse postgres url - let config = tokio_postgres::Config::from_str(&db_url).unwrap(); + let config = tokio_postgres::Config::from_str(&conf.db_url).unwrap(); // TODO find a way to clean this ugly mess - let mut cfg = Config::new(); + let mut cfg = deadpool_postgres::Config::new(); cfg.user = config.get_user().map(|it| it.to_string()); cfg.password = config .get_password() @@ -72,7 +63,7 @@ async fn main() { cfg.connect_timeout = config.get_connect_timeout().cloned(); let pool = cfg.create_pool(Some(Runtime::Tokio1), NoTls).unwrap(); - let state = ServerState { pool, pay_to }; + let state = ServerState { pool, config: conf }; let state: &'static ServerState = Box::leak(Box::new(state)); let make_service = make_service_fn(move |_| async move { Ok::<_, Infallible>(service_fn(move |req| async move { @@ -111,7 +102,7 @@ async fn main() { ); Server::from_tcp(listener).unwrap().serve(make_service) } else { - let addr = ([0, 0, 0, 0], port as u16).into(); + let addr = ([0, 0, 0, 0], state.config.port).into(); info!("Server listening on http://{}", &addr); Server::bind(&addr).serve(make_service) }; @@ -238,7 +229,7 @@ async fn router( let timestamp = Timestamp::now(); let row = db.query_one("INSERT INTO tx_out (_date, amount, wtid, debit_acc, credit_acc, exchange_url, status, request_uid) VALUES (now(), $1, $2, $3, $4, $5, $6, $7) RETURNING id", &[ - &request.amount.to_string(), &request.wtid.as_ref(), &state.pay_to, &request.credit_account.to_string(), &request.exchange_base_url.to_string(), &0i16, &request.request_uid.as_ref() + &request.amount.to_string(), &request.wtid.as_ref(), &state.config.payto.to_string(), &request.credit_account.to_string(), &request.exchange_base_url.to_string(), &0i16, &request.request_uid.as_ref() ]).await?; encode_body( parts, @@ -348,7 +339,7 @@ async fn router( "/admin/add-incoming" => { // We do not check input as this is a test admin endpoint assert_method(&parts, Method::POST).unwrap(); - let request: wire_gateway::api_wire::AddIncomingRequest = + let request: taler_api::api_wire::AddIncomingRequest = parse_body(&parts, body).await.unwrap(); let timestamp = Timestamp::now(); let db = state.pool.get().await.catch_code(