depolymerization

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

commit 48b3266bbffa290d96e3f7d7fb6d2ca48679d8a9
parent d76ac36c53783d5d6e358f71845944cd286a77aa
Author: Antoine A <>
Date:   Wed,  2 Feb 2022 15:59:23 +0100

Preparation for eth-wire

Diffstat:
MCargo.lock | 302++++---------------------------------------------------------------------------
MCargo.toml | 2+-
Mbtc-wire/Cargo.toml | 4++--
Mbtc-wire/README.md | 4++--
Rbtc-wire/src/bin/btc_test.rs -> btc-wire/src/bin/btc-test.rs | 0
Mbtc-wire/src/bin/btc-wire-cli.rs | 10+++++++---
Mbtc-wire/src/bin/btc-wire-utils.rs | 8++++++--
Mbtc-wire/src/loops/analysis.rs | 4++--
Mbtc-wire/src/loops/worker.rs | 11++++++-----
Mbtc-wire/src/main.rs | 31+++++++++++++++++--------------
Mdb/btc.sql | 4++--
Adb/eth.sql | 39+++++++++++++++++++++++++++++++++++++++
Aeth-wire/Cargo.toml | 24++++++++++++++++++++++++
Aeth-wire/src/bin/eth-test.rs | 140+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aeth-wire/src/bin/eth-wire-cli.rs | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Reth_wire/src/bin/eth_wire_utils.rs -> eth-wire/src/bin/eth-wire-utils.rs | 0
Aeth-wire/src/lib.rs | 122+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aeth-wire/src/main.rs | 146+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aeth-wire/src/metadata.rs | 177+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aeth-wire/src/rpc.rs | 400+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Deth_wire/Cargo.toml | 21---------------------
Deth_wire/src/bin/eth_test.rs | 139-------------------------------------------------------------------------------
Deth_wire/src/lib.rs | 84-------------------------------------------------------------------------------
Deth_wire/src/main.rs | 165-------------------------------------------------------------------------------
Deth_wire/src/metadata.rs | 177-------------------------------------------------------------------------------
Deth_wire/src/rpc.rs | 316-------------------------------------------------------------------------------
Mmakefile | 1+
Mtaler-common/Cargo.toml | 8++++----
Mtaler-common/src/config.rs | 125++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Mtest/btc/analysis.sh | 5+++--
Mtest/btc/bumpfee.sh | 4++--
Mtest/btc/config.sh | 1+
Mtest/btc/conflict.sh | 7++++---
Mtest/btc/hell.sh | 11++++++-----
Mtest/btc/lifetime.sh | 6+++---
Mtest/btc/maxfee.sh | 3++-
Mtest/btc/reconnect.sh | 5+++--
Mtest/btc/reorg.sh | 3++-
Mtest/btc/stress.sh | 3++-
Mtest/btc/wire.sh | 3++-
Mtest/common.sh | 55++++++++++++++++++++++++++++++++++---------------------
Atest/conf/taler_btc.conf | 12++++++++++++
Rtest/conf/taler_bump.conf -> test/conf/taler_btc_bump.conf | 0
Rtest/conf/taler_lifetime.conf -> test/conf/taler_btc_lifetime.conf | 0
Atest/conf/taler_eth.conf | 12++++++++++++
Dtest/conf/taler_test.conf | 13-------------
Atest/eth/test.sh | 20++++++++++++++++++++
Mtest/gateway/api.sh | 1+
Muri-pack/Cargo.toml | 2+-
Mwire-gateway/Cargo.toml | 6+++---
Mwire-gateway/src/main.rs | 17+++++++++--------
51 files changed, 1400 insertions(+), 1338 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -24,12 +24,6 @@ dependencies = [ ] [[package]] -name = "anyhow" -version = "1.0.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0" - -[[package]] name = "argh" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -59,18 +53,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6f8c380fa28aa1b36107cd97f0196474bb7241bb95a453c5c01a15ac74b2eac" [[package]] -name = "arrayvec" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" - -[[package]] -name = "arrayvec" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" - -[[package]] name = "async-trait" version = "0.1.52" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -144,28 +126,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] -name = "bitvec" -version = "0.20.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7774144344a4faa177370406a7ff5f1da24303817368584c6206c8303eb07848" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - -[[package]] -name = "block-buffer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "block-padding", - "generic-array", -] - -[[package]] name = "block-buffer" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -175,12 +135,6 @@ dependencies = [ ] [[package]] -name = "block-padding" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" - -[[package]] name = "bstr" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -217,12 +171,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" [[package]] -name = "byte-slice-cast" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d30c751592b77c499e7bce34d99d67c2c11bdc0574e9a488ddade14150a4698" - -[[package]] name = "byteorder" version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -279,12 +227,6 @@ dependencies = [ ] [[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - -[[package]] name = "cpufeatures" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -479,34 +421,12 @@ dependencies = [ ] [[package]] -name = "derive_more" -version = "0.99.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn", -] - -[[package]] -name = "digest" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" -dependencies = [ - "generic-array", -] - -[[package]] name = "digest" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b697d66081d42af4fba142d56918a3cb21dc8eb63372c6b85d14f44fb9c5979b" dependencies = [ - "block-buffer 0.10.0", + "block-buffer", "crypto-common", "generic-array", "subtle", @@ -538,33 +458,18 @@ dependencies = [ ] [[package]] -name = "eth_wire" +name = "eth-wire" version = "0.1.0" dependencies = [ "argh", + "ethereum-types", + "hex", "serde", "serde_json", "serde_repr", "taler-common", "thiserror", "uri-pack", - "web3", -] - -[[package]] -name = "ethabi" -version = "14.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01317735d563b3bad2d5f90d2e1799f414165408251abb762510f40e790e69a" -dependencies = [ - "anyhow", - "ethereum-types", - "hex", - "serde", - "serde_json", - "sha3", - "thiserror", - "uint", ] [[package]] @@ -575,20 +480,18 @@ checksum = "bfb684ac8fa8f6c5759f788862bb22ec6fe3cb392f6bfd08e3c64b603661e3f8" dependencies = [ "crunchy", "fixed-hash", - "impl-rlp", "impl-serde", "tiny-keccak", ] [[package]] name = "ethereum-types" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f64b5df66a228d85e4b17e5d6c6aa43b0310898ffe8a85988c4c032357aaabfd" +checksum = "05136f7057fe789f06e6d41d07b34e6f70d8c86e5693b60f97aaa6553553bdaf" dependencies = [ "ethbloom", "fixed-hash", - "impl-rlp", "impl-serde", "primitive-types", "uint", @@ -616,16 +519,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" dependencies = [ "byteorder", - "rand", "rustc-hex", "static_assertions", ] [[package]] name = "flexi_logger" -version = "0.22.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b51b4517f4422bfa0515dafcc10b4cc4cd3953d69a19608fd74afb3b19e227c" +checksum = "969940c39bc718475391e53a3a59b0157e64929c80cf83ad5dde5f770ecdc423" dependencies = [ "chrono", "glob", @@ -653,12 +555,6 @@ dependencies = [ ] [[package]] -name = "funty" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" - -[[package]] name = "futures" version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -730,12 +626,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72" [[package]] -name = "futures-timer" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" - -[[package]] name = "futures-util" version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -825,7 +715,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddca131f3e7f2ce2df364b57949a9d47915cfbd35e46cfee355ccebbf794d6a2" dependencies = [ - "digest 0.10.1", + "digest", ] [[package]] @@ -916,24 +806,6 @@ dependencies = [ ] [[package]] -name = "impl-codec" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "161ebdfec3c8e3b52bf61c4f3550a1eea4f9579d10dc1b936f3171ebdcd6c443" -dependencies = [ - "parity-scale-codec", -] - -[[package]] -name = "impl-rlp" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" -dependencies = [ - "rlp", -] - -[[package]] name = "impl-serde" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -943,17 +815,6 @@ dependencies = [ ] [[package]] -name = "impl-trait-for-tuples" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5dacb10c5b3bb92d46ba347505a9041e676bb20ad220101326bffb0c93031ee" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] name = "instant" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -993,27 +854,6 @@ dependencies = [ ] [[package]] -name = "jsonrpc-core" -version = "18.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f7f76aef2d054868398427f6c54943cf3d1caa9a7ec7d0c38d69df97a965eb" -dependencies = [ - "futures", - "futures-executor", - "futures-util", - "log", - "serde", - "serde_derive", - "serde_json", -] - -[[package]] -name = "keccak" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" - -[[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1066,7 +906,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6a38fc55c8bbc10058782919516f88826e70320db6d206aebc49611d24216ae" dependencies = [ - "digest 0.10.1", + "digest", ] [[package]] @@ -1169,12 +1009,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] -name = "opaque-debug" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" - -[[package]] name = "ordered-multimap" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1185,32 +1019,6 @@ dependencies = [ ] [[package]] -name = "parity-scale-codec" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373b1a4c1338d9cd3d1fa53b3a11bdab5ab6bd80a20f7f7becd76953ae2be909" -dependencies = [ - "arrayvec 0.7.2", - "bitvec", - "byte-slice-cast", - "impl-trait-for-tuples", - "parity-scale-codec-derive", - "serde", -] - -[[package]] -name = "parity-scale-codec-derive" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1557010476e0595c9b568d16dcfb81b93cdeb157612726f5170d31aa707bed27" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", -] - -[[package]] name = "parking_lot" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1370,28 +1178,16 @@ checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "primitive-types" -version = "0.9.1" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06345ee39fbccfb06ab45f3a1a5798d9dafa04cb8921a76d227040003a234b0e" +checksum = "05e4722c697a58a99d5d06a08c30821d7c082a4632198de1eaa5a6c22ef42373" dependencies = [ "fixed-hash", - "impl-codec", - "impl-rlp", "impl-serde", "uint", ] [[package]] -name = "proc-macro-crate" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebace6889caf889b4d3f76becee12e90353f2b8c7d875534a71e5742f8f6f83" -dependencies = [ - "thiserror", - "toml", -] - -[[package]] name = "proc-macro2" version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1432,12 +1228,6 @@ dependencies = [ ] [[package]] -name = "radium" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" - -[[package]] name = "rand" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1535,16 +1325,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" [[package]] -name = "rlp" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "999508abb0ae792aabed2460c45b89106d97fe4adac593bdaef433c2605847b5" -dependencies = [ - "bytes", - "rustc-hex", -] - -[[package]] name = "rust-ini" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1716,19 +1496,7 @@ checksum = "99c3bd8169c58782adad9290a9af5939994036b76187f7b4f0e6de91dbbfc0ec" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.1", -] - -[[package]] -name = "sha3" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" -dependencies = [ - "block-buffer 0.9.0", - "digest 0.9.0", - "keccak", - "opaque-debug", + "digest", ] [[package]] @@ -1817,12 +1585,6 @@ dependencies = [ ] [[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - -[[package]] name = "textwrap" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1968,15 +1730,6 @@ dependencies = [ ] [[package]] -name = "toml" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" -dependencies = [ - "serde", -] - -[[package]] name = "tower-service" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2190,29 +1943,6 @@ dependencies = [ ] [[package]] -name = "web3" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd24abe6f2b68e0677f843059faea87bcbd4892e39f02886f366d8222c3c540d" -dependencies = [ - "arrayvec 0.5.2", - "derive_more", - "ethabi", - "ethereum-types", - "futures", - "futures-timer", - "hex", - "jsonrpc-core", - "log", - "parking_lot", - "pin-project", - "rlp", - "serde", - "serde_json", - "tiny-keccak", -] - -[[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2262,9 +1992,3 @@ dependencies = [ "tokio", "tokio-postgres", ] - -[[package]] -name = "wyz" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" diff --git a/Cargo.toml b/Cargo.toml @@ -2,7 +2,7 @@ members = [ "wire-gateway", "btc-wire", - "eth_wire", + "eth-wire", "uri-pack", "taler-common", ] diff --git a/btc-wire/Cargo.toml b/btc-wire/Cargo.toml @@ -19,8 +19,8 @@ argh = "0.1.7" # Bech32 encoding and decoding bech32 = "0.8.1" # Serialization library -serde = { version = "1.0.133", features = ["derive"] } -serde_json = "1.0.75" +serde = { version = "1.0.136", features = ["derive"] } +serde_json = "1.0.78" serde_repr = "0.1.7" # Error macros thiserror = "1.0.30" diff --git a/btc-wire/README.md b/btc-wire/README.md @@ -18,8 +18,8 @@ The configuration is based on [taler.conf](https://docs.taler.net/manpages/taler ``` ini # taler.conf - btc_wire config -[depolymerizer-___] -BTC_DATA_DIR = +[depolymerizer-bitcoin] +DATA_DIR = CONFIRMATION = 6 BOUNCE_FEE = 1000 BUMP_DELAY = diff --git a/btc-wire/src/bin/btc_test.rs b/btc-wire/src/bin/btc-test.rs diff --git a/btc-wire/src/bin/btc-wire-cli.rs b/btc-wire/src/bin/btc-wire-cli.rs @@ -18,12 +18,16 @@ use btc_wire::{ rpc::{BtcRpc, Error, ErrorCode}, rpc_utils::default_data_dir, }; -use taler_common::postgres::{NoTls, Client}; +use taler_common::{ + config::{Config, CoreConfig}, + postgres::{Client, NoTls}, +}; fn main() { let args: Vec<_> = std::env::args().collect(); // Parse taler config - let config = taler_common::config::InitConfig::load_from_file(&args[2]); + let config = CoreConfig::load_from_file(&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"); @@ -44,7 +48,7 @@ fn main() { "initwallet" => { // Parse bitcoin config let btc_conf = - BitcoinConfig::load(config.btc_data_dir.unwrap_or_else(default_data_dir)) + BitcoinConfig::load(config.data_dir.unwrap_or_else(default_data_dir)) .expect("Failed to load bitcoin configuration"); // Connect to bitcoin node let mut rpc = diff --git a/btc-wire/src/bin/btc-wire-utils.rs b/btc-wire/src/bin/btc-wire-utils.rs @@ -21,7 +21,11 @@ use btc_wire::{ rpc::{BtcRpc, Category, Error, ErrorCode}, rpc_utils::default_data_dir, }; -use taler_common::{config::Config, rand_slice, postgres::{NoTls, Client}}; +use taler_common::{ + config::{Config, CoreConfig}, + postgres::{Client, NoTls}, + rand_slice, +}; #[derive(argh::FromArgs)] /// Bitcoin wire test client @@ -166,7 +170,7 @@ fn main() { } } Cmd::ClearDB(ClearCmd { config }) => { - let config = Config::load_from_file(&config); + let config = CoreConfig::load_from_file(&config); let mut db = Client::connect(&config.db_url, NoTls).unwrap(); db.execute("DROP TABLE IF EXISTS state, tx_in, tx_out, bounce", &[]) .unwrap(); diff --git a/btc-wire/src/loops/analysis.rs b/btc-wire/src/loops/analysis.rs @@ -17,7 +17,7 @@ use std::sync::atomic::Ordering; use btc_wire::rpc::ChainTipsStatus; use taler_common::{ - config::Config, + config::BtcConfig, log::log::{error, warn}, postgres::fallible_iterator::FallibleIterator, }; @@ -33,7 +33,7 @@ use super::LoopResult; pub fn analysis( mut rpc: AutoReconnectRPC, mut db: AutoReconnectSql, - config: &Config, + config: &BtcConfig, state: &WireState, ) { // The biggest fork ever seen diff --git a/btc-wire/src/loops/worker.rs b/btc-wire/src/loops/worker.rs @@ -29,7 +29,7 @@ use btc_wire::{ use postgres::{fallible_iterator::FallibleIterator, Client}; use taler_common::{ api_common::base32, - config::Config, + config::BtcConfig, log::log::{error, info, warn}, postgres, sql::{sql_array, sql_url}, @@ -51,7 +51,7 @@ use super::{LoopError, LoopResult}; pub fn worker( mut rpc: AutoReconnectRPC, mut db: AutoReconnectSql, - config: &Config, + config: &BtcConfig, state: &WireState, ) { let mut lifetime = config.wire_lifetime; @@ -131,6 +131,7 @@ pub fn worker( skip_notification = !matches!( e, LoopError::RPC(rpc::Error::RPC { .. } | rpc::Error::Bitcoin(_)) + | LoopError::Concurrency ); } else { skip_notification = false; @@ -240,7 +241,7 @@ fn last_hash(db: &mut Client) -> Result<Option<BlockHash>, postgres::Error> { fn sync_chain( rpc: &mut BtcRpc, db: &mut Client, - config: &Config, + config: &BtcConfig, state: &WireState, status: &mut bool, ) -> LoopResult<bool> { @@ -398,7 +399,7 @@ fn sync_chain_outgoing( confirmations: i32, rpc: &mut BtcRpc, db: &mut Client, - config: &Config, + config: &BtcConfig, ) -> LoopResult<()> { match rpc .get_tx_op_return(id) @@ -429,7 +430,7 @@ fn sync_chain_outgoing_send( rpc: &mut BtcRpc, db: &mut Client, confirmations: i32, - config: &Config, + config: &BtcConfig, ) -> LoopResult<()> { let credit_addr = full.details[0].address.as_ref().unwrap(); let amount = btc_to_taler(&full.amount); diff --git a/btc-wire/src/main.rs b/btc-wire/src/main.rs @@ -21,7 +21,10 @@ use btc_wire::{ }; use reconnect::{AutoReconnectRPC, AutoReconnectSql}; use std::{sync::atomic::AtomicU16, thread::JoinHandle}; -use taler_common::{config::Config, log::log::info}; +use taler_common::{ + config::{load_btc_config, BtcConfig}, + log::log::info, +}; use crate::loops::{analysis::analysis, watcher::watcher, worker::worker}; @@ -29,9 +32,9 @@ mod fail_point; mod info; mod loops; mod reconnect; +mod sql; mod status; mod taler_util; -mod sql; pub struct WireState { confirmation: AtomicU16, @@ -40,15 +43,14 @@ pub struct WireState { fn main() { taler_common::log::init(); - let config = taler_common::config::Config::load_from_file( - std::env::args_os().nth(1).expect("Missing conf path arg"), - ); + let config = load_btc_config(std::env::args_os().nth(1).expect("Missing conf path arg")); let data_dir = config - .btc_data_dir + .init + .data_dir .as_ref() .cloned() .unwrap_or_else(default_data_dir); - let config: &'static Config = Box::leak(Box::new(config)); + let config: &'static BtcConfig = Box::leak(Box::new(config)); let btc_config = BitcoinConfig::load(&data_dir).unwrap(); #[cfg(feature = "fail")] @@ -76,12 +78,10 @@ fn main() { let rpc_analysis = AutoReconnectRPC::new(btc_config.clone(), WIRE_WALLET_NAME); let rpc_worker = AutoReconnectRPC::new(btc_config, WIRE_WALLET_NAME); - let db_watcher = AutoReconnectSql::new(&config.db_url); - let db_analysis = AutoReconnectSql::new(&config.db_url); - let db_worker = AutoReconnectSql::new(&config.db_url); - named_spawn("watcher", move || { - watcher(rpc_watcher, db_watcher) - }); + let db_watcher = AutoReconnectSql::new(&config.init.db_url); + let db_analysis = AutoReconnectSql::new(&config.init.db_url); + let db_worker = AutoReconnectSql::new(&config.init.db_url); + named_spawn("watcher", move || watcher(rpc_watcher, db_watcher)); named_spawn("analysis", move || { analysis(rpc_analysis, db_analysis, config, state) }); @@ -94,5 +94,8 @@ where F: Send + 'static, T: Send + 'static, { - std::thread::Builder::new().name(name.into()).spawn(f).unwrap() + std::thread::Builder::new() + .name(name.into()) + .spawn(f) + .unwrap() } diff --git a/db/btc.sql b/db/btc.sql @@ -23,9 +23,9 @@ CREATE TABLE tx_out ( debit_acc TEXT NOT NULL, credit_acc TEXT NOT NULL, exchange_url TEXT NOT NULL, + request_uid BYTEA UNIQUE, status SMALLINT NOT NULL DEFAULT 0, - txid BYTEA UNIQUE, - request_uid BYTEA UNIQUE + txid BYTEA UNIQUE ); -- Bounced transaction diff --git a/db/eth.sql b/db/eth.sql @@ -0,0 +1,38 @@ +-- Key value state +CREATE TABLE state ( + name TEXT PRIMARY KEY, + value BYTEA NOT NULL +); + +-- Incoming transactions +CREATE TABLE tx_in ( + id SERIAL PRIMARY KEY, + _date TIMESTAMP NOT NULL DEFAULT now(), + amount TEXT NOT NULL, + reserve_pub BYTEA NOT NULL UNIQUE, + debit_acc TEXT NOT NULL, + credit_acc TEXT NOT NULL +); + +-- Outgoing transactions +CREATE TABLE tx_out ( + id SERIAL PRIMARY KEY, + _date TIMESTAMP NOT NULL DEFAULT now(), + amount TEXT NOT NULL, + wtid BYTEA NOT NULL UNIQUE, + debit_acc TEXT NOT NULL, + credit_acc TEXT NOT NULL, + exchange_url TEXT NOT NULL, + request_uid BYTEA UNIQUE, + status SMALLINT NOT NULL DEFAULT 0, + txid BYTEA UNIQUE +); + +-- Bounced transaction +CREATE TABLE bounce ( + id SERIAL PRIMARY KEY, + bounced BYTEA UNIQUE NOT NULL, + txid BYTEA UNIQUE, + _date TIMESTAMP NOT NULL DEFAULT now(), + status SMALLINT NOT NULL DEFAULT 0 +) +\ No newline at end of file diff --git a/eth-wire/Cargo.toml b/eth-wire/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "eth-wire" +version = "0.1.0" +edition = "2021" +license = "AGPL-3.0-or-later" + +[dependencies] +# Cli args +argh = "0.1.7" +# Serialization library +serde = { version = "1.0.136", features = ["derive"] } +serde_json = "1.0.78" +serde_repr = "0.1.7" +hex = "0.4.3" +# Ethereum serializable types +ethereum-types = { version = "0.12.1", default-features = false, features = [ + "serialize", +] } +# Error macros +thiserror = "1.0.30" +# Optimized uri binary format +uri-pack = { path = "../uri-pack" } +# Taler libs +taler-common = { path = "../taler-common" } diff --git a/eth-wire/src/bin/eth-test.rs b/eth-wire/src/bin/eth-test.rs @@ -0,0 +1,140 @@ +/* + 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 std::{panic::AssertUnwindSafe, str::FromStr}; + +use eth_wire::{ + metadata::{InMetadata, OutMetadata}, + rpc::{self, Rpc}, +}; +use ethereum_types::{H256, U256}; +use taler_common::{config::load_eth_config, rand_slice, url::Url}; + +pub fn main() { + let path = std::env::args().nth(1).unwrap(); + let config = load_eth_config(path); + let mut rpc = Rpc::new(config.init.data_dir.unwrap().join("geth.ipc")).unwrap(); + + let accounts = rpc.list_accounts().unwrap(); + for account in &accounts { + rpc.unlock_account(&account, "password").unwrap(); + } + let wire = accounts[0]; + let client = accounts[1]; + let reserve = accounts[2]; + + let test_value = U256::from(15000u32); + let bounce_value = U256::from(10000u32); + let test_url = Url::from_str("http://test.com").unwrap(); + + let mut runner = TestRunner::new(); + + runner.test("Deposit", || { + let rng = rand_slice(); + let hash = rpc.deposit(client, wire, test_value, rng).unwrap(); + assert!(tx_pending(&mut rpc, &hash).unwrap()); + mine_pending(&mut rpc).unwrap(); + assert!(!tx_pending(&mut rpc, &hash).unwrap()); + let tx = rpc.get_transaction(&hash).unwrap().unwrap(); + let metadata = InMetadata::decode(&tx.input).unwrap(); + assert!(matches!(metadata, InMetadata::Deposit { reserve_pub } if reserve_pub == rng)); + assert_eq!(tx.value, test_value); + }); + + runner.test("Withdraw", || { + let rng = rand_slice(); + let hash = rpc + .withdraw(wire, client, test_value, rng, test_url.clone()) + .unwrap(); + assert!(tx_pending(&mut rpc, &hash).unwrap()); + mine_pending(&mut rpc).unwrap(); + assert!(!tx_pending(&mut rpc, &hash).unwrap()); + let tx = rpc.get_transaction(&hash).unwrap().unwrap(); + let metadata = OutMetadata::decode(&tx.input).unwrap(); + assert!( + matches!(metadata, OutMetadata::Withdraw { wtid, url } if wtid == rng && url == url) + ); + assert_eq!(tx.value, test_value); + }); + + runner.test("Bounce", || { + let rng = rand_slice(); + let deposit = rpc.deposit(client, wire, test_value, rng).unwrap(); + let hash = rpc.bounce(deposit, bounce_value).unwrap(); + assert!(tx_pending(&mut rpc, &hash).unwrap()); + mine_pending(&mut rpc).unwrap(); + assert!(!tx_pending(&mut rpc, &hash).unwrap()); + let tx = rpc.get_transaction(&hash).unwrap().unwrap(); + let metadata = OutMetadata::decode(&tx.input).unwrap(); + assert!(matches!(metadata, OutMetadata::Bounce { bounced } if bounced == deposit)); + assert_eq!(tx.value, test_value - bounce_value); + }); + + runner.conclude(); +} + +/// Check a specific transaction is pending +fn tx_pending(rpc: &mut Rpc, id: &H256) -> rpc::Result<bool> { + Ok(rpc.pending_transactions()?.iter().any(|t| t.hash == *id)) +} + +/// Mine pending transactions +fn mine_pending(rpc: &mut Rpc) -> rpc::Result<()> { + let mut notifier = rpc.subscribe_new_head()?; + rpc.miner_start()?; + while !rpc.pending_transactions()?.is_empty() { + notifier.next()?; + } + rpc.miner_stop()?; + Ok(()) +} + +/// Run test track success and errors +struct TestRunner { + nb_ok: usize, + nb_err: usize, +} + +impl TestRunner { + fn new() -> Self { + Self { + nb_err: 0, + nb_ok: 0, + } + } + + fn test(&mut self, name: &str, test: impl FnOnce()) { + println!("{}", name); + + let result = std::panic::catch_unwind(AssertUnwindSafe(test)); + if result.is_ok() { + println!("OK"); + self.nb_ok += 1; + } else { + println!("ERR"); + self.nb_err += 1; + } + } + + /// Wait for tests completion and print results + fn conclude(self) { + println!( + "Result for {} tests: {} ok and {} err", + self.nb_ok + self.nb_err, + self.nb_ok, + self.nb_err + ); + } +} diff --git a/eth-wire/src/bin/eth-wire-cli.rs b/eth-wire/src/bin/eth-wire-cli.rs @@ -0,0 +1,85 @@ +/* + 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 eth_wire::{rpc::Rpc, BlockState}; +use taler_common::{ + config::{Config, CoreConfig}, + postgres::{Client, NoTls}, +}; + +fn main() { + let args: Vec<_> = std::env::args().collect(); + let config = CoreConfig::load_from_file(&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.current_block().expect("Failed to get current block"); + let state = BlockState { + hash: block.hash.unwrap(), + nb: block.number.unwrap(), + }; + let nb_row = db + .execute( + "INSERT INTO state (name, value) VALUES ('last_block', $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.nb); + } + + // Load previous address + let mut addresses = rpc.list_accounts().expect("Failed to get accounts"); + if addresses.is_empty() { + addresses = rpc + .new_account("password") + .expect("Failed creating account"); + println!("Created new wallet"); + } else { + println!("Created new wallet"); + } + + let addr = addresses[0]; + 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 diff --git a/eth-wire/src/lib.rs b/eth-wire/src/lib.rs @@ -0,0 +1,122 @@ +/* + 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 ethereum_types::{Address, H256, U256, U64}; +use metadata::{InMetadata, OutMetadata}; +use rpc::hex::Hex; +use taler_common::url::Url; + +pub mod metadata; +pub mod rpc; + +impl rpc::Rpc { + pub fn deposit( + &mut self, + from: Address, + to: Address, + value: U256, + reserve_pub: [u8; 32], + ) -> rpc::Result<H256> { + let metadata = InMetadata::Deposit { reserve_pub }; + self.send_transaction(&rpc::TransactionRequest { + from, + to, + value, + gas: None, + gas_price: None, + data: Hex(metadata.encode()), + }) + } + + pub fn withdraw( + &mut self, + from: Address, + to: Address, + value: U256, + wtid: [u8; 32], + url: Url, + ) -> rpc::Result<H256> { + let metadata = OutMetadata::Withdraw { wtid, url }; + self.send_transaction(&rpc::TransactionRequest { + from, + to, + value, + gas: None, + gas_price: None, + data: Hex(metadata.encode()), + }) + } + + pub fn bounce(&mut self, hash: H256, bounce_fee: U256) -> rpc::Result<H256> { + let tx = self + .get_transaction(&hash)? + .expect("Cannot bounce a non existent transaction"); + let bounce_value = tx.value.saturating_sub(bounce_fee); + let metadata = OutMetadata::Bounce { bounced: hash }; + // TODO do not bounce empty amount + self.send_transaction(&rpc::TransactionRequest { + from: tx.to.expect("Cannot bounce contract transaction"), + to: tx.from.expect("Cannot bounce coinbase transaction"), + value: bounce_value, + gas: None, + gas_price: None, + data: Hex(metadata.encode()), + }) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct BlockState { + pub hash: H256, + pub nb: U64, +} + +impl BlockState { + pub fn to_bytes(&self) -> [u8; 40] { + let mut bytes = [0; 40]; + bytes[..32].copy_from_slice(self.hash.as_bytes()); + self.nb.to_little_endian(&mut bytes[32..]); + bytes + } + + pub fn from_bytes(bytes: &[u8; 40]) -> Self { + Self { + hash: H256::from_slice(&bytes[..32]), + nb: U64::from_little_endian(&bytes[32..]), + } + } +} + +#[cfg(test)] +mod test { + use ethereum_types::{H256, U64}; + use taler_common::{rand::random, rand_slice}; + + use crate::BlockState; + + #[test] + fn to_from_bytes_block_state() { + for _ in 0..4 { + let state = BlockState { + hash: H256::from_slice(&rand_slice::<32>()), + nb: U64::from(random::<u64>()), + }; + let encoded = state.to_bytes(); + let decoded = BlockState::from_bytes(&encoded); + assert_eq!(state, decoded); + } + } +} diff --git a/eth-wire/src/main.rs b/eth-wire/src/main.rs @@ -0,0 +1,146 @@ +/* + 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 std::time::Duration; + +use crate::rpc::Rpc; + +mod rpc; + +fn main() { + taler_common::log::init(); + let home = std::env::var("HOME").unwrap(); + let mut rpc = Rpc::new(format!("{}/.ethereum/geth.ipc", home)).unwrap(); + + /*let config = taler_common::config::Config::load_from_file( + std::env::args_os().nth(1).expect("Missing conf path arg"), + );*/ + //println!("Calling accounts."); + //let mut accounts = web3.eth().accounts().await?; + //println!("Accounts: {:?}", accounts); + /*accounts.push("00a329c0648769a73afac7f9381e08fb43dbea72".parse().unwrap()); + + println!("Calling balance."); + for account in &accounts { + let balance = web3.eth().balance(*account, None).await?; + println!("Balance of {:?}: {}", account, balance); + } + + web3.eth() + .send_transaction(TransactionRequest { + from: accounts[0], + to: Some(accounts[1]), + value: Some(U256::exp10(8)), + data: Some(Bytes::from(vec![0, 1, 2, 3, 4, 5])), + ..Default::default() + }) + .await?; + + println!("Calling balance."); + for account in &accounts { + let balance = web3.eth().balance(*account, None).await?; + println!("Balance of {:?}: {}", account, balance); + } + + let filter = web3 + .eth_filter() + .create_logs_filter( + FilterBuilder::default() + .from_block(BlockNumber::Earliest) + .to_block(BlockNumber::Latest) + .address(vec![accounts[1]]) + .build(), + ) + .await?; + + let result = filter.poll().await?; + dbg!(result); + web3.eth() + .send_transaction(TransactionRequest { + from: accounts[0], + to: Some(accounts[1]), + value: Some(U256::exp10(8)), + data: Some(Bytes::from(vec![0, 1, 2, 3, 4, 5])), + ..Default::default() + }) + .await?; + + println!("Calling balance."); + for account in &accounts { + let balance = web3.eth().balance(*account, None).await?; + println!("Balance of {:?}: {}", account, balance); + } + let result = filter.poll().await?; + dbg!(result); + + let filter = web3.eth_filter().create_blocks_filter().await?; + let result = filter.poll().await?; + dbg!(result);*/ + + //let nb = web3.eth().block_number().await.unwrap().as_u64(); + //println!("{} blocks", nb); + + /*let mut db = rusty_leveldb::DB::open( + format!("{}/.ethereum/geth/chaindata", home), + Options::default(), + ) + .unwrap(); + + // Getting hash using hashKey + let mut key = [b'h', 0, 0, 0, 0, 0, 0, 0, 0, b'n']; + U64::from(40).to_big_endian(&mut key[1..9]); + dbg!(&key); + let block_hash = db.get(&key).unwrap(); + dbg!(&block_hash); + + let mut key = Vec::new(); + key.push(b'h'); + key.extend_from_slice(&40u64.to_be_bytes()); + key.extend_from_slice(&block_hash); + dbg!(&key); + let header = db.get(&key).unwrap(); + //let header = BlockHeader:: ::from(header); + dbg!(header); + + return;*/ + /*let start = Instant::now(); + let mut nb_block = 0; + let mut nb_tx = 0; + let mut prev = 0; + { + let stdout = std::io::stdout(); + let mut stdout = stdout.lock(); + while let Some(block) = rpc.block(nb_block).unwrap() { + nb_block += 1; + nb_tx += block.transactions.len(); + if nb_block % 1000 == 0 { + let elapsed = start.elapsed().as_secs(); + writeln!( + &mut stdout, + "{:>5}kb {:>10}t {:>7}+ {:}h{:0>2}m{:0>2}s", + nb_block / 1000, + nb_tx, + nb_tx - prev, + elapsed / (60 * 60), + (elapsed % (60 * 60)) / 60, + elapsed % 60 + ) + .ok(); + prev = nb_tx; + } + } + } + println!("Done scanned {} blocks in {:?}", nb_block, start.elapsed());*/ +} diff --git a/eth-wire/src/metadata.rs b/eth-wire/src/metadata.rs @@ -0,0 +1,177 @@ +/* + 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 taler_common::url::Url; +use ethereum_types::H256; + +#[derive(Debug, Clone, Copy, thiserror::Error)] +pub enum DecodeErr { + #[error("Unknown first byte: {0}")] + UnknownFirstByte(u8), + #[error(transparent)] + UriPack(#[from] uri_pack::DecodeErr), + #[error("Unexpected end of file")] + UnexpectedEOF, +} + +/// Encoded metadata for outgoing transaction +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum OutMetadata { + Withdraw { wtid: [u8; 32], url: Url }, + Bounce { bounced: H256 }, +} + +// We leave a potential special meaning for u8::MAX +const BOUNCE_BYTE: u8 = u8::MAX - 1; + +impl OutMetadata { + pub fn encode(&self) -> Vec<u8> { + let mut buffer = Vec::new(); + match self { + OutMetadata::Withdraw { wtid, url } => { + buffer.push(if url.scheme() == "http" { 1 } else { 0 }); + buffer.extend_from_slice(wtid); + let parts = format!("{}{}", url.domain().unwrap_or(""), url.path()); + let packed = uri_pack::pack_uri(&parts).unwrap(); + buffer.extend_from_slice(&packed); + } + OutMetadata::Bounce { bounced } => { + buffer.push(BOUNCE_BYTE); + buffer.extend_from_slice(bounced.as_bytes()); + } + } + return buffer; + } + + pub fn decode(bytes: &[u8]) -> Result<Self, DecodeErr> { + if bytes.is_empty() { + return Err(DecodeErr::UnexpectedEOF); + } + match bytes[0] { + 0..=1 => { + if bytes.len() < 33 { + return Err(DecodeErr::UnexpectedEOF); + } + let packed = format!( + "http{}://{}", + if bytes[0] == 0 { "s" } else { "" }, + uri_pack::unpack_uri(&bytes[33..])?, + ); + let url = Url::parse(&packed).unwrap(); + Ok(OutMetadata::Withdraw { + wtid: bytes[1..33].try_into().unwrap(), + url, + }) + } + BOUNCE_BYTE => { + if bytes.len() < 33 { + return Err(DecodeErr::UnexpectedEOF); + } + Ok(OutMetadata::Bounce { + bounced: H256::from_slice(&bytes[1..33]), + }) + } + unknown => Err(DecodeErr::UnknownFirstByte(unknown)), + } + } +} + +/// Encoded metadata for incoming transaction +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum InMetadata { + Deposit { reserve_pub: [u8; 32] }, +} + +impl InMetadata { + pub fn encode(&self) -> Vec<u8> { + let mut buffer = Vec::new(); + match self { + InMetadata::Deposit { reserve_pub } => { + buffer.push(0); + buffer.extend_from_slice(reserve_pub); + } + } + return buffer; + } + + pub fn decode(bytes: &[u8]) -> Result<Self, DecodeErr> { + if bytes.is_empty() { + return Err(DecodeErr::UnexpectedEOF); + } + match bytes[0] { + 0 => { + if bytes.len() < 33 { + return Err(DecodeErr::UnexpectedEOF); + } + Ok(InMetadata::Deposit { + reserve_pub: bytes[1..33].try_into().unwrap(), + }) + } + unknown => Err(DecodeErr::UnknownFirstByte(unknown)), + } + } +} + +#[cfg(test)] +mod test { + use taler_common::{rand_slice, url::Url}; + use ethereum_types::H256; + + use crate::metadata::{InMetadata, OutMetadata}; + + #[test] + fn decode_encode_deposit() { + for _ in 0..4 { + let metadata = InMetadata::Deposit { + reserve_pub: rand_slice(), + }; + let encoded = metadata.encode(); + let decoded = InMetadata::decode(&encoded).unwrap(); + assert_eq!(decoded, metadata); + } + } + + #[test] + fn decode_encode_withdraw() { + 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 wtid = rand_slice(); + let url = Url::parse(url).unwrap(); + let metadata = OutMetadata::Withdraw { wtid, url }; + let encoded = metadata.encode(); + let decoded = OutMetadata::decode(&encoded).unwrap(); + assert_eq!(decoded, metadata); + } + } + + #[test] + fn decode_encode_bounce() { + for _ in 0..4 { + let id: [u8; 32] = rand_slice(); + let metadata = OutMetadata::Bounce { + bounced: H256::from_slice(&id), + }; + let encoded = metadata.encode(); + let decoded = OutMetadata::decode(&encoded).unwrap(); + assert_eq!(decoded, metadata); + } + } +} diff --git a/eth-wire/src/rpc.rs b/eth-wire/src/rpc.rs @@ -0,0 +1,400 @@ +/* + 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/> +*/ +//! This is a very simple RPC client designed only for a specific geth version +//! and to use on an secure unix domain socket to a trusted node +//! +//! We only parse the thing we actually use, this reduce memory usage and +//! make our code more compatible with future deprecation + +use ethereum_types::{Address, H256, U256, U64}; +use serde::de::DeserializeOwned; +use serde_json::error::Category; +use std::{ + fmt::Debug, + io::{self, BufWriter, ErrorKind, Read, Write}, + marker::PhantomData, + os::unix::net::UnixStream, + path::{Path, PathBuf}, +}; + +use self::hex::Hex; + +#[derive(Debug, serde::Serialize)] +struct RpcRequest<'a, T: serde::Serialize> { + method: &'a str, + id: u64, + params: &'a T, +} + +#[derive(Debug, serde::Deserialize)] +struct RpcResponse<T> { + result: Option<T>, + error: Option<RpcErr>, + id: u64, +} + +#[derive(Debug, serde::Deserialize)] +struct RpcErr { + code: i64, + message: String, +} + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("{0:?}")] + Transport(#[from] std::io::Error), + #[error("{code:?} - {msg}")] + RPC { code: i64, msg: String }, + #[error("JSON: {0}")] + Json(#[from] serde_json::Error), + #[error("No result or error")] + Null, +} + +pub type Result<T> = std::result::Result<T, Error>; + +const EMPTY: [(); 0] = []; + +pub trait RpcTrait {} + +/// Bitcoin RPC connection +pub struct Rpc { + path: PathBuf, + id: u64, + conn: BufWriter<UnixStream>, + read_buf: Vec<u8>, + cursor: usize, +} + +impl Rpc { + pub fn new(path: impl AsRef<Path>) -> io::Result<Self> { + let conn = UnixStream::connect(&path)?; + + Ok(Self { + path: path.as_ref().to_path_buf(), + id: 0, + conn: BufWriter::new(conn), + read_buf: vec![0u8; 8 * 1024], + cursor: 0, + }) + } + + fn send(&mut self, method: &str, params: &impl serde::Serialize) -> Result<()> { + // TODO rethink timeout + let request = RpcRequest { + method, + id: self.id, + params, + }; + + // Send request + serde_json::to_writer(&mut self.conn, &request)?; + self.conn.flush()?; + Ok(()) + } + + fn receive<T>(&mut self) -> Result<T> + where + T: serde::de::DeserializeOwned + Debug, + { + loop { + if self.cursor == self.read_buf.len() { + self.read_buf.resize(self.cursor * 2, 0); + } + match self.conn.get_mut().read(&mut self.read_buf[self.cursor..]) { + Ok(nb) => { + self.cursor += nb; + let mut de: serde_json::StreamDeserializer<_, T> = + serde_json::Deserializer::from_slice(&self.read_buf[..self.cursor]) + .into_iter(); + + if let Some(result) = de.next() { + match result { + Ok(response) => { + let read = de.byte_offset(); + self.read_buf.copy_within(read..self.cursor, 0); + self.cursor -= read; + return Ok(response); + } + Err(err) if err.classify() == Category::Eof => { + if nb == 0 { + return Err(std::io::Error::new( + ErrorKind::UnexpectedEof, + "Stream EOF", + ))?; + } + } + Err(e) => Err(e)?, + } + } + } + Err(e) if e.kind() == ErrorKind::Interrupted => {} + Err(e) => Err(e)?, + } + } + } + + fn call<T>(&mut self, method: &str, params: &impl serde::Serialize) -> Result<T> + where + T: serde::de::DeserializeOwned + Debug, + { + self.send(method, params)?; + let response: RpcResponse<T> = self.receive()?; + + assert_eq!(self.id, response.id); + self.id += 1; + return if let Some(ok) = response.result { + Ok(ok) + } else { + Err(match response.error { + Some(err) => Error::RPC { + code: err.code, + msg: err.message, + }, + None => Error::Null, + }) + }; + } + + pub fn list_accounts(&mut self) -> Result<Vec<Address>> { + self.call("personal_listAccounts", &EMPTY) + } + + pub fn new_account(&mut self, passwd: &str) -> Result<Vec<Address>> { + self.call("personal_newAccount", &[passwd]) + } + + pub fn import_account(&mut self, hex: &str, passwd: &str) -> Result<bool> { + self.call("personal_importRawKey", &(hex, passwd)) + } + + pub fn unlock_account(&mut self, account: &Address, passwd: &str) -> Result<bool> { + self.call("personal_unlockAccount", &(account, passwd, 0)) + } + + pub fn get_transaction(&mut self, hash: &H256) -> Result<Option<Transaction>> { + self.call("eth_getTransactionByHash", &[hash]) + } + + pub fn send_transaction(&mut self, params: &TransactionRequest) -> Result<H256> { + self.call("eth_sendTransaction", &[params]) + } + + pub fn block(&mut self, nb: U64) -> Result<Option<Block>> { + self.call("eth_getBlockByNumber", &(nb, &true)) + } + + pub fn pending_transactions(&mut self) -> Result<Vec<Transaction>> { + self.call("eth_pendingTransactions", &EMPTY) + } + + pub fn miner_start(&mut self) -> Result<()> { + match self.call("miner_start", &[1]) { + Err(Error::Null) => Ok(()), + i => i, + } + } + + pub fn miner_stop(&mut self) -> Result<()> { + match self.call("miner_stop", &EMPTY) { + Err(Error::Null) => Ok(()), + i => i, + } + } + + pub fn subscribe_new_head(&mut self) -> Result<RpcStream<BlockHead>> { + let mut rpc = Self::new(&self.path)?; + let id: String = rpc.call("eth_subscribe", &["newHeads"])?; + Ok(RpcStream::new(rpc, id)) + } + + pub fn current_block(&mut self) -> Result<Block> { + let number: U64 = self.call("eth_blockNumber", &EMPTY)?; + Ok(self.block(number)?.expect("Current block must exist")) + } +} + +pub struct RpcStream<T: Debug + DeserializeOwned> { + rpc: Rpc, + id: String, + phantom: PhantomData<T>, +} + +impl<T: Debug + DeserializeOwned> RpcStream<T> { + fn new(rpc: Rpc, id: String) -> Self { + Self { + rpc, + id, + phantom: PhantomData, + } + } + + pub fn next(&mut self) -> Result<T> { + let notification: Wrapper<T> = self.rpc.receive()?; + let notification = notification.params; + assert_eq!(self.id, notification.subscription); + Ok(notification.result) + } +} + +#[derive(Debug, serde::Deserialize)] +pub struct Notification<T> { + subscription: String, + result: T, +} + +#[derive(Debug, serde::Deserialize)] +struct Wrapper<T> { + params: Notification<T>, +} + +#[derive(Debug, serde::Deserialize)] +#[serde(untagged)] +pub enum NotifEnd<T> { + Notification(Notification<T>), + End(bool), +} + +#[derive(Debug, serde::Deserialize)] +pub struct Block { + /// Hash of the block + pub hash: Option<H256>, + /// Block number (None if pending) + pub number: Option<U64>, + /// Hash of the parent + #[serde(rename = "parentHash")] + pub parent_hash: H256, + /// Transactions + pub transactions: Vec<Transaction>, +} + +#[derive(Debug, serde::Deserialize)] +pub struct BlockHead {} + +/// Description of a Transaction, pending or in the chain. +#[derive(Debug, serde::Deserialize)] +pub struct Transaction { + /// Hash + pub hash: H256, + /// Sender address (None when coinbase) + pub from: Option<Address>, + /// Recipient address (None when contract creation) + pub to: Option<Address>, + /// Transferred value + pub value: U256, + /// Input data + pub input: Hex, +} + +/// Send Transaction Parameters +#[derive(Debug, serde::Serialize)] +pub struct TransactionRequest { + /// Sender address + pub from: Address, + /// Recipient address + pub to: Address, + /// Transferred value + pub value: U256, + /// Supplied gas (None for sensible default) + #[serde(skip_serializing_if = "Option::is_none")] + pub gas: Option<U256>, + /// Gas price (None for sensible default) + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "gasPrice")] + pub gas_price: Option<U256>, + /// Transaction data + pub data: Hex, +} + +pub mod hex { + use std::{ + fmt, + ops::{Deref, DerefMut}, + }; + + use serde::{ + de::{Error, Unexpected, Visitor}, + Deserialize, Deserializer, Serialize, Serializer, + }; + + /// Raw bytes wrapper + #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] + pub struct Hex(pub Vec<u8>); + + impl Deref for Hex { + type Target = Vec<u8>; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl DerefMut for Hex { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } + } + + impl Serialize for Hex { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + let mut serialized = "0x".to_owned(); + serialized.push_str(&hex::encode(&self.0)); + serializer.serialize_str(serialized.as_ref()) + } + } + + impl<'a> Deserialize<'a> for Hex { + fn deserialize<D>(deserializer: D) -> Result<Hex, D::Error> + where + D: Deserializer<'a>, + { + deserializer.deserialize_identifier(BytesVisitor) + } + } + + struct BytesVisitor; + + impl<'a> Visitor<'a> for BytesVisitor { + type Value = Hex; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "a 0x-prefixed hex-encoded vector of bytes") + } + + fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> + where + E: Error, + { + if value.len() >= 2 && &value[0..2] == "0x" { + let bytes = hex::decode(&value[2..]) + .map_err(|e| Error::custom(format!("Invalid hex: {}", e)))?; + Ok(Hex(bytes)) + } else { + Err(Error::invalid_value(Unexpected::Str(value), &"0x prefix")) + } + } + + fn visit_string<E>(self, value: String) -> Result<Self::Value, E> + where + E: Error, + { + self.visit_str(value.as_ref()) + } + } +} diff --git a/eth_wire/Cargo.toml b/eth_wire/Cargo.toml @@ -1,21 +0,0 @@ -[package] -name = "eth_wire" -version = "0.1.0" -edition = "2021" -license = "AGPL-3.0-or-later" - -[dependencies] -# Cli args -argh = "0.1.7" -# Serialization library -serde = { version = "1.0.133", features = ["derive"] } -serde_json = "1.0.75" -serde_repr = "0.1.7" -# Ethereum RPC client -web3 = { version = "0.17.0", default-features = false} -# Error macros -thiserror = "1.0.30" -# Optimized uri binary format -uri-pack = { path = "../uri-pack" } -# Taler libs -taler-common = { path = "../taler-common" } diff --git a/eth_wire/src/bin/eth_test.rs b/eth_wire/src/bin/eth_test.rs @@ -1,139 +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 std::{panic::AssertUnwindSafe, str::FromStr}; - -use eth_wire::{ - metadata::{InMetadata, OutMetadata}, - rpc::{self, Rpc}, -}; -use taler_common::{rand_slice, url::Url}; -use web3::types::{H256, U256}; - -pub fn main() { - let path = std::env::args().nth(1).unwrap(); - let mut rpc = Rpc::new(path).unwrap(); - - let accounts = rpc.list_accounts().unwrap(); - for account in &accounts { - rpc.unlock_account(&account, "password").unwrap(); - } - let wire = accounts[0]; - let client = accounts[1]; - let reserve = accounts[2]; - - let test_value = U256::from(1500032); - let bounce_value = U256::from(10000u32); - let test_url = Url::from_str("http://test.com").unwrap(); - - let mut runner = TestRunner::new(); - - runner.test("Deposit", || { - let rng = rand_slice(); - let hash = rpc.deposit(client, wire, test_value, rng).unwrap(); - assert!(tx_pending(&mut rpc, &hash).unwrap()); - mine_pending(&mut rpc).unwrap(); - assert!(!tx_pending(&mut rpc, &hash).unwrap()); - let tx = rpc.get_transaction(&hash).unwrap().unwrap(); - let metadata = InMetadata::decode(&tx.input.0).unwrap(); - assert!(matches!(metadata, InMetadata::Deposit { reserve_pub } if reserve_pub == rng)); - assert_eq!(tx.value, test_value); - }); - - runner.test("Withdraw", || { - let rng = rand_slice(); - let hash = rpc - .withdraw(wire, client, test_value, rng, test_url.clone()) - .unwrap(); - assert!(tx_pending(&mut rpc, &hash).unwrap()); - mine_pending(&mut rpc).unwrap(); - assert!(!tx_pending(&mut rpc, &hash).unwrap()); - let tx = rpc.get_transaction(&hash).unwrap().unwrap(); - let metadata = OutMetadata::decode(&tx.input.0).unwrap(); - assert!( - matches!(metadata, OutMetadata::Withdraw { wtid, url } if wtid == rng && url == url) - ); - assert_eq!(tx.value, test_value); - }); - - runner.test("Bounce", || { - let rng = rand_slice(); - let deposit = rpc.deposit(client, wire, test_value, rng).unwrap(); - let hash = rpc.bounce(deposit, bounce_value).unwrap(); - assert!(tx_pending(&mut rpc, &hash).unwrap()); - mine_pending(&mut rpc).unwrap(); - assert!(!tx_pending(&mut rpc, &hash).unwrap()); - let tx = rpc.get_transaction(&hash).unwrap().unwrap(); - let metadata = OutMetadata::decode(&tx.input.0).unwrap(); - assert!(matches!(metadata, OutMetadata::Bounce { bounced } if bounced == deposit)); - assert_eq!(tx.value, test_value - bounce_value); - }); - - runner.conclude(); -} - -/// Check a specific transaction is pending -fn tx_pending(rpc: &mut Rpc, id: &H256) -> rpc::Result<bool> { - Ok(rpc.pending_transactions()?.iter().any(|t| t.hash == *id)) -} - -/// Mine pending transactions -fn mine_pending(rpc: &mut Rpc) -> rpc::Result<()> { - let mut notifier = rpc.subscribe_new_head()?; - rpc.miner_start()?; - while !rpc.pending_transactions()?.is_empty() { - notifier.next()?; - } - rpc.miner_stop()?; - Ok(()) -} - -/// Run test track success and errors -struct TestRunner { - nb_ok: usize, - nb_err: usize, -} - -impl TestRunner { - fn new() -> Self { - Self { - nb_err: 0, - nb_ok: 0, - } - } - - fn test(&mut self, name: &str, test: impl FnOnce()) { - println!("{}", name); - - let result = std::panic::catch_unwind(AssertUnwindSafe(test)); - if result.is_ok() { - println!("OK"); - self.nb_ok += 1; - } else { - println!("ERR"); - self.nb_err += 1; - } - } - - /// Wait for tests completion and print results - fn conclude(self) { - println!( - "Result for {} tests: {} ok and {} err", - self.nb_ok + self.nb_err, - self.nb_ok, - self.nb_err - ); - } -} diff --git a/eth_wire/src/lib.rs b/eth_wire/src/lib.rs @@ -1,84 +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 metadata::{InMetadata, OutMetadata}; -use taler_common::url::Url; -use web3::{ - ethabi::Address, - types::{Bytes, H256, U256}, -}; - -pub mod metadata; -pub mod rpc; - -impl rpc::Rpc { - pub fn deposit( - &mut self, - from: Address, - to: Address, - value: U256, - reserve_pub: [u8; 32], - ) -> rpc::Result<H256> { - let metadata = InMetadata::Deposit { reserve_pub }; - let encoded = metadata.encode(); - self.send_transaction(&rpc::TransactionRequest { - from, - to, - value, - gas: None, - gas_price: None, - data: Some(Bytes::from(encoded)), - }) - } - - pub fn withdraw( - &mut self, - from: Address, - to: Address, - value: U256, - wtid: [u8; 32], - url: Url, - ) -> rpc::Result<H256> { - let metadata = OutMetadata::Withdraw { wtid, url }; - let encoded = metadata.encode(); - self.send_transaction(&rpc::TransactionRequest { - from, - to, - value, - gas: None, - gas_price: None, - data: Some(Bytes::from(encoded)), - }) - } - - pub fn bounce(&mut self, hash: H256, bounce_fee: U256) -> rpc::Result<H256> { - let tx = self - .get_transaction(&hash)? - .expect("Cannot bounce a non existent transaction"); - let bounce_value = tx.value.saturating_sub(bounce_fee); - let metadata = OutMetadata::Bounce { bounced: hash }; - let encoded = metadata.encode(); - // TODO do not bounce empty amount - self.send_transaction(&rpc::TransactionRequest { - from: tx.to.expect("Cannot bounce contract transaction"), - to: tx.from.expect("Cannot bounce coinbase transaction"), - value: bounce_value, - gas: None, - gas_price: None, - data: Some(Bytes::from(encoded)), - }) - } -} diff --git a/eth_wire/src/main.rs b/eth_wire/src/main.rs @@ -1,165 +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 std::time::Duration; - -use web3::types::SyncState; - -use crate::rpc::Rpc; - -mod rpc; - -fn main() { - taler_common::log::init(); - let home = std::env::var("HOME").unwrap(); - let mut rpc = Rpc::new(format!("{}/.ethereum/geth.ipc", home)).unwrap(); - - let config = taler_common::config::Config::load_from_file( - std::env::args_os().nth(1).expect("Missing conf path arg"), - ); - //println!("Calling accounts."); - //let mut accounts = web3.eth().accounts().await?; - //println!("Accounts: {:?}", accounts); - /*accounts.push("00a329c0648769a73afac7f9381e08fb43dbea72".parse().unwrap()); - - println!("Calling balance."); - for account in &accounts { - let balance = web3.eth().balance(*account, None).await?; - println!("Balance of {:?}: {}", account, balance); - } - - web3.eth() - .send_transaction(TransactionRequest { - from: accounts[0], - to: Some(accounts[1]), - value: Some(U256::exp10(8)), - data: Some(Bytes::from(vec![0, 1, 2, 3, 4, 5])), - ..Default::default() - }) - .await?; - - println!("Calling balance."); - for account in &accounts { - let balance = web3.eth().balance(*account, None).await?; - println!("Balance of {:?}: {}", account, balance); - } - - let filter = web3 - .eth_filter() - .create_logs_filter( - FilterBuilder::default() - .from_block(BlockNumber::Earliest) - .to_block(BlockNumber::Latest) - .address(vec![accounts[1]]) - .build(), - ) - .await?; - - let result = filter.poll().await?; - dbg!(result); - web3.eth() - .send_transaction(TransactionRequest { - from: accounts[0], - to: Some(accounts[1]), - value: Some(U256::exp10(8)), - data: Some(Bytes::from(vec![0, 1, 2, 3, 4, 5])), - ..Default::default() - }) - .await?; - - println!("Calling balance."); - for account in &accounts { - let balance = web3.eth().balance(*account, None).await?; - println!("Balance of {:?}: {}", account, balance); - } - let result = filter.poll().await?; - dbg!(result); - - let filter = web3.eth_filter().create_blocks_filter().await?; - let result = filter.poll().await?; - dbg!(result);*/ - - loop { - let sync = rpc.syncing().unwrap(); - match sync { - SyncState::Syncing(info) => { - println!( - "{}% ({}/{})", - info.current_block * 100 / info.highest_block, - info.current_block, - info.highest_block - ); - break; - std::thread::sleep(Duration::from_secs(60)); - } - SyncState::NotSyncing => break, - } - } - - //let nb = web3.eth().block_number().await.unwrap().as_u64(); - //println!("{} blocks", nb); - - /*let mut db = rusty_leveldb::DB::open( - format!("{}/.ethereum/geth/chaindata", home), - Options::default(), - ) - .unwrap(); - - // Getting hash using hashKey - let mut key = [b'h', 0, 0, 0, 0, 0, 0, 0, 0, b'n']; - U64::from(40).to_big_endian(&mut key[1..9]); - dbg!(&key); - let block_hash = db.get(&key).unwrap(); - dbg!(&block_hash); - - let mut key = Vec::new(); - key.push(b'h'); - key.extend_from_slice(&40u64.to_be_bytes()); - key.extend_from_slice(&block_hash); - dbg!(&key); - let header = db.get(&key).unwrap(); - //let header = BlockHeader:: ::from(header); - dbg!(header); - - return;*/ - /*let start = Instant::now(); - let mut nb_block = 0; - let mut nb_tx = 0; - let mut prev = 0; - { - let stdout = std::io::stdout(); - let mut stdout = stdout.lock(); - while let Some(block) = rpc.block(nb_block).unwrap() { - nb_block += 1; - nb_tx += block.transactions.len(); - if nb_block % 1000 == 0 { - let elapsed = start.elapsed().as_secs(); - writeln!( - &mut stdout, - "{:>5}kb {:>10}t {:>7}+ {:}h{:0>2}m{:0>2}s", - nb_block / 1000, - nb_tx, - nb_tx - prev, - elapsed / (60 * 60), - (elapsed % (60 * 60)) / 60, - elapsed % 60 - ) - .ok(); - prev = nb_tx; - } - } - } - println!("Done scanned {} blocks in {:?}", nb_block, start.elapsed());*/ -} diff --git a/eth_wire/src/metadata.rs b/eth_wire/src/metadata.rs @@ -1,177 +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 taler_common::url::Url; -use web3::types::H256; - -#[derive(Debug, Clone, Copy, thiserror::Error)] -pub enum DecodeErr { - #[error("Unknown first byte: {0}")] - UnknownFirstByte(u8), - #[error(transparent)] - UriPack(#[from] uri_pack::DecodeErr), - #[error("Unexpected end of file")] - UnexpectedEOF, -} - -/// Encoded metadata for outgoing transaction -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum OutMetadata { - Withdraw { wtid: [u8; 32], url: Url }, - Bounce { bounced: H256 }, -} - -// We leave a potential special meaning for u8::MAX -const BOUNCE_BYTE: u8 = u8::MAX - 1; - -impl OutMetadata { - pub fn encode(&self) -> Vec<u8> { - let mut buffer = Vec::new(); - match self { - OutMetadata::Withdraw { wtid, url } => { - buffer.push(if url.scheme() == "http" { 1 } else { 0 }); - buffer.extend_from_slice(wtid); - let parts = format!("{}{}", url.domain().unwrap_or(""), url.path()); - let packed = uri_pack::pack_uri(&parts).unwrap(); - buffer.extend_from_slice(&packed); - } - OutMetadata::Bounce { bounced } => { - buffer.push(BOUNCE_BYTE); - buffer.extend_from_slice(bounced.as_bytes()); - } - } - return buffer; - } - - pub fn decode(bytes: &[u8]) -> Result<Self, DecodeErr> { - if bytes.is_empty() { - return Err(DecodeErr::UnexpectedEOF); - } - match bytes[0] { - 0..=1 => { - if bytes.len() < 33 { - return Err(DecodeErr::UnexpectedEOF); - } - let packed = format!( - "http{}://{}", - if bytes[0] == 0 { "s" } else { "" }, - uri_pack::unpack_uri(&bytes[33..])?, - ); - let url = Url::parse(&packed).unwrap(); - Ok(OutMetadata::Withdraw { - wtid: bytes[1..33].try_into().unwrap(), - url, - }) - } - BOUNCE_BYTE => { - if bytes.len() < 33 { - return Err(DecodeErr::UnexpectedEOF); - } - Ok(OutMetadata::Bounce { - bounced: H256::from_slice(&bytes[1..33]), - }) - } - unknown => Err(DecodeErr::UnknownFirstByte(unknown)), - } - } -} - -/// Encoded metadata for incoming transaction -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum InMetadata { - Deposit { reserve_pub: [u8; 32] }, -} - -impl InMetadata { - pub fn encode(&self) -> Vec<u8> { - let mut buffer = Vec::new(); - match self { - InMetadata::Deposit { reserve_pub } => { - buffer.push(0); - buffer.extend_from_slice(reserve_pub); - } - } - return buffer; - } - - pub fn decode(bytes: &[u8]) -> Result<Self, DecodeErr> { - if bytes.is_empty() { - return Err(DecodeErr::UnexpectedEOF); - } - match bytes[0] { - 0 => { - if bytes.len() < 33 { - return Err(DecodeErr::UnexpectedEOF); - } - Ok(InMetadata::Deposit { - reserve_pub: bytes[1..33].try_into().unwrap(), - }) - } - unknown => Err(DecodeErr::UnknownFirstByte(unknown)), - } - } -} - -#[cfg(test)] -mod test { - use taler_common::{rand_slice, url::Url}; - use web3::types::H256; - - use crate::metadata::{InMetadata, OutMetadata}; - - #[test] - fn decode_encode_deposit() { - for _ in 0..4 { - let metadata = InMetadata::Deposit { - reserve_pub: rand_slice(), - }; - let encoded = metadata.encode(); - let decoded = InMetadata::decode(&encoded).unwrap(); - assert_eq!(decoded, metadata); - } - } - - #[test] - fn decode_encode_withdraw() { - 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 wtid = rand_slice(); - let url = Url::parse(url).unwrap(); - let metadata = OutMetadata::Withdraw { wtid, url }; - let encoded = metadata.encode(); - let decoded = OutMetadata::decode(&encoded).unwrap(); - assert_eq!(decoded, metadata); - } - } - - #[test] - fn decode_encode_bounce() { - for _ in 0..4 { - let id: [u8; 32] = rand_slice(); - let metadata = OutMetadata::Bounce { - bounced: H256::from_slice(&id), - }; - let encoded = metadata.encode(); - let decoded = OutMetadata::decode(&encoded).unwrap(); - assert_eq!(decoded, metadata); - } - } -} diff --git a/eth_wire/src/rpc.rs b/eth_wire/src/rpc.rs @@ -1,316 +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/> -*/ -//! This is a very simple RPC client designed only for a specific geth version -//! and to use on an secure unix domain socket to a trusted node -//! -//! We only parse the thing we actually use, this reduce memory usage and -//! make our code more compatible with future deprecation - -use serde::de::DeserializeOwned; -use serde_json::error::Category; -use std::{ - fmt::Debug, - io::{self, BufWriter, ErrorKind, Read, Write}, - marker::PhantomData, - os::unix::net::UnixStream, - path::{Path, PathBuf}, -}; -use web3::types::{Address, Bytes, SyncState, H256, U256, U64}; - -#[derive(Debug, serde::Serialize)] -struct RpcRequest<'a, T: serde::Serialize> { - method: &'a str, - id: u64, - params: &'a T, -} - -#[derive(Debug, serde::Deserialize)] -struct RpcResponse<T> { - result: Option<T>, - error: Option<RpcErr>, - id: u64, -} - -#[derive(Debug, serde::Deserialize)] -struct RpcErr { - code: i64, - message: String, -} - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("{0:?}")] - Transport(#[from] std::io::Error), - #[error("{code:?} - {msg}")] - RPC { code: i64, msg: String }, - #[error("JSON: {0}")] - Json(#[from] serde_json::Error), - #[error("No result or error")] - Null, -} - -pub type Result<T> = std::result::Result<T, Error>; - -const EMPTY: [(); 0] = []; - -pub trait RpcTrait {} - -/// Bitcoin RPC connection -pub struct Rpc { - path: PathBuf, - id: u64, - conn: BufWriter<UnixStream>, - read_buf: Vec<u8>, - cursor: usize, -} - -impl Rpc { - pub fn new(path: impl AsRef<Path>) -> io::Result<Self> { - let conn = UnixStream::connect(&path)?; - - Ok(Self { - path: path.as_ref().to_path_buf(), - id: 0, - conn: BufWriter::new(conn), - read_buf: vec![0u8; 8 * 1024], - cursor: 0, - }) - } - - fn send(&mut self, method: &str, params: &impl serde::Serialize) -> Result<()> { - // TODO rethink timeout - let request = RpcRequest { - method, - id: self.id, - params, - }; - - // Send request - serde_json::to_writer(&mut self.conn, &request)?; - self.conn.flush()?; - Ok(()) - } - - fn receive<T>(&mut self) -> Result<T> - where - T: serde::de::DeserializeOwned + Debug, - { - loop { - if self.cursor == self.read_buf.len() { - self.read_buf.resize(self.cursor * 2, 0); - } - match self.conn.get_mut().read(&mut self.read_buf[self.cursor..]) { - Ok(nb) => { - self.cursor += nb; - let mut de: serde_json::StreamDeserializer<_, T> = - serde_json::Deserializer::from_slice(&self.read_buf[..self.cursor]) - .into_iter(); - - if let Some(result) = de.next() { - match result { - Ok(response) => { - let read = de.byte_offset(); - self.read_buf.copy_within(read..self.cursor, 0); - self.cursor -= read; - return Ok(response); - } - Err(err) if err.classify() == Category::Eof => { - if nb == 0 { - return Err(std::io::Error::new( - ErrorKind::UnexpectedEof, - "Stream EOF", - ))?; - } - } - Err(e) => Err(e)?, - } - } - } - Err(e) if e.kind() == ErrorKind::Interrupted => {} - Err(e) => Err(e)?, - } - } - } - - fn call<T>(&mut self, method: &str, params: &impl serde::Serialize) -> Result<T> - where - T: serde::de::DeserializeOwned + Debug, - { - self.send(method, params)?; - let response: RpcResponse<T> = self.receive()?; - - assert_eq!(self.id, response.id); - self.id += 1; - return if let Some(ok) = response.result { - Ok(ok) - } else { - Err(match response.error { - Some(err) => Error::RPC { - code: err.code, - msg: err.message, - }, - None => Error::Null, - }) - }; - } - - pub fn syncing(&mut self) -> Result<SyncState> { - self.call("eth_syncing", &EMPTY) - } - - pub fn list_accounts(&mut self) -> Result<Vec<Address>> { - self.call("personal_listAccounts", &EMPTY) - } - - pub fn new_account(&mut self, passwd: &str) -> Result<Vec<Address>> { - self.call("personal_newAccount", &[passwd]) - } - - pub fn import_account(&mut self, hex: &str, passwd: &str) -> Result<bool> { - self.call("personal_importRawKey", &(hex, passwd)) - } - - pub fn unlock_account(&mut self, account: &Address, passwd: &str) -> Result<bool> { - self.call("personal_unlockAccount", &(account, passwd, 0)) - } - - pub fn get_transaction(&mut self, hash: &H256) -> Result<Option<Transaction>> { - self.call("eth_getTransactionByHash", &[hash]) - } - - pub fn send_transaction(&mut self, params: &TransactionRequest) -> Result<H256> { - self.call("eth_sendTransaction", &[params]) - } - - pub fn block(&mut self, nb: u64) -> Result<Option<Block>> { - self.call("eth_getBlockByNumber", &(U64::from(nb), &true)) - } - - pub fn pending_transactions(&mut self) -> Result<Vec<Transaction>> { - self.call("eth_pendingTransactions", &EMPTY) - } - - pub fn miner_start(&mut self) -> Result<()> { - match self.call("miner_start", &[1]) { - Err(Error::Null) => Ok(()), - i => i, - } - } - - pub fn miner_stop(&mut self) -> Result<()> { - match self.call("miner_stop", &EMPTY) { - Err(Error::Null) => Ok(()), - i => i, - } - } - - pub fn subscribe_new_head(&mut self) -> Result<RpcStream<BlockHead>> { - let mut rpc = Self::new(&self.path)?; - let id: String = rpc.call("eth_subscribe", &["newHeads"])?; - Ok(RpcStream::new(rpc, id)) - } -} - -pub struct RpcStream<T: Debug + DeserializeOwned> { - rpc: Rpc, - id: String, - phantom: PhantomData<T>, -} - -impl<T: Debug + DeserializeOwned> RpcStream<T> { - fn new(rpc: Rpc, id: String) -> Self { - Self { - rpc, - id, - phantom: PhantomData, - } - } - - pub fn next(&mut self) -> Result<T> { - let notification: Wrapper<T> = self.rpc.receive()?; - let notification = notification.params; - assert_eq!(self.id, notification.subscription); - Ok(notification.result) - } -} - -#[derive(Debug, serde::Deserialize)] -pub struct Notification<T> { - subscription: String, - result: T, -} - -#[derive(Debug, serde::Deserialize)] -struct Wrapper<T> { - params: Notification<T>, -} - -#[derive(Debug, serde::Deserialize)] -#[serde(untagged)] -pub enum NotifEnd<T> { - Notification(Notification<T>), - End(bool), -} - -#[derive(Debug, serde::Deserialize)] -pub struct Block { - /// Hash of the block - pub hash: Option<H256>, - /// Hash of the parent - #[serde(rename = "parentHash")] - pub parent_hash: H256, - /// Transactions - pub transactions: Vec<Transaction>, -} - -#[derive(Debug, serde::Deserialize)] -pub struct BlockHead {} - -/// Description of a Transaction, pending or in the chain. -#[derive(Debug, serde::Deserialize)] -pub struct Transaction { - /// Hash - pub hash: H256, - /// Sender address (None when coinbase) - pub from: Option<Address>, - /// Recipient address (None when contract creation) - pub to: Option<Address>, - /// Transferred value - pub value: U256, - /// Input data - pub input: Bytes, -} - -/// Send Transaction Parameters -#[derive(Debug, serde::Serialize)] -pub struct TransactionRequest { - /// Sender address - pub from: Address, - /// Recipient address - pub to: Address, - /// Transferred value - pub value: U256, - /// Supplied gas (None for sensible default) - #[serde(skip_serializing_if = "Option::is_none")] - pub gas: Option<U256>, - /// Gas price (None for sensible default) - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(rename = "gasPrice")] - pub gas_price: Option<U256>, - /// Transaction data - #[serde(skip_serializing_if = "Option::is_none")] - pub data: Option<Bytes>, -} diff --git a/makefile b/makefile @@ -1,5 +1,6 @@ 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 wire-gateway test_gateway: diff --git a/taler-common/Cargo.toml b/taler-common/Cargo.toml @@ -8,11 +8,11 @@ license = "AGPL-3.0-or-later" [dependencies] # Serialization framework -serde = { version = "1.0.133", features = ["derive"] } +serde = { version = "1.0.136", features = ["derive"] } # Serialization helper serde_with = "1.11.0" # JSON serialization -serde_json = "1.0.75" +serde_json = "1.0.78" # Url format url = { version = "2.2.2", features = ["serde"] } # Crockford’s base32 @@ -23,11 +23,11 @@ thiserror = "1.0.30" rust-ini = "0.17.0" # Logging log = "0.4.14" -flexi_logger = { version = "0.22.2", default-features = false, features = [ +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 -time = { version = "0.3.5", features = ["formatting", "macros"] } +time = { version = "0.3.7", features = ["formatting", "macros"] } # Postgres client postgres = "0.19.2" # Secure random diff --git a/taler-common/src/config.rs b/taler-common/src/config.rs @@ -20,69 +20,115 @@ use std::{ }; use url::Url; -pub struct InitConfig { +pub trait Config: Sized { + /// Load from a file + fn load_from_file(config_file: impl AsRef<Path>) -> Self { + let conf = ini::Ini::load_from_file(config_file).expect("Failed to open the config file"); + let taler = section(&conf, "taler"); + let currency = require(&taler, "CURRENCY", string); + let section_name = match currency.as_str() { + "BTC" => "depolymerizer-bitcoin", + "ETH" => "depolymerizer-ethereum", + currency => unimplemented!("Unsupported currency {}", currency), + }; + + let dep = section(&conf, section_name); + return Self::load_from_ini(&conf, &currency, dep); + } + /// Load from loaded ini file + fn load_from_ini(ini: &Ini, currency: &str, dep: &Properties) -> Self; +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CoreConfig { pub db_url: String, - pub btc_data_dir: Option<PathBuf>, + pub data_dir: Option<PathBuf>, + pub currency: String, } -impl InitConfig { - /// Load from a file - pub fn load_from_file(config_file: impl AsRef<Path>) -> Self { - let conf = ini::Ini::load_from_file(config_file).unwrap(); - assert(section(&conf, "taler"), "CURRENCY", "BTC"); - let self_conf = section(&conf, "depolymerizer-bitcoin"); +impl Config for CoreConfig { + fn load_from_ini(_: &Ini, currency: &str, dep: &Properties) -> Self { Self { - db_url: require(self_conf, "DB_URL", string), - btc_data_dir: path(self_conf, "BTC_DATA_DIR"), + db_url: require(dep, "DB_URL", string), + data_dir: path(dep, "DATA_DIR"), + currency: currency.to_string(), } } } -/// Taler config with depolymerizer config #[derive(Debug, Clone, PartialEq, Eq)] -pub struct Config { - pub base_url: Url, - pub db_url: String, +pub struct GatewayConfig { + pub http_lifetime: Option<u32>, pub port: u16, pub unix_path: Option<PathBuf>, - pub btc_data_dir: Option<PathBuf>, pub payto: Url, + pub init: 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), + init: CoreConfig::load_from_ini(ini, currency, dep), + } + } +} + +// TODO currency name as const generic + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct WireConfig<const DEFAULT_FEE: u64, const DEFAULT_CONFIRMATION: u16> { + pub base_url: Url, pub confirmation: u16, pub bounce_fee: u64, pub wire_lifetime: Option<u32>, - pub http_lifetime: Option<u32>, pub bump_delay: Option<u32>, + pub payto: Url, + pub init: CoreConfig, } -impl Config { - /// Load from a file - pub fn load_from_file(config_file: impl AsRef<Path>) -> Self { - let conf = ini::Ini::load_from_file(config_file).unwrap(); - assert(section(&conf, "taler"), "CURRENCY", "BTC"); - let ex_conf = section(&conf, "exchange"); - let self_conf = section(&conf, "depolymerizer-bitcoin"); +impl<const DEFAULT_FEE: u64, const DEFAULT_CONFIRMATION: u16> Config + for WireConfig<DEFAULT_FEE, DEFAULT_CONFIRMATION> +{ + fn load_from_ini(ini: &Ini, currency: &str, dep: &Properties) -> Self { + let ex = section(ini, "exchange"); Self { - base_url: require(ex_conf, "BASE_URL", url), - db_url: require(self_conf, "DB_URL", string), - port: nb(self_conf, "PORT").unwrap_or(8080), - unix_path: path(self_conf, "UNIXPATH"), - btc_data_dir: path(self_conf, "BTC_DATA_DIR"), - payto: require(self_conf, "PAYTO", url), - confirmation: nb(self_conf, "CONFIRMATION").unwrap_or(6), - bounce_fee: nb(self_conf, "BOUNCE_FEE").unwrap_or(1000), - wire_lifetime: nb(self_conf, "WIRE_LIFETIME") - .and_then(|nb| (nb != 0).then(|| Some(nb))) - .unwrap_or(None), - http_lifetime: nb(self_conf, "HTTP_LIFETIME") + base_url: require(ex, "BASE_URL", url), + payto: require(dep, "PAYTO", url), + confirmation: nb(dep, "CONFIRMATION").unwrap_or(DEFAULT_CONFIRMATION), + bounce_fee: nb(dep, "BOUNCE_FEE").unwrap_or(DEFAULT_FEE), + wire_lifetime: nb(dep, "WIRE_LIFETIME") .and_then(|nb| (nb != 0).then(|| Some(nb))) .unwrap_or(None), - bump_delay: nb(self_conf, "BUMP_DELAY") + bump_delay: nb(dep, "BUMP_DELAY") .and_then(|nb| (nb != 0).then(|| Some(nb))) .unwrap_or(None), + init: CoreConfig::load_from_ini(ini, currency, dep), } } } +pub type BtcConfig = WireConfig<1000, 6>; + +pub fn load_btc_config(path: impl AsRef<Path>) -> BtcConfig { + let config = WireConfig::load_from_file(path); + assert_eq!(config.init.currency, "BTC"); + return config; +} + +pub type EthConfig = WireConfig<1000000, 24>; + +pub fn load_eth_config(path: impl AsRef<Path>) -> EthConfig { + let config = WireConfig::load_from_file(path); + assert_eq!(config.init.currency, "ETH"); + return config; +} + /* ----- Helper functions ----- */ fn section<'a>(ini: &'a Ini, name: &str) -> &'a Properties { @@ -90,13 +136,6 @@ fn section<'a>(ini: &'a Ini, name: &str) -> &'a Properties { .unwrap_or_else(|| panic!("missing config section {}", name)) } -fn assert(properties: &Properties, name: &str, expected: &str) { - let value = require(properties, name, string); - if value != expected { - panic!("config {} expected '{}' got '{}'", name, expected, value); - } -} - fn require<T>( properties: &Properties, name: &str, diff --git a/test/btc/analysis.sh b/test/btc/analysis.sh @@ -6,6 +6,7 @@ set -eu source "${BASH_SOURCE%/*}/../common.sh" SCHEMA=btc.sql +CONFIG=taler_btc.conf echo "----- Setup -----" echo "Load config file" @@ -28,7 +29,7 @@ echo "Loose second bitcoin node" btc2_deco echo -n "Making wire transfer to exchange:" -btc-wire-utils -d $BTC_DIR transfer 0.042 > /dev/null +btc-wire-utils -d $WIRE_DIR transfer 0.042 > /dev/null next_btc # Trigger btc_wire check_balance 9.95799209 0.04200000 echo " OK" @@ -50,7 +51,7 @@ echo "Loose second bitcoin node" btc2_deco echo -n "Making wire transfer to exchange:" -btc-wire-utils -d $BTC_DIR transfer 0.064 > /dev/null +btc-wire-utils -d $WIRE_DIR transfer 0.064 > /dev/null next_btc 5 # More block needed to confirm check_balance 9.89398418 0.10600000 echo " OK" diff --git a/test/btc/bumpfee.sh b/test/btc/bumpfee.sh @@ -6,7 +6,7 @@ set -eu source "${BASH_SOURCE%/*}/../common.sh" SCHEMA=btc.sql -CONFIG=taler_bump.conf +CONFIG=taler_btc_bump.conf echo "----- Setup -----" echo "Load config file" @@ -28,7 +28,7 @@ SEQ="seq 10 30" echo -n "Making wire transfer to exchange:" for n in `$SEQ`; do - btc-wire-utils -d $BTC_DIR transfer 0.$n > /dev/null + btc-wire-utils -d $WIRE_DIR transfer 0.$n > /dev/null mine_btc # Mine transactions done next_btc # Trigger btc_wire diff --git a/test/btc/config.sh b/test/btc/config.sh @@ -5,6 +5,7 @@ set -eu SCHEMA=btc.sql +CONFIG=taler_btc.conf function test() { echo "----- Config $1 -----" diff --git a/test/btc/conflict.sh b/test/btc/conflict.sh @@ -6,6 +6,7 @@ set -eu source "${BASH_SOURCE%/*}/../common.sh" SCHEMA=btc.sql +CONFIG=taler_btc.conf echo "----- Setup -----" echo "Load config file" @@ -25,7 +26,7 @@ echo "" echo "----- Conflict send -----" echo -n "Making wire transfer to exchange:" -btc-wire-utils -d $BTC_DIR transfer 0.042 > /dev/null +btc-wire-utils -d $WIRE_DIR transfer 0.042 > /dev/null next_btc check_balance 9.95799209 0.04200000 echo " OK" @@ -41,7 +42,7 @@ echo " OK" echo -n "Abandon pending transaction:" restart_btc -minrelaytxfee=0.0001 -btc-wire-utils -d $BTC_DIR abandon +btc-wire-utils -d $WIRE_DIR abandon check_balance 9.95799209 0.04200000 echo " OK" @@ -92,7 +93,7 @@ echo " OK" echo -n "Abandon pending transaction:" restart_btc -minrelaytxfee=0.0001 -btc-wire-utils -d $BTC_DIR abandon +btc-wire-utils -d $WIRE_DIR abandon check_balance 9.95999859 0.04000000 echo " OK" diff --git a/test/btc/hell.sh b/test/btc/hell.sh @@ -5,6 +5,7 @@ set -eu source "${BASH_SOURCE%/*}/../common.sh" +CONFIG=taler_btc.conf SCHEMA=btc.sql echo "----- Setup -----" @@ -28,7 +29,7 @@ echo "Loose second bitcoin node" btc2_deco echo -n "Gen incoming transactions:" -btc-wire-utils -d $BTC_DIR transfer 0.0042 > /dev/null +btc-wire-utils -d $WIRE_DIR transfer 0.0042 > /dev/null next_btc # Trigger btc_wire check_balance 9.99579209 0.00420000 echo " OK" @@ -42,8 +43,8 @@ echo " OK" echo -n "Generate conflict:" restart_btc -minrelaytxfee=0.0001 -btc-wire-utils -d $BTC_DIR abandon client -btc-wire-utils -d $BTC_DIR transfer 0.0054 > /dev/null +btc-wire-utils -d $WIRE_DIR abandon client +btc-wire-utils -d $WIRE_DIR transfer 0.0054 > /dev/null next_btc check_balance 9.99457382 0.00540000 echo " OK" @@ -95,8 +96,8 @@ echo " OK" echo -n "Generate conflict:" restart_btc -minrelaytxfee=0.0001 -btc-wire-utils -d $BTC_DIR abandon client -btc-wire-utils -d $BTC_DIR transfer 0.054 > /dev/null +btc-wire-utils -d $WIRE_DIR abandon client +btc-wire-utils -d $WIRE_DIR transfer 0.054 > /dev/null next_btc check_balance 9.94597382 0.05400000 echo " OK" diff --git a/test/btc/lifetime.sh b/test/btc/lifetime.sh @@ -2,12 +2,12 @@ ## Check btc-wire and wire-gateway correctly stop when a lifetime limit is configured -CONFIG=taler_lifetime.conf - set -eu source "${BASH_SOURCE%/*}/../common.sh" SCHEMA=btc.sql +CONFIG=taler_btc_lifetime.conf + echo "----- Setup -----" echo "Load config file" @@ -33,7 +33,7 @@ echo " OK" echo -n "Do some work:" for n in `$SEQ`; do - btc-wire-utils -d $BTC_DIR transfer 0.000$n > /dev/null + btc-wire-utils -d $WIRE_DIR transfer 0.000$n > /dev/null mine_btc # Mine transactions done next_btc # Trigger btc_wire diff --git a/test/btc/maxfee.sh b/test/btc/maxfee.sh @@ -6,6 +6,7 @@ set -eu source "${BASH_SOURCE%/*}/../common.sh" SCHEMA=btc.sql +CONFIG=taler_btc.conf echo "----- Setup -----" echo "Load config file" @@ -26,7 +27,7 @@ SEQ="seq 10 30" echo -n "Making wire transfer to exchange:" for n in `$SEQ`; do - btc-wire-utils -d $BTC_DIR transfer 0.$n > /dev/null + btc-wire-utils -d $WIRE_DIR transfer 0.$n > /dev/null mine_btc # Mine transactions done next_btc # Trigger btc_wire diff --git a/test/btc/reconnect.sh b/test/btc/reconnect.sh @@ -6,6 +6,7 @@ set -eu source "${BASH_SOURCE%/*}/../common.sh" SCHEMA=btc.sql +CONFIG=taler_btc.conf echo "----- Setup -----" echo "Load config file" @@ -22,7 +23,7 @@ echo "" echo "----- With DB -----" echo "Making wire transfer to exchange:" -btc-wire-utils -d $BTC_DIR transfer 0.000042 > /dev/null +btc-wire-utils -d $WIRE_DIR transfer 0.000042 > /dev/null next_btc check_balance 9.99995009 0.00004200 echo -n "Requesting exchange incoming transaction list:" @@ -35,7 +36,7 @@ pg_ctl stop -D $DB_DIR > /dev/null echo "Making incomplete wire transfer to exchange" $BTC_CLI -rpcwallet=client sendtoaddress $WIRE 0.00042 &> /dev/null echo -n "Making wire transfer to exchange:" -btc-wire-utils -d $BTC_DIR transfer 0.00004 > /dev/null +btc-wire-utils -d $WIRE_DIR transfer 0.00004 > /dev/null next_btc check_balance 9.99948077 0.00050200 echo " OK" diff --git a/test/btc/reorg.sh b/test/btc/reorg.sh @@ -6,6 +6,7 @@ set -eu source "${BASH_SOURCE%/*}/../common.sh" SCHEMA=btc.sql +CONFIG=taler_btc.conf echo "----- Setup -----" echo "Load config file" @@ -31,7 +32,7 @@ btc2_deco echo -n "Gen incoming transactions:" for n in `$SEQ`; do - btc-wire-utils -d $BTC_DIR transfer 0.000$n > /dev/null + btc-wire-utils -d $WIRE_DIR transfer 0.000$n > /dev/null mine_btc # Mine transactions done next_btc # Trigger btc_wire diff --git a/test/btc/stress.sh b/test/btc/stress.sh @@ -6,6 +6,7 @@ set -eu source "${BASH_SOURCE%/*}/../common.sh" SCHEMA=btc.sql +CONFIG=taler_btc.conf echo "----- Setup stressed -----" echo "Load config file" @@ -26,7 +27,7 @@ echo "----- Handle incoming -----" echo -n "Making wire transfer to exchange:" for n in `$SEQ`; do - btc-wire-utils -d $BTC_DIR transfer 0.000$n > /dev/null + btc-wire-utils -d $WIRE_DIR transfer 0.000$n > /dev/null mine_btc # Mine transactions done next_btc # Confirm all transactions diff --git a/test/btc/wire.sh b/test/btc/wire.sh @@ -6,6 +6,7 @@ set -eu source "${BASH_SOURCE%/*}/../common.sh" SCHEMA=btc.sql +CONFIG=taler_btc.conf echo "----- Setup -----" echo "Load config file" @@ -26,7 +27,7 @@ echo "----- Receive -----" echo -n "Making wire transfer to exchange:" for n in `$SEQ`; do - btc-wire-utils -d $BTC_DIR transfer 0.000$n > /dev/null + btc-wire-utils -d $WIRE_DIR transfer 0.000$n > /dev/null mine_btc # Mine transactions done next_btc # Trigger btc_wire diff --git a/test/common.sh b/test/common.sh @@ -2,6 +2,8 @@ ## Test utils +set -eu + # Cleanup to run whenever we exit function cleanup() { pg_ctl stop -D $DB_DIR -w &> /dev/null @@ -17,13 +19,11 @@ trap cleanup EXIT # Init temporary dirs DIR=$(mktemp -d) -BTC_DIR=$DIR/bitcoin -BTC_DIR2=$DIR/bitcoin2 -ETH_DIR=$DIR/eth -ETH_DIR2=$DIR/eth2 +WIRE_DIR=$DIR/wire +WIRE_DIR2=$DIR/wire2 DB_DIR=$DIR/db CONF=$DIR/taler.conf -for dir in $BTC_DIR $BTC_DIR2 $ETH_DIR $ETH_DIR2 $DB_DIR log; do +for dir in $WIRE_DIR $WIRE_DIR2 $DB_DIR log; do mkdir -p $dir done @@ -33,17 +33,24 @@ for log in log/*; do done # Setup command helpers -BTC_CLI="bitcoin-cli -datadir=$BTC_DIR" -BTC_CLI2="bitcoin-cli -datadir=$BTC_DIR2" -ETH_CLI="geth -datadir=$ETH_DIR" -ETH_CLI2="geth -datadir=$ETH_DIR2" +BTC_CLI="bitcoin-cli -datadir=$WIRE_DIR" +BTC_CLI2="bitcoin-cli -datadir=$WIRE_DIR2" +ETH_CLI="geth -datadir=$WIRE_DIR" +ETH_CLI2="geth -datadir=$WIRE_DIR2" # Load test.conf as bash variables function load_config() { - cp ${BASH_SOURCE%/*}/conf/${CONFIG:-taler_test.conf} $CONF - echo -e "\nBTC_DATA_DIR = ${BTC_DIR}" >> $CONF + cp ${BASH_SOURCE%/*}/conf/$CONFIG $CONF + echo -e "\nDATA_DIR = ${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-cli + WIRE_UTILS=btc-wire-utils + else + WIRE_CLI=eth-wire-cli + WIRE_UTILS=eth-wire-utils + fi } # Check process is running @@ -72,27 +79,27 @@ 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 - btc-wire-cli initdb $CONF > /dev/null + $WIRE_CLI initdb $CONF > /dev/null } # Erase database function reset_db() { - btc-wire-utils cleardb $CONF - btc-wire-cli initdb $CONF + $WIRE_UTILS cleardb $CONF + $WIRE_CLI initdb $CONF } # ----- Bitcoin node ----- # # Start a bitcoind regtest node, generate money, wallet and addresses function init_btc() { - cp ${BASH_SOURCE%/*}/conf/${BTC_CONFIG:-bitcoin.conf} $BTC_DIR/bitcoin.conf - bitcoind -datadir=$BTC_DIR $* &>> log/btc.log & + cp ${BASH_SOURCE%/*}/conf/${BTC_CONFIG:-bitcoin.conf} $WIRE_DIR/bitcoin.conf + bitcoind -datadir=$WIRE_DIR $* &>> log/btc.log & BTC_PID="$!" # Wait for RPC server to be online $BTC_CLI -rpcwait getnetworkinfo > /dev/null # Create wire wallet btc-wire-cli initwallet $CONF > /dev/null - # Load wallets + # Create other wallets for wallet in client reserve; do $BTC_CLI createwallet $wallet > /dev/null done @@ -108,8 +115,8 @@ function init_btc() { # Start a second bitcoind regtest node connected to the first one function init_btc2() { - cp ${BASH_SOURCE%/*}/conf/bitcoin2.conf $BTC_DIR2/bitcoin.conf - bitcoind -datadir=$BTC_DIR2 $* &>> log/btc2.log & + cp ${BASH_SOURCE%/*}/conf/bitcoin2.conf $WIRE_DIR2/bitcoin.conf + bitcoind -datadir=$WIRE_DIR2 $* &>> log/btc2.log & $BTC_CLI2 -rpcwait getnetworkinfo > /dev/null $BTC_CLI addnode 127.0.0.1:8346 onetry } @@ -129,7 +136,7 @@ function btc2_fork() { # Restart a bitcoind regest server in a previously created temporary directory and load wallets function resume_btc() { # Restart node - bitcoind -datadir=$BTC_DIR $* &>> log/btc.log & + bitcoind -datadir=$WIRE_DIR $* &>> log/btc.log & BTC_PID="$!" # Load wallets for wallet in wire client reserve; do @@ -204,13 +211,17 @@ function stress_btc_wire() { # Start a geth dev node, generate money, wallet and addresses function init_eth() { + # TODO find a way to init with eth-wire-cli + # Create wallets for pswd in "wire" "client" "reserve"; do $ETH_CLI account new --password <(echo "password") &> /dev/null done + # Retrieve addresses local ADDR=`$ETH_CLI account list 2> /dev/null | grep -oP '(?<={).*?(?=})'` WIRE=`sed -n '1p' <(echo "$ADDR")` CLIENT=`sed -n '2p' <(echo "$ADDR")` RESERVE=`sed -n '3p' <(echo "$ADDR")` + # Generate genesis echo "{ \"config\": { \"chainId\": 42, @@ -232,8 +243,10 @@ function init_eth() { \"$WIRE\": { \"balance\": \"500000000000000000\" } } }" > $DIR/genesis.json + # Initialize blockchain $ETH_CLI init $DIR/genesis.json - $ETH_CLI + # Start node + $ETH_CLI $* &>> log/eth.log & } # ----- Gateway ------ # diff --git a/test/conf/taler_btc.conf b/test/conf/taler_btc.conf @@ -0,0 +1,11 @@ +[taler] +CURRENCY = BTC + +[exchange] +BASE_URL = http://test.com + +[depolymerizer-bitcoin] +DB_URL = postgres://localhost:5454/postgres?user=postgres&password=password +PORT = 8060 +PAYTO = payto://bitcoin/bcrt1qgkgxkjj27g3f7s87mcvjjsghay7gh34cx39prj +CONFIRMATION = 3 +\ No newline at end of file diff --git a/test/conf/taler_bump.conf b/test/conf/taler_btc_bump.conf diff --git a/test/conf/taler_lifetime.conf b/test/conf/taler_btc_lifetime.conf diff --git a/test/conf/taler_eth.conf b/test/conf/taler_eth.conf @@ -0,0 +1,11 @@ +[taler] +CURRENCY = ETH + +[exchange] +BASE_URL = http://test.com + +[depolymerizer-ethereum] +DB_URL = postgres://localhost:5454/postgres?user=postgres&password=password +PORT = 8060 +PAYTO = payto://euthereum/bcrt1qgkgxkjj27g3f7s87mcvjjsghay7gh34cx39prj +CONFIRMATION = 3 +\ No newline at end of file diff --git a/test/conf/taler_test.conf b/test/conf/taler_test.conf @@ -1,12 +0,0 @@ -[taler] -CURRENCY = BTC - -[exchange] -BASE_URL = http://test.com - -[depolymerizer-bitcoin] -DB_URL = postgres://localhost:5454/postgres?user=postgres&password=password -PORT = 8060 -PAYTO = payto://bitcoin/bcrt1qgkgxkjj27g3f7s87mcvjjsghay7gh34cx39prj -CONFIRMATION = 3 -BOUNCE_FEE = 1000 -\ No newline at end of file diff --git a/test/eth/test.sh b/test/eth/test.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +set -eu + +source "${BASH_SOURCE%/*}/../common.sh" +SCHEMA=eth.sql +CONFIG=taler_eth.conf + +echo "----- Setup -----" +echo "Load config file" +load_config +echo "Start database" +setup_db +echo "Start ethereum node" +init_eth +echo "" + +echo "----- Test -----" +cargo run --bin eth-test --release -- $CONF +\ No newline at end of file diff --git a/test/gateway/api.sh b/test/gateway/api.sh @@ -19,6 +19,7 @@ trap cleanup EXIT source "${BASH_SOURCE%/*}/../common.sh" ADDRESS=mpTJZxWPerz1Gife6mQSdHT8mMuJK6FP85 SCHEMA=btc.sq +CONFIG=taler_btc.conf echo "----- Setup -----" echo "Load config file" diff --git a/uri-pack/Cargo.toml b/uri-pack/Cargo.toml @@ -19,7 +19,7 @@ url = "2.2.2" # statistics-driven micro-benchmarks criterion = "0.3.5" # Fast insecure random -fastrand = "1.6.0" +fastrand = "1.7.0" # Fuzzing test quickcheck = "1.0.3" quickcheck_macros = "1.0.0" diff --git a/wire-gateway/Cargo.toml b/wire-gateway/Cargo.toml @@ -14,13 +14,13 @@ hyper = { version = "0.14.16", features = ["http1", "server", "runtime"] } # Hyper compat lib for unix domain socket hyperlocal = "0.8.0" # Async runtime -tokio = { version = "1.15.0", features = ["net", "macros", "rt-multi-thread"] } +tokio = { version = "1.16.1", features = ["net", "macros", "rt-multi-thread"] } # Serialization framework -serde = { version = "1.0.133", features = ["derive"] } +serde = { version = "1.0.136", features = ["derive"] } # Serialization helper serde_with = "1.11.0" # JSON serialization -serde_json = "1.0.75" +serde_json = "1.0.78" # Url query serialization serde_urlencoded = "0.7.1" # Error macros diff --git a/wire-gateway/src/main.rs b/wire-gateway/src/main.rs @@ -34,10 +34,12 @@ use taler_common::{ HistoryParams, IncomingBankTransaction, IncomingHistory, OutgoingBankTransaction, OutgoingHistory, TransferRequest, TransferResponse, }, + config::{Config, GatewayConfig}, error_codes::ErrorCode, log::log::{error, info, log, Level}, + postgres::{fallible_iterator::FallibleIterator, Client}, sql::{sql_amount, sql_array, sql_safe_u64, sql_url}, - url::Url, postgres::{Client, fallible_iterator::FallibleIterator}, + url::Url, }; use tokio::sync::Notify; use tokio_postgres::{config::Host, NoTls}; @@ -47,7 +49,7 @@ mod json; struct ServerState { pool: Pool, - config: taler_common::config::Config, + config: GatewayConfig, notify: Notify, lifetime: Option<AtomicU32>, status: AtomicBool, @@ -85,15 +87,14 @@ impl ServerState { async fn main() { taler_common::log::init(); - let conf = taler_common::config::Config::load_from_file( - std::env::args_os().nth(1).expect("Missing conf path arg"), - ); + let conf = + GatewayConfig::load_from_file(std::env::args_os().nth(1).expect("Missing conf path arg")); #[cfg(feature = "test")] taler_common::log::log::warn!("Running with test admin endpoint unsuitable for production"); // Parse postgres url - let config = tokio_postgres::Config::from_str(&conf.db_url).unwrap(); + let config = tokio_postgres::Config::from_str(&conf.init.db_url).unwrap(); // 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()); @@ -276,7 +277,7 @@ async fn router( ErrorCode::GENERIC_PAYTO_URI_MALFORMED, )); } - if request.amount.currency != "BTC" { + if request.amount.currency != state.config.init.currency { return Err(ServerError::code( StatusCode::BAD_REQUEST, ErrorCode::GENERIC_PARAMETER_MALFORMED, @@ -449,7 +450,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 = Client::connect(&state.config.db_url, NoTls)?; + let mut db = Client::connect(&state.config.init.db_url, NoTls)?; // Register as listener db.batch_execute("LISTEN status")?; loop {