commit a29a24356438bdbb3d2e342c066d47e66eeb2c81
parent e32c20e7c0d488116a6f020e4b730b0495d3ab9f
Author: Antoine A <>
Date: Wed, 20 Jul 2022 19:34:59 +0200
Rewriting tests in rust
more robust
less reliance on waiting times
support of variable transaction fees
better platform support
Diffstat:
67 files changed, 3055 insertions(+), 3040 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -3,6 +3,15 @@
version = 3
[[package]]
+name = "addr2line"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -57,6 +66,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
+name = "backtrace"
+version = "0.3.66"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide 0.5.1",
+ "object",
+ "rustc-demangle",
+]
+
+[[package]]
name = "base32"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -249,6 +273,17 @@ dependencies = [
]
[[package]]
+name = "color-backtrace"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd6c04463c99389fff045d2b90ce84f5131332712c7ffbede020f5e9ad1ed685"
+dependencies = [
+ "atty",
+ "backtrace",
+ "termcolor",
+]
+
+[[package]]
name = "common"
version = "0.1.0"
dependencies = [
@@ -725,6 +760,12 @@ dependencies = [
]
[[package]]
+name = "gimli"
+version = "0.26.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d"
+
+[[package]]
name = "glob"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -904,10 +945,15 @@ dependencies = [
"btc-wire",
"clap 3.1.6",
"clap_mangen",
+ "color-backtrace",
"common",
"eth-wire",
"ethereum-types",
+ "fastrand",
"hex",
+ "libdeflater",
+ "owo-colors",
+ "tempfile",
"ureq",
]
@@ -954,6 +1000,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f"
[[package]]
+name = "libdeflate-sys"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43afa5b192ff058426ba20a4f35c290ef402478d6045ac934ac15aa947a3898d"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "libdeflater"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e656b7960ec49e864badc7ad1b810427a7ac8b78511a699ce5cdc3ead0b32e5b"
+dependencies = [
+ "libdeflate-sys",
+]
+
+[[package]]
name = "listenfd"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1102,6 +1166,15 @@ dependencies = [
]
[[package]]
+name = "object"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
name = "once_cell"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1133,6 +1206,12 @@ dependencies = [
]
[[package]]
+name = "owo-colors"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "decf7381921fea4dcb2549c5667eda59b3ec297ab7e2b5fc33eac69d2e7da87b"
+
+[[package]]
name = "parking_lot"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1453,6 +1532,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
+name = "remove_dir_all"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
name = "ring"
version = "0.16.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1484,6 +1572,12 @@ dependencies = [
]
[[package]]
+name = "rustc-demangle"
+version = "0.1.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
+
+[[package]]
name = "rustc-hex"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1744,6 +1838,20 @@ dependencies = [
]
[[package]]
+name = "tempfile"
+version = "3.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "libc",
+ "redox_syscall",
+ "remove_dir_all",
+ "winapi",
+]
+
+[[package]]
name = "termcolor"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/btc-wire/benches/metadata.rs b/btc-wire/benches/metadata.rs
@@ -1,39 +1,39 @@
-/*
- This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Affero General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-*/
-use btc_wire::segwit::{decode_segwit_msg, encode_segwit_key, rand_addresses};
-use criterion::{criterion_group, criterion_main, Criterion};
-use common::rand_slice;
-
-fn criterion_benchmark(c: &mut Criterion) {
- let mut group = c.benchmark_group("SegWit addresses");
- group.bench_function("encode", |b| {
- b.iter_batched(
- || rand_slice(),
- |key| encode_segwit_key("bench", &key),
- criterion::BatchSize::SmallInput,
- );
- });
- group.bench_function("decode", |b| {
- b.iter_batched(
- || rand_addresses("bench", &rand_slice()),
- |addrs| decode_segwit_msg(&addrs),
- criterion::BatchSize::SmallInput,
- );
- });
-}
-
-criterion_group!(benches, criterion_benchmark);
-criterion_main!(benches);
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+use btc_wire::segwit::{decode_segwit_msg, encode_segwit_key, rand_addresses};
+use common::rand_slice;
+use criterion::{criterion_group, criterion_main, Criterion};
+
+fn criterion_benchmark(c: &mut Criterion) {
+ let mut group = c.benchmark_group("SegWit addresses");
+ group.bench_function("encode", |b| {
+ b.iter_batched(
+ || rand_slice(),
+ |key| encode_segwit_key("bench", &key),
+ criterion::BatchSize::SmallInput,
+ );
+ });
+ group.bench_function("decode", |b| {
+ b.iter_batched(
+ || rand_addresses("bench", &rand_slice()),
+ |addrs| decode_segwit_msg(&addrs),
+ criterion::BatchSize::SmallInput,
+ );
+ });
+}
+
+criterion_group!(benches, criterion_benchmark);
+criterion_main!(benches);
diff --git a/btc-wire/src/bin/btc-wire-utils.rs b/btc-wire/src/bin/btc-wire-utils.rs
@@ -1,134 +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::path::PathBuf;
-
-use bitcoin::{Address, Amount, BlockHash, Network};
-use btc_wire::{
- btc_config::BitcoinConfig,
- load_taler_config,
- rpc::{Category, Rpc},
-};
-use clap::StructOpt;
-use common::{postgres::NoTls, rand_slice};
-
-/// btc-wire test utils
-#[derive(clap::Parser, Debug)]
-#[clap(name = "btc-wire-utils")]
-struct Args {
- /// Override default configuration file path
- #[clap(global = true, short, long)]
- config: Option<PathBuf>,
- /// Override default data directory path
- #[clap(global = true, short, long)]
- datadir: Option<PathBuf>,
- #[clap(subcommand)]
- cmd: Cmd,
-}
-
-#[derive(clap::Subcommand, Debug)]
-enum Cmd {
- /// Perform wire credit transactions
- Transfer {
- #[clap(short, long, default_value_t = String::from("client"))]
- /// sender wallet
- from: String,
- #[clap(short, long, default_value_t = String::from("wire"))]
- /// receiver wallet
- to: String,
- /// amount to send in btc
- amount: f64,
- },
- /// Wait or mine the next block
- Nblock {
- #[clap(default_value_t = String::from("wire"))]
- /// receiver wallet
- to: String,
- },
- /// Abandon all unconfirmed transaction
- Abandon {
- #[clap(default_value_t = String::from("wire"))]
- /// sender wallet
- from: String,
- },
- /// Clear database
- Resetdb,
-}
-
-pub fn auto_wallet(rpc: &mut Rpc, config: &BitcoinConfig, name: &str) -> (Rpc, Address) {
- // Auto load
- rpc.load_wallet(name).ok();
- let mut wallet = Rpc::wallet(config, name).unwrap();
- let addr = wallet
- .gen_addr()
- .unwrap_or_else(|_| panic!("Failed to get wallet address {}", name));
- (wallet, addr)
-}
-
-fn main() {
- common::log::init();
- let args = Args::parse();
- let (taler_config, path, currency) = load_taler_config(args.config.as_deref());
- let btc_config = BitcoinConfig::load(args.datadir.unwrap_or(path), currency).unwrap();
- let mut rpc = Rpc::common(&btc_config).unwrap();
-
- match args.cmd {
- Cmd::Transfer { from, to, amount } => {
- let (mut client, _) = auto_wallet(&mut rpc, &btc_config, &from);
- let (_, to) = auto_wallet(&mut rpc, &btc_config, &to);
- let tx = client
- .send_segwit_key(&to, &Amount::from_btc(amount).unwrap(), &rand_slice())
- .unwrap();
- println!("{}", tx);
- }
- Cmd::Nblock { to } => {
- match btc_config.network {
- Network::Regtest => {
- // Manually mine a block
- let (_, addr) = auto_wallet(&mut rpc, &btc_config, &to);
- rpc.mine(1, &addr).unwrap();
- }
- _ => {
- // Wait for next network block
- rpc.wait_for_new_block().ok();
- }
- }
- }
- Cmd::Abandon { from } => {
- let (mut wire, _) = auto_wallet(&mut rpc, &btc_config, &from);
- let list = wire.list_since_block(None, 1).unwrap();
- for tx in list.transactions {
- if tx.category == Category::Send && tx.confirmations == 0 {
- wire.abandon_tx(&tx.txid).unwrap();
- }
- }
- }
- Cmd::Resetdb => {
- let hash: BlockHash = rpc.get_genesis().unwrap();
- let mut db = taler_config.db_config().connect(NoTls).unwrap();
- let mut tx = db.transaction().unwrap();
- // Clear transaction tables and reset state
- tx.execute("DELETE FROM tx_in", &[]).unwrap();
- tx.execute("DELETE FROM tx_out", &[]).unwrap();
- tx.execute("DELETE FROM bounce", &[]).unwrap();
- tx.execute(
- "UPDATE state SET value=$1 WHERE name='last_hash'",
- &[&hash.as_ref()],
- )
- .unwrap();
- tx.commit().unwrap();
- }
- }
-}
diff --git a/btc-wire/src/bin/segwit-demo.rs b/btc-wire/src/bin/segwit-demo.rs
@@ -1,8 +1,8 @@
use std::str::FromStr;
use bitcoin::{Address, Amount, Network};
+use btc_wire::segwit::decode_segwit_msg;
use btc_wire::{rpc_utils, segwit::encode_segwit_addr};
-use btc_wire::{segwit::decode_segwit_msg};
use common::{
base32::{self, Alphabet},
rand_slice,
@@ -81,12 +81,14 @@ pub fn main() {
"Make sure the amount show 0.10000588 BTC, else you have to change the base unit to BTC"
);
-
let key1 = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4";
let key2 = "tb1qzxwu2p7urkqx0gq2ltfazf9w2jdu48ya8qwlm0";
let key3 = "tb1qzxwu2pef8a224xagwq8hej8akuvd63yluu3wrh";
let addresses = vec![key1, key2, key3];
let dec = decode_segwit_msg(&addresses);
-
- println!("Decode reserve public key: 0x{}", hex::encode(&dec.unwrap()[..]));
+
+ println!(
+ "Decode reserve public key: 0x{}",
+ hex::encode(&dec.unwrap()[..])
+ );
}
diff --git a/btc-wire/src/loops.rs b/btc-wire/src/loops.rs
@@ -35,4 +35,4 @@ pub enum LoopError {
Injected(#[from] Injected),
}
-pub type LoopResult<T> = Result<T, LoopError>;
-\ No newline at end of file
+pub type LoopResult<T> = Result<T, LoopError>;
diff --git a/btc-wire/src/loops/analysis.rs b/btc-wire/src/loops/analysis.rs
@@ -22,8 +22,8 @@ use common::{
reconnect::AutoReconnectDb,
};
-use crate::WireState;
use super::LoopResult;
+use crate::WireState;
/// Analyse blockchain behavior and adapt confirmations in real time
pub fn analysis(mut rpc: AutoRpcCommon, mut db: AutoReconnectDb, state: &WireState) {
@@ -40,9 +40,7 @@ pub fn analysis(mut rpc: AutoRpcCommon, mut db: AutoReconnectDb, state: &WireSta
let fork = rpc
.get_chain_tips()?
.into_iter()
- .filter_map(|t| {
- (t.status == ChainTipsStatus::ValidFork).then(|| t.length)
- })
+ .filter_map(|t| (t.status == ChainTipsStatus::ValidFork).then(|| t.length))
.max()
.unwrap_or(0) as u32;
// The first time we see a fork that big
diff --git a/btc-wire/src/loops/watcher.rs b/btc-wire/src/loops/watcher.rs
@@ -13,8 +13,8 @@
You should have received a copy of the GNU Affero General Public License along with
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-use common::{log::log::error, reconnect::AutoReconnectDb};
use btc_wire::rpc::AutoRpcCommon;
+use common::{log::log::error, reconnect::AutoReconnectDb};
use std::time::Duration;
use super::LoopResult;
diff --git a/btc-wire/src/loops/worker.rs b/btc-wire/src/loops/worker.rs
@@ -227,8 +227,8 @@ fn sync_chain(
// Move last_hash forward
db.execute(
- "UPDATE state SET value=$1 WHERE name='last_hash'",
- &[&lastblock.as_ref()],
+ "UPDATE state SET value=$1 WHERE name='last_hash' AND value=$2",
+ &[&lastblock.as_ref(), &last_hash.as_inner().as_slice()],
)?;
Ok(Some(stuck))
diff --git a/btc-wire/src/rpc.rs b/btc-wire/src/rpc.rs
@@ -47,7 +47,7 @@ pub fn auto_rpc_wallet(config: BitcoinConfig, wallet: &'static str) -> AutoRpcWa
let mut rpc = Rpc::wallet(config, wallet)
.map_err(|err| error!("connect RPC: {}", err))
.ok()?;
- rpc.load_wallet(wallet).ok();
+ rpc.load_wallet(wallet).ok();
rpc.unlock_wallet(&password())
.map_err(|err| error!("connect RPC: {}", err))
.ok()?;
@@ -114,6 +114,13 @@ pub type Result<T> = std::result::Result<T, Error>;
const EMPTY: [(); 0] = [];
+fn expect_null(result: Result<()>) -> Result<()> {
+ match result {
+ Err(Error::Null) => Ok(()),
+ i => i,
+ }
+}
+
/// Bitcoin RPC connection
pub struct Rpc {
last_call: Instant,
@@ -241,10 +248,7 @@ impl Rpc {
/// Unlock loaded wallet
pub fn unlock_wallet(&mut self, passwd: &str) -> Result<()> {
// TODO Capped at 3yrs, is it enough ?
- match self.call("walletpassphrase", &(passwd, 100000000)) {
- Err(Error::Null) => Ok(()),
- i => i,
- }
+ expect_null(self.call("walletpassphrase", &(passwd, 100000000)))
}
/* ----- Wallet utils ----- */
@@ -367,10 +371,7 @@ impl Rpc {
/// Abandon a pending transaction
pub fn abandon_tx(&mut self, id: &Txid) -> Result<()> {
- match self.call("abandontransaction", &[&id]) {
- Err(Error::Null) => Ok(()),
- i => i,
- }
+ expect_null(self.call("abandontransaction", &[&id]))
}
/* ----- Watcher ----- */
@@ -388,6 +389,18 @@ impl Rpc {
) -> Result<ListSinceBlock> {
self.call("listsinceblock", &(hash, confirmation.max(1), (), true))
}
+
+ /* ----- Cluster ----- */
+
+ /// Try a connection to a node once
+ pub fn add_node(&mut self, addr: &str) -> Result<()> {
+ expect_null(self.call("addnode", &(addr, "onetry")))
+ }
+
+ /// Immediately disconnects from the specified peer node.
+ pub fn disconnect_node(&mut self, addr: &str) -> Result<()> {
+ expect_null(self.call("disconnectnode", &(addr, ())))
+ }
}
#[derive(Debug, serde::Deserialize)]
diff --git a/common/src/sql.rs b/common/src/sql.rs
@@ -19,7 +19,10 @@ use std::str::FromStr;
use postgres::Row;
use url::Url;
-use crate::{api_common::{Amount, SafeU64}, log::OrFail};
+use crate::{
+ api_common::{Amount, SafeU64},
+ log::OrFail,
+};
/// URL from sql
pub fn sql_url(row: &Row, idx: usize) -> Url {
@@ -30,8 +33,7 @@ pub fn sql_url(row: &Row, idx: usize) -> Url {
/// Ethereum amount from sql
pub fn sql_amount(row: &Row, idx: usize) -> Amount {
let str: &str = row.get(idx);
- Amount::from_str(str)
- .or_fail(|_| format!("Database invariant: expected an amount got {}", str))
+ Amount::from_str(str).or_fail(|_| format!("Database invariant: expected an amount got {}", str))
}
/// Byte array from sql
diff --git a/common/src/status.rs b/common/src/status.rs
@@ -16,7 +16,7 @@
//! Transactions status in database
/// Debit transaction status
-///
+///
/// -> Requested API request
/// Requested -> Sent Announced to the bitcoin network
/// Sent -> Requested Conflicting transaction (reorg)
diff --git a/eth-wire/src/bin/eth-wire-utils.rs b/eth-wire/src/bin/eth-wire-utils.rs
@@ -1,234 +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::{
- path::PathBuf,
- process::exit,
- str::FromStr,
- time::{Duration, Instant},
-};
-
-use clap::StructOpt;
-use common::{api_common::Amount, log::init, password, postgres::NoTls, rand_slice};
-use eth_wire::{
- load_taler_config,
- rpc::{hex::Hex, Rpc, RpcClient, TransactionRequest},
- taler_util::{taler_to_eth, TRUNC},
- RpcExtended, SyncState,
-};
-use ethereum_types::{H160, U256};
-
-#[derive(clap::Parser, Debug)]
-#[clap(name = "eth-wire-utils")]
-/// eth-wire test utils
-struct Args {
- /// Override default configuration file path
- #[clap(global = true, short, long)]
- config: Option<PathBuf>,
- /// Override default data directory path
- #[clap(global = true, short, long)]
- datadir: Option<PathBuf>,
- #[clap(subcommand)]
- cmd: Cmd,
-}
-
-#[derive(clap::Parser, Debug)]
-struct TransactionCmd {
- /// sender wallet
- from: String,
- /// receiver wallet
- to: String,
- /// sender wallet
- fmt: String,
- /// amounts to send in eth
- amounts: Vec<u32>,
-}
-
-#[derive(clap::Subcommand, Debug)]
-enum Cmd {
- /// Send common ethereum transactions
- Send(TransactionCmd),
- /// Send taler credit transactions
- Credit(TransactionCmd),
- /// Mine pending transactions and more blocks
- Mine {
- /// receiver wallet
- to: String,
- #[clap(default_value_t = 0)]
- /// amount to mine in eth
- amount: u64,
- },
- /// Clear database
- Resetdb,
- /// Get eth balance
- Balance {
- /// account address
- addr: String,
- },
- // Check client and wire balance
- CheckBalance {
- client_addr: String,
- client: u64,
- wire_addr: String,
- wire: u64,
- },
- /// Abandon all unconfirmed transaction
- Abandon {
- /// sender address
- from: String,
- },
- Export {
- path: String,
- },
-}
-
-fn main() {
- init();
- let args: Args = Args::parse();
- let (taler_config, ipc_path, currency) = load_taler_config(args.config.as_deref());
-
- let ipc_path = args.datadir.unwrap_or(ipc_path);
- let mut rpc = Rpc::new(ipc_path).unwrap();
- let passwd = password();
- match args.cmd {
- Cmd::Credit(TransactionCmd {
- from,
- to,
- fmt,
- amounts,
- }) => {
- let from = H160::from_str(&from).unwrap();
- let to = H160::from_str(&to).unwrap();
- rpc.unlock_account(&from, &passwd).ok();
- for amount in amounts {
- let amount =
- Amount::from_str(&format!("{}:{}{}", currency.to_str(), fmt, amount)).unwrap();
- let value = taler_to_eth(&amount, currency).unwrap();
- rpc.credit(from, to, value, rand_slice()).unwrap();
- }
- }
- Cmd::Send(TransactionCmd {
- from,
- to,
- fmt,
- amounts,
- }) => {
- let from = H160::from_str(&from).unwrap();
- let to = H160::from_str(&to).unwrap();
- rpc.unlock_account(&from, &passwd).ok();
- for amount in amounts {
- let amount =
- Amount::from_str(&format!("{}:{}{}", currency.to_str(), fmt, amount)).unwrap();
- let value = taler_to_eth(&amount, currency).unwrap();
- rpc.send_transaction(&TransactionRequest {
- from,
- to,
- value,
- nonce: None,
- gas_price: None,
- data: Hex(vec![]),
- })
- .unwrap();
- }
- }
- Cmd::Mine { to, mut amount } => {
- let to = H160::from_str(&to).unwrap();
- rpc.unlock_account(&to, &passwd).ok();
- let mut rpc = rpc.subscribe_new_head().unwrap();
-
- rpc.miner_start().unwrap();
- while !rpc.pending_transactions().unwrap().is_empty() {
- rpc.next().unwrap();
- amount = amount.saturating_sub(1);
- }
- for _ in 0..amount {
- rpc.next().unwrap();
- }
- rpc.miner_stop().unwrap();
- }
- Cmd::Resetdb => {
- let block = rpc.earliest_block().unwrap();
- let mut db = taler_config.db_config().connect(NoTls).unwrap();
- let mut tx = db.transaction().unwrap();
- // Clear transaction tables and reset state
- tx.execute("DELETE FROM tx_in", &[]).unwrap();
- tx.execute("DELETE FROM tx_out", &[]).unwrap();
- tx.execute("DELETE FROM bounce", &[]).unwrap();
- tx.execute(
- "UPDATE state SET value=$1 WHERE name='sync'",
- &[&SyncState {
- tip_hash: block.hash.unwrap(),
- tip_height: block.number.unwrap(),
- conf_height: block.number.unwrap(),
- }
- .to_bytes()
- .as_ref()],
- )
- .unwrap();
- tx.commit().unwrap();
- }
- Cmd::Balance { addr } => {
- let addr = H160::from_str(&addr).unwrap();
- let balance = rpc.get_balance(&addr).unwrap();
- println!("{}", (balance / TRUNC).as_u64());
- }
- Cmd::CheckBalance {
- client_addr,
- client,
- wire_addr,
- wire,
- } => {
- let start = Instant::now();
- let client_addr = H160::from_str(&client_addr).unwrap();
- let wire_addr = H160::from_str(&wire_addr).unwrap();
- loop {
- let client_balance = (rpc.get_balance(&client_addr).unwrap() / TRUNC).as_u64();
- let wire_balance = (rpc.get_balance(&wire_addr).unwrap() / TRUNC).as_u64();
- if client_balance == client && wire_balance == wire {
- break;
- } else if start.elapsed() > Duration::from_secs(60) {
- println!(
- "Expected {} {} got {} {}",
- client, wire, client_balance, wire_balance
- );
- exit(1);
- } else {
- std::thread::sleep(Duration::from_secs(5))
- }
- }
- }
- Cmd::Abandon { from } => {
- let from = H160::from_str(&from).unwrap();
- rpc.unlock_account(&from, &passwd).ok();
- let pending = rpc.pending_transactions().unwrap();
- for tx in pending.into_iter().filter(|t| t.from == Some(from)) {
- // Replace transaction value with 0
- rpc.send_transaction(&TransactionRequest {
- from,
- to: tx.to.unwrap(),
- value: U256::zero(),
- gas_price: Some(U256::from(1u8)), // Bigger gas price to replace fee
- data: Hex(vec![]),
- nonce: Some(tx.nonce),
- })
- .unwrap();
- }
- }
- Cmd::Export { path } => {
- std::fs::remove_file(&path).ok();
- assert!(rpc.export_chain(&path).unwrap())
- }
- }
-}
diff --git a/eth-wire/src/loops/analysis.rs b/eth-wire/src/loops/analysis.rs
@@ -21,7 +21,7 @@ use common::{
postgres::fallible_iterator::FallibleIterator,
reconnect::AutoReconnectDb,
};
-use eth_wire::rpc::{ Rpc, AutoRpcCommon, RpcClient};
+use eth_wire::rpc::{AutoRpcCommon, Rpc, RpcClient};
use ethereum_types::{H256, U64};
use crate::WireState;
diff --git a/eth-wire/src/loops/worker.rs b/eth-wire/src/loops/worker.rs
@@ -382,7 +382,11 @@ fn sync_chain_outgoing(tx: &SyncTransaction, db: &mut Client, state: &WireState)
],
)?;
if nb_row > 0 {
- warn!("|| (recovered) {} in {}", &bounced, hex::encode(tx.hash));
+ warn!(
+ "|| (recovered) {} in {}",
+ hex::encode(bounced),
+ hex::encode(tx.hash)
+ );
}
}
BounceStatus::Ignored => error!(
@@ -399,7 +403,11 @@ fn sync_chain_outgoing(tx: &SyncTransaction, db: &mut Client, state: &WireState)
&[&bounced.as_ref(), &tx.hash.as_ref(), &(BounceStatus::Sent as i16)],
)?;
if nb > 0 {
- warn!("|| (onchain) {} in {}", &bounced, hex::encode(tx.hash));
+ warn!(
+ "|| (onchain) {} in {}",
+ hex::encode(bounced),
+ hex::encode(tx.hash)
+ );
}
}
}
diff --git a/eth-wire/src/rpc.rs b/eth-wire/src/rpc.rs
@@ -372,11 +372,16 @@ pub trait RpcClient {
self.call("eth_getBlockByNumber", &("earliest", &true))
}
- /// Get account balance
- fn get_balance(&mut self, addr: &Address) -> Result<U256> {
+ /// Get latest account balance
+ fn get_balance_latest(&mut self, addr: &Address) -> Result<U256> {
self.call("eth_getBalance", &(addr, "latest"))
}
+ /// Get pending account balance
+ fn get_balance_pending(&mut self, addr: &Address) -> Result<U256> {
+ self.call("eth_getBalance", &(addr, "pending"))
+ }
+
/// Get node info
fn node_info(&mut self) -> Result<NodeInfo> {
self.call("admin_nodeInfo", &EMPTY)
diff --git a/instrumentation/Cargo.toml b/instrumentation/Cargo.toml
@@ -18,6 +18,17 @@ ethereum-types = { version = "0.13.1", default-features = false }
hex = "0.4.3"
# Wire Gateway
ureq = { version = "2.4.0", features = ["json"] }
+# In memory deflate library
+libdeflater = "0.10.0"
+# Generate temporary files
+tempfile = "3.3.0"
+# RNG
+fastrand = "1.7.0"
+# terminal color
+owo-colors = "3.4.0"
+# Better backtrace
+color-backtrace = "0.5.1"
+
[build-dependencies]
clap_mangen = "0.1"
diff --git a/instrumentation/conf/bitcoin.conf b/instrumentation/conf/bitcoin.conf
@@ -0,0 +1,9 @@
+regtest=1
+txindex=1
+maxtxfee=0.01
+fallbackfee=0.00000001
+rpcservertimeout=10
+
+[regtest]
+port=8345
+rpcport=18345
+\ No newline at end of file
diff --git a/instrumentation/conf/bitcoin2.conf b/instrumentation/conf/bitcoin2.conf
@@ -0,0 +1,9 @@
+regtest=1
+txindex=1
+maxtxfee=0.01
+fallbackfee=0.00000001
+rpcservertimeout=0
+
+[regtest]
+port=8346
+rpcport=18346
+\ No newline at end of file
diff --git a/test/conf/bitcoin_auth0.conf b/instrumentation/conf/bitcoin_auth0.conf
diff --git a/instrumentation/conf/bitcoin_auth1.conf b/instrumentation/conf/bitcoin_auth1.conf
@@ -0,0 +1,12 @@
+regtest=1
+txindex=1
+maxtxfee=0.1
+fallbackfee=0.00000001
+rpcuser=bob
+rpcpassword=password
+
+[regtest]
+port=8346
+rpcport=18346
+rpcuser=alice
+rpcpassword=password TODO
+\ No newline at end of file
diff --git a/test/conf/bitcoin_auth2.conf b/instrumentation/conf/bitcoin_auth2.conf
diff --git a/test/conf/bitcoin_auth3.conf b/instrumentation/conf/bitcoin_auth3.conf
diff --git a/test/conf/bitcoin_auth4.conf b/instrumentation/conf/bitcoin_auth4.conf
diff --git a/test/conf/bitcoin_auth5.conf b/instrumentation/conf/bitcoin_auth5.conf
diff --git a/test/conf/taler_btc.conf b/instrumentation/conf/taler_btc.conf
diff --git a/test/conf/taler_btc_auth.conf b/instrumentation/conf/taler_btc_auth.conf
diff --git a/test/conf/taler_btc_bump.conf b/instrumentation/conf/taler_btc_bump.conf
diff --git a/instrumentation/conf/taler_btc_lifetime.conf b/instrumentation/conf/taler_btc_lifetime.conf
@@ -0,0 +1,14 @@
+[taler]
+CURRENCY = DEVBTC
+
+[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
+HTTP_LIFETIME = 10
+WIRE_LIFETIME = 110
+AUTH_METHOD = none
+\ No newline at end of file
diff --git a/test/conf/taler_eth.conf b/instrumentation/conf/taler_eth.conf
diff --git a/test/conf/taler_eth_bump.conf b/instrumentation/conf/taler_eth_bump.conf
diff --git a/test/conf/taler_eth_lifetime.conf b/instrumentation/conf/taler_eth_lifetime.conf
diff --git a/instrumentation/src/btc.rs b/instrumentation/src/btc.rs
@@ -14,18 +14,29 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-use std::{path::Path, time::Duration};
+use std::{
+ ops::{Deref, DerefMut},
+ path::{Path, PathBuf},
+ str::FromStr,
+ sync::atomic::Ordering,
+ thread::sleep,
+ time::Duration,
+};
-use bitcoin::{Amount, BlockHash, Network, SignedAmount, Txid, hashes::Hash};
+use bitcoin::{hashes::Hash, Address, Amount, BlockHash, Network, SignedAmount, Txid};
use btc_wire::{
+ btc_config::BitcoinConfig,
rpc::{self, Category, ErrorCode, Rpc, Transaction},
- rpc_utils,
+ rpc_utils::{self, segwit_min_amount},
taler_utils::{btc_payto_url, btc_to_taler},
WireState,
};
-use common::{rand_slice, metadata::OutMetadata};
+use common::{currency::CurrencyBtc, metadata::OutMetadata, postgres::NoTls, rand_slice};
-use crate::{check_incoming, check_outgoing, print_now, transfer};
+use crate::utils::{
+ check_incoming, check_outgoing, cmd_redirect, cmd_redirect_ok, print_now, retry, retry_opt,
+ transfer, ChildGuard, CommonCtx, Dirs,
+};
pub const CLIENT: &str = "client";
pub const WIRE: &str = "wire";
@@ -54,7 +65,7 @@ fn wait_for_pending(since: &mut BlockHash, client_rpc: &mut Rpc, wire_rpc: &mut
}
break;
}
- println!("");
+ println!();
}
pub fn btc_test(config: Option<&Path>, base_url: &str) {
@@ -95,7 +106,7 @@ pub fn btc_test(config: Option<&Path>, base_url: &str) {
client_rpc.wait_for_new_block().unwrap();
print_now(".");
}
- println!("");
+ println!();
}
let mut since = client_rpc.list_since_block(None, 1).unwrap().lastblock;
// Load wire
@@ -166,7 +177,7 @@ pub fn btc_test(config: Option<&Path>, base_url: &str) {
rpc.wait_for_new_block().unwrap();
print_now(".");
};
- println!("");
+ println!();
wait_for_pending(&mut since, &mut client_rpc, &mut wire_rpc);
println!("Check balance");
@@ -181,7 +192,11 @@ pub fn btc_test(config: Option<&Path>, base_url: &str) {
new_wire_balance
);
- check_incoming(base_url, &reserve_pub_key, &taler_test_amount);
+ println!("Check incoming history");
+ assert!(check_incoming(
+ base_url,
+ &[(reserve_pub_key, taler_test_amount.clone())]
+ ));
println!("Get back some money");
let wtid = rand_slice();
@@ -198,5 +213,931 @@ pub fn btc_test(config: Option<&Path>, base_url: &str) {
let last_client_balance = client_rpc.get_balance().unwrap();
assert_eq!(new_client_balance + test_amount, last_client_balance);
- check_outgoing(base_url, &wtid, &state.base_url, &taler_test_amount);
+ println!("Check outgoing history");
+ assert!(check_outgoing(
+ base_url,
+ &state.base_url,
+ &[(wtid, taler_test_amount)]
+ ));
+}
+
+pub struct BtcCtx {
+ btc_node: ChildGuard,
+ _btc_node2: ChildGuard,
+ common_rpc: Rpc,
+ common_rpc2: Rpc,
+ wire_rpc: Rpc,
+ client_rpc: Rpc,
+ reserve_rpc: Rpc,
+ wire_addr: Address,
+ pub client_addr: Address,
+ reserve_addr: Address,
+ state: WireState,
+ conf: u16,
+ common: CommonCtx,
+}
+
+impl Deref for BtcCtx {
+ type Target = CommonCtx;
+
+ fn deref(&self) -> &Self::Target {
+ &self.common
+ }
+}
+
+impl DerefMut for BtcCtx {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.common
+ }
+}
+
+impl BtcCtx {
+ pub fn setup(config: &str, stressed: bool) -> Self {
+ Self::_setup(config, stressed)
+ }
+
+ pub fn config(config: &str) {
+ std::fs::create_dir_all("log").unwrap();
+ for file in std::fs::read_dir("log").unwrap() {
+ std::fs::write(file.unwrap().path(), "").unwrap();
+ }
+ // Generate temporary dirs
+ let dirs = Dirs::generate();
+ // Bitcoin config
+ let config = PathBuf::from_str("instrumentation/conf")
+ .unwrap()
+ .join(config);
+ std::fs::copy(config, dirs.wire_dir.join("bitcoin.conf")).unwrap();
+ // Load config
+ let config =
+ BitcoinConfig::load(dirs.wire_dir.join("bitcoin.conf"), CurrencyBtc::Dev).unwrap();
+ // Start bitcoin nodes
+ let _btc_node = cmd_redirect(
+ "bitcoind",
+ &[&format!("-datadir={}", dirs.wire_dir.to_string_lossy())],
+ "log/node.log",
+ );
+ // Connect
+ retry(|| {
+ Rpc::common(&config)
+ .ok()
+ .and_then(|mut it| it.get_blockchain_info().ok())
+ .is_some()
+ })
+ }
+
+ fn _setup(taler_config: &str, stressed: bool) -> Self {
+ let dirs = CommonCtx::setup_dirs(taler_config);
+ // Prepare config
+ let (btc_config2, state) = {
+ // Bitcoin config
+ let config = PathBuf::from_str("instrumentation/conf")
+ .unwrap()
+ .join("bitcoin.conf");
+ std::fs::copy(config, dirs.wire_dir.join("bitcoin.conf")).unwrap();
+ std::fs::copy(
+ "instrumentation/conf/bitcoin2.conf",
+ dirs.wire_dir2.join("bitcoin.conf"),
+ )
+ .unwrap();
+ // Load config
+ let state = WireState::load_taler_config(Some(&dirs.conf));
+ (
+ BitcoinConfig::load(dirs.wire_dir2.join("bitcoin.conf"), state.currency).unwrap(),
+ state,
+ )
+ };
+ // Start bitcoin nodes
+ let btc_node = cmd_redirect(
+ "bitcoind",
+ &[&format!("-datadir={}", dirs.wire_dir.to_string_lossy())],
+ "log/node.log",
+ );
+ let btc_node2 = cmd_redirect(
+ "bitcoind",
+ &[&format!("-datadir={}", dirs.wire_dir2.to_string_lossy())],
+ "log/node2.log",
+ );
+
+ let common = CommonCtx::setup(dirs, "btc-wire", stressed, |dirs| {
+ // Generate wallet
+ cmd_redirect_ok(
+ "btc-wire",
+ &["-c", dirs.conf.to_str().unwrap(), "initwallet"],
+ "log/cmd.log",
+ "wire initwallet",
+ );
+ });
+
+ // Setup wallets
+ let mut common_rpc = retry_opt(|| Rpc::common(&state.btc_config).ok());
+ common_rpc.add_node("127.0.0.1:8346").unwrap();
+
+ for name in ["client", "reserve"] {
+ common_rpc.create_wallet(name, "").unwrap();
+ }
+ let common_rpc2 = retry_opt(|| Rpc::common(&btc_config2).ok());
+
+ // Generate money
+ let mut reserve_rpc = Rpc::wallet(&state.btc_config, "reserve").unwrap();
+ let mut client_rpc = Rpc::wallet(&state.btc_config, "client").unwrap();
+ let mut wire_rpc = Rpc::wallet(&state.btc_config, "wire").unwrap();
+ let reserve_addr = reserve_rpc.gen_addr().unwrap();
+ let client_addr = client_rpc.gen_addr().unwrap();
+ let wire_addr = wire_rpc.gen_addr().unwrap();
+ common_rpc.mine(101, &reserve_addr).unwrap();
+ reserve_rpc
+ .send(&client_addr, &(Amount::ONE_BTC * 10), None, false)
+ .unwrap();
+ common_rpc.mine(1, &reserve_addr).unwrap();
+
+ let conf = state.confirmation.load(Ordering::SeqCst) as u16;
+ Self {
+ common,
+ btc_node,
+ common_rpc,
+ wire_rpc,
+ client_rpc,
+ reserve_rpc,
+ wire_addr,
+ client_addr,
+ reserve_addr,
+ state,
+ conf,
+ _btc_node2: btc_node2,
+ common_rpc2,
+ }
+ }
+
+ pub fn reset_db(&mut self) {
+ let hash: BlockHash = self.common_rpc.get_genesis().unwrap();
+ let mut db = self.common.taler_conf.db_config().connect(NoTls).unwrap();
+ let mut tx = db.transaction().unwrap();
+ // Clear transaction tables and reset state
+ tx.batch_execute("DELETE FROM tx_in;DELETE FROM tx_out;DELETE FROM bounce;")
+ .unwrap();
+ tx.execute(
+ "UPDATE state SET value=$1 WHERE name='last_hash'",
+ &[&hash.as_ref()],
+ )
+ .unwrap();
+ tx.commit().unwrap();
+ }
+
+ pub fn stop_node(&mut self) {
+ // We need to kill bitcoin gracefully to avoid corruption
+ #[cfg(unix)]
+ {
+ cmd_redirect_ok(
+ "kill",
+ &[&self.btc_node.0.id().to_string()],
+ "/dev/null",
+ "fill btc node",
+ );
+ self.btc_node.0.wait().unwrap();
+ }
+ }
+
+ pub fn cluster_deco(&mut self) {
+ self.common_rpc.disconnect_node("127.0.0.1:8346").unwrap();
+ }
+
+ pub fn cluster_fork(&mut self, length: u16) {
+ self.common_rpc2.mine(length, &self.reserve_addr).unwrap();
+ self.common_rpc.add_node("127.0.0.1:8346").unwrap();
+ }
+
+ pub fn restart_node(&mut self, additional_args: &[&str]) {
+ self.stop_node();
+ self.resume_node(additional_args);
+ }
+
+ pub fn resume_node(&mut self, additional_args: &[&str]) {
+ let datadir = format!("-datadir={}", self.common.dirs.wire_dir.to_string_lossy());
+ let mut args = vec![datadir.as_str()];
+ args.extend_from_slice(additional_args);
+ self.btc_node = cmd_redirect("bitcoind", &args, "log/node.log");
+ self.common_rpc = retry_opt(|| Rpc::common(&self.state.btc_config).ok());
+ self.common_rpc.add_node("127.0.0.1:8346").unwrap();
+ for name in ["client", "reserve", "wire"] {
+ self.common_rpc.load_wallet(name).ok();
+ }
+
+ self.reserve_rpc = Rpc::wallet(&self.state.btc_config, "reserve").unwrap();
+ self.client_rpc = Rpc::wallet(&self.state.btc_config, "client").unwrap();
+ self.wire_rpc = Rpc::wallet(&self.state.btc_config, "wire").unwrap();
+ }
+
+ /* ----- Transaction ------ */
+
+ pub fn credit(&mut self, amount: Amount, metadata: [u8; 32]) {
+ self.client_rpc
+ .send_segwit_key(&self.wire_addr, &amount, &metadata)
+ .unwrap();
+ }
+
+ pub fn debit(&mut self, amount: Amount, metadata: [u8; 32]) {
+ transfer(
+ &self.common.gateway_url,
+ &metadata,
+ &self.state.base_url,
+ btc_payto_url(&self.client_addr),
+ &btc_to_taler(&amount.to_signed().unwrap(), self.state.currency),
+ )
+ }
+
+ pub fn malformed_credit(&mut self, amount: &Amount) {
+ self.client_rpc
+ .send(&self.wire_addr, amount, None, false)
+ .unwrap();
+ }
+
+ pub fn reset_wallet(&mut self) {
+ let amount = self.wire_balance();
+ self.wire_rpc
+ .send(&self.client_addr, &amount, None, true)
+ .unwrap();
+ self.next_block();
+ }
+
+ fn abandon(rpc: &mut Rpc) {
+ let list = rpc.list_since_block(None, 1).unwrap();
+ for tx in list.transactions {
+ if tx.category == Category::Send && tx.confirmations == 0 {
+ rpc.abandon_tx(&tx.txid).unwrap();
+ }
+ }
+ }
+
+ pub fn abandon_wire(&mut self) {
+ Self::abandon(&mut self.wire_rpc);
+ }
+
+ pub fn abandon_client(&mut self) {
+ Self::abandon(&mut self.client_rpc);
+ }
+
+ /* ----- Mining ----- */
+
+ fn mine(&mut self, nb: u16) {
+ self.common_rpc.mine(nb, &self.reserve_addr).unwrap();
+ }
+
+ pub fn next_conf(&mut self) {
+ self.mine(self.conf)
+ }
+
+ pub fn next_block(&mut self) {
+ self.mine(1)
+ }
+
+ /* ----- Balances ----- */
+
+ pub fn client_balance(&mut self) -> Amount {
+ self.client_rpc.get_balance().unwrap()
+ }
+
+ pub fn wire_balance(&mut self) -> Amount {
+ self.wire_rpc.get_balance().unwrap()
+ }
+
+ fn expect_balance(&mut self, balance: Amount, mine: bool, lambda: fn(&mut Self) -> Amount) {
+ retry(|| {
+ let check = balance == lambda(self);
+ if !check && mine {
+ self.next_block();
+ }
+ check
+ });
+ }
+
+ pub fn expect_client_balance(&mut self, balance: Amount, mine: bool) {
+ self.expect_balance(balance, mine, Self::client_balance)
+ }
+
+ pub fn expect_wire_balance(&mut self, balance: Amount, mine: bool) {
+ self.expect_balance(balance, mine, Self::wire_balance)
+ }
+
+ /* ----- Wire Gateway ----- */
+
+ pub fn expect_credits(&self, txs: &[([u8; 32], Amount)]) {
+ let txs: Vec<_> = txs
+ .iter()
+ .map(|(metadata, amount)| {
+ (
+ *metadata,
+ btc_to_taler(&amount.to_signed().unwrap(), self.state.currency),
+ )
+ })
+ .collect();
+ self.common.expect_credits(&txs)
+ }
+
+ pub fn expect_debits(&self, txs: &[([u8; 32], Amount)]) {
+ let txs: Vec<_> = txs
+ .iter()
+ .map(|(metadata, amount)| {
+ (
+ *metadata,
+ btc_to_taler(&amount.to_signed().unwrap(), self.state.currency),
+ )
+ })
+ .collect();
+ self.common.expect_debits(&self.state.base_url, &txs)
+ }
+}
+
+pub const TESTS: &[(fn(), &str)] = &[
+ (wire, "btc_wire"),
+ (lifetime, "btc_lifetime"),
+ (reconnect, "btc_reconnect"),
+ (stress, "btc_stress"),
+ (conflict, "btc_conflict"),
+ (reorg, "btc_reorg"),
+ (hell, "btc_hell"),
+ (analysis, "btc_analysis"),
+ (bumpfee, "btc_bumpfee"),
+ (maxfee, "btc_maxfee"),
+ (config, "btc_config"),
+];
+
+/// Test btc-wire correctly receive and send transactions on the blockchain
+fn wire() {
+ println!("Setup");
+ let mut ctx = BtcCtx::setup("taler_btc.conf", false);
+
+ println!("Credit");
+ {
+ // Send transactions
+ let mut balance = ctx.wire_balance();
+ let mut txs = Vec::new();
+ for n in 10..100 {
+ let metadata = rand_slice();
+ let amount = Amount::from_sat(n * 1000);
+ ctx.credit(amount, metadata);
+ txs.push((metadata, amount));
+ balance += amount;
+ ctx.next_block();
+ }
+ ctx.next_conf();
+ ctx.expect_credits(&txs);
+ ctx.expect_wire_balance(balance, true);
+ };
+
+ println!("Debit");
+ {
+ let mut balance = ctx.client_balance();
+ let mut txs = Vec::new();
+ for n in 10..100 {
+ let metadata = rand_slice();
+ let amount = Amount::from_sat(n * 100);
+ balance += amount;
+ ctx.debit(amount, metadata);
+ txs.push((metadata, amount));
+ }
+ ctx.next_block();
+ ctx.expect_debits(&txs);
+ ctx.expect_client_balance(balance, true);
+ }
+
+ println!("Bounce");
+ {
+ ctx.reset_wallet();
+ // Send bad transactions
+ let mut balance = ctx.wire_balance();
+ for n in 10..40 {
+ ctx.malformed_credit(&Amount::from_sat(n * 1000));
+ balance += ctx.state.bounce_fee;
+ }
+ ctx.next_conf();
+ ctx.expect_wire_balance(balance, true);
+ }
+}
+
+/// Check btc-wire and wire-gateway correctly stop when a lifetime limit is configured
+fn lifetime() {
+ println!("Setup");
+ let mut ctx = BtcCtx::setup("taler_btc_lifetime.conf", false);
+ println!("Check lifetime");
+ // Start up
+ retry(|| ctx.wire_running() && ctx.gateway_running());
+ // Consume lifetime
+ for _ in 0..=ctx.taler_conf.wire_lifetime().unwrap() {
+ ctx.credit(segwit_min_amount(), rand_slice());
+ ctx.next_block();
+ }
+ for _ in 0..=ctx.taler_conf.http_lifetime().unwrap() {
+ ctx.debit(segwit_min_amount(), rand_slice());
+ ctx.next_block();
+ }
+ // End down
+ retry(|| !ctx.wire_running() && !ctx.gateway_running());
+}
+
+/// Check the capacity of wire-gateway and btc-wire to recover from database and node loss
+fn reconnect() {
+ println!("Setup");
+ let mut ctx = BtcCtx::setup("taler_btc.conf", false);
+
+ let mut credits = Vec::new();
+ let mut debits = Vec::new();
+
+ println!("With DB");
+ {
+ let metadata = rand_slice();
+ let amount = Amount::from_sat(42000);
+ ctx.credit(amount, metadata);
+ credits.push((metadata, amount));
+ ctx.next_block();
+ ctx.next_conf();
+ ctx.expect_credits(&credits);
+ };
+
+ println!("Without DB");
+ {
+ ctx.stop_db();
+ ctx.malformed_credit(&Amount::from_sat(24000));
+ let metadata = rand_slice();
+ let amount = Amount::from_sat(40000);
+ ctx.credit(amount, metadata);
+ credits.push((metadata, amount));
+ ctx.stop_node();
+ ctx.expect_error();
+ }
+
+ println!("Reconnect DB");
+ {
+ ctx.resume_db();
+ ctx.resume_node(&[]);
+ let metadata = rand_slice();
+ let amount = Amount::from_sat(2000);
+ ctx.debit(amount, metadata);
+ debits.push((metadata, amount));
+ ctx.next_block();
+ sleep(Duration::from_secs(3));
+ ctx.next_block();
+ sleep(Duration::from_secs(3));
+ ctx.next_block();
+ ctx.expect_debits(&debits);
+ ctx.expect_credits(&credits);
+ }
+
+ println!("Recover DB");
+ {
+ let balance = ctx.wire_balance();
+ ctx.reset_db();
+ ctx.next_block();
+ ctx.expect_debits(&debits);
+ ctx.expect_credits(&credits);
+ ctx.expect_wire_balance(balance, true);
+ }
+}
+
+/// Test btc-wire ability to recover from errors in correctness critical paths and prevent concurrent sending
+fn stress() {
+ println!("Setup");
+ let mut ctx = BtcCtx::setup("taler_btc.conf", true);
+
+ let mut credits = Vec::new();
+ let mut debits = Vec::new();
+
+ println!("Credit");
+ {
+ let mut balance = ctx.wire_balance();
+ for n in 10..30 {
+ let metadata = rand_slice();
+ let amount = Amount::from_sat(n * 1000);
+ ctx.credit(amount, metadata);
+ credits.push((metadata, amount));
+ balance += amount;
+ ctx.next_block();
+ }
+ ctx.next_conf();
+ ctx.expect_credits(&credits);
+ ctx.expect_wire_balance(balance, true);
+ };
+
+ println!("Debit");
+ {
+ let mut balance = ctx.client_balance();
+ for n in 10..30 {
+ let metadata = rand_slice();
+ let amount = Amount::from_sat(n * 100);
+ balance += amount;
+ ctx.debit(amount, metadata);
+ debits.push((metadata, amount));
+ }
+ ctx.next_block();
+ ctx.expect_debits(&debits);
+ ctx.expect_client_balance(balance, true);
+ }
+
+ println!("Bounce");
+ {
+ ctx.reset_wallet();
+ let mut balance = ctx.wire_balance();
+ for n in 10..30 {
+ ctx.malformed_credit(&Amount::from_sat(n * 1000));
+ balance += ctx.state.bounce_fee;
+ }
+ ctx.next_conf();
+ ctx.expect_wire_balance(balance, true);
+ }
+
+ println!("Recover DB");
+ {
+ let balance = ctx.wire_balance();
+ ctx.reset_db();
+ ctx.next_block();
+ ctx.expect_debits(&debits);
+ ctx.expect_credits(&credits);
+ ctx.expect_wire_balance(balance, true);
+ }
+}
+
+/// Test btc-wire ability to handle conflicting outgoing transactions
+fn conflict() {
+ println!("Setup");
+ let mut ctx = BtcCtx::setup("taler_btc.conf", false);
+
+ println!("Conflict send");
+ {
+ // Perform credit
+ let amount = Amount::from_sat(4200000);
+ ctx.credit(amount, rand_slice());
+ ctx.next_conf();
+ ctx.expect_wire_balance(amount, true);
+ let client = ctx.client_balance();
+ let wire = ctx.wire_balance();
+
+ // Perform debit
+ ctx.debit(Amount::from_sat(400000), rand_slice());
+ retry(|| ctx.wire_balance() < wire);
+
+ // Abandon pending transaction
+ ctx.restart_node(&["-minrelaytxfee=0.0001"]);
+ ctx.abandon_wire();
+ ctx.expect_client_balance(client, false);
+ ctx.expect_wire_balance(wire, false);
+
+ // Generate conflict
+ ctx.debit(Amount::from_sat(500000), rand_slice());
+ retry(|| ctx.wire_balance() < wire);
+
+ // Resend conflicting transaction
+ ctx.restart_node(&[]);
+ ctx.next_block();
+ let wire = ctx.wire_balance();
+ retry(|| ctx.wire_balance() < wire);
+ }
+
+ println!("Setup");
+ drop(ctx);
+ let mut ctx = BtcCtx::setup("taler_btc.conf", false);
+ ctx.credit(Amount::from_sat(3000000), rand_slice());
+ ctx.next_block();
+
+ println!("Conflict bounce");
+ {
+ // Perform bounce
+ let wire = ctx.wire_balance();
+ let bounce_amount = Amount::from_sat(4000000);
+ ctx.malformed_credit(&bounce_amount);
+ ctx.next_conf();
+ let fee = ctx.state.bounce_fee;
+ ctx.expect_wire_balance(wire + fee, true);
+
+ // Abandon pending transaction
+ ctx.restart_node(&["-minrelaytxfee=0.0001"]);
+ ctx.abandon_wire();
+ ctx.expect_wire_balance(wire + bounce_amount, false);
+
+ // Generate conflict
+ let amount = Amount::from_sat(50000);
+ ctx.debit(amount, rand_slice());
+ retry(|| ctx.wire_balance() < (wire + bounce_amount));
+
+ // Resend conflicting transaction
+ ctx.restart_node(&[]);
+ let wire = ctx.wire_balance();
+ ctx.next_block();
+ retry(|| ctx.wire_balance() < wire);
+ }
+}
+
+/// Test btc-wire correctness when a blockchain reorganization occurs
+fn reorg() {
+ println!("Setup");
+ let mut ctx = BtcCtx::setup("taler_btc.conf", false);
+
+ println!("Handle reorg incoming transactions");
+ {
+ // Loose second bitcoin node
+ ctx.cluster_deco();
+
+ // Perform credits
+ let before = ctx.wire_balance();
+ for n in 10..21 {
+ ctx.credit(Amount::from_sat(n * 10000), rand_slice());
+ ctx.next_block();
+ }
+ let after = ctx.wire_balance();
+
+ // Perform fork and check btc-wire hard error
+ ctx.expect_gateway_up();
+ ctx.cluster_fork(22);
+ ctx.expect_wire_balance(before, false);
+ ctx.expect_gateway_down();
+
+ // Recover orphaned transaction
+ ctx.mine(12);
+ ctx.expect_wire_balance(after, false);
+ ctx.expect_gateway_up();
+ }
+
+ println!("Handle reorg outgoing transactions");
+ {
+ // Loose second bitcoin node
+ ctx.cluster_deco();
+
+ // Perform debits
+ let before = ctx.client_balance();
+ let mut after = ctx.client_balance();
+ for n in 10..21 {
+ let amount = Amount::from_sat(n * 100);
+ ctx.debit(amount, rand_slice());
+ after += amount;
+ }
+ ctx.next_block();
+ ctx.expect_client_balance(after, true);
+
+ // Perform fork and check btc-wire still up
+ ctx.expect_gateway_up();
+ ctx.cluster_fork(22);
+ ctx.expect_client_balance(before, false);
+ ctx.expect_gateway_up();
+
+ // Recover orphaned transaction
+ ctx.next_conf();
+ ctx.expect_client_balance(after, false);
+ }
+
+ println!("Handle reorg bounce");
+ {
+ ctx.reset_wallet();
+
+ // Loose second bitcoin node
+ ctx.cluster_deco();
+
+ // Perform bounce
+ let before = ctx.wire_balance();
+ let mut after = ctx.wire_balance();
+ for n in 10..21 {
+ ctx.malformed_credit(&Amount::from_sat(n * 1000));
+ after += ctx.state.bounce_fee;
+ }
+ ctx.next_conf();
+ ctx.expect_wire_balance(after, true);
+
+ // Perform fork and check btc-wire hard error
+ ctx.expect_gateway_up();
+ ctx.cluster_fork(22);
+ ctx.expect_wire_balance(before, false);
+ ctx.expect_gateway_down();
+
+ // Recover orphaned transaction
+ ctx.mine(10);
+ ctx.expect_wire_balance(after, false);
+ ctx.expect_gateway_up();
+ }
+}
+
+/// Test btc-wire correctness when a blockchain reorganization occurs leading to past incoming transaction conflict
+fn hell() {
+ fn step(name: &str, action: impl FnOnce(&mut BtcCtx)) {
+ println!("Setup");
+ let mut ctx = BtcCtx::setup("taler_btc.conf", false);
+ println!("{}", name);
+
+ // Loose second bitcoin node
+ ctx.cluster_deco();
+
+ // Perform action
+ action(&mut ctx);
+
+ // Perform fork and check btc-wire hard error
+ ctx.expect_gateway_up();
+ ctx.cluster_fork(ctx.conf * 2);
+ ctx.expect_gateway_down();
+
+ // Generate conflict
+ ctx.restart_node(&["-minrelaytxfee=0.001"]);
+ ctx.abandon_client();
+ let amount = Amount::from_sat(54000);
+ ctx.credit(amount, rand_slice());
+ ctx.expect_wire_balance(amount, true);
+
+ // Check btc-wire suspend operation
+ let bounce_amount = Amount::from_sat(34000);
+ ctx.malformed_credit(&bounce_amount);
+ ctx.next_conf();
+ ctx.expect_wire_balance(amount + bounce_amount, true);
+ ctx.expect_gateway_down();
+ }
+
+ step("Handle reorg conflicting incoming credit", |ctx| {
+ let amount = Amount::from_sat(420000);
+ ctx.credit(amount, rand_slice());
+ ctx.next_conf();
+ ctx.expect_wire_balance(amount, true);
+ });
+
+ step("Handle reorg conflicting incoming bounce", |ctx| {
+ let amount = Amount::from_sat(420000);
+ ctx.malformed_credit(&amount);
+ ctx.next_conf();
+ let fee = ctx.state.bounce_fee;
+ ctx.expect_wire_balance(fee, true);
+ });
+}
+
+/// Test btc-wire ability to learn and protect itself from blockchain behavior
+fn analysis() {
+ println!("Setup");
+ let mut ctx = BtcCtx::setup("taler_btc.conf", false);
+
+ println!("Learn from reorg");
+
+ // Loose second bitcoin node
+ ctx.cluster_deco();
+
+ // Perform credit
+ let before = ctx.wire_balance();
+ ctx.credit(Amount::from_sat(42000), rand_slice());
+ ctx.next_conf();
+ let after = ctx.wire_balance();
+
+ // Perform fork and check btc-wire hard error
+ ctx.expect_gateway_up();
+ ctx.cluster_fork(5);
+ ctx.expect_wire_balance(before, false);
+ ctx.expect_gateway_down();
+
+ // Recover orphaned transaction
+ ctx.next_conf();
+ ctx.expect_wire_balance(after, false);
+ ctx.expect_gateway_up();
+
+ // Loose second bitcoin node
+ ctx.cluster_deco();
+
+ // Perform credit
+ let before = ctx.wire_balance();
+ ctx.credit(Amount::from_sat(42000), rand_slice());
+ ctx.next_conf();
+
+ // Perform fork and check btc-wire learned from previous attack
+ ctx.expect_gateway_up();
+ ctx.cluster_fork(5);
+ ctx.expect_wire_balance(before, false);
+ ctx.expect_gateway_up();
+}
+
+/// Test btc-wire ability to handle stuck transaction correctly
+fn bumpfee() {
+ println!("Setup");
+ let mut ctx = BtcCtx::setup("taler_btc_bump.conf", false);
+
+ // Perform credits to allow wire to perform debits latter
+ for n in 10..13 {
+ ctx.credit(Amount::from_sat(n * 100000), rand_slice());
+ ctx.next_block();
+ }
+ ctx.next_conf();
+
+ println!("Bump fee");
+ {
+ // Perform debit
+ let mut client = ctx.client_balance();
+ let wire = ctx.wire_balance();
+ let amount = Amount::from_sat(40000);
+ ctx.debit(amount, rand_slice());
+ retry(|| ctx.wire_balance() < wire);
+
+ // Bump min relay fee making the previous debit stuck
+ ctx.restart_node(&["-minrelaytxfee=0.0001"]);
+
+ // Check bump happen
+ client += amount;
+ ctx.expect_client_balance(client, true);
+ }
+
+ println!("Bump fee reorg");
+ {
+ // Loose second bitcoin node
+ ctx.cluster_deco();
+
+ // Perform debit
+ let mut client = ctx.client_balance();
+ let wire = ctx.wire_balance();
+ let amount = Amount::from_sat(40000);
+ ctx.debit(amount, rand_slice());
+ retry(|| ctx.wire_balance() < wire);
+
+ // Bump min relay fee and fork making the previous debit stuck and problematic
+ ctx.cluster_fork(6);
+ ctx.restart_node(&["-minrelaytxfee=0.0001"]);
+
+ // Check bump happen
+ client += amount;
+ ctx.expect_client_balance(client, true);
+ }
+
+ println!("Setup");
+ drop(ctx);
+ let mut ctx = BtcCtx::setup("taler_btc_bump.conf", true);
+
+ // Perform credits to allow wire to perform debits latter
+ for n in 10..61 {
+ ctx.credit(Amount::from_sat(n * 100000), rand_slice());
+ ctx.next_block();
+ }
+ ctx.next_conf();
+
+ println!("Bump fee stress");
+ {
+ // Loose second bitcoin node
+ ctx.cluster_deco();
+
+ // Perform debits
+ let client = ctx.client_balance();
+ let wire = ctx.wire_balance();
+ let mut total_amount = Amount::ZERO;
+ for n in 10..31 {
+ let amount = Amount::from_sat(n * 10000);
+ total_amount += amount;
+ ctx.debit(amount, rand_slice());
+ }
+ retry(|| ctx.wire_balance() < wire - total_amount);
+
+ // Bump min relay fee making the previous debits stuck
+ ctx.restart_node(&["-minrelaytxfee=0.0001"]);
+
+ // Check bump happen
+ ctx.expect_client_balance(client + total_amount, true);
+ }
+}
+
+/// Test btc-wire handle transaction fees exceeding limits
+fn maxfee() {
+ println!("Setup");
+ let mut ctx = BtcCtx::setup("taler_btc.conf", false);
+
+ // Perform credits to allow wire to perform debits latter
+ for n in 10..31 {
+ ctx.credit(Amount::from_sat(n * 100000), rand_slice());
+ ctx.next_block();
+ }
+ ctx.next_conf();
+
+ let client = ctx.client_balance();
+ let wire = ctx.wire_balance();
+ let mut total_amount = Amount::ZERO;
+
+ println!("Too high fee");
+ {
+ // Change fee config
+ ctx.restart_node(&["-maxtxfee=0.0000001", "-minrelaytxfee=0.0000001"]);
+
+ // Perform debits
+ for n in 10..31 {
+ let amount = Amount::from_sat(n * 10000);
+ total_amount += amount;
+ ctx.debit(amount, rand_slice());
+ }
+ sleep(Duration::from_secs(3));
+
+ // Check no transaction happen
+ ctx.expect_wire_balance(wire, true);
+ ctx.expect_client_balance(client, true);
+ }
+
+ println!("Good feed");
+ {
+ // Restore default config
+ ctx.restart_node(&[]);
+
+ // Check transaction now have been made
+ ctx.expect_client_balance(client + total_amount, true);
+ }
+}
+
+/// Test btc-wire ability to configure itself from bitcoin configuration
+fn config() {
+ for n in 0..5 {
+ let config_name = format!("bitcoin_auth{}.conf", n);
+ println!("Config {}", config_name);
+ BtcCtx::config(&config_name);
+ }
}
diff --git a/instrumentation/src/eth.rs b/instrumentation/src/eth.rs
@@ -1,14 +1,40 @@
-use std::{path::Path, time::Duration};
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
-use common::{metadata::OutMetadata, rand_slice};
+ 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::{
+ io::Write,
+ ops::{Deref, DerefMut},
+ path::Path,
+ sync::atomic::Ordering,
+ thread::sleep,
+ time::Duration,
+};
+
+use common::{metadata::OutMetadata, postgres::NoTls, rand_slice};
use eth_wire::{
rpc::{hex::Hex, Rpc, RpcClient, TransactionRequest},
taler_util::{eth_payto_url, eth_to_taler, TRUNC},
RpcExtended, SyncState, WireState,
};
-use ethereum_types::{H256, U256};
+use ethereum_types::{H160, H256, U256};
-use crate::{check_incoming, check_outgoing, print_now, transfer};
+use crate::utils::{
+ check_incoming, check_outgoing, cmd_out, cmd_redirect, cmd_redirect_ok, print_now, retry,
+ retry_opt, transfer, ChildGuard, CommonCtx,
+};
fn wait_for_pending(rpc: &mut Rpc) {
print_now("Wait for pending transactions mining:");
@@ -19,7 +45,7 @@ fn wait_for_pending(rpc: &mut Rpc) {
print_now(".");
std::thread::sleep(Duration::from_secs(1)); // Wait for eth-wire to act
}
- println!("");
+ println!();
}
pub fn eth_test(config: Option<&Path>, base_url: &str) {
@@ -41,7 +67,7 @@ pub fn eth_test(config: Option<&Path>, base_url: &str) {
.unwrap_or_else(|| rpc.new_account("password").unwrap()); // Else create account
rpc.unlock_account(&client_addr, "password").unwrap();
- if rpc.get_balance(&client_addr).unwrap() < min_fund {
+ if rpc.get_balance_latest(&client_addr).unwrap() < min_fund {
println!(
"Client need a minimum of {} WEI to run this test, send coins to this address: {}",
min_fund.as_u64(),
@@ -49,17 +75,17 @@ pub fn eth_test(config: Option<&Path>, base_url: &str) {
);
print_now("Waiting for fund:");
let mut rpc = rpc.subscribe_new_head().unwrap();
- while rpc.get_balance(&client_addr).unwrap() < min_fund {
+ while rpc.get_balance_latest(&client_addr).unwrap() < min_fund {
rpc.next().unwrap();
print_now(".");
}
- println!("");
+ println!();
}
wait_for_pending(&mut rpc);
// Load balances
- let client_balance = rpc.get_balance(&client_addr).unwrap();
- let wire_balance = rpc.get_balance(&state.address).unwrap();
+ let client_balance = rpc.get_balance_latest(&client_addr).unwrap();
+ let wire_balance = rpc.get_balance_latest(&state.address).unwrap();
// Start sync state
let latest = rpc.latest_block().unwrap();
let mut sync_state = SyncState {
@@ -120,12 +146,12 @@ pub fn eth_test(config: Option<&Path>, base_url: &str) {
print_now(".");
}
};
- println!("");
+ println!();
wait_for_pending(&mut rpc);
println!("Check balance");
- let new_client_balance = rpc.get_balance(&client_addr).unwrap();
- let new_wire_balance = rpc.get_balance(&state.address).unwrap();
+ let new_client_balance = rpc.get_balance_latest(&client_addr).unwrap();
+ let new_wire_balance = rpc.get_balance_latest(&state.address).unwrap();
let client_sent_amount_cost = test_amount * U256::from(2u8);
let client_sent_fees_cost = [credit_id, zero_id, bounce_id]
.into_iter()
@@ -146,8 +172,13 @@ pub fn eth_test(config: Option<&Path>, base_url: &str) {
new_wire_balance
);
- check_incoming(base_url, &reserve_pub_key, &taler_test_amount);
+ println!("Check incoming history");
+ assert!(check_incoming(
+ base_url,
+ &[(reserve_pub_key, taler_test_amount.clone())]
+ ));
+ println!("Get back some money");
let wtid = rand_slice();
transfer(
base_url,
@@ -159,8 +190,935 @@ pub fn eth_test(config: Option<&Path>, base_url: &str) {
wait_for_pending(&mut rpc);
println!("Check balances");
- let last_client_balance = rpc.get_balance(&client_addr).unwrap();
+ let last_client_balance = rpc.get_balance_latest(&client_addr).unwrap();
assert_eq!(new_client_balance + test_amount, last_client_balance);
- check_outgoing(base_url, &wtid, &state.base_url, &taler_test_amount);
+ println!("Check outgoing history");
+ assert!(check_outgoing(
+ base_url,
+ &state.base_url,
+ &[(wtid, taler_test_amount)]
+ ));
+}
+
+struct EthCtx {
+ node: ChildGuard,
+ rpc: Rpc,
+ wire_addr: H160,
+ client_addr: H160,
+ reserve_addr: H160,
+ state: WireState,
+ conf: u16,
+ common: CommonCtx,
+ passwd: String,
+}
+
+impl Deref for EthCtx {
+ type Target = CommonCtx;
+
+ fn deref(&self) -> &Self::Target {
+ &self.common
+ }
+}
+
+impl DerefMut for EthCtx {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.common
+ }
+}
+
+impl EthCtx {
+ pub fn setup(config: &str, stressed: bool) -> Self {
+ let dirs = CommonCtx::setup_dirs(config);
+ // Init chain
+ let passwd = std::env::var("PASSWORD").unwrap();
+ let pswd_path = dirs.dir.path().join("pswd");
+ std::fs::write(&pswd_path, passwd.as_bytes()).unwrap();
+ for _ in ["reserve", "client"] {
+ cmd_redirect_ok(
+ "geth",
+ &[
+ "--datadir",
+ dirs.wire_dir.to_str().unwrap(),
+ "account",
+ "new",
+ "--password",
+ pswd_path.to_str().unwrap(),
+ ],
+ "log/node.log",
+ "create account",
+ )
+ }
+ cmd_redirect_ok(
+ "geth",
+ &[
+ "--datadir",
+ dirs.wire_dir2.to_str().unwrap(),
+ "account",
+ "new",
+ "--password",
+ pswd_path.to_str().unwrap(),
+ ],
+ "log/node2.log",
+ "create account",
+ );
+ let list = cmd_out(
+ "geth",
+ &[
+ "--datadir",
+ dirs.wire_dir.to_str().unwrap(),
+ "account",
+ "list",
+ ],
+ );
+ let addr = &list.lines().nth(1).unwrap()[13..][..40];
+ let genesis = format!(
+ "{{
+ \"config\": {{
+ \"chainId\": 42,
+ \"homesteadBlock\": 0,
+ \"eip150Block\": 0,
+ \"eip155Block\": 0,
+ \"eip158Block\": 0,
+ \"byzantiumBlock\": 0,
+ \"constantinopleBlock\": 0,
+ \"petersburgBlock\": 0,
+ \"istanbulBlock\": 0,
+ \"berlinBlock\": 0,
+ \"londonBlock:\": 0,
+ \"ethash\": {{}}
+ }},
+ \"difficulty\": \"1\",
+ \"gasLimit\": \"0\",
+ \"baseFeePerGas\": null,
+ \"alloc\": {{
+ \"{}\": {{ \"balance\": \"10000000000000000000\" }}
+ }}
+ }}",
+ addr
+ );
+ std::fs::write(dirs.wire_dir.join("genesis.json"), genesis.as_bytes()).unwrap();
+
+ cmd_redirect_ok(
+ "geth",
+ &[
+ "--datadir",
+ dirs.wire_dir.to_str().unwrap(),
+ "init",
+ dirs.wire_dir.join("genesis.json").to_str().unwrap(),
+ ],
+ "log/node.log",
+ "init chain",
+ );
+ cmd_redirect_ok(
+ "geth",
+ &[
+ "--datadir",
+ dirs.wire_dir2.to_str().unwrap(),
+ "init",
+ dirs.wire_dir.join("genesis.json").to_str().unwrap(),
+ ],
+ "log/node2.log",
+ "init chain2",
+ );
+
+ let node = cmd_redirect(
+ "geth",
+ &[
+ "--datadir",
+ dirs.wire_dir.to_str().unwrap(),
+ "--miner.gasprice",
+ "10",
+ ],
+ "log/node.log",
+ );
+ let mut rpc = retry_opt(|| Rpc::new(&dirs.wire_dir).ok());
+
+ let common = CommonCtx::setup(dirs, "eth-wire", stressed, |dirs| {
+ // Generate wallet
+ let out = cmd_out(
+ "eth-wire",
+ &["-c", dirs.conf.to_str().unwrap(), "initwallet"],
+ );
+ let payto = format!("\n{}", out.lines().nth(6).unwrap());
+ std::fs::OpenOptions::new()
+ .append(true)
+ .open(&dirs.conf)
+ .unwrap()
+ .write_all(payto.as_bytes())
+ .unwrap();
+ });
+ let state = WireState::load_taler_config(Some(&common.dirs.conf));
+ let accounts = rpc.list_accounts().unwrap();
+ let reserve_addr = accounts[0];
+ let client_addr = accounts[1];
+ let wire_addr = accounts[2];
+ for addr in [&client_addr, &reserve_addr] {
+ rpc.unlock_account(addr, &passwd).unwrap();
+ }
+ let conf = state.confirmation.load(Ordering::SeqCst) as u16;
+
+ Self {
+ node,
+ rpc,
+ reserve_addr,
+ client_addr,
+ wire_addr,
+ state,
+ common,
+ conf,
+ passwd,
+ }
+ }
+
+ pub fn reset_db(&mut self) {
+ let block = self.rpc.earliest_block().unwrap();
+ let mut db = self.common.taler_conf.db_config().connect(NoTls).unwrap();
+ let mut tx = db.transaction().unwrap();
+ // Clear transaction tables and reset state
+ tx.batch_execute("DELETE FROM tx_in;DELETE FROM tx_out;DELETE FROM bounce;")
+ .unwrap();
+ tx.execute(
+ "UPDATE state SET value=$1 WHERE name='sync'",
+ &[&SyncState {
+ tip_hash: block.hash.unwrap(),
+ tip_height: block.number.unwrap(),
+ conf_height: block.number.unwrap(),
+ }
+ .to_bytes()
+ .as_ref()],
+ )
+ .unwrap();
+ tx.commit().unwrap();
+ }
+
+ pub fn stop_node(&mut self) {
+ // We need to kill node gracefully to avoid corruption
+ #[cfg(unix)]
+ {
+ cmd_redirect_ok(
+ "kill",
+ &[&self.node.0.id().to_string()],
+ "/dev/null",
+ "fill btc node",
+ );
+ self.node.0.wait().unwrap();
+ }
+ }
+
+ // We use the import/export chain functionality to simulate a connected node peer
+ // because local network peer are not reliable
+ // importChain RPC crash so we have to use the cli for now
+
+ fn export(rpc: &mut Rpc, path: &str) {
+ std::fs::remove_file(path).ok();
+ assert!(rpc.export_chain(path).unwrap())
+ }
+
+ pub fn cluster_deco(&mut self) {
+ let path = self.dirs.dir.path().join("chain");
+ let path = path.to_str().unwrap();
+ Self::export(&mut self.rpc, path);
+ cmd_redirect_ok(
+ "geth",
+ &[
+ "--datadir",
+ self.dirs.wire_dir2.to_str().unwrap(),
+ "import",
+ path,
+ ],
+ "log/node2.log",
+ "import chain",
+ );
+ }
+
+ pub fn cluster_fork(&mut self, length: u16) {
+ let node2 = cmd_redirect(
+ "geth",
+ &[
+ "--datadir",
+ self.dirs.wire_dir2.to_str().unwrap(),
+ "--port",
+ "30305",
+ "--miner.gasprice",
+ "10",
+ ],
+ "log/node2.log",
+ );
+ let mut rpc = retry_opt(|| Rpc::new(&self.dirs.wire_dir2).ok());
+ Self::_mine(&mut rpc, &self.reserve_addr, length, &self.passwd);
+ let path = self.dirs.dir.path().join("chain");
+ let path = path.to_str().unwrap();
+ Self::export(&mut rpc, path);
+ drop(node2);
+ self.stop_node();
+ cmd_redirect_ok(
+ "geth",
+ &[
+ "--datadir",
+ self.dirs.wire_dir.to_str().unwrap(),
+ "import",
+ path,
+ ],
+ "log/node.log",
+ "import chain",
+ );
+ self.resume_node(&[]);
+ }
+
+ pub fn restart_node(&mut self, additional_args: &[&str]) {
+ self.stop_node();
+ self.resume_node(additional_args);
+ }
+
+ pub fn resume_node(&mut self, additional_args: &[&str]) {
+ let datadir = format!("-datadir={}", self.dirs.wire_dir.to_string_lossy());
+ let mut args = vec![datadir.as_str()];
+ args.extend_from_slice(additional_args);
+ self.node = cmd_redirect(
+ "geth",
+ &["--datadir", self.dirs.wire_dir.to_str().unwrap()],
+ "log/node.log",
+ );
+ self.rpc = retry_opt(|| Rpc::new(&self.dirs.wire_dir).ok());
+ for addr in [&self.wire_addr, &self.client_addr, &self.reserve_addr] {
+ self.rpc.unlock_account(addr, &self.passwd).unwrap();
+ }
+ }
+
+ pub fn amount(&self, amount: u32) -> U256 {
+ return U256::from(amount) * TRUNC;
+ }
+
+ /* ----- Transaction ------ */
+
+ pub fn credit(&mut self, amount: U256, metadata: [u8; 32]) {
+ self.rpc
+ .credit(self.client_addr, self.wire_addr, amount, metadata)
+ .unwrap();
+ }
+
+ pub fn debit(&mut self, amount: U256, metadata: [u8; 32]) {
+ transfer(
+ &self.common.gateway_url,
+ &metadata,
+ &self.state.base_url,
+ eth_payto_url(&self.client_addr),
+ ð_to_taler(&amount, self.state.currency),
+ )
+ }
+
+ pub fn malformed_credit(&mut self, amount: U256) {
+ self.rpc
+ .send_transaction(&TransactionRequest {
+ from: self.client_addr,
+ to: self.wire_addr,
+ value: amount,
+ nonce: None,
+ gas_price: None,
+ data: Hex(vec![]),
+ })
+ .unwrap();
+ }
+
+ pub fn abandon(&mut self) {
+ let pending = self.rpc.pending_transactions().unwrap();
+ for tx in pending
+ .into_iter()
+ .filter(|t| t.from == Some(self.client_addr))
+ {
+ // Replace transaction value with 0
+ self.rpc
+ .send_transaction(&TransactionRequest {
+ from: self.client_addr,
+ to: tx.to.unwrap(),
+ value: U256::zero(),
+ gas_price: Some(U256::from(110)), // Bigger gas price to replace fee
+ data: Hex(vec![]),
+ nonce: Some(tx.nonce),
+ })
+ .unwrap();
+ }
+ }
+
+ /* ----- Mining ----- */
+
+ fn _mine(rpc: &mut Rpc, addr: &H160, mut amount: u16, passwd: &str) {
+ rpc.unlock_account(addr, passwd).ok();
+ let mut rpc = rpc.subscribe_new_head().unwrap();
+
+ rpc.miner_start().unwrap();
+ while !rpc.pending_transactions().unwrap().is_empty() {
+ rpc.next().unwrap();
+ amount = amount.saturating_sub(1);
+ }
+ for _ in 0..amount {
+ rpc.next().unwrap();
+ }
+ rpc.miner_stop().unwrap();
+ }
+
+ fn mine(&mut self, nb: u16) {
+ Self::_mine(&mut self.rpc, &self.reserve_addr, nb, &self.passwd)
+ }
+
+ pub fn next_conf(&mut self) {
+ self.mine(self.conf)
+ }
+
+ pub fn next_block(&mut self) {
+ self.mine(1)
+ }
+
+ /* ----- Balances ----- */
+
+ pub fn client_balance(&mut self) -> U256 {
+ self.rpc.get_balance_latest(&self.client_addr).unwrap()
+ }
+
+ pub fn wire_balance(&mut self) -> U256 {
+ self.rpc.get_balance_latest(&self.wire_addr).unwrap()
+ }
+
+ pub fn wire_balance_pending(&mut self) -> U256 {
+ self.rpc.get_balance_pending(&self.wire_addr).unwrap()
+ }
+
+ fn expect_balance(&mut self, balance: U256, mine: bool, lambda: fn(&mut Self) -> U256) {
+ retry(|| {
+ let check = balance == lambda(self);
+ if !check && mine {
+ self.next_block();
+ }
+ check
+ });
+ }
+
+ pub fn expect_client_balance(&mut self, balance: U256, mine: bool) {
+ self.expect_balance(balance, mine, Self::client_balance)
+ }
+
+ pub fn expect_wire_balance(&mut self, balance: U256, mine: bool) {
+ self.expect_balance(balance, mine, Self::wire_balance)
+ }
+
+ /* ----- Wire Gateway ----- */
+
+ pub fn expect_credits(&self, txs: &[([u8; 32], U256)]) {
+ let txs: Vec<_> = txs
+ .iter()
+ .map(|(metadata, amount)| (*metadata, eth_to_taler(amount, self.state.currency)))
+ .collect();
+ self.common.expect_credits(&txs)
+ }
+
+ pub fn expect_debits(&self, txs: &[([u8; 32], U256)]) {
+ let txs: Vec<_> = txs
+ .iter()
+ .map(|(metadata, amount)| (*metadata, eth_to_taler(amount, self.state.currency)))
+ .collect();
+ self.common.expect_debits(&self.state.base_url, &txs)
+ }
+}
+
+pub const TESTS: &[(fn(), &str)] = &[
+ (wire, "eth_wire"),
+ (lifetime, "eth_lifetime"),
+ (reconnect, "eth_reconnect"),
+ (stress, "eth_stress"),
+ (reorg, "eth_reorg"),
+ (hell, "eth_hell"),
+ (analysis, "eth_analysis"),
+ (bumpfee, "eth_bumpfee"),
+ (maxfee, "eth_maxfee"),
+];
+
+/// Test eth-wire correctly receive and send transactions on the blockchain
+fn wire() {
+ println!("Setup");
+ let mut ctx = EthCtx::setup("taler_eth.conf", false);
+
+ println!("Credit");
+ {
+ // Send transactions
+ let mut balance = ctx.wire_balance();
+ let mut txs = Vec::new();
+ for n in 10..100 {
+ let metadata = rand_slice();
+ let amount = ctx.amount(n * 1000);
+ ctx.credit(amount, metadata);
+ txs.push((metadata, amount));
+ balance += amount;
+ }
+ ctx.next_conf();
+ ctx.expect_credits(&txs);
+ ctx.expect_wire_balance(balance, true);
+ };
+
+ println!("Debit");
+ {
+ let mut balance = ctx.client_balance();
+ let mut txs = Vec::new();
+ for n in 10..100 {
+ let metadata = rand_slice();
+ let amount = ctx.amount(n * 100);
+ balance += amount;
+ ctx.debit(amount, metadata);
+ txs.push((metadata, amount));
+ }
+ ctx.next_block();
+ ctx.expect_debits(&txs);
+ ctx.expect_client_balance(balance, true);
+ }
+
+ println!("Bounce");
+ {
+ // Send bad transactions
+ let mut balance = ctx.wire_balance();
+ for n in 10..40 {
+ ctx.malformed_credit(ctx.amount(n * 1000));
+ balance += ctx.state.bounce_fee;
+ }
+ ctx.next_conf();
+ ctx.expect_wire_balance(balance, true);
+ }
+}
+
+/// Test eth-wire and wire-gateway correctly stop when a lifetime limit is configured
+fn lifetime() {
+ println!("Setup");
+ let mut ctx = EthCtx::setup("taler_eth_lifetime.conf", false);
+ println!("Check lifetime");
+ // Start up
+ retry(|| ctx.wire_running() && ctx.gateway_running());
+ // Consume lifetime
+ for n in 0..=ctx.taler_conf.wire_lifetime().unwrap() {
+ ctx.credit(ctx.amount(n * 1000), rand_slice());
+ ctx.next_block();
+ }
+ for n in 0..=ctx.taler_conf.http_lifetime().unwrap() {
+ ctx.debit(ctx.amount(n * 1000), rand_slice());
+ }
+ // End down
+ retry(|| !ctx.wire_running() && !ctx.gateway_running());
+}
+
+/// Check the capacity of wire-gateway and eth-wire to recover from database and node loss
+fn reconnect() {
+ println!("Setup");
+ let mut ctx = EthCtx::setup("taler_eth.conf", false);
+
+ let mut credits = Vec::new();
+ let mut debits = Vec::new();
+
+ println!("With DB");
+ {
+ let metadata = rand_slice();
+ let amount = ctx.amount(42000);
+ ctx.credit(amount, metadata);
+ credits.push((metadata, amount));
+ ctx.next_block();
+ ctx.next_conf();
+ ctx.expect_credits(&credits);
+ };
+
+ println!("Without DB");
+ {
+ ctx.stop_db();
+ ctx.malformed_credit(ctx.amount(24000));
+ let metadata = rand_slice();
+ let amount = ctx.amount(4000);
+ ctx.credit(amount, metadata);
+ credits.push((metadata, amount));
+ ctx.stop_node();
+ ctx.expect_error();
+ }
+
+ println!("Reconnect DB");
+ {
+ ctx.resume_db();
+ ctx.resume_node(&[]);
+ let metadata = rand_slice();
+ let amount = ctx.amount(2000);
+ ctx.debit(amount, metadata);
+ debits.push((metadata, amount));
+ ctx.next_block();
+ sleep(Duration::from_secs(3));
+ ctx.next_block();
+ sleep(Duration::from_secs(3));
+ ctx.next_block();
+ ctx.expect_debits(&debits);
+ ctx.expect_credits(&credits);
+ }
+
+ println!("Recover DB");
+ {
+ ctx.next_block();
+ sleep(Duration::from_secs(3));
+ ctx.next_block();
+ sleep(Duration::from_secs(3));
+ let balance = ctx.wire_balance();
+ ctx.reset_db();
+ ctx.next_block();
+ ctx.expect_debits(&debits);
+ ctx.expect_credits(&credits);
+ ctx.expect_wire_balance(balance, true);
+ }
+}
+
+/// Test eth-wire ability to recover from errors in correctness critical paths and prevent concurrent sending
+fn stress() {
+ println!("Setup");
+ let mut ctx = EthCtx::setup("taler_eth.conf", true);
+
+ let mut credits = Vec::new();
+ let mut debits = Vec::new();
+
+ println!("Credit");
+ {
+ let mut balance = ctx.wire_balance();
+ for n in 10..30 {
+ let metadata = rand_slice();
+ let amount = ctx.amount(n * 1000);
+ ctx.credit(amount, metadata);
+ credits.push((metadata, amount));
+ balance += amount;
+ }
+ ctx.next_conf();
+ ctx.expect_credits(&credits);
+ ctx.expect_wire_balance(balance, true);
+ };
+
+ println!("Debit");
+ {
+ let mut balance = ctx.client_balance();
+ for n in 10..30 {
+ let metadata = rand_slice();
+ let amount = ctx.amount(n * 100);
+ balance += amount;
+ ctx.debit(amount, metadata);
+ debits.push((metadata, amount));
+ }
+ ctx.next_block();
+ ctx.expect_debits(&debits);
+ ctx.expect_client_balance(balance, true);
+ }
+
+ println!("Bounce");
+ {
+ let mut balance = ctx.wire_balance();
+ for n in 10..30 {
+ ctx.malformed_credit(ctx.amount(n * 1000));
+ balance += ctx.state.bounce_fee;
+ }
+ ctx.next_conf();
+ ctx.expect_wire_balance(balance, true);
+ }
+
+ println!("Recover DB");
+ {
+ let balance = ctx.wire_balance();
+ ctx.reset_db();
+ ctx.next_block();
+ ctx.expect_debits(&debits);
+ ctx.expect_credits(&credits);
+ ctx.expect_wire_balance(balance, true);
+ }
+}
+
+/// Test eth-wire correctness when a blockchain reorganization occurs
+fn reorg() {
+ println!("Setup");
+ let mut ctx = EthCtx::setup("taler_eth.conf", false);
+
+ println!("Handle reorg incoming transactions");
+ {
+ // Loose second node
+ ctx.cluster_deco();
+
+ // Perform credits
+ let before = ctx.wire_balance();
+ for n in 10..21 {
+ ctx.credit(ctx.amount(n * 10000), rand_slice());
+ }
+ ctx.next_conf();
+ let after = ctx.wire_balance();
+
+ // Perform fork and check eth-wire hard error
+ ctx.expect_gateway_up();
+ ctx.cluster_fork(10);
+ ctx.expect_wire_balance(before, false);
+ ctx.expect_gateway_down();
+
+ // Recover orphaned transaction
+ ctx.mine(6);
+ ctx.expect_wire_balance(after, false);
+ ctx.expect_gateway_up();
+ }
+
+ println!("Handle reorg outgoing transactions");
+ {
+ // Loose second node
+ ctx.cluster_deco();
+
+ // Perform debits
+ let before = ctx.client_balance();
+ let mut after = ctx.client_balance();
+ for n in 10..21 {
+ let amount = ctx.amount(n * 100);
+ ctx.debit(amount, rand_slice());
+ after += amount;
+ }
+ ctx.next_block();
+ ctx.expect_client_balance(after, true);
+
+ // Perform fork and check eth-wire still up
+ ctx.expect_gateway_up();
+ ctx.cluster_fork(10);
+ ctx.expect_client_balance(before, false);
+ ctx.expect_gateway_up();
+
+ // Recover orphaned transaction
+ ctx.next_conf();
+ ctx.expect_client_balance(after, false);
+ }
+
+ println!("Handle reorg bounce");
+ {
+ // Loose second node
+ ctx.cluster_deco();
+
+ // Perform bounce
+ let before = ctx.wire_balance();
+ let mut after = ctx.wire_balance();
+ for n in 10..21 {
+ ctx.malformed_credit(ctx.amount(n * 1000));
+ after += ctx.state.bounce_fee;
+ }
+ ctx.next_conf();
+ ctx.expect_wire_balance(after, true);
+
+ // Perform fork and check eth-wire hard error
+ ctx.expect_gateway_up();
+ ctx.cluster_fork(10);
+ ctx.expect_wire_balance(before, false);
+ ctx.expect_gateway_down();
+
+ // Recover orphaned transaction
+ ctx.mine(10);
+ sleep(Duration::from_secs(3));
+ ctx.next_block();
+ ctx.expect_wire_balance(after, false);
+ ctx.expect_gateway_up();
+ }
+}
+
+/// Test eth-wire correctness when a blockchain reorganization occurs leading to past incoming transaction conflict
+fn hell() {
+ fn step(name: &str, action: impl FnOnce(&mut EthCtx)) {
+ println!("Setup");
+ let mut ctx = EthCtx::setup("taler_eth.conf", false);
+ println!("{}", name);
+
+ // Loose second node
+ ctx.cluster_deco();
+
+ // Perform action
+ action(&mut ctx);
+
+ // Perform fork and check eth-wire hard error
+ ctx.expect_gateway_up();
+ ctx.cluster_fork(ctx.conf * 2);
+ ctx.expect_gateway_down();
+
+ // Generate conflict
+ ctx.restart_node(&["--miner.gasprice", "1000"]);
+ ctx.abandon();
+ let amount = ctx.amount(54000);
+ ctx.credit(amount, rand_slice());
+ ctx.expect_wire_balance(amount, true);
+
+ // Check eth-wire suspend operation
+ let bounce_amount = ctx.amount(34000);
+ ctx.malformed_credit(bounce_amount);
+ ctx.next_conf();
+ ctx.expect_wire_balance(amount + bounce_amount, true);
+ ctx.expect_gateway_down();
+ }
+
+ step("Handle reorg conflicting incoming credit", |ctx| {
+ let amount = ctx.amount(420000);
+ ctx.credit(amount, rand_slice());
+ ctx.next_conf();
+ ctx.expect_wire_balance(amount, true);
+ });
+
+ step("Handle reorg conflicting incoming bounce", |ctx| {
+ let amount = ctx.amount(420000);
+ ctx.malformed_credit(amount);
+ ctx.next_conf();
+ retry(|| ctx.wire_balance_pending() == ctx.state.bounce_fee);
+ });
+}
+
+/// Test eth-wire ability to learn and protect itself from blockchain behavior
+fn analysis() {
+ println!("Setup");
+ let mut ctx = EthCtx::setup("taler_eth.conf", false);
+
+ println!("Learn from reorg");
+
+ // Loose second node
+ ctx.cluster_deco();
+
+ // Perform credit
+ let before = ctx.wire_balance();
+ ctx.credit(ctx.amount(42000), rand_slice());
+ ctx.next_conf();
+ let after = ctx.wire_balance();
+
+ // Perform fork and check eth-wire hard error
+ ctx.expect_gateway_up();
+ ctx.cluster_fork(6);
+ ctx.expect_wire_balance(before, false);
+ ctx.expect_gateway_down();
+
+ // Recover orphaned transaction
+ ctx.mine(6);
+ ctx.expect_wire_balance(after, false);
+ ctx.expect_gateway_up();
+
+ // Loose second node
+ ctx.cluster_deco();
+
+ // Perform credit
+ let before = ctx.wire_balance();
+ ctx.credit(ctx.amount(42000), rand_slice());
+ ctx.next_conf();
+
+ // Perform fork and check eth-wire learned from previous attack
+ ctx.expect_gateway_up();
+ ctx.cluster_fork(5);
+ ctx.expect_wire_balance(before, false);
+ ctx.expect_gateway_up();
+}
+
+/// Test eth-wire ability to handle stuck transaction correctly
+fn bumpfee() {
+ println!("Setup");
+ let mut ctx = EthCtx::setup("taler_eth_bump.conf", false);
+
+ // Perform credits to allow wire to perform debits latter
+ ctx.credit(ctx.amount(90000000), rand_slice());
+ ctx.next_conf();
+
+ println!("Bump fee");
+ {
+ // Perform debit
+ let mut client = ctx.client_balance();
+ let wire = ctx.wire_balance();
+ let amount = ctx.amount(40000);
+ ctx.debit(amount, rand_slice());
+ retry(|| ctx.wire_balance_pending() < wire);
+
+ // Bump min relay fee making the previous debit stuck
+ ctx.restart_node(&["--miner.gasprice", "1000"]);
+
+ // Check bump happen
+ client += amount;
+ ctx.expect_client_balance(client, true);
+ }
+
+ println!("Bump fee reorg");
+ {
+ // Loose second node
+ ctx.cluster_deco();
+
+ // Perform debit
+ let mut client = ctx.client_balance();
+ let wire = ctx.wire_balance();
+ let amount = ctx.amount(40000);
+ ctx.debit(amount, rand_slice());
+ retry(|| ctx.wire_balance_pending() < wire);
+
+ // Bump min relay fee and fork making the previous debit stuck and problematic
+ ctx.cluster_fork(6);
+ ctx.restart_node(&["--miner.gasprice", "2000"]);
+
+ // Check bump happen
+ client += amount;
+ ctx.expect_client_balance(client, true);
+ }
+
+ println!("Setup");
+ drop(ctx);
+ let mut ctx = EthCtx::setup("taler_eth_bump.conf", true);
+
+ // Perform credit to allow wire to perform debits latter
+ ctx.credit(ctx.amount(9000000), rand_slice());
+ ctx.next_conf();
+
+ println!("Bump fee stress");
+ {
+ // Loose second node
+ ctx.cluster_deco();
+
+ // Perform debits
+ let client = ctx.client_balance();
+ let wire = ctx.wire_balance();
+ let mut total_amount = U256::zero();
+ for n in 10..31 {
+ let amount = ctx.amount(n * 10000);
+ total_amount += amount;
+ ctx.debit(amount, rand_slice());
+ }
+ retry(|| ctx.wire_balance_pending() < wire - total_amount);
+
+ // Bump min relay fee making the previous debits stuck
+ ctx.restart_node(&["--miner.gasprice", "1000"]);
+
+ // Check bump happen
+ ctx.expect_client_balance(client + total_amount, true);
+ }
+}
+
+/// Test eth-wire handle transaction fees exceeding limits
+fn maxfee() {
+ println!("Setup");
+ let mut ctx = EthCtx::setup("taler_eth.conf", false);
+
+ // Perform credit to allow wire to perform debits latter
+ ctx.credit(ctx.amount(9000000), rand_slice());
+ ctx.next_conf();
+
+ let client = ctx.client_balance();
+ let wire = ctx.wire_balance();
+ let mut total_amount = U256::zero();
+
+ println!("Too high fee");
+ {
+ // Change fee config
+ ctx.restart_node(&["--rpc.txfeecap", "0.00001"]);
+
+ // Perform debits
+ for n in 10..31 {
+ let amount = ctx.amount(n * 10000);
+ total_amount += amount;
+ ctx.debit(amount, rand_slice());
+ }
+ sleep(Duration::from_secs(3));
+
+ // Check no transaction happen
+ ctx.expect_wire_balance(wire, true);
+ ctx.expect_client_balance(client, true);
+ }
+
+ println!("Good feed");
+ {
+ // Restore default config
+ ctx.restart_node(&[""]);
+
+ // Check transaction now have been made
+ ctx.expect_client_balance(client + total_amount, true);
+ }
}
diff --git a/instrumentation/src/gateway.rs b/instrumentation/src/gateway.rs
@@ -0,0 +1,244 @@
+/*
+ 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::str::FromStr;
+
+use btc_wire::taler_utils::btc_payto_url;
+use common::{
+ api_common::{Amount as TalerAmount, Base32},
+ api_wire::TransferRequest,
+ rand_slice,
+ url::Url,
+};
+use libdeflater::{CompressionLvl, Compressor};
+use ureq::Response;
+
+use crate::{
+ btc::BtcCtx,
+ utils::{cmd_out, cmd_redirect_ok, gateway_error},
+};
+
+pub const TESTS: &[(fn(), &str)] = &[(api, "api"), (auth, "auth")];
+
+fn client_transfer(gateway_url: &str, payto_url: &str, amount: &str) -> String {
+ cmd_out(
+ "taler-exchange-wire-gateway-client",
+ &["-b", gateway_url, "-C", payto_url, "-a", amount],
+ )
+}
+
+fn http_code(response: Result<Response, ureq::Error>) -> u16 {
+ match response {
+ Ok(resp) => resp.status(),
+ Err(err) => match err {
+ ureq::Error::Status(err, _) => err,
+ ureq::Error::Transport(_) => unreachable!(),
+ },
+ }
+}
+
+/// Test wire-gateway conformance to documentation and its security
+fn api() {
+ println!("Setup");
+ let ctx = BtcCtx::setup("taler_btc.conf", false);
+
+ println!("Gateway API");
+ {
+ // Perform debits
+ let mut amounts = Vec::new();
+ for n in 1..10 {
+ let amount = format!("{}:0.000{}", ctx.taler_conf.currency.to_str(), n);
+ cmd_out(
+ "taler-exchange-wire-gateway-client",
+ &[
+ "-b",
+ &ctx.gateway_url,
+ "-D",
+ &btc_payto_url(&ctx.client_addr).to_string(),
+ "-a",
+ &amount,
+ ],
+ );
+ amounts.push(amount);
+ }
+
+ // Check history
+ let result = cmd_out(
+ "taler-exchange-wire-gateway-client",
+ &["-b", &ctx.gateway_url, "-i"],
+ );
+ for amount in &amounts {
+ assert!(result.contains(amount));
+ }
+
+ // Perform credits
+ let mut amounts = Vec::new();
+ for n in 1..10 {
+ let amount = format!("{}:0.0000{}", ctx.taler_conf.currency.to_str(), n);
+ client_transfer(
+ &ctx.gateway_url,
+ &btc_payto_url(&ctx.client_addr).to_string(),
+ &amount,
+ );
+ amounts.push(amount);
+ }
+
+ // Check history
+ let result = cmd_out(
+ "taler-exchange-wire-gateway-client",
+ &["-b", &ctx.gateway_url, "-o"],
+ );
+ for amount in &amounts {
+ assert!(result.contains(amount));
+ }
+ };
+
+ println!("Endpoint & Method");
+ {
+ // Unknown endpoint
+ gateway_error(&format!("{}test", ctx.gateway_url), 404);
+ // Method not allowed
+ gateway_error(&format!("{}transfer", ctx.gateway_url), 405);
+ }
+
+ let amount = &format!("{}:0.00042", ctx.taler_conf.currency.to_str());
+ let payto = btc_payto_url(&ctx.client_addr).to_string();
+
+ println!("Request format");
+ {
+ // Bad payto_url
+ for url in [
+ "http://bitcoin/$CLIENT",
+ "payto://btc/$CLIENT",
+ "payto://bitcoin/$CLIENT?id=admin",
+ "payto://bitcoin/$CLIENT#admin",
+ "payto://bitcoin/42$CLIENT",
+ ] {
+ let url = url.replace("$CLIENT", &ctx.client_addr.to_string());
+ let result = client_transfer(&ctx.gateway_url, &url, amount);
+ assert!(result.contains("(400/24)"));
+ }
+
+ // Bad transaction amount
+ let result = client_transfer(&ctx.gateway_url, &payto, "ATC:0.00042");
+ assert!(result.contains("(400/26)"));
+
+ // Bad history delta
+ for delta in [
+ "incoming",
+ "outgoing",
+ "incoming?delta=0",
+ "outgoing?delta=0;",
+ ] {
+ let code =
+ http_code(ureq::get(&format!("{}history/{}", ctx.gateway_url, delta)).call());
+ assert_eq!(code, 400);
+ }
+ }
+
+ println!("Transfer idempotence");
+ {
+ let mut request = TransferRequest {
+ request_uid: Base32::from(rand_slice()),
+ amount: TalerAmount::from_str(amount).unwrap(),
+ exchange_base_url: ctx.taler_conf.base_url(),
+ wtid: Base32::from(rand_slice()),
+ credit_account: Url::from_str(&payto).unwrap(),
+ };
+ // Same
+ assert_eq!(
+ http_code(ureq::post(&format!("{}transfer", ctx.gateway_url)).send_json(&request)),
+ 200
+ );
+ assert_eq!(
+ http_code(ureq::post(&format!("{}transfer", ctx.gateway_url)).send_json(&request)),
+ 200
+ );
+ // Collision
+ request.amount.fraction += 42;
+ assert_eq!(
+ http_code(ureq::post(&format!("{}transfer", ctx.gateway_url)).send_json(&request)),
+ 409
+ );
+ }
+
+ println!("Security");
+ {
+ let big_hello: String = (0..1000).map(|_| "Hello_world").collect();
+ // Huge body
+ assert_eq!(
+ http_code(ureq::post(&format!("{}transfer", ctx.gateway_url)).send_json(&big_hello)),
+ 400
+ );
+
+ // Body length liar
+ assert_eq!(
+ http_code(
+ ureq::post(&format!("{}transfer", ctx.gateway_url))
+ .set("Content-Length", "1024")
+ .send_json(&big_hello)
+ ),
+ 400
+ );
+
+ // Compression bomb
+ let mut compressor = Compressor::new(CompressionLvl::best());
+ let mut compressed = Vec::new();
+ compressed.resize(compressor.deflate_compress_bound(big_hello.len()), 0);
+ let size = compressor
+ .deflate_compress(big_hello.as_bytes(), &mut compressed)
+ .unwrap();
+ compressed.resize(size, 0);
+ assert_eq!(
+ http_code(
+ ureq::post(&format!("{}transfer", ctx.gateway_url))
+ .set("Content-Encoding", "deflate")
+ .send_bytes(&compressed)
+ ),
+ 400
+ );
+ }
+}
+
+/// Check btc-wire and wire-gateway correctly stop when a lifetime limit is configured
+fn auth() {
+ println!("Setup");
+ let ctx = BtcCtx::setup("taler_btc_auth.conf", false);
+
+ println!("Authentication");
+ // No auth
+ assert_eq!(
+ http_code(ureq::get(&format!("{}history.outgoing", ctx.gateway_url)).call()),
+ 401
+ );
+
+ // Auth
+ cmd_redirect_ok(
+ "taler-exchange-wire-gateway-client",
+ &[
+ "--config",
+ ctx.dirs.conf.to_str().unwrap(),
+ "-s",
+ "exchange-accountcredentials-admin",
+ "-C",
+ &btc_payto_url(&ctx.client_addr).to_string(),
+ "-a",
+ &format!("{}:0.00042", ctx.taler_conf.currency.to_str()),
+ ],
+ "log/client.log",
+ "",
+ );
+}
diff --git a/instrumentation/src/main.rs b/instrumentation/src/main.rs
@@ -14,92 +14,71 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-use std::{fmt::Display, io::Write, path::PathBuf};
+use std::{path::PathBuf, time::Instant};
use btc::btc_test;
use clap::StructOpt;
-use common::{
- api_common::{Amount, Base32},
- api_wire::{IncomingBankTransaction, IncomingHistory, OutgoingHistory, TransferRequest},
- config::TalerConfig,
- currency::Currency,
- rand_slice,
- url::Url,
-};
+use common::{config::TalerConfig, currency::Currency};
use eth::eth_test;
+use owo_colors::OwoColorize;
mod btc;
mod eth;
-
-fn print_now(disp: impl Display) {
- print!("{}", disp);
- std::io::stdout().flush().unwrap();
-}
-
-fn check_incoming(base_url: &str, reserve_pub_key: &[u8; 32], taler_amount: &Amount) {
- println!("Check incoming history");
- let history: IncomingHistory = ureq::get(&format!("{}/history/incoming", base_url))
- .query("delta", "-5")
- .call()
- .unwrap()
- .into_json()
- .unwrap();
- assert!(history.incoming_transactions.iter().any(|h| {
- matches!(
- h,
- IncomingBankTransaction::IncomingReserveTransaction {
- reserve_pub,
- amount,
- ..
- } if reserve_pub == &Base32::from(*reserve_pub_key) && amount == taler_amount
- )
- }));
-}
-
-fn transfer(base_url: &str, wtid: &[u8; 32], url: &Url, credit_account: Url, amount: &Amount) {
- println!("Get back some money");
- ureq::post(&format!("{}/transfer", base_url))
- .send_json(TransferRequest {
- request_uid: Base32::from(rand_slice()),
- amount: amount.clone(),
- exchange_base_url: url.clone(),
- wtid: Base32::from(*wtid),
- credit_account,
- })
- .unwrap();
-}
-
-fn check_outgoing(base_url: &str, wtid: &[u8; 32], url: &Url, amount: &Amount) {
- println!("Check outgoing history");
- let history: OutgoingHistory = ureq::get(&format!("{}/history/outgoing", base_url))
- .query("delta", "-5")
- .call()
- .unwrap()
- .into_json()
- .unwrap();
- assert!(history.outgoing_transactions.iter().any(|h| {
- h.wtid == Base32::from(*wtid) && &h.exchange_base_url == url && &h.amount == amount
- }));
-}
+mod gateway;
+mod utils;
/// Depolymerizer instrumentation test
#[derive(clap::Parser, Debug)]
struct Args {
- /// Override default configuration file path
- #[clap(global = true, short, long)]
- config: Option<PathBuf>,
+ #[clap(subcommand)]
+ cmd: Cmd,
+}
+
+#[derive(clap::Subcommand, Debug)]
+enum Cmd {
+ /// Perform online tests on running blockchain
+ Online {
+ /// Override default configuration file path
+ #[clap(global = true, short, long)]
+ config: Option<PathBuf>,
+ },
+ /// Perform offline tests on local private blockchain
+ Offline,
}
pub fn main() {
common::log::init();
+ color_backtrace::install();
+
let args = Args::parse();
- let taler_config = TalerConfig::load(args.config.as_deref());
- let base_url = format!("http://localhost:{}", taler_config.port());
+ match args.cmd {
+ Cmd::Online { config } => {
+ let taler_config = TalerConfig::load(config.as_deref());
+ let base_url = format!("http://localhost:{}", taler_config.port());
- match taler_config.currency {
- Currency::BTC(_) => btc_test(args.config.as_deref(), &base_url),
- Currency::ETH(_) => eth_test(args.config.as_deref(), &base_url),
+ match taler_config.currency {
+ Currency::BTC(_) => btc_test(config.as_deref(), &base_url),
+ Currency::ETH(_) => eth_test(config.as_deref(), &base_url),
+ }
+ println!("Instrumentation test successful");
+ }
+ Cmd::Offline => {
+ let mut tests = Vec::new();
+ tests.extend_from_slice(gateway::TESTS);
+ tests.extend_from_slice(btc::TESTS);
+ tests.extend_from_slice(eth::TESTS);
+ let start = Instant::now();
+ for (test, name) in &tests {
+ let start = Instant::now();
+ println!("{}", name.magenta());
+ test();
+ println!(
+ "{} {}",
+ "OK".green(),
+ format_args!("{:?}", start.elapsed()).bright_black()
+ );
+ }
+ println!("{} tests in {:?}", tests.len(), start.elapsed());
+ }
}
-
- println!("Instrumentation test successful");
}
diff --git a/instrumentation/src/utils.rs b/instrumentation/src/utils.rs
@@ -0,0 +1,444 @@
+/*
+ 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::{
+ fmt::Display,
+ fmt::Write as _,
+ io::Write as _,
+ path::{Path, PathBuf},
+ process::{Child, Command, Stdio},
+ str::FromStr,
+ thread::sleep,
+ time::{Duration, Instant},
+};
+
+use common::{
+ api_common::{Amount, Base32},
+ api_wire::{IncomingBankTransaction, IncomingHistory, OutgoingHistory, TransferRequest},
+ config::TalerConfig,
+ rand_slice,
+ url::Url,
+};
+use tempfile::TempDir;
+
+pub fn print_now(disp: impl Display) {
+ print!("{}", disp);
+ std::io::stdout().flush().unwrap();
+}
+
+#[must_use]
+pub fn check_incoming(base_url: &str, txs: &[([u8; 32], Amount)]) -> bool {
+ let history: IncomingHistory = ureq::get(&format!("{}history/incoming", base_url))
+ .query("delta", &format!("-{}", txs.len()))
+ .call()
+ .unwrap()
+ .into_json()
+ .unwrap();
+
+ history.incoming_transactions.len() == txs.len()
+ && txs.iter().all(|(reserve_pub_key, taler_amount)| {
+ history.incoming_transactions.iter().any(|h| {
+ matches!(
+ h,
+ IncomingBankTransaction::IncomingReserveTransaction {
+ reserve_pub,
+ amount,
+ ..
+ } if reserve_pub == &Base32::from(*reserve_pub_key) && amount == taler_amount
+ )
+ })
+ })
+}
+
+pub fn gateway_error(path: &str, error: u16) {
+ let err = ureq::get(path).call().unwrap_err();
+ match err {
+ ureq::Error::Status(nb, _) => assert_eq!(nb, error),
+ ureq::Error::Transport(_) => unreachable!(),
+ }
+}
+
+#[must_use]
+pub fn check_gateway_error(base_url: &str) -> bool {
+ matches!(
+ ureq::get(&format!("{}history/incoming", base_url))
+ .query("delta", "-5")
+ .call(),
+ Err(ureq::Error::Status(504, _))
+ )
+}
+
+#[must_use]
+pub fn check_gateway_down(base_url: &str) -> bool {
+ matches!(
+ ureq::get(&format!("{}history/incoming", base_url))
+ .query("delta", "-5")
+ .call(),
+ Err(ureq::Error::Status(502, _))
+ )
+}
+
+#[must_use]
+pub fn check_gateway_up(base_url: &str) -> bool {
+ ureq::get(&format!("{}history/incoming", base_url))
+ .query("delta", "-5")
+ .call()
+ .is_ok()
+}
+
+pub fn transfer(base_url: &str, wtid: &[u8; 32], url: &Url, credit_account: Url, amount: &Amount) {
+ ureq::post(&format!("{}transfer", base_url))
+ .send_json(TransferRequest {
+ request_uid: Base32::from(rand_slice()),
+ amount: amount.clone(),
+ exchange_base_url: url.clone(),
+ wtid: Base32::from(*wtid),
+ credit_account,
+ })
+ .unwrap();
+}
+
+#[must_use]
+pub fn check_outgoing(base_url: &str, url: &Url, txs: &[([u8; 32], Amount)]) -> bool {
+ let history: OutgoingHistory = ureq::get(&format!("{}history/outgoing", base_url))
+ .query("delta", &format!("-{}", txs.len()))
+ .call()
+ .unwrap()
+ .into_json()
+ .unwrap();
+ history.outgoing_transactions.len() == txs.len()
+ && txs.iter().all(|(wtid, amount)| {
+ history.outgoing_transactions.iter().any(|h| {
+ h.wtid == Base32::from(*wtid) && &h.exchange_base_url == url && &h.amount == amount
+ })
+ })
+}
+
+pub struct Dirs {
+ pub dir: TempDir,
+ pub wire_dir: PathBuf,
+ pub wire_dir2: PathBuf,
+ pub db_dir: PathBuf,
+ pub conf: PathBuf,
+}
+
+impl Dirs {
+ pub fn generate() -> Self {
+ let dir = TempDir::new().unwrap();
+ let wire_dir = dir.path().join("wire");
+ let wire_dir2 = dir.path().join("wire2");
+ let db_dir = dir.path().join("db");
+ for dir in [&wire_dir, &wire_dir2, &db_dir] {
+ std::fs::create_dir_all(dir).unwrap();
+ }
+ let conf = dir.path().join("taler.conf");
+ Self {
+ wire_dir,
+ wire_dir2,
+ db_dir,
+ conf,
+ dir,
+ }
+ }
+}
+
+pub struct ChildGuard(pub Child);
+
+impl Drop for ChildGuard {
+ fn drop(&mut self) {
+ self.0.kill().ok();
+ }
+}
+
+pub fn cmd_out(cmd: &str, args: &[&str]) -> String {
+ let output = Command::new(cmd)
+ .args(args)
+ .stdin(Stdio::null())
+ .output()
+ .unwrap();
+ if output.stdout.is_empty() {
+ String::from_utf8(output.stderr).unwrap()
+ } else {
+ String::from_utf8(output.stdout).unwrap()
+ }
+}
+
+pub fn try_cmd_redirect(cmd: &str, args: &[&str], path: &str) -> std::io::Result<ChildGuard> {
+ let log_file = std::fs::OpenOptions::new()
+ .create(true)
+ .append(true)
+ .open(path)?;
+
+ let child = Command::new(cmd)
+ .args(args)
+ .stderr(log_file.try_clone()?)
+ .stdout(log_file)
+ .stdin(Stdio::null())
+ .spawn()?;
+ Ok(ChildGuard(child))
+}
+
+pub fn cmd_redirect(cmd: &str, args: &[&str], path: &str) -> ChildGuard {
+ try_cmd_redirect(cmd, args, path).unwrap()
+}
+
+pub fn cmd_ok(mut child: ChildGuard, name: &str) {
+ let result = child.0.wait().unwrap();
+ if !result.success() {
+ panic!("cmd {name} failed");
+ }
+}
+
+pub fn cmd_redirect_ok(cmd: &str, args: &[&str], path: &str, name: &str) {
+ cmd_ok(cmd_redirect(cmd, args, path), name)
+}
+
+pub fn retry_opt<T>(mut lambda: impl FnMut() -> Option<T>) -> T {
+ let start = Instant::now();
+ loop {
+ let result = lambda();
+ if result.is_none() && start.elapsed() < Duration::from_secs(20) {
+ sleep(Duration::from_millis(500));
+ } else {
+ return result.unwrap();
+ }
+ }
+}
+
+pub fn retry(mut lambda: impl FnMut() -> bool) {
+ retry_opt(|| lambda().then(|| ()))
+}
+
+pub struct CommonCtx {
+ pub dirs: Dirs,
+ gateway: ChildGuard,
+ pub gateway_url: String,
+ pub taler_conf: TalerConfig,
+ wire: ChildGuard,
+ _wire2: Option<ChildGuard>,
+}
+
+impl CommonCtx {
+ pub fn setup_dirs(config: &str) -> Dirs {
+ // Setup logs
+ {
+ // Create log dir if no exist
+ std::fs::create_dir_all("log").unwrap();
+ // Clear all previous logs file
+ for file in std::fs::read_dir("log").unwrap() {
+ std::fs::write(file.unwrap().path(), "").unwrap();
+ }
+ }
+ // Generate temporary dirs
+ let dirs = Dirs::generate();
+ // Prepare config
+ {
+ // Generate taler config from base
+ let config = PathBuf::from_str("instrumentation/conf")
+ .unwrap()
+ .join(config);
+ let mut config = std::fs::read_to_string(config).unwrap();
+ write!(
+ &mut config,
+ "\nCONF_PATH = {}\nIPC_PATH={}",
+ dirs.wire_dir.to_string_lossy(),
+ dirs.wire_dir.to_string_lossy()
+ )
+ .unwrap();
+ std::fs::write(&dirs.conf, config).unwrap();
+ }
+
+ // Generate password
+ let pwd: String = (0..30).map(|_| fastrand::alphanumeric()).collect();
+ std::env::set_var("PASSWORD", &pwd);
+
+ dirs
+ }
+
+ pub fn setup(dirs: Dirs, name: &str, stressed: bool, init_wallet: impl FnOnce(&Dirs)) -> Self {
+ let taler_conf = TalerConfig::load(Some(&dirs.conf));
+ // Setup database
+ {
+ // Init databases files
+ cmd_redirect_ok(
+ "pg_ctl",
+ &["init", "-D", dirs.db_dir.to_string_lossy().as_ref()],
+ "log/postgres.log",
+ "init_db",
+ );
+ // Generate database config
+ std::fs::write(
+ dirs.db_dir.join("postgresql.conf"),
+ format!(
+ "port=5454\nunix_socket_directories='{}'\n",
+ dirs.db_dir.to_string_lossy().as_ref()
+ ),
+ )
+ .unwrap();
+ Self::_start_db(&dirs.db_dir);
+
+ let mut psql = ChildGuard(
+ Command::new("psql")
+ .args(["-h", "localhost", "-p", "5454", "postgres"])
+ .stderr(Stdio::null())
+ .stdout(Stdio::null())
+ .stdin(Stdio::piped())
+ .spawn()
+ .unwrap(),
+ );
+ psql.0
+ .stdin
+ .as_mut()
+ .unwrap()
+ .write_all("CREATE ROLE postgres LOGIN SUPERUSER PASSWORD 'password'".as_bytes())
+ .unwrap();
+ cmd_ok(psql, "psql setup");
+ }
+
+ // Wire
+ let wire_path = format!("target/release/{}", name);
+ let mut args = vec!["build", "--bin", name, "--release"];
+ if stressed {
+ args.extend_from_slice(&["--features", "fail"]);
+ }
+ cmd_redirect_ok("cargo", &args, "log/cargo.log", "build wire");
+ cmd_redirect_ok(
+ &wire_path,
+ &["-c", dirs.conf.to_string_lossy().as_ref(), "initdb"],
+ "log/cmd.log",
+ "wire initdb",
+ );
+
+ init_wallet(&dirs);
+
+ // Start wires
+ let wire = cmd_redirect(
+ &wire_path,
+ &["-c", dirs.conf.to_string_lossy().as_ref()],
+ "log/wire.log",
+ );
+ let wire2 = stressed.then(|| {
+ cmd_redirect(
+ &wire_path,
+ &["-c", dirs.conf.to_string_lossy().as_ref()],
+ "log/wire2.log",
+ )
+ });
+
+ // Gateway
+ cmd_redirect_ok(
+ "cargo",
+ &[
+ "build",
+ "--bin",
+ "wire-gateway",
+ "--release",
+ "--features",
+ "test",
+ ],
+ "log/cargo.log",
+ "build gateway",
+ );
+ let gateway = cmd_redirect(
+ "target/release/wire-gateway",
+ &["-c", dirs.conf.to_string_lossy().as_ref()],
+ "log/gateway.log",
+ );
+
+ let gateway_url = format!("http://localhost:{}/", taler_conf.port());
+
+ Self {
+ dirs,
+ gateway,
+ gateway_url,
+ taler_conf,
+ wire,
+ _wire2: wire2,
+ }
+ }
+
+ /* ----- Process ----- */
+
+ #[must_use]
+ pub fn wire_running(&mut self) -> bool {
+ self.wire.0.try_wait().unwrap().is_none()
+ }
+
+ #[must_use]
+ pub fn gateway_running(&mut self) -> bool {
+ self.gateway.0.try_wait().unwrap().is_none()
+ }
+
+ /* ----- Database ----- */
+
+ fn _start_db(path: &Path) {
+ cmd_redirect_ok(
+ "pg_ctl",
+ &["start", "-D", path.to_string_lossy().as_ref()],
+ "log/postgres.log",
+ "start db",
+ )
+ }
+
+ fn _stop_db(path: &Path, log: &Path) -> std::io::Result<ChildGuard> {
+ try_cmd_redirect(
+ "pg_ctl",
+ &["stop", "-D", path.to_string_lossy().as_ref()],
+ log.to_string_lossy().as_ref(),
+ )
+ }
+
+ pub fn resume_db(&self) {
+ Self::_start_db(&self.dirs.db_dir)
+ }
+
+ pub fn stop_db(&self) {
+ cmd_ok(
+ Self::_stop_db(&self.dirs.db_dir, "log/postgres.log".as_ref()).unwrap(),
+ "stop db",
+ )
+ }
+
+ /* ----- Wire Gateway -----*/
+
+ pub fn expect_credits(&self, txs: &[([u8; 32], Amount)]) {
+ retry(|| check_incoming(&self.gateway_url, txs))
+ }
+
+ pub fn expect_debits(&self, base_url: &Url, txs: &[([u8; 32], Amount)]) {
+ retry(|| check_outgoing(&self.gateway_url, base_url, txs))
+ }
+
+ pub fn expect_error(&self) {
+ retry(|| check_gateway_error(&self.gateway_url));
+ }
+
+ pub fn expect_gateway_up(&self) {
+ retry(|| check_gateway_up(&self.gateway_url));
+ }
+
+ pub fn expect_gateway_down(&self) {
+ retry(|| check_gateway_down(&self.gateway_url));
+ }
+}
+
+impl Drop for CommonCtx {
+ fn drop(&mut self) {
+ Self::_stop_db(&self.dirs.db_dir, "/dev/null".as_ref())
+ .and_then(|mut it| it.0.wait())
+ .ok();
+ }
+}
diff --git a/makefile b/makefile
@@ -3,40 +3,5 @@ install:
cargo install --path eth-wire --bin eth-wire
cargo install --path wire-gateway
-install_test: install
- cargo install --path btc-wire --bin btc-wire-utils
- cargo install --path eth-wire --bin eth-wire-utils
- cargo install --path instrumentation
-
-test_gateway: install_test
- test/gateway/api.sh
- test/gateway/auth.sh
-
-test_btc: install_test
- test/btc/wire.sh
- test/btc/lifetime.sh
- test/btc/reconnect.sh
- test/btc/stress.sh
- test/btc/conflict.sh
- test/btc/reorg.sh
- test/btc/hell.sh
- test/btc/analysis.sh
- test/btc/bumpfee.sh
- test/btc/maxfee.sh
- test/btc/config.sh
-
-test_eth: install_test
- test/eth/wire.sh
- test/eth/lifetime.sh
- test/eth/reconnect.sh
- test/eth/stress.sh
- test/eth/reorg.sh
- test/eth/hell.sh
- test/eth/analysis.sh
- test/eth/bumpfee.sh
- test/eth/maxfee.sh
-
-test: test_gateway test_eth test_btc
-
segwit_demo:
cargo run --release --bin segwit-demo
\ No newline at end of file
diff --git a/script/prepare.sh b/script/prepare.sh
@@ -22,7 +22,7 @@ echo "Found version $PG_VER"
echo "â…¡ - Install bitcoind version 0.22"
cd $DIR
-curl -L https://bitcoin.org/bin/bitcoin-core-22.0/bitcoin-22.0-x86_64-linux-gnu.tar.gz -o btc.tar.gz
+curl -L https://bitcoincore.org/bin/bitcoin-core-22.0/bitcoin-22.0-x86_64-linux-gnu.tar.gz -o btc.tar.gz
rm -rfv ~/bitcoin
mkdir -pv ~/bitcoin
tar xvzf btc.tar.gz
diff --git a/test/btc/analysis.sh b/test/btc/analysis.sh
@@ -1,63 +0,0 @@
-#!/bin/bash
-
-## Test btc-wire ability to learn and protect itself from blockchain behavior
-
-set -eu
-
-source "${BASH_SOURCE%/*}/../common.sh"
-CONFIG=taler_btc.conf
-
-echo "----- Setup -----"
-echo "Load config file"
-load_config
-echo "Start bitcoin node"
-init_btc
-echo "Start second bitcoin node"
-init_btc2
-echo "Start btc-wire"
-btc_wire
-echo "Start gateway"
-gateway
-echo ""
-
-echo "----- Learn from reorg -----"
-
-echo "Loose second bitcoin node"
-btc_deco
-
-echo -n "Making wire transfer to exchange:"
-$WIRE_UTILS transfer 0.042 > /dev/null
-next_btc # Trigger btc-wire
-check_balance 9.95799209 0.04200000
-echo " OK"
-
-echo -n "Perform fork and check btc-wire hard error:"
-gateway_up
-btc_fork 5
-check_balance 9.95799209 0.00000000
-gateway_down
-echo " OK"
-
-echo -n "Recover orphaned transactions:"
-next_btc 5 # More block needed to confirm
-check_balance 9.95799209 0.04200000
-gateway_up
-echo " OK"
-
-echo "Loose second bitcoin node"
-btc_deco
-
-echo -n "Making wire transfer to exchange:"
-$WIRE_UTILS transfer 0.064 > /dev/null
-next_btc 5 # More block needed to confirm
-check_balance 9.89398418 0.10600000
-echo " OK"
-
-echo -n "Perform fork and check btc-wire learned from previous attack:"
-gateway_up
-btc_fork 5
-check_balance 9.89398418 0.10600000
-gateway_up
-echo " OK"
-
-echo "All tests passed!"
-\ No newline at end of file
diff --git a/test/btc/bumpfee.sh b/test/btc/bumpfee.sh
@@ -1,112 +0,0 @@
-#!/bin/bash
-
-## Test btc-wire ability to handle stuck transaction correctly
-
-set -eu
-
-source "${BASH_SOURCE%/*}/../common.sh"
-CONFIG=taler_btc_bump.conf
-
-echo "----- Setup -----"
-echo "Load config file"
-load_config
-echo "Start bitcoin node"
-init_btc
-echo "Start second bitcoin node"
-init_btc2
-echo "Start btc-wire"
-btc_wire
-echo "Start gateway"
-gateway
-echo ""
-
-SEQ="seq 10 30"
-
-echo -n "Making wire transfer to exchange:"
-for n in `$SEQ`; do
- $WIRE_UTILS transfer 0.$n > /dev/null
- mine_btc # Mine transactions
-done
-next_btc # Trigger btc-wire
-check_balance 5.79983389 4.20000000
-echo " OK"
-
-echo "----- Bump fee -----"
-
-echo -n "Making wire transfer from exchange:"
-taler-exchange-wire-gateway-client \
- -b $BANK_ENDPOINT \
- -C payto://bitcoin/$CLIENT \
- -a $CURRENCY:0.004 > /dev/null
-sleep 1
-check_balance 5.79983389 4.19599801
-echo " OK"
-
-echo -n "Bump relay fee:"
-restart_btc -minrelaytxfee=0.0001
-echo " OK"
-
-echo -n "Check bump:"
-sleep 5
-mine_btc
-sleep 1
-check_balance 5.80383389 4.19598010
-echo " OK"
-
-echo "----- Bump fee reorg -----"
-
-echo "Loose second bitcoin node"
-btc_deco
-
-echo -n "Making wire transfer from exchange:"
-taler-exchange-wire-gateway-client \
- -b $BANK_ENDPOINT \
- -C payto://bitcoin/$CLIENT \
- -a $CURRENCY:0.004 > /dev/null
-sleep 1
-check_balance 5.80383389 4.19196020
-echo " OK"
-
-echo -n "Perform fork and bump relay fee:"
-btc_fork 6
-restart_btc -minrelaytxfee=0.0002
-mine_btc
-echo " OK"
-
-echo -n "Check bump:"
-sleep 5
-next_btc
-sleep 1
-check_balance 5.80783389 4.19194030
-echo " OK"
-
-echo "----- Bump fee stress -----"
-
-echo -n "Replace btc-wire with stressed btc-wire"
-kill $WIRE_PID
-stress_btc_wire
-echo " OK"
-
-echo -n "Making wire transfer from exchange:"
-for n in `$SEQ`; do
- taler-exchange-wire-gateway-client \
- -b $BANK_ENDPOINT \
- -C payto://bitcoin/$CLIENT \
- -a $CURRENCY:0.00$n > /dev/null
-done
-sleep 5
-echo " OK"
-
-echo -n "Bump relay fee:"
-restart_btc -minrelaytxfee=0.0003
-echo " OK"
-
-echo -n "Check bump:"
-for n in `seq 0 4`; do
- sleep 2
- mine_btc
-done
-check_balance 5.84983389 4.14868660
-echo " OK"
-
-echo "All tests passed!"
-\ No newline at end of file
diff --git a/test/btc/config.sh b/test/btc/config.sh
@@ -1,27 +0,0 @@
-#!/bin/bash
-
-## Test btc-wire ability to configure itself from bitcoin configuration
-
-set -eu
-
-CONFIG=taler_btc.conf
-
-function test() {
- echo "----- Config $1 -----"
-
- BTC_CONFIG=$1
- source "${BASH_SOURCE%/*}/../common.sh"
-
- echo "Load config file"
- load_config
- echo "Start bitcoin node"
- init_btc
- echo "Cleanup"
- cleanup
-}
-
-for n in `seq 0 5`; do
- test "bitcoin_auth$n.conf"
-done
-
-echo "All tests passed!"
-\ No newline at end of file
diff --git a/test/btc/conflict.sh b/test/btc/conflict.sh
@@ -1,110 +0,0 @@
-#!/bin/bash
-
-## Test btc-wire ability to handle conflicting outgoing transactions
-
-set -eu
-
-source "${BASH_SOURCE%/*}/../common.sh"
-CONFIG=taler_btc.conf
-
-echo "----- Setup -----"
-echo "Load config file"
-load_config
-echo "Start bitcoin node"
-init_btc
-echo "Start second bitcoin node"
-init_btc2
-echo "Start btc-wire"
-btc_wire
-echo "Start gateway"
-gateway
-echo ""
-
-echo "----- Conflict send -----"
-
-echo -n "Making wire transfer to exchange:"
-$WIRE_UTILS transfer 0.042 > /dev/null
-next_btc
-check_balance 9.95799209 0.04200000
-echo " OK"
-
-echo -n "Making wire transfer from exchange:"
-taler-exchange-wire-gateway-client \
- -b $BANK_ENDPOINT \
- -C payto://bitcoin/$CLIENT \
- -a $CURRENCY:0.004 > /dev/null
-sleep 1
-check_balance 9.95799209 0.03799801
-echo " OK"
-
-echo -n "Abandon pending transaction:"
-restart_btc -minrelaytxfee=0.0001
-$WIRE_UTILS abandon
-check_balance 9.95799209 0.04200000
-echo " OK"
-
-echo -n "Generate conflict:"
-taler-exchange-wire-gateway-client \
- -b $BANK_ENDPOINT \
- -C payto://bitcoin/$CLIENT \
- -a $CURRENCY:0.005 > /dev/null
-sleep 5
-restart_btc
-mine_btc
-check_balance 9.96299209 0.03698010
-echo " OK"
-
-echo -n "Resend conflicting transaction:"
-sleep 5 # Wait for reconnection
-mine_btc
-check_delta "outgoing?delta=-100" "seq 4 5" "0.00"
-check_balance 9.96699209 0.03297811
-echo " OK"
-
-echo "----- Reset -----"
-echo "Cleanup"
-cleanup
-source "${BASH_SOURCE%/*}/../common.sh"
-echo "Load config file"
-load_config
-echo "Start bitcoin node"
-init_btc
-echo "Start second bitcoin node"
-init_btc2
-echo "Start btc-wire"
-btc_wire
-echo "Start gateway"
-gateway
-echo ""
-
-echo "----- Conflict bounce -----"
-
-echo -n "Bounce:"
-$BTC_CLI -rpcwallet=client sendtoaddress $WIRE 0.04 > /dev/null
-mine_btc $CONFIRMATION
-sleep 1
-check_balance 9.95999859 0.00001000
-echo " OK"
-
-echo -n "Abandon pending transaction:"
-restart_btc -minrelaytxfee=0.0001
-$WIRE_UTILS abandon
-check_balance 9.95999859 0.04000000
-echo " OK"
-
-echo -n "Generate conflict:"
-$BTC_CLI -rpcwallet=client sendtoaddress $WIRE 0.05 > /dev/null
-mine_btc $CONFIRMATION
-sleep 1
-restart_btc
-mine_btc
-check_balance 9.94997264 0.05001000
-echo " OK"
-
-echo -n "Resend conflicting transaction:"
-sleep 5 # Wait for reconnection
-mine_btc
-check_balance 9.99996079 0.00002000
-echo " OK"
-
-echo "All tests passed!"
-\ No newline at end of file
diff --git a/test/btc/hell.sh b/test/btc/hell.sh
@@ -1,112 +0,0 @@
-#!/bin/bash
-
-## Test btc-wire correctness when a blockchain reorganization occurs leading to past incoming transaction conflict
-
-set -eu
-
-source "${BASH_SOURCE%/*}/../common.sh"
-CONFIG=taler_btc.conf
-
-echo "----- Setup -----"
-echo "Load config file"
-load_config
-echo "Start bitcoin node"
-init_btc
-echo "Start second bitcoin node"
-init_btc2
-echo "Start btc-wire"
-btc_wire
-echo "Start gateway"
-gateway
-echo ""
-
-echo "----- Handle reorg conflicting incoming credit -----"
-
-echo "Loose second bitcoin node"
-btc_deco
-
-echo -n "Gen incoming transactions:"
-$WIRE_UTILS transfer 0.0042 > /dev/null
-next_btc # Trigger btc-wire
-check_balance 9.99579209 0.00420000
-echo " OK"
-
-echo -n "Perform fork and check btc-wire hard error:"
-gateway_up
-btc_fork 5
-check_balance 9.99579209 0.00000000
-gateway_down
-echo " OK"
-
-echo -n "Generate conflict:"
-restart_btc -minrelaytxfee=0.0001
-$WIRE_UTILS abandon client
-$WIRE_UTILS transfer 0.0054 > /dev/null
-mine_btc
-check_balance 9.99457382 0.00540000
-echo " OK"
-
-echo -n "Check btc-wire suspend function:"
-sleep 5 # Wait for reconnection
-$BTC_CLI -rpcwallet=client sendtoaddress $WIRE 0.0042 > /dev/null
-next_btc
-sleep 1
-gateway_down
-check_balance 9.99035972 0.00960000 # Not bounced
-echo " OK"
-
-# Recover by paying for the customer ?
-
-echo "----- Reset -----"
-echo "Cleanup"
-cleanup
-source "${BASH_SOURCE%/*}/../common.sh"
-echo "Load config file"
-load_config
-echo "Start bitcoin node"
-init_btc
-echo "Start second bitcoin node"
-init_btc2
-echo "Start btc-wire"
-btc_wire
-echo "Start gateway"
-gateway
-echo ""
-
-echo "----- Handle reorg conflicting incoming bounce -----"
-
-echo "Loose second bitcoin node"
-btc_deco
-
-echo -n "Generate bounce:"
-$BTC_CLI -rpcwallet=client sendtoaddress $WIRE 0.042 > /dev/null
-next_btc
-check_balance 9.99998674 0.00001000
-echo " OK"
-
-echo -n "Perform fork and check btc-wire hard error:"
-gateway_up
-btc_fork 5
-check_balance 9.95799859 0.00000000
-gateway_down
-echo " OK"
-
-echo -n "Generate conflict:"
-restart_btc -minrelaytxfee=0.0001
-$WIRE_UTILS abandon client
-$WIRE_UTILS transfer 0.054 > /dev/null
-mine_btc
-check_balance 9.94597382 0.05400000
-echo " OK"
-
-echo -n "Check btc-wire suspend function:"
-sleep 5 # Wait for reconnection
-$BTC_CLI -rpcwallet=client sendtoaddress $WIRE 0.0042 > /dev/null
-next_btc
-sleep 1
-gateway_down
-check_balance 9.94175972 0.05820000 # Not bounced
-echo " OK"
-
-
-echo "All tests passed!"
-\ No newline at end of file
diff --git a/test/btc/lifetime.sh b/test/btc/lifetime.sh
@@ -1,51 +0,0 @@
-#!/bin/bash
-
-## Check btc-wire and wire-gateway correctly stop when a lifetime limit is configured
-
-set -eu
-
-source "${BASH_SOURCE%/*}/../common.sh"
-CONFIG=taler_btc_lifetime.conf
-
-
-echo "----- Setup -----"
-echo "Load config file"
-load_config
-echo "Start bitcoin node"
-init_btc
-echo "Start btc-wire"
-btc_wire
-echo "Start gateway"
-gateway
-echo ""
-
-SEQ="seq 10 20"
-
-echo "---- Check lifetime -----"
-
-echo -n "Check up:"
-check_up $WIRE_PID btc-wire
-check_up $GATEWAY_PID wire-gateway
-echo " OK"
-
-echo -n "Do some work:"
-for n in `$SEQ`; do
- $WIRE_UTILS transfer 0.000$n > /dev/null
- mine_btc # Mine transactions
-done
-next_btc # Trigger btc-wire
-check_balance 9.99826299 0.00165000
-for n in `$SEQ`; do
- taler-exchange-wire-gateway-client \
- -b $BANK_ENDPOINT \
- -C payto://bitcoin/$CLIENT \
- -a $CURRENCY:0.0000$n &> /dev/null || break;
-done
-echo " OK"
-
-echo -n "Check down:"
-check_down $WIRE_PID btc-wire
-check_down $GATEWAY_PID wire-gateway
-echo " OK"
-
-echo "All tests passed!"
diff --git a/test/btc/maxfee.sh b/test/btc/maxfee.sh
@@ -1,64 +0,0 @@
-#!/bin/bash
-
-## Test btc-wire handle transaction fees exceeding limits
-
-set -eu
-
-source "${BASH_SOURCE%/*}/../common.sh"
-CONFIG=taler_btc.conf
-
-echo "----- Setup -----"
-echo "Load config file"
-load_config
-echo "Start bitcoin node"
-init_btc
-echo "Start btc-wire"
-btc_wire
-echo "Start gateway"
-gateway
-echo ""
-
-SEQ="seq 10 30"
-
-echo -n "Making wire transfer to exchange: "
-for n in `$SEQ`; do
- $WIRE_UTILS transfer 0.$n > /dev/null
- mine_btc # Mine transactions
-done
-next_btc # Trigger btc-wire
-check_balance 5.79983389 4.20000000
-echo "OK"
-
-echo "----- Too high fees -----"
-
-echo -n "Set up node: "
-restart_btc -maxtxfee=0.0000001 -minrelaytxfee=0.0000001
-echo "OK"
-
-echo -n "Making wire transfer from exchange: "
-for n in `$SEQ`; do
- taler-exchange-wire-gateway-client \
- -b $BANK_ENDPOINT \
- -C payto://bitcoin/$CLIENT \
- -a $CURRENCY:0.0$n > /dev/null
-done
-sleep 5
-mine_btc
-echo "OK"
-
-echo -n "Check no transaction have been made: "
-check_balance 5.79983389 4.20000000
-echo "OK"
-
-echo "----- Good fees -----"
-
-echo -n "Set up node: "
-restart_btc
-echo "OK"
-
-echo -n "Check transaction have been made: "
-sleep 6
-check_balance 5.79983389 3.77995821
-echo "OK"
-
-echo "All tests passed!"
-\ No newline at end of file
diff --git a/test/btc/reconnect.sh b/test/btc/reconnect.sh
@@ -1,86 +0,0 @@
-#!/bin/bash
-
-## Check the capacity of wire-gateway and btc-wire to recover from database and node loss
-
-set -eu
-
-source "${BASH_SOURCE%/*}/../common.sh"
-CONFIG=taler_btc.conf
-
-echo "----- Setup -----"
-echo "Load config file"
-load_config
-echo "Start bitcoin node"
-init_btc
-echo "Start btc-wire"
-btc_wire
-echo "Start gateway"
-gateway
-echo ""
-
-echo "----- With DB -----"
-echo "Making wire transfer to exchange:"
-$WIRE_UTILS transfer 0.000042 > /dev/null
-next_btc
-check_balance 9.99995009 0.00004200
-echo -n "Requesting exchange incoming transaction list:"
-check_delta "incoming?delta=-100" "echo 42"
-echo "OK"
-
-echo "----- Without DB -----"
-
-echo "Stop database"
-stop_db
-echo "Making incomplete wire transfer to exchange"
-$BTC_CLI -rpcwallet=client sendtoaddress $WIRE 0.00042 &> /dev/null
-echo -n "Making wire transfer to exchange:"
-$WIRE_UTILS transfer 0.00004 > /dev/null
-next_btc
-check_balance 9.99948077 0.00050200
-echo " OK"
-echo "Stop bitcoin node"
-stop_node
-echo -n "Requesting exchange incoming transaction list:"
-taler-exchange-wire-gateway-client -b $BANK_ENDPOINT -i 2>&1 | grep -q "504" && echo " OK" || echo " Failed"
-
-echo "----- Reconnect DB -----"
-
-echo "Start database"
-start_db
-echo "Resume bitcoin node"
-resume_btc
-sleep 6 # Wait for connection to be available
-echo -n "Making wire transfer from exchange:"
-taler-exchange-wire-gateway-client \
- -b $BANK_ENDPOINT \
- -C payto://bitcoin/$CLIENT \
- -a $CURRENCY:0.00002 > /dev/null
-sleep 1
-mine_btc
-check_balance 9.99990892 0.00007001
-echo " OK"
-
-echo -n "Requesting exchange's outgoing transaction list:"
-check_delta "outgoing?delta=-100" "echo 2"
-echo " OK"
-
-echo "----- Recover DB -----"
-
-echo "Reset database"
-reset_db
-mine_btc # Trigger worker
-sleep 2
-
-echo -n "Checking recover incoming transactions:"
-check_delta "incoming?delta=-100" "echo 42 4"
-echo " OK"
-
-echo -n "Requesting exchange's outgoing transaction list:"
-check_delta "outgoing?delta=-100" "echo 2"
-echo " OK"
-
-echo -n "Balance should not have changed:"
-check_balance 9.99990892 0.00007001
-echo " OK"
-
-echo "All tests passed!"
-\ No newline at end of file
diff --git a/test/btc/reorg.sh b/test/btc/reorg.sh
@@ -1,114 +0,0 @@
-#!/bin/bash
-
-## Test btc-wire correctness when a blockchain reorganization occurs
-
-set -eu
-
-source "${BASH_SOURCE%/*}/../common.sh"
-CONFIG=taler_btc.conf
-
-echo "----- Setup -----"
-echo "Load config file"
-load_config
-echo "Start bitcoin node"
-init_btc
-echo "Start second bitcoin node"
-init_btc2
-echo "Start btc-wire"
-btc_wire
-echo "Start gateway"
-gateway
-echo ""
-
-SEQ="seq 10 20"
-
-echo "----- Handle reorg incoming transactions -----"
-
-echo "Loose second bitcoin node"
-btc_deco
-
-echo -n "Gen incoming transactions:"
-for n in `$SEQ`; do
- $WIRE_UTILS transfer 0.000$n > /dev/null
- mine_btc # Mine transactions
-done
-next_btc # Trigger btc-wire
-check_delta "incoming?delta=-100" "$SEQ" "0.000"
-check_balance 9.99826299 0.00165000
-echo " OK"
-
-echo -n "Perform fork and check btc-wire hard error:"
-gateway_up
-btc_fork 22
-check_balance 9.99826299 0.00000000
-gateway_down
-echo " OK"
-
-echo -n "Recover orphaned transactions:"
-next_btc 6 # More block needed to confirm
-check_balance 9.99826299 0.00165000
-gateway_up
-echo " OK"
-
-echo "----- Handle reorg outgoing transactions -----"
-
-echo "Loose second bitcoin node"
-btc_deco
-
-echo -n "Gen outgoing transactions:"
-for n in `$SEQ`; do
- taler-exchange-wire-gateway-client \
- -b $BANK_ENDPOINT \
- -C payto://bitcoin/$CLIENT \
- -a $CURRENCY:0.0000$n > /dev/null
-done
-sleep 1
-mine_btc # Mine transactions
-check_delta "outgoing?delta=-100" "$SEQ"
-check_balance 9.99842799 0.00146311
-echo " OK"
-
-echo -n "Perform fork and check btc-wire still up:"
-gateway_up
-btc_fork 22
-check_balance 9.99826299 0.00146311
-gateway_up
-echo " OK"
-
-echo -n "Recover orphaned transactions:"
-next_btc 6 # More block needed to confirm
-check_balance 9.99842799 0.00146311
-echo " OK"
-
-echo "----- Handle reorg bounce -----"
-
-clear_wallet
-check_balance "*" 0.00000000
-
-echo "Loose second bitcoin node"
-btc_deco
-
-echo -n "Generate bounce:"
-for n in `$SEQ`; do
- $BTC_CLI -rpcwallet=client sendtoaddress $WIRE 0.000$n > /dev/null
- mine_btc
-done
-next_btc 6 # More block needed to confirm
-sleep 1
-check_balance "*" 0.00011000
-echo " OK"
-
-echo -n "Perform fork and check btc-wire hard error:"
-gateway_up
-btc_fork 22
-check_balance "*" 0.00000000
-gateway_down
-echo " OK"
-
-echo -n "Recover orphaned transactions:"
-next_btc 6 # More block needed to confirm
-check_balance "*" 0.00011000
-gateway_up
-echo " OK"
-
-echo "All tests passed!"
-\ No newline at end of file
diff --git a/test/btc/stress.sh b/test/btc/stress.sh
@@ -1,123 +0,0 @@
-#!/bin/bash
-
-## Test btc-wire ability to recover from errors in correctness critical paths and prevent concurrent sending
-
-set -eu
-
-source "${BASH_SOURCE%/*}/../common.sh"
-CONFIG=taler_btc.conf
-
-echo "----- Setup stressed -----"
-echo "Load config file"
-load_config
-echo "Start bitcoin node"
-init_btc
-echo "Start stressed btc-wire"
-stress_btc_wire
-echo "Start gateway"
-gateway
-echo ""
-
-SEQ="seq 10 30"
-
-echo "----- Handle incoming -----"
-
-echo -n "Making wire transfer to exchange:"
-for n in `$SEQ`; do
- $WIRE_UTILS transfer 0.000$n > /dev/null
- mine_btc # Mine transactions
-done
-next_btc # Confirm all transactions
-echo " OK"
-
-echo -n "Requesting exchange incoming transaction list:"
-check_delta "incoming?delta=-100" "$SEQ" "0.000"
-echo " OK"
-
-echo -n "Check balance:"
-check_balance 9.99563389 0.00420000
-echo " OK"
-
-echo "----- Handle outgoing -----"
-
-echo -n "Making wire transfer from exchange:"
-for n in `$SEQ`; do
- taler-exchange-wire-gateway-client \
- -b $BANK_ENDPOINT \
- -C payto://bitcoin/$CLIENT \
- -a $CURRENCY:0.0000$n > /dev/null
-done
-sleep 10 # Give time for btc-wire worker to process
-next_btc # Mine transactions
-echo " OK"
-
-echo -n "Requesting exchange outgoing transaction list:"
-check_delta "outgoing?delta=-100" "$SEQ"
-echo " OK"
-
-echo -n "Check balance:"
-check_balance 9.99605389 0.00373821
-echo " OK"
-
-echo "----- Recover DB -----"
-
-echo "Reset database"
-reset_db
-mine_btc # Trigger worker
-sleep 10
-
-echo -n "Requesting exchange incoming transaction list:"
-check_delta "incoming?delta=-100" "$SEQ" "0.000"
-echo " OK"
-
-echo -n "Requesting exchange outgoing transaction list:"
-check_delta "outgoing?delta=-100" "$SEQ"
-echo " OK"
-
-echo -n "Check balance:"
-# Balance should not have changed
-check_balance 9.99605389 0.00373821
-echo " OK"
-
-echo "----- Handle bounce -----"
-
-echo -n "Clear wire wallet:"
-$BTC_CLI -rpcwallet=wire sendtoaddress $CLIENT `$BTC_CLI -rpcwallet=wire getbalance` "" "" true > /dev/null
-echo " OK"
-
-echo -n "Making incomplete wire transfer to exchange:"
-for n in `$SEQ`; do
- $BTC_CLI -rpcwallet=client sendtoaddress $WIRE 0.000$n > /dev/null
- mine_btc
-done
-next_btc
-sleep 5
-mine_btc
-sleep 5
-echo " OK"
-
-echo -n "Check balance:"
-check_balance "*" 0.00021000
-echo " OK"
-
-echo "----- Recover DB -----"
-
-echo "Reset database"
-reset_db
-mine_btc # Trigger worker
-sleep 10
-
-echo -n "Requesting exchange incoming transaction list:"
-check_delta "incoming?delta=-100" "$SEQ" "0.000"
-echo " OK"
-
-echo -n "Requesting exchange outgoing transaction list:"
-check_delta "outgoing?delta=-100" "$SEQ"
-echo " OK"
-
-echo -n "Check balance:"
-# Balance should not have changed
-check_balance "*" 0.00021000
-echo " OK"
-
-echo "All tests passed!"
-\ No newline at end of file
diff --git a/test/btc/wire.sh b/test/btc/wire.sh
@@ -1,70 +0,0 @@
-#!/bin/bash
-
-## Test btc-wire correctly receive and send transactions on the blockchain
-
-set -eu
-
-source "${BASH_SOURCE%/*}/../common.sh"
-CONFIG=taler_btc.conf
-
-echo "----- Setup -----"
-echo "Load config file"
-load_config
-echo "Start bitcoin node"
-init_btc
-echo "Start btc-wire"
-btc_wire
-echo "Start gateway"
-gateway
-echo ""
-
-SEQ="seq 10 99"
-
-echo "----- Receive -----"
-
-echo -n "Making wire transfer to exchange:"
-for n in `$SEQ`; do
- $WIRE_UTILS transfer 0.000$n > /dev/null
- mine_btc # Mine transactions
-done
-next_btc # Trigger btc-wire
-check_balance 9.95023810 0.04905000
-echo " OK"
-
-echo -n "Requesting exchange incoming transaction list:"
-check_delta "incoming?delta=-100" "$SEQ" "0.000"
-echo " OK"
-
-echo "----- Send -----"
-
-echo -n "Making wire transfer from exchange:"
-for n in `$SEQ`; do
- taler-exchange-wire-gateway-client \
- -b $BANK_ENDPOINT \
- -C payto://bitcoin/$CLIENT \
- -a $CURRENCY:0.0000$n > /dev/null
-done
-sleep 15
-mine_btc # Mine transactions
-check_balance 9.95514310 ""
-echo " OK"
-
-echo -n "Requesting exchange's outgoing transaction list:"
-check_delta "outgoing?delta=-100" "$SEQ"
-echo " OK"
-
-echo "----- Bounce -----"
-
-clear_wallet
-
-echo -n "Bounce:"
-for n in `seq 10 40`; do
- $BTC_CLI -rpcwallet=client sendtoaddress $WIRE 0.000$n > /dev/null
- mine_btc
-done
-next_btc
-sleep 3
-check_balance "*" 0.00031000
-echo " OK"
-
-echo "All tests passed!"
-\ No newline at end of file
diff --git a/test/common.sh b/test/common.sh
@@ -1,405 +0,0 @@
-#!/bin/bash
-
-## Test utils
-
-set -eu
-
-# Cleanup to run whenever we exit
-function cleanup() {
- pg_ctl stop -D $DB_DIR -w &> /dev/null
- for n in `jobs -p`; do
- kill $n &> /dev/null || true
- done
- rm -rf $DIR &> /dev/null || true
- wait
-}
-
-# Install cleanup handler (except for kill -9)
-trap cleanup EXIT
-
-# Init temporary dirs
-DIR=$(mktemp -d)
-WIRE_DIR=$DIR/wire
-WIRE_DIR2=$DIR/wire2
-DB_DIR=$DIR/db
-CONF=$DIR/taler.conf
-for dir in $WIRE_DIR $WIRE_DIR2 $DB_DIR log; do
- mkdir -p "$dir"
-done
-
-# Clear logs
-for log in log/*; do
- echo -n "" > $log
-done
-
-# Generate random password
-export PASSWORD=`cat /proc/sys/kernel/random/uuid`
-
-# Setup command helpers
-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"
-
-
-# ----- Common ----- #
-
-# Load test.conf as bash variables
-function load_config() {
- cp ${BASH_SOURCE%/*}/conf/$CONFIG $CONF
- echo -e "\nCONF_PATH = ${WIRE_DIR}" >> $CONF
- echo -e "IPC_PATH = ${WIRE_DIR}" >> $CONF
- source <(grep = $CONF | sed 's/ *= */=/' | sed 's/=\(.*\)/="\1"/g1')
- BANK_ENDPOINT=http://127.0.0.1:${PORT:-8080}/
- if [[ "$CURRENCY" =~ "BTC" ]]; then
- WIRE_CLI="btc-wire -c $CONF"
- WIRE_UTILS="btc-wire-utils -c $CONF"
- WIRE_UTILS2="btc-wire-utils -c $CONF -d $WIRE_DIR2"
- else
- WIRE_CLI="eth-wire -c $CONF"
- WIRE_UTILS="eth-wire-utils -c $CONF"
- WIRE_UTILS2="eth-wire-utils -c $CONF -d $WIRE_DIR2"
- fi
-}
-
-# Check process is running
-function check_up() {
- if [ `ps -p $1 | grep -c $1` == 0 ]; then
- echo "${2:-process} with pid $1 should be up"
- ps -p $1
- exit 1
- fi
-}
-
-# Check process is not running
-function check_down() {
- if [ `ps -p $1 | grep -c $1` != 0 ]; then
- echo "${2:-process} with pid $1 should be down"
- ps -p $1
- exit 1
- fi
-}
-
-function stop_node() {
- kill $NODE_PID
- wait $NODE_PID
-}
-
-# ----- Database ----- #
-
-# Create new postgresql cluster and init database
-function setup_db() {
- pg_ctl init -D $DB_DIR &>> log/postgres.log
- echo "port=5454" >> $DB_DIR/postgresql.conf
- start_db
- echo "CREATE ROLE postgres LOGIN SUPERUSER PASSWORD 'password'" | psql -h localhost -p 5454 postgres > /dev/null
- $WIRE_CLI initdb > /dev/null
-}
-
-# Erase database
-function reset_db() {
- sleep 5 # Wait for loop to stop
- $WIRE_UTILS resetdb
-}
-
-# Stop database
-function stop_db() {
- pg_ctl stop -D $DB_DIR >> log/postgres.log
-}
-
-# Start database
-function start_db() {
- pg_ctl start -D $DB_DIR -o "-c unix_socket_directories=$DIR" >> log/postgres.log
-}
-
-# ----- Bitcoin node ----- #
-
-# Start a bitcoind regtest node, generate money, wallet and addresses
-function init_btc() {
- cp ${BASH_SOURCE%/*}/conf/${BTC_CONFIG:-bitcoin.conf} $WIRE_DIR/bitcoin.conf
- bitcoind -datadir=$WIRE_DIR $* &>> log/node.log &
- NODE_PID="$!"
- # Wait for RPC server to be online
- $BTC_CLI -rpcwait getnetworkinfo > /dev/null
- # Setup db
- setup_db
- # Create wire wallet
- $WIRE_CLI initwallet > /dev/null
- # Create other wallets
- for wallet in client reserve; do
- $BTC_CLI createwallet $wallet > /dev/null
- done
- # Generate addresses
- RESERVE=`$BTC_CLI -rpcwallet=reserve getnewaddress`
- CLIENT=`$BTC_CLI -rpcwallet=client getnewaddress`
- WIRE=`$BTC_CLI -rpcwallet=wire getnewaddress`
- # Generate money
- mine_btc 101
- $BTC_CLI -rpcwallet=reserve sendtoaddress $CLIENT 10 > /dev/null
- mine_btc
-}
-
-# Start a second bitcoind regtest node connected to the first one
-function init_btc2() {
- cp ${BASH_SOURCE%/*}/conf/bitcoin2.conf $WIRE_DIR2/bitcoin.conf
- bitcoind -datadir=$WIRE_DIR2 $* &>> log/node2.log &
- $BTC_CLI2 -rpcwait getnetworkinfo > /dev/null
- $BTC_CLI addnode 127.0.0.1:8346 onetry
-}
-
-# Disconnect the two nodes
-function btc_deco() {
- $BTC_CLI disconnectnode 127.0.0.1:8346
-}
-
-# Create a fork on the second node and reconnect the two node
-function btc_fork() {
- $BTC_CLI2 generatetoaddress $1 $RESERVE > /dev/null
- $BTC_CLI addnode 127.0.0.1:8346 onetry
- sleep 1
-}
-
-# Restart a bitcoind regest server in a previously created temporary directory and load wallets
-function resume_btc() {
- # Restart node
- bitcoind -datadir=$WIRE_DIR $* &>> log/node.log &
- NODE_PID="$!"
- # Load wallets
- for wallet in wire client reserve; do
- $BTC_CLI -rpcwait loadwallet $wallet > /dev/null
- done
- # Connect second node
- $BTC_CLI addnode 127.0.0.1:8346 onetry
-}
-
-function restart_btc() {
- stop_node
- resume_btc $*
-}
-
-# Mine bitcoin blocks
-function mine_btc() {
- $BTC_CLI generatetoaddress "${1:-1}" $RESERVE > /dev/null
-}
-
-# Mine previous transactions
-function next_btc() {
- # Mine enough block to confirm previous transactions
- mine_btc ${1:-$CONFIRMATION}
- # Wait for btc-wire to catch up
- sleep 0.2
- # Mine one more block to trigger btc-wire
- mine_btc
-}
-
-# Remove money from wallet wire
-function clear_wallet() {
- $BTC_CLI -rpcwallet=wire sendtoaddress $CLIENT `$BTC_CLI -rpcwallet=wire getbalance` "" "" true > /dev/null
- mine_btc
-}
-
-# Check client and wire balance
-function check_balance() {
- local CLIENT_BALANCE=`$BTC_CLI -rpcwallet=client getbalance`
- local WIRE_BALANCE=`$BTC_CLI -rpcwallet=wire getbalance`
- local CLIENT="${1:-*}"
- if [ "$1" == "*" ]; then
- local CLIENT="$CLIENT_BALANCE"
- fi
- if [ "$CLIENT_BALANCE" != "$CLIENT" ] || [ "$WIRE_BALANCE" != "${2:-$WIRE_BALANCE}" ]; then
- echo "expected: client $CLIENT wire ${2:-$WIRE_BALANCE} got: client $CLIENT_BALANCE wire $WIRE_BALANCE"
- exit 1
- fi
-}
-
-# ----- btc-wire ----- #
-
-# Start btc-wire
-function btc_wire() {
- cargo build --bin btc-wire --release &>> log/cargo.log
- target/release/btc-wire -c $CONF &>> log/wire.log &
- WIRE_PID="$!"
-}
-
-# Start multiple btc-wire with random failures in parallel
-function stress_btc_wire() {
- cargo build --bin btc-wire --release --features fail &>> log/cargo.log
- target/release/btc-wire -c $CONF &>> log/wire.log &
- target/release/btc-wire -c $CONF &>> log/wire1.log &
-}
-
-# ----- Ethereum node ----- #
-
-# Start a geth dev node, generate money, wallet and addresses
-function init_eth() {
- # Create wallets
- for pswd in "reserve" "client"; do
- $ETH_CLI account new --password <(echo $PASSWORD) &> /dev/null
- done
- # Retrieve addresses
- local ADDR=`$ETH_CLI account list 2> /dev/null | grep -oP '(?<={).*?(?=})'`
- RESERVE=`sed -n '1p' <(echo "$ADDR")`
- CLIENT=`sed -n '2p' <(echo "$ADDR")`
- # Generate genesis
- echo "{
- \"config\": {
- \"chainId\": 42,
- \"homesteadBlock\": 0,
- \"eip150Block\": 0,
- \"eip155Block\": 0,
- \"eip158Block\": 0,
- \"byzantiumBlock\": 0,
- \"constantinopleBlock\": 0,
- \"petersburgBlock\": 0,
- \"istanbulBlock\": 0,
- \"berlinBlock\": 0,
- \"londonBlock:\": 0,
- \"ethash\": {}
- },
- \"difficulty\": \"1\",
- \"gasLimit\": \"0\",
- \"baseFeePerGas\": null,
- \"alloc\": {
- \"$CLIENT\": { \"balance\": \"10000000000000000000\" }
- }
-}" > $DIR/genesis.json
- # Initialize blockchain
- $ETH_CLI init $DIR/genesis.json &>> log/node.log
- # Start node
- start_eth $*
- # Setup db
- setup_db
- # Create wire address
- WIRE=`$WIRE_CLI initwallet | grep -oP '(?<=is ).*'`
- echo -e "PAYTO = payto://ethereum/$WIRE" >> $CONF
-}
-
-# Initialize a second geth dev node
-function init_eth2() {
- # Initialize blockchain
- $ETH_CLI2 init $DIR/genesis.json &>> log/node2.log
- $ETH_CLI2 account new --password <(echo $PASSWORD) &> /dev/null
-}
-
-# We use the import/export chain functionality to simulate a connected node peer
-# because local network peer are not reliable
-# importChain RPC crash so we have to use the cli for now
-
-# Disconnect the two nodes
-function eth_deco() {
- $WIRE_UTILS export $DIR/chain &>> log/node.log
- $ETH_CLI2 import $DIR/chain &>> log/node2.log
-}
-
-# Create a fork on the second node and reconnect the two node
-function eth_fork() {
- $ETH_CLI2 --port 30305 --miner.gasprice 0 &>> log/node2.log &
- NODE2_PID="$!"
- sleep 1
- $WIRE_UTILS2 mine $RESERVE ${1:-}
- $WIRE_UTILS2 export $DIR/chain &>> log/node2.log
- kill $NODE2_PID
- stop_node
- $ETH_CLI import $DIR/chain &>> log/node.log
- start_eth --miner.gasprice 0
- sleep 5 # Wait for reconnect
-}
-
-# Restart an initialized geth dev node
-function start_eth() {
- # Start node
- $ETH_CLI $* &>> log/node.log &
- NODE_PID="$!"
- sleep 2
-}
-
-function restart_eth() {
- stop_node
- start_eth $*
-}
-
-# Check client and wire balance
-function check_balance_eth() {
- $WIRE_UTILS check-balance $CLIENT $1 $WIRE $2
-}
-
-
-# ----- eth-wire ----- #
-
-# Start eth-wire
-function eth_wire() {
- cargo build --bin eth-wire --release &>> log/cargo.log
- target/release/eth-wire -c $CONF &>> log/wire.log &
- WIRE_PID="$!"
-}
-
-# Start multiple eth-wire with random failures in parallel
-function stress_eth_wire() {
- cargo build --bin eth-wire --release --features fail &>> log/cargo.log
- target/release/eth-wire -c $CONF &>> log/wire.log &
- target/release/eth-wire -c $CONF &>> log/wire1.log &
-}
-
-# Mine ethereum blocks
-function mine_eth() {
- $WIRE_UTILS mine $RESERVE ${1:-}
-}
-
-# Mine previous transactions
-function next_eth() {
- # Mine enough block to confirm previous transactions
- mine_eth ${1:-$CONFIRMATION}
- # Wait for eth-wire to catch up
- sleep 0.2
- # Mine one more block to trigger eth-wire
- mine_eth
-}
-
-# ----- Gateway ------ #
-
-# Start wire-gateway in test mode
-function gateway() {
- cargo build --bin wire-gateway --release --features test &> /dev/null
- target/release/wire-gateway -c $CONF &>> log/gateway.log &
- GATEWAY_PID="$!"
- for n in `seq 1 50`; do
- echo -n "."
- sleep 0.2
- curl -s $BANK_ENDPOINT -o /dev/null && break
- done
-}
-
-# Check wire-gateway is healthy
-function gateway_up() {
- if [ `curl -w %{http_code} -s ${BANK_ENDPOINT}history/outgoing -o /dev/null` -ne 400 ]; then
- echo "gateway should be up"
- exit 1
- fi
-}
-
-# Check wire-gateway is down (backend is blocked)
-function gateway_down() {
- if [ `curl -w %{http_code} -s ${BANK_ENDPOINT}history/outgoing -o /dev/null` -ne 502 ]; then
- echo "gateway should be down"
- exit 1
- fi
-}
-
-# Check history endpoint request return a specific amount of transactions of specific amounts
-# usage: check_delta endpoint nb_txs amount_sequence
-function check_delta() {
- ALL=`curl -s ${BANK_ENDPOINT}history/$1`
- PRE=${3:-0.0000}
- for n in `$2`; do
- if ! `echo $ALL | grep $CURRENCY:$PRE$n > /dev/null`; then
- echo -n " missing tx with amount: $CURRENCY:$PRE$n"
- return 1
- fi
- done
- NB=`echo $ALL | grep -o $CURRENCY:$PRE | wc -l`
- EXPECTED=`$2 | wc -w`
- if [ "$EXPECTED" != "$NB" ]; then
- echo -n " expected: $EXPECTED txs found $NB"
- return 1
- fi
-}
diff --git a/test/conf/bitcoin.conf b/test/conf/bitcoin.conf
@@ -1,8 +0,0 @@
-regtest=1
-txindex=1
-maxtxfee=0.01
-fallbackfee=0.00000001
-
-[regtest]
-port=8345
-rpcport=18345
-\ No newline at end of file
diff --git a/test/conf/bitcoin2.conf b/test/conf/bitcoin2.conf
@@ -1,7 +0,0 @@
-regtest=1
-txindex=1
-fallbackfee=0.00000001
-
-[regtest]
-port=8346
-rpcport=18346
-\ No newline at end of file
diff --git a/test/conf/bitcoin_auth1.conf b/test/conf/bitcoin_auth1.conf
@@ -1,12 +0,0 @@
-regtest=1
-txindex=1
-maxtxfee=0.1
-fallbackfee=0.00000001
-rpcuser=bob
-rpcpassword=password
-
-[regtest]
-port=8346
-rpcport=18346
-rpcuser=alice
-rpcpassword=password
-\ No newline at end of file
diff --git a/test/conf/taler_btc_lifetime.conf b/test/conf/taler_btc_lifetime.conf
@@ -1,14 +0,0 @@
-[taler]
-CURRENCY = DEVBTC
-
-[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
-HTTP_LIFETIME = 10
-WIRE_LIFETIME = 10
-AUTH_METHOD = none
-\ No newline at end of file
diff --git a/test/eth/analysis.sh b/test/eth/analysis.sh
@@ -1,63 +0,0 @@
-#!/bin/bash
-
-## Test eth-wire ability to learn and protect itself from blockchain behavior
-
-set -eu
-
-source "${BASH_SOURCE%/*}/../common.sh"
-CONFIG=taler_eth.conf
-
-echo "----- Setup -----"
-echo "Load config file"
-load_config
-echo "Start ethereum node"
-init_eth --miner.gasprice 0
-echo "Start second ethereum node"
-init_eth2 --miner.gasprice 0
-echo "Start eth-wire"
-eth_wire
-echo "Start gateway"
-gateway
-echo ""
-
-echo "----- Learn from reorg -----"
-
-echo "Loose second ethereum node"
-eth_deco
-
-echo -n "Making wire transfer to exchange:"
-$WIRE_UTILS credit $CLIENT $WIRE 0.00 42
-next_eth # Trigger eth-wire
-check_balance_eth 999580000 420000
-echo " OK"
-
-echo -n "Perform fork and check eth-wire hard error:"
-gateway_up
-eth_fork 6
-check_balance_eth 1000000000 0
-gateway_down
-echo " OK"
-
-echo -n "Recover orphaned transactions:"
-next_eth 6 # More block needed to confirm
-check_balance_eth 999580000 420000
-gateway_up
-echo " OK"
-
-echo "Loose second bitcoin node"
-eth_deco
-
-echo -n "Making wire transfer to exchange:"
-$WIRE_UTILS credit $CLIENT $WIRE 0.00 42
-next_eth # Trigger eth-wire
-check_balance_eth 999160000 840000
-echo " OK"
-
-echo -n "Perform fork and check eth-wire learned from previous attack:"
-gateway_up
-eth_fork 10
-check_balance_eth 999580000 420000
-gateway_up
-echo " OK"
-
-echo "All tests passed!"
-\ No newline at end of file
diff --git a/test/eth/bumpfee.sh b/test/eth/bumpfee.sh
@@ -1,110 +0,0 @@
-#!/bin/bash
-
-## Test eth-wire ability to handle stuck transaction correctly
-
-set -eu
-
-source "${BASH_SOURCE%/*}/../common.sh"
-CONFIG=taler_eth_bump.conf
-
-echo "----- Setup -----"
-echo "Load config file"
-load_config
-echo "Start ethereum node"
-init_eth --miner.gasprice 0
-echo "Start second ethereum node"
-init_eth2 --miner.gasprice 0
-echo "Start eth-wire"
-eth_wire
-echo "Start gateway"
-gateway
-echo ""
-
-SEQ="seq 10 20"
-
-echo -n "Making wire transfer to exchange: "
-$WIRE_UTILS credit $CLIENT $WIRE 0.000 `$SEQ`
-sleep 1
-mine_eth # Trigger eth-wire
-check_balance_eth 999835000 165000
-echo "OK"
-
-echo "----- Bump fee -----"
-
-echo -n "Making wire transfer from exchange: "
-for n in `$SEQ`; do
- taler-exchange-wire-gateway-client \
- -b $BANK_ENDPOINT \
- -C payto://ethereum/$CLIENT \
- -a $CURRENCY:0.0000$n > /dev/null
-done
-sleep 1
-echo "OK"
-
-echo -n "Bump miner fee:"
-restart_eth --miner.gasprice 1000
-sleep 5
-echo "OK"
-
-echo -n "Check bump: "
-sleep 1
-mine_eth # Mine transactions
-check_balance_eth 999851500 148499
-echo "OK"
-
-echo "----- Bump fee reorg -----"
-
-echo "Loose second ethereum node"
-eth_deco
-
-echo -n "Making wire transfer from exchange: "
-for n in `$SEQ`; do
- taler-exchange-wire-gateway-client \
- -b $BANK_ENDPOINT \
- -C payto://ethereum/$CLIENT \
- -a $CURRENCY:0.0000$n > /dev/null
-done
-sleep 1
-echo "OK"
-
-echo -n "Perform fork and bump miner fee:"
-eth_fork 10
-restart_eth --miner.gasprice 2000
-sleep 5
-echo "OK"
-
-echo -n "Check bump: "
-sleep 1
-mine_eth # Mine transactions
-check_balance_eth 999868000 131999
-echo "OK"
-
-echo "----- Bump fee stress -----"
-
-echo -n "Replace btc-wire with stressed btc-wire"
-kill $WIRE_PID
-stress_eth_wire
-echo " OK"
-
-echo -n "Making wire transfer from exchange: "
-for n in `$SEQ`; do
- taler-exchange-wire-gateway-client \
- -b $BANK_ENDPOINT \
- -C payto://ethereum/$CLIENT \
- -a $CURRENCY:0.0000$n > /dev/null
-done
-sleep 1
-echo "OK"
-
-echo -n "Bump miner fee:"
-restart_eth --miner.gasprice 3000
-sleep 5
-echo "OK"
-
-echo -n "Check bump: "
-sleep 1
-mine_eth # Mine transactions
-check_balance_eth 999884500 115499
-echo "OK"
-
-echo "All tests passed!"
-\ No newline at end of file
diff --git a/test/eth/hell.sh b/test/eth/hell.sh
@@ -1,107 +0,0 @@
-#!/bin/bash
-
-## Test eth-wire correctness when a blockchain reorganization occurs leading to past incoming transaction conflict
-
-set -eu
-
-source "${BASH_SOURCE%/*}/../common.sh"
-CONFIG=taler_eth.conf
-
-echo "----- Setup -----"
-echo "Load config file"
-load_config
-echo "Start ethereum node"
-init_eth --miner.gasprice 0
-echo "Start second ethereum node"
-init_eth2 --miner.gasprice 0
-echo "Start eth-wire"
-eth_wire
-echo "Start gateway"
-gateway
-echo ""
-
-echo "----- Handle reorg conflicting incoming credit -----"
-
-echo "Loose second ethereum node"
-eth_deco
-
-echo -n "Making wire transfer to exchange:"
-$WIRE_UTILS credit $CLIENT $WIRE 0.00 42
-next_eth # Trigger eth-wire
-check_balance_eth 999580000 420000
-echo " OK"
-
-echo -n "Perform fork and check eth-wire hard error:"
-gateway_up
-eth_fork 10
-check_balance_eth 1000000000 0
-gateway_down
-echo " OK"
-
-echo -n "Generate conflict:"
-$WIRE_UTILS abandon $CLIENT
-next_eth 5
-check_balance_eth 999999999 0
-echo " OK"
-
-echo -n "Check eth-wire suspend function:"
-gateway_down
-$WIRE_UTILS send $CLIENT $WIRE 0.000 42
-next_eth 6
-sleep 1
-check_balance_eth 999957999 42000 # Not bounced
-echo " OK"
-
-# Recover by paying for the customer ?
-
-echo "----- Reset -----"
-echo "Cleanup"
-cleanup
-source "${BASH_SOURCE%/*}/../common.sh"
-echo "Load config file"
-load_config
-echo "Start ethereum node"
-init_eth --miner.gasprice 0
-echo "Start second ethereum node"
-init_eth2 --miner.gasprice 0
-echo "Start eth-wire"
-eth_wire
-echo "Start gateway"
-gateway
-echo ""
-
-echo "----- Handle reorg conflicting incoming bounce -----"
-
-echo "Loose second ethereum node"
-eth_deco
-
-echo -n "Bounce:"
-$WIRE_UTILS send $CLIENT $WIRE 0.00 42
-next_eth 6
-sleep 1
-check_balance_eth 999999000 1000
-echo " OK"
-
-echo -n "Perform fork and check eth-wire hard error:"
-gateway_up
-eth_fork 10
-check_balance_eth 1000000000 0
-gateway_down
-echo " OK"
-
-echo -n "Generate conflict:"
-$WIRE_UTILS abandon $CLIENT
-next_eth 5
-check_balance_eth 999999999 0
-echo " OK"
-
-echo -n "Check eth-wire suspend function:"
-$WIRE_UTILS send $CLIENT $WIRE 0.000 42
-next_eth 6
-sleep 1
-gateway_down
-check_balance_eth 999957999 42000 # Not bounced
-echo " OK"
-
-
-echo "All tests passed!"
-\ No newline at end of file
diff --git a/test/eth/lifetime.sh b/test/eth/lifetime.sh
@@ -1,49 +0,0 @@
-#!/bin/bash
-
-## Check eth-wire and wire-gateway correctly stop when a lifetime limit is configured
-
-set -eu
-
-source "${BASH_SOURCE%/*}/../common.sh"
-CONFIG=taler_eth_lifetime.conf
-
-
-echo "----- Setup -----"
-echo "Load config file"
-load_config
-echo "Start ethereum node"
-init_eth --miner.gasprice 0
-echo "Start eth-wire"
-eth_wire
-echo "Start gateway"
-gateway
-echo ""
-
-SEQ="seq 10 20"
-
-echo "---- Check lifetime -----"
-
-echo -n "Check up:"
-check_up $WIRE_PID eth-wire
-check_up $GATEWAY_PID wire-gateway
-echo " OK"
-
-echo -n "Do some work:"
-$WIRE_UTILS credit $CLIENT $WIRE 0.000 `$SEQ` > /dev/null
-next_eth # Trigger eth-wire
-check_balance_eth 999835000 165000
-for n in `$SEQ`; do
- taler-exchange-wire-gateway-client \
- -b $BANK_ENDPOINT \
- -C payto://ethereum/$CLIENT \
- -a $CURRENCY:0.0000$n &> /dev/null || break;
-done
-sleep 1
-echo " OK"
-
-echo -n "Check down:"
-check_down $WIRE_PID eth-wire
-check_down $GATEWAY_PID wire-gateway
-echo " OK"
-
-echo "All tests passed!"
diff --git a/test/eth/maxfee.sh b/test/eth/maxfee.sh
@@ -1,65 +0,0 @@
-#!/bin/bash
-
-## Test eth-wire handle transaction fees exceeding limits
-
-set -eu
-
-source "${BASH_SOURCE%/*}/../common.sh"
-CONFIG=taler_eth.conf
-
-echo "----- Setup -----"
-echo "Load config file"
-load_config
-echo "Start ethereum node"
-init_eth --miner.gasprice 0
-echo "Start eth-wire"
-eth_wire
-echo "Start gateway"
-gateway
-echo ""
-
-SEQ="seq 10 20"
-
-echo -n "Making wire transfer to exchange: "
-$WIRE_UTILS credit $CLIENT $WIRE 0.000 `$SEQ`
-next_eth # Trigger eth-wire
-sleep 1
-check_delta "incoming?delta=-100" "$SEQ" "0.000"
-check_balance_eth 999835000 165000
-echo "OK"
-
-
-echo "----- Too high fees -----"
-
-echo -n "Set up node: "
-restart_eth --rpc.txfeecap 0.00001
-echo "OK"
-
-echo -n "Making wire transfer from exchange: "
-for n in `$SEQ`; do
- taler-exchange-wire-gateway-client \
- -b $BANK_ENDPOINT \
- -C payto://ethereum/$CLIENT \
- -a $CURRENCY:0.0000$n > /dev/null
-done
-sleep 1
-mine_eth # Mine transactions
-echo "OK"
-
-echo -n "Check no transaction have been made: "
-check_balance_eth 999835000 165000
-echo "OK"
-
-echo "----- Good fees -----"
-
-echo -n "Set up node: "
-restart_eth --miner.gasprice 0
-echo "OK"
-
-echo -n "Check transaction have been made: "
-sleep 5
-mine_eth # Mine transactions
-check_balance_eth 999851500 148500
-echo "OK"
-
-echo "All tests passed!"
-\ No newline at end of file
diff --git a/test/eth/reconnect.sh b/test/eth/reconnect.sh
@@ -1,90 +0,0 @@
-#!/bin/bash
-
-## Check the capacity of wire-gateway and eth-wire to recover from database and node loss
-
-set -eu
-
-source "${BASH_SOURCE%/*}/../common.sh"
-CONFIG=taler_eth.conf
-
-echo "----- Setup -----"
-echo "Load config file"
-load_config
-echo "Start ethereum node"
-init_eth --miner.gasprice 0
-echo "Start eth-wire"
-eth_wire
-echo "Start gateway"
-gateway
-echo ""
-
-echo "----- With DB -----"
-
-echo -n "Making wire transfer to exchange:"
-$WIRE_UTILS credit $CLIENT $WIRE 0.0000 42
-next_eth # Trigger eth-wire
-check_balance_eth 999995800 4200
-echo " OK"
-
-echo -n "Requesting exchange incoming transaction list:"
-check_delta "incoming?delta=-100" "echo 42"
-echo " OK"
-
-echo "----- Without DB -----"
-
-echo "Stop database"
-stop_db
-echo "Making incomplete wire transfer to exchange"
-$WIRE_UTILS send $CLIENT $WIRE 0.0000 42
-echo -n "Making wire transfer to exchange:"
-$WIRE_UTILS credit $CLIENT $WIRE 0.0000 4
-next_eth
-check_balance_eth 999987600 12400
-echo "OK"
-echo "Stop ethereum node"
-stop_node
-echo -n "Requesting exchange incoming transaction list:"
-taler-exchange-wire-gateway-client -b $BANK_ENDPOINT -i 2>&1 | grep -q "504" && echo " OK" || echo " Failed"
-
-echo "----- Reconnect DB -----"
-
-echo "Start database"
-start_db
-echo "Resume ethereum node"
-start_eth --miner.gasprice 0
-sleep 6 # Wait for connection to be available
-echo -n "Making wire transfer from exchange:"
- taler-exchange-wire-gateway-client \
- -b $BANK_ENDPOINT \
- -C payto://ethereum/$CLIENT \
- -a $CURRENCY:0.00002 > /dev/null
-sleep 1
-mine_eth # Mine transactions
-check_balance_eth 999992800 7200
-echo " OK"
-
-echo -n "Requesting exchange's outgoing transaction list:"
-check_delta "outgoing?delta=-100" "echo 2"
-echo " OK"
-
-echo "----- Recover DB -----"
-
-echo "Reset database"
-reset_db
-mine_eth 1 # Trigger worker
-sleep 2
-
-echo -n "Checking recover incoming transactions:"
-check_delta "incoming?delta=-100" "echo 42 4"
-echo " OK"
-
-echo -n "Requesting exchange's outgoing transaction list:"
-check_delta "outgoing?delta=-100" "echo 2"
-echo " OK"
-
-echo -n "Balance should not have changed:"
-check_balance_eth 999992800 7200
-echo " OK"
-
-
-echo "All tests passed!"
-\ No newline at end of file
diff --git a/test/eth/reorg.sh b/test/eth/reorg.sh
@@ -1,106 +0,0 @@
-#!/bin/bash
-
-## Test eth-wire correctness when a blockchain reorganization occurs
-
-set -eu
-
-source "${BASH_SOURCE%/*}/../common.sh"
-CONFIG=taler_eth.conf
-
-echo "----- Setup -----"
-echo "Load config file"
-load_config
-echo "Start ethereum node"
-init_eth --miner.gasprice 0
-echo "Start second ethereum node"
-init_eth2 --miner.gasprice 0
-echo "Start eth-wire"
-eth_wire
-echo "Start gateway"
-gateway
-echo ""
-
-SEQ="seq 10 20"
-
-echo "----- Handle reorg incoming transactions -----"
-
-echo "Loose second ethereum node"
-eth_deco
-
-echo -n "Making wire transfer to exchange:"
-$WIRE_UTILS credit $CLIENT $WIRE 0.000 `$SEQ`
-next_eth # Trigger eth-wire
-check_delta "incoming?delta=-100" "$SEQ" "0.000"
-check_balance_eth 999835000 165000
-echo " OK"
-
-echo -n "Perform fork and check eth-wire hard error:"
-gateway_up
-eth_fork 10
-check_balance_eth 1000000000 0
-gateway_down
-echo " OK"
-
-echo -n "Recover orphaned transactions:"
-next_eth 6 # More block needed to confirm
-check_balance_eth 999835000 165000
-gateway_up
-echo " OK"
-
-echo "----- Handle reorg outgoing transactions -----"
-
-echo "Loose second ethereum node"
-eth_deco
-
-echo -n "Making wire transfer from exchange:"
-for n in `$SEQ`; do
- taler-exchange-wire-gateway-client \
- -b $BANK_ENDPOINT \
- -C payto://ethereum/$CLIENT \
- -a $CURRENCY:0.0000$n > /dev/null
-done
-sleep 1
-mine_eth # Mine transactions
-check_delta "outgoing?delta=-100" "$SEQ"
-check_balance_eth 999851500 148500
-echo " OK"
-
-echo -n "Perform fork and check eth-wire still up:"
-gateway_up
-eth_fork 10
-check_balance_eth 999835000 165000
-gateway_up
-echo " OK"
-
-echo -n "Recover orphaned transactions:"
-next_eth 6 # More block needed to confirm
-check_balance_eth 999851500 148500
-echo " OK"
-
-echo "----- Handle reorg bounce -----"
-
-echo "Loose second ethereum node"
-eth_deco
-
-echo -n "Bounce:"
-$WIRE_UTILS send $CLIENT $WIRE 0.000 `$SEQ`
-sleep 1
-next_eth 6
-check_balance_eth 999840500 159500
-echo " OK"
-
-echo -n "Perform fork and check eth-wire hard error:"
-gateway_up
-eth_fork 10
-check_balance_eth 999851500 148500
-sleep 5 # Wait for reconnect
-gateway_down
-echo " OK"
-
-echo -n "Recover orphaned transactions:"
-next_eth 6 # More block needed to confirm
-check_balance_eth 999840500 159500
-gateway_up
-echo " OK"
-
-echo "All tests passed!"
-\ No newline at end of file
diff --git a/test/eth/stress.sh b/test/eth/stress.sh
@@ -1,109 +0,0 @@
-#!/bin/bash
-
-## Test eth-wire ability to recover from errors in correctness critical paths and prevent concurrent sending
-
-set -eu
-
-source "${BASH_SOURCE%/*}/../common.sh"
-CONFIG=taler_eth.conf
-
-echo "----- Setup -----"
-echo "Load config file"
-load_config
-echo "Start ethereum node"
-init_eth --miner.gasprice 0
-echo "Start stressed eth-wire"
-stress_eth_wire
-echo "Start gateway"
-gateway
-echo ""
-
-SEQ="seq 10 30"
-
-echo "----- Handle incoming -----"
-
-echo -n "Making wire transfer to exchange:"
-$WIRE_UTILS credit $CLIENT $WIRE 0.000 `$SEQ`
-next_eth # Trigger eth-wire
-echo " OK"
-
-echo -n "Requesting exchange incoming transaction list:"
-check_delta "incoming?delta=-100" "$SEQ" "0.000"
-echo " OK"
-
-echo -n "Check balance:"
-check_balance_eth 999580000 420000
-echo " OK"
-
-echo "----- Handle outgoing -----"
-
-echo -n "Making wire transfer from exchange:"
-for n in `$SEQ`; do
- taler-exchange-wire-gateway-client \
- -b $BANK_ENDPOINT \
- -C payto://ethereum/$CLIENT \
- -a $CURRENCY:0.0000$n > /dev/null
-done
-sleep 1
-next_eth # Mine transactions
-echo " OK"
-
-echo -n "Requesting exchange's outgoing transaction list:"
-check_delta "outgoing?delta=-100" "$SEQ"
-echo " OK"
-
-echo -n "Check balance:"
-check_balance_eth 999622000 378000
-echo " OK"
-
-echo "----- Recover DB -----"
-
-echo "Reset database"
-reset_db
-mine_eth 1 # Trigger worker
-sleep 10
-
-echo -n "Requesting exchange incoming transaction list:"
-check_delta "incoming?delta=-100" "$SEQ" "0.000"
-echo " OK"
-
-echo -n "Requesting exchange outgoing transaction list:"
-check_delta "outgoing?delta=-100" "$SEQ"
-echo " OK"
-
-echo -n "Balance have not changed:"
-check_balance_eth 999622000 378000
-echo " OK"
-
-echo "----- Handle bounce -----"
-
-echo -n "Bounce:"
-$WIRE_UTILS send $CLIENT $WIRE 0.000 `$SEQ`
-sleep 1
-next_eth
-echo " OK"
-
-echo -n "Check balance:"
-check_balance_eth 999601000 399000
-echo " OK"
-
-echo "----- Recover DB -----"
-
-echo "Reset database"
-reset_db
-mine_eth 1 # Trigger worker
-sleep 10
-
-echo -n "Requesting exchange incoming transaction list:"
-check_delta "incoming?delta=-100" "$SEQ" "0.000"
-echo " OK"
-
-echo -n "Requesting exchange outgoing transaction list:"
-check_delta "outgoing?delta=-100" "$SEQ"
-echo " OK"
-
-echo -n "Balance have not changed:"
-check_balance_eth 999601000 399000
-echo " OK"
-
-echo "All tests passed!"
-\ No newline at end of file
diff --git a/test/eth/test.sh b/test/eth/test.sh
@@ -1,16 +0,0 @@
-#!/bin/bash
-
-set -eu
-
-source "${BASH_SOURCE%/*}/../common.sh"
-CONFIG=taler_eth.conf
-
-echo "----- Setup -----"
-echo "Load config file"
-load_config
-echo "Start ethereum node"
-init_eth --miner.gasprice 0
-echo ""
-
-echo "----- Test -----"
-cargo run --bin eth-test --release -- $CONF
-\ No newline at end of file
diff --git a/test/eth/wire.sh b/test/eth/wire.sh
@@ -1,62 +0,0 @@
-#!/bin/bash
-
-## Test eth-wire correctly receive and send transactions on the blockchain
-
-set -eu
-
-source "${BASH_SOURCE%/*}/../common.sh"
-CONFIG=taler_eth.conf
-
-echo "----- Setup -----"
-echo "Load config file"
-load_config
-echo "Start ethereum node"
-init_eth --miner.gasprice 0
-echo "Start eth-wire"
-eth_wire
-echo "Start gateway"
-gateway
-echo ""
-
-SEQ="seq 10 99"
-
-echo "----- Receive -----"
-
-echo -n "Making wire transfer to exchange:"
-$WIRE_UTILS credit $CLIENT $WIRE 0.000 `$SEQ`
-next_eth # Trigger eth-wire
-check_balance_eth 995095000 4905000
-echo " OK"
-
-echo -n "Requesting exchange incoming transaction list:"
-check_delta "incoming?delta=-100" "$SEQ" "0.000"
-echo " OK"
-
-echo "----- Send -----"
-
-echo -n "Making wire transfer from exchange:"
-for n in `$SEQ`; do
- taler-exchange-wire-gateway-client \
- -b $BANK_ENDPOINT \
- -C payto://ethereum/$CLIENT \
- -a $CURRENCY:0.0000$n > /dev/null
-done
-sleep 1
-mine_eth # Mine transactions
-check_balance_eth 995585500 4414500
-echo " OK"
-
-echo -n "Requesting exchange's outgoing transaction list:"
-check_delta "outgoing?delta=-100" "$SEQ"
-echo " OK"
-
-echo "----- Bounce -----"
-
-echo -n "Bounce:"
-$WIRE_UTILS send $CLIENT $WIRE 0.000 `seq 10 40`
-sleep 1
-next_eth
-check_balance_eth 995554500 4445500
-echo " OK"
-
-echo "All tests passed!"
-\ No newline at end of file
diff --git a/test/gateway/api.sh b/test/gateway/api.sh
@@ -1,133 +0,0 @@
-#!/bin/bash
-
-## Test wire-gateway conformance to documentation and its security
-
-set -eu
-
-# Create temp file
-TEMP_FILE=$(mktemp)
-
-# Cleanup to run whenever we exit
-function cleanup() {
- rm -f $TEMP_FILE
- wait
-}
-
-# Install cleanup handler (except for kill -9)
-trap cleanup EXIT
-
-source "${BASH_SOURCE%/*}/../common.sh"
-CONFIG=taler_btc.conf
-
-echo "----- Setup -----"
-echo "Load config file"
-load_config
-echo "Start bitcoin node"
-init_btc
-echo "Start gateway"
-gateway
-echo ""
-
-echo "----- Gateway API -----"
-
-echo -n "Making wire transfer to exchange:"
-for n in `seq 1 9`; do
- taler-exchange-wire-gateway-client \
- -b $BANK_ENDPOINT \
- -D payto://bitcoin/$CLIENT \
- -a $CURRENCY:0.0000$n > /dev/null
-done
-echo " OK"
-
-echo -n "Requesting exchange incoming transaction list:"
-ALL=`taler-exchange-wire-gateway-client -b $BANK_ENDPOINT -i`
-for n in `seq 1 9`; do
- echo $ALL | grep $CURRENCY:0.0000$n > /dev/null
-done
-echo " OK"
-
-echo -n "Making wire transfer from exchange:"
-for n in `seq 1 9`; do
- taler-exchange-wire-gateway-client \
- -b $BANK_ENDPOINT \
- -C payto://bitcoin/$CLIENT \
- -a $CURRENCY:0.0000$n > /dev/null
-done
-
-echo " OK"
-
-echo -n "Requesting exchange's outgoing transaction list:"
-ALL=`taler-exchange-wire-gateway-client -b $BANK_ENDPOINT -o`
-for n in `seq 1 9`; do
- echo $ALL | grep $CURRENCY:0.0000$n > /dev/null
-done
-echo " OK"
-
-echo "----- Endpoint & Method -----"
-
-echo -n "Unknown endpoint:"
-test `curl -w %{http_code} -s -o /dev/null ${BANK_ENDPOINT}test` -eq 404 && echo " OK" || echo " Failed"
-
-echo -n "Method not allowed:"
-test `curl -w %{http_code} -s -o /dev/null ${BANK_ENDPOINT}transfer` -eq 405 && echo " OK" || echo " Failed"
-
-echo "----- Request format -----"
-
-echo -n "Bad payto url:"
-for bad_payto in http://bitcoin/$CLIENT payto://btc/$CLIENT payto://bitcoin/$CLIENT?id=admin payto://bitcoin/$CLIENT#admin; do
- taler-exchange-wire-gateway-client -b $BANK_ENDPOINT -C $bad_payto -a $CURRENCY:0.00042 2>&1 | grep -q "(400/24)" && echo -n " OK" || echo " Failed"
-done
-echo ""
-
-echo -n "Bad bitcoin address:"
-taler-exchange-wire-gateway-client -b $BANK_ENDPOINT -C payto://bitcoin/42$CLIENT -a $CURRENCY:0.00042 2>&1 | grep -q "(400/24)" && echo " OK" || echo " Failed"
-
-echo -n "Bad transaction amount:"
-taler-exchange-wire-gateway-client -b $BANK_ENDPOINT -C payto://bitcoin/$CLIENT -a ATC:0.00042 2>&1 | grep -q "(400/26)" && echo " OK" || echo " Failed"
-
-echo -n "Bad history delta:"
-for bad_delta in incoming outgoing incoming?delta=0 outgoing?delta=0; do
- test `curl -w %{http_code} -s -o /dev/null "${BANK_ENDPOINT}history/$bad_delta"` -eq 400 && echo -n " OK" || echo -n " Failed"
-done
-echo ""
-
-echo "----- History delta -----"
-
-for endpoint in incoming outgoing; do
- echo -n "History $endpoint:"
- check_delta ${endpoint}?delta=-9 "seq 1 9" && echo -n " OK" || echo -n " Failed"
- check_delta ${endpoint}?delta=9 "seq 1 9" && echo -n " OK" || echo -n " Failed"
- check_delta ${endpoint}?delta=-4 "seq 6 9" && echo -n " OK" || echo -n " Failed"
- check_delta ${endpoint}?delta=4 "seq 1 4" && echo -n " OK" || echo -n " Failed"
- check_delta "${endpoint}?delta=-3&start=5" "seq 2 4" && echo -n " OK" || echo -n " Failed"
- check_delta "${endpoint}?delta=3&start=4" "seq 5 7" && echo -n " OK" || echo -n " Failed"
- echo ""
-done
-
-echo "----- Transfer idempotence -----"
-DATA="{\"request_uid\":\"0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00\",\"amount\":\"$CURRENCY:0.000034\",\"exchange_base_url\":\"$BASE_URL\",\"wtid\":\"0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00\",\"credit_account\":\"payto://bitcoin/$CLIENT\"}"
-echo -n "Same:"
-test `curl -w %{http_code} -s -o /dev/null -H "Content-Type: application/json" -d $DATA ${BANK_ENDPOINT}transfer` -eq 200 && echo -n " OK" || echo -n " Failed"
-test `curl -w %{http_code} -s -o /dev/null -H "Content-Type: application/json" -d $DATA ${BANK_ENDPOINT}transfer` -eq 200 && echo " OK" || echo " Failed"
-echo -n "Collision:"
-DATA="{\"request_uid\":\"0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00\",\"amount\":\"$CURRENCY:0.000042\",\"exchange_base_url\":\"$BASE_URL\",\"wtid\":\"0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00\",\"credit_account\":\"payto://bitcoin/$CLIENT\"}"
-test `curl -w %{http_code} -s -o /dev/null -H "Content-Type: application/json" -d $DATA ${BANK_ENDPOINT}transfer` -eq 409 && echo " OK" || echo " Failed"
-
-echo "----- Security -----"
-
-# Generate big random file
-printf 'HelloWorld%s' {1..1000} > $TEMP_FILE
-
-echo -n "Handle huge body:"
-test `curl -w %{http_code} -X POST -s -o /dev/null -d @$TEMP_FILE ${BANK_ENDPOINT}transfer` -eq 400 && echo " OK" || echo " Failed"
-
-echo -n "Handle body length liar:"
-test `curl -w %{http_code} -X POST -H"Content-Length:1024" -s -o /dev/null -d @$TEMP_FILE ${BANK_ENDPOINT}transfer` -eq 400 && echo " OK" || echo " Failed"
-
-# Generate compression bomb
-printf 'HelloWorld%s' {1..1000} | pigz -z9 > $TEMP_FILE
-
-echo -n "Handle compression bomb:"
-test `curl -w %{http_code} -X POST -H"Content-Encoding:deflate" -s -o /dev/null --data-binary @$TEMP_FILE ${BANK_ENDPOINT}transfer` -eq 400 && echo " OK" || echo " Failed"
-
-echo "All tests passed!"
diff --git a/test/gateway/auth.sh b/test/gateway/auth.sh
@@ -1,31 +0,0 @@
-#!/bin/bash
-
-## Test wire-gateway authentication
-
-set -eu
-
-source "${BASH_SOURCE%/*}/../common.sh"
-CONFIG=taler_btc_auth.conf
-
-echo "----- Setup -----"
-echo "Load config file"
-load_config
-echo "Start bitcoin node"
-init_btc
-echo "Start gateway"
-gateway
-echo ""
-
-echo "----- Authentication -----"
-
-echo -n "Check 401:"
-test `curl -w %{http_code} -s -o /dev/null ${BANK_ENDPOINT}history/outgoing` -eq 401 && echo " OK" || echo " Failed"
-
-echo -n "Check auth:"
-taler-exchange-wire-gateway-client \
- --config $CONF -s exchange-accountcredentials-admin \
- -C payto://bitcoin/$CLIENT \
- -a $CURRENCY:0.0000$n > /dev/null
-echo " OK"
-
-echo "All tests passed!"
diff --git a/wire-gateway/src/error.rs b/wire-gateway/src/error.rs
@@ -1,128 +1,128 @@
-/*
- 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 hyper::{header, Body, Response, StatusCode};
-use common::{api_common::ErrorDetail, error_codes::ErrorCode};
-
-/// Generic http error
-#[derive(Debug)]
-pub struct ServerError {
- pub status: StatusCode,
- content: Content,
- pub msg: String,
-}
-
-#[derive(Debug)]
-enum Content {
- None,
- Json(Vec<u8>),
-}
-
-impl ServerError {
- fn new(status: StatusCode, content: Content, msg: String) -> Self {
- Self {
- status,
- content,
- msg,
- }
- }
-
- pub fn response(self) -> (Response<Body>, String) {
- let builder = Response::builder().status(self.status);
- let result = match self.content {
- Content::None => builder.body(Body::empty()),
- Content::Json(it) => builder
- .header(header::CONTENT_TYPE, "application/json")
- .body(Body::from(it)),
- };
- (result.unwrap(), self.msg)
- }
-
- pub fn unexpected<E: std::error::Error>(e: E) -> Self {
- Self::new(
- StatusCode::INTERNAL_SERVER_ERROR,
- Content::None,
- format!("unexpected: {}", e),
- )
- }
-
- fn detail(status: StatusCode, code: ErrorCode, msg: String) -> Self {
- let detail = ErrorDetail {
- code: code as i64,
- hint: None,
- detail: None,
- parameter: None,
- path: None,
- offset: None,
- index: None,
- object: None,
- currency: None,
- type_expected: None,
- type_actual: None,
- };
- match serde_json::to_vec(&detail) {
- Ok(json) => Self::new(status, Content::Json(json), msg),
- Err(e) => Self::unexpected(e),
- }
- }
-
- pub fn status(status: StatusCode) -> Self {
- Self::new(
- status,
- Content::None,
- status.canonical_reason().unwrap_or("").to_string(),
- )
- }
-
- pub fn code(status: StatusCode, code: ErrorCode) -> Self {
- Self::detail(
- status,
- code,
- format!(
- "standard {}: {}",
- code as i64,
- status.canonical_reason().unwrap_or("")
- ),
- )
- }
-
- pub fn catch_code<E: std::error::Error>(e: E, status: StatusCode, code: ErrorCode) -> Self {
- Self::detail(status, code, format!("standard {}: {}", code as i64, e))
- }
-}
-
-impl From<tokio_postgres::Error> for ServerError {
- fn from(e: tokio_postgres::Error) -> Self {
- ServerError::catch_code(
- e,
- StatusCode::BAD_GATEWAY,
- ErrorCode::GENERIC_DB_FETCH_FAILED,
- )
- }
-}
-pub trait CatchResult<T: Sized> {
- fn unexpected(self) -> Result<T, ServerError>;
- fn catch_code(self, status: StatusCode, code: ErrorCode) -> Result<T, ServerError>;
-}
-
-impl<T, E: std::error::Error> CatchResult<T> for Result<T, E> {
- fn unexpected(self) -> Result<T, ServerError> {
- self.map_err(|e| ServerError::unexpected(e))
- }
-
- fn catch_code(self, status: StatusCode, code: ErrorCode) -> Result<T, ServerError> {
- self.map_err(|e| ServerError::catch_code(e, status, code))
- }
-}
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+use common::{api_common::ErrorDetail, error_codes::ErrorCode};
+use hyper::{header, Body, Response, StatusCode};
+
+/// Generic http error
+#[derive(Debug)]
+pub struct ServerError {
+ pub status: StatusCode,
+ content: Content,
+ pub msg: String,
+}
+
+#[derive(Debug)]
+enum Content {
+ None,
+ Json(Vec<u8>),
+}
+
+impl ServerError {
+ fn new(status: StatusCode, content: Content, msg: String) -> Self {
+ Self {
+ status,
+ content,
+ msg,
+ }
+ }
+
+ pub fn response(self) -> (Response<Body>, String) {
+ let builder = Response::builder().status(self.status);
+ let result = match self.content {
+ Content::None => builder.body(Body::empty()),
+ Content::Json(it) => builder
+ .header(header::CONTENT_TYPE, "application/json")
+ .body(Body::from(it)),
+ };
+ (result.unwrap(), self.msg)
+ }
+
+ pub fn unexpected<E: std::error::Error>(e: E) -> Self {
+ Self::new(
+ StatusCode::INTERNAL_SERVER_ERROR,
+ Content::None,
+ format!("unexpected: {}", e),
+ )
+ }
+
+ fn detail(status: StatusCode, code: ErrorCode, msg: String) -> Self {
+ let detail = ErrorDetail {
+ code: code as i64,
+ hint: None,
+ detail: None,
+ parameter: None,
+ path: None,
+ offset: None,
+ index: None,
+ object: None,
+ currency: None,
+ type_expected: None,
+ type_actual: None,
+ };
+ match serde_json::to_vec(&detail) {
+ Ok(json) => Self::new(status, Content::Json(json), msg),
+ Err(e) => Self::unexpected(e),
+ }
+ }
+
+ pub fn status(status: StatusCode) -> Self {
+ Self::new(
+ status,
+ Content::None,
+ status.canonical_reason().unwrap_or("").to_string(),
+ )
+ }
+
+ pub fn code(status: StatusCode, code: ErrorCode) -> Self {
+ Self::detail(
+ status,
+ code,
+ format!(
+ "standard {}: {}",
+ code as i64,
+ status.canonical_reason().unwrap_or("")
+ ),
+ )
+ }
+
+ pub fn catch_code<E: std::error::Error>(e: E, status: StatusCode, code: ErrorCode) -> Self {
+ Self::detail(status, code, format!("standard {}: {}", code as i64, e))
+ }
+}
+
+impl From<tokio_postgres::Error> for ServerError {
+ fn from(e: tokio_postgres::Error) -> Self {
+ ServerError::catch_code(
+ e,
+ StatusCode::BAD_GATEWAY,
+ ErrorCode::GENERIC_DB_FETCH_FAILED,
+ )
+ }
+}
+pub trait CatchResult<T: Sized> {
+ fn unexpected(self) -> Result<T, ServerError>;
+ fn catch_code(self, status: StatusCode, code: ErrorCode) -> Result<T, ServerError>;
+}
+
+impl<T, E: std::error::Error> CatchResult<T> for Result<T, E> {
+ fn unexpected(self) -> Result<T, ServerError> {
+ self.map_err(|e| ServerError::unexpected(e))
+ }
+
+ fn catch_code(self, status: StatusCode, code: ErrorCode) -> Result<T, ServerError> {
+ self.map_err(|e| ServerError::catch_code(e, status, code))
+ }
+}