commit ec1d6043820b5a5d37e46fe2d26f228528663d60
parent 87a6363582cf58e8fbefaeb0fdff59ee068237af
Author: Antoine A <>
Date: Thu, 24 Feb 2022 13:59:39 +0100
instrumentation: readme and better code
Diffstat:
5 files changed, 316 insertions(+), 284 deletions(-)
diff --git a/btc-wire/src/lib.rs b/btc-wire/src/lib.rs
@@ -14,14 +14,16 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
use std::str::FromStr;
+use std::sync::atomic::AtomicU16;
use bitcoin::{hashes::hex::FromHex, Address, Amount, Network, Txid};
+use common::api_common::Amount as TalerAmount;
use common::config::BtcConfig;
+use config::BitcoinConfig;
use rpc::{Category, Rpc, Transaction};
-use rpc_utils::{segwit_min_amount, sender_address};
+use rpc_utils::{default_data_dir, segwit_min_amount, sender_address};
use segwit::{decode_segwit_msg, encode_segwit_key};
use taler_utils::taler_to_btc;
-use common::api_common::Amount as TalerAmount;
pub mod config;
pub mod metadata;
@@ -140,11 +142,41 @@ impl Rpc {
}
const DEFAULT_BOUNCE_FEE: &'static str = "BTC:0.00001";
+const DEFAULT_CONFIRMATION: u16 = 6;
-pub fn config_bounce_fee(config: &BtcConfig) -> Amount {
+fn config_bounce_fee(config: &BtcConfig) -> Amount {
let config = config.bounce_fee.as_deref().unwrap_or(DEFAULT_BOUNCE_FEE);
TalerAmount::from_str(&config)
.ok()
.and_then(|a| taler_to_btc(&a).ok())
.expect("config value BOUNCE_FEE is no a valid bitcoin amount")
}
+
+pub struct WireState {
+ pub confirmation: AtomicU16,
+ pub max_confirmation: u16,
+ pub config: BtcConfig,
+ pub btc_config: BitcoinConfig,
+ pub bounce_fee: Amount,
+}
+
+impl WireState {
+ pub fn from_taler_config(config: BtcConfig) -> Self {
+ let data_dir = config
+ .core
+ .data_dir
+ .as_ref()
+ .cloned()
+ .unwrap_or_else(default_data_dir);
+ let btc_config =
+ BitcoinConfig::load(&data_dir).expect("Failed to read bitcoin configuration file");
+ let init_confirmation = config.confirmation.unwrap_or(DEFAULT_CONFIRMATION);
+ Self {
+ confirmation: AtomicU16::new(init_confirmation),
+ max_confirmation: init_confirmation * 2,
+ bounce_fee: config_bounce_fee(&config),
+ config,
+ btc_config,
+ }
+ }
+}
diff --git a/btc-wire/src/main.rs b/btc-wire/src/main.rs
@@ -13,22 +13,22 @@
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 bitcoin::{Amount as BtcAmount, Network};
+use bitcoin::Network;
use btc_wire::{
config::{BitcoinConfig, WIRE_WALLET_NAME},
- config_bounce_fee,
rpc::{self, auto_rpc_common, auto_rpc_wallet, ErrorCode, Rpc},
rpc_utils::default_data_dir,
+ WireState,
};
use clap::StructOpt;
use common::{
- config::{load_btc_config, BtcConfig, Config, CoreConfig},
+ config::{load_btc_config, Config, CoreConfig},
log::log::info,
named_spawn, password,
postgres::{Client, NoTls},
reconnect::auto_reconnect_db,
};
-use std::{path::PathBuf, sync::atomic::AtomicU16};
+use std::path::PathBuf;
use crate::loops::{analysis::analysis, watcher::watcher, worker::worker};
@@ -36,14 +36,6 @@ mod fail_point;
mod loops;
mod sql;
-const DEFAULT_CONFIRMATION: u16 = 6;
-pub struct WireState {
- confirmation: AtomicU16,
- max_confirmation: u16,
- config: BtcConfig,
- bounce_fee: BtcAmount,
-}
-
/// Taler wire for bitcoincore
#[derive(clap::Parser, Debug)]
struct Args {
@@ -168,22 +160,16 @@ fn init(config: Option<PathBuf>, init: Init) {
fn run(config: Option<PathBuf>) {
let config = load_btc_config(config.as_deref());
- let data_dir = config
- .core
- .data_dir
- .as_ref()
- .cloned()
- .unwrap_or_else(default_data_dir);
- let btc_config = BitcoinConfig::load(&data_dir).unwrap();
+ let state: &'static _ = Box::leak(Box::new(WireState::from_taler_config(config)));
#[cfg(feature = "fail")]
- if btc_config.network == Network::Regtest {
+ if state.btc_config.network == Network::Regtest {
common::log::log::warn!("Running with random failures");
} else {
common::log::log::error!("Running with random failures is unsuitable for production");
std::process::exit(1);
}
- let chain_name = match btc_config.network {
+ let chain_name = match state.btc_config.network {
Network::Bitcoin => "main",
Network::Testnet => "test",
Network::Signet => "signet",
@@ -191,17 +177,9 @@ fn run(config: Option<PathBuf>) {
};
info!("Running on {} chain", chain_name);
- let init_confirmation = config.confirmation.unwrap_or(DEFAULT_CONFIRMATION);
- let state: &'static WireState = Box::leak(Box::new(WireState {
- confirmation: AtomicU16::new(init_confirmation),
- max_confirmation: init_confirmation * 2,
- bounce_fee: config_bounce_fee(&config),
- config,
- }));
-
- let rpc_watcher = auto_rpc_common(btc_config.clone());
- let rpc_analysis = auto_rpc_common(btc_config.clone());
- let rpc_worker = auto_rpc_wallet(btc_config, WIRE_WALLET_NAME);
+ let rpc_watcher = auto_rpc_common(state.btc_config.clone());
+ let rpc_analysis = auto_rpc_common(state.btc_config.clone());
+ let rpc_worker = auto_rpc_wallet(state.btc_config.clone(), WIRE_WALLET_NAME);
let db_watcher = auto_reconnect_db(state.config.core.db_url.clone());
let db_analysis = auto_reconnect_db(state.config.core.db_url.clone());
diff --git a/instrumentation/README.md b/instrumentation/README.md
@@ -0,0 +1,20 @@
+# Depolymerizer instrumentation test
+
+Unit tests running on a private development network are meant to test the good
+behavior in case of extreme situation but do not attest our capacity to handle
+real network behavior.
+
+## Install
+
+`cargo install --path instrumentation`
+
+## Run
+
+First, follow a normal setup for the adapter and then run `instrumentation`. The
+tested blockchain will be determined based on the taler configuration.
+
+## Temporary database
+
+If you do not want to use a persistent database for instrumentation tests, there
+is a [script](../script/tmp_db.sh) to generate a temporary database similar to
+unit tests.
diff --git a/instrumentation/src/btc.rs b/instrumentation/src/btc.rs
@@ -0,0 +1,246 @@
+/*
+ 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::Path, time::Duration};
+
+use bitcoin::{Amount, BlockHash, Network, SignedAmount};
+use btc_wire::{
+ metadata::OutMetadata,
+ rpc::{self, Category, ErrorCode, Rpc, Transaction},
+ rpc_utils,
+ taler_utils::{btc_payto_url, btc_to_taler},
+ WireState,
+};
+use common::{
+ api_common::Base32,
+ api_wire::{IncomingBankTransaction, IncomingHistory, OutgoingHistory, TransferRequest},
+ config::load_btc_config,
+ rand_slice,
+};
+
+use crate::print_now;
+
+pub const CLIENT: &str = "client";
+pub const WIRE: &str = "wire";
+
+fn unsigned(amount: SignedAmount) -> Amount {
+ amount.abs().to_unsigned().unwrap()
+}
+
+fn wait_for_pending(since: &mut BlockHash, client_rpc: &mut Rpc, wire_rpc: &mut Rpc) {
+ print_now("Wait for pending transactions mining:");
+ 'l: loop {
+ std::thread::sleep(Duration::from_secs(1)); // Wait for btc-wire to act
+ let sync = client_rpc.list_since_block(Some(&since), 1).unwrap();
+ let sync2 = wire_rpc.list_since_block(Some(&since), 1).unwrap();
+ *since = sync.lastblock;
+ for tx in sync
+ .transactions
+ .into_iter()
+ .chain(sync2.transactions.into_iter())
+ {
+ if tx.confirmations == 0 {
+ client_rpc.wait_for_new_block().unwrap();
+ print_now(".");
+ continue 'l;
+ }
+ }
+ break;
+ }
+ println!("");
+}
+
+pub fn btc_test(config: Option<&Path>, base_url: &str) {
+ let config = load_btc_config(config);
+ let state = WireState::from_taler_config(config);
+
+ if state.btc_config.network == Network::Bitcoin {
+ panic!("You should never run this test on a real bitcoin network");
+ }
+
+ let min_fund = Amount::from_sat(10_000);
+
+ let mut rpc = Rpc::common(&state.btc_config).unwrap();
+ // Load client
+ match rpc.load_wallet(CLIENT) {
+ Ok(_) => {}
+ Err(rpc::Error::RPC {
+ code: ErrorCode::RpcWalletNotFound,
+ ..
+ }) => {
+ rpc.create_wallet(CLIENT, "").unwrap();
+ }
+ Err(rpc::Error::RPC {
+ code: ErrorCode::RpcWalletError | ErrorCode::RpcWalletAlreadyLoaded,
+ ..
+ }) => {}
+ Err(e) => panic!("{}", e),
+ };
+ let mut client_rpc = Rpc::wallet(&state.btc_config, CLIENT).unwrap();
+ let client_addr = client_rpc.gen_addr().unwrap();
+ if client_rpc.get_balance().unwrap() < min_fund {
+ println!(
+ "Client need a minimum of {} BTC to run this test, send coins to this address: {}",
+ min_fund.as_btc(),
+ client_addr
+ );
+ print_now("Waiting for fund:");
+ while client_rpc.get_balance().unwrap() < min_fund {
+ client_rpc.wait_for_new_block().unwrap();
+ print_now(".");
+ }
+ println!("");
+ }
+ let mut since = client_rpc.list_since_block(None, 1).unwrap().lastblock;
+ // Load wire
+ let mut wire_rpc = Rpc::wallet(&state.btc_config, WIRE).unwrap();
+ let wire_addr = wire_rpc.gen_addr().unwrap();
+
+ wait_for_pending(&mut since, &mut client_rpc, &mut wire_rpc);
+
+ // Load balances
+ let client_balance = client_rpc.get_balance().unwrap();
+ let wire_balance = wire_rpc.get_balance().unwrap();
+ // Test amount
+ let test_amount = Amount::from_sat(2000);
+ let min_send_amount = rpc_utils::segwit_min_amount(); // To small to send back
+ let min_bounce_amount = min_send_amount + Amount::from_sat(999); // To small after bounce fee
+ let taler_test_amount = btc_to_taler(&test_amount.to_signed().unwrap());
+
+ println!("Send transaction");
+ let reserve_pub_key = rand_slice();
+ let deposit_id = client_rpc
+ .send_segwit_key(&wire_addr, &test_amount, &reserve_pub_key)
+ .unwrap();
+ let bounce_min_id = client_rpc
+ .send(&wire_addr, &min_bounce_amount, None, false)
+ .unwrap();
+ let send_min_id = client_rpc
+ .send(&wire_addr, &min_send_amount, None, false)
+ .unwrap();
+ let bounce_id = client_rpc
+ .send(&wire_addr, &test_amount, None, false)
+ .unwrap();
+ let client_sent_amount_cost =
+ test_amount + min_send_amount * 2 + min_bounce_amount + min_send_amount + test_amount;
+ let client_sent_fees_cost: Amount = [deposit_id, bounce_min_id, send_min_id, bounce_id]
+ .into_iter()
+ .map(|id| unsigned(client_rpc.get_tx(&id).unwrap().fee.unwrap()))
+ .reduce(|acc, i| acc + i)
+ .unwrap();
+ let new_balance = client_rpc.get_balance().unwrap();
+ assert_eq!(
+ client_balance - client_sent_amount_cost - client_sent_fees_cost,
+ new_balance
+ );
+
+ print_now("Wait for bounce:");
+ let bounce: Transaction = 'l: loop {
+ let sync = client_rpc.list_since_block(Some(&since), 1).unwrap();
+ for tx in sync.transactions {
+ if tx.category == Category::Receive {
+ let (tx, metadata) = client_rpc.get_tx_op_return(&tx.txid).unwrap();
+ let metadata = OutMetadata::decode(&metadata).unwrap();
+ match metadata {
+ OutMetadata::Withdraw { .. } => {}
+ OutMetadata::Bounce { bounced } => {
+ if bounced == bounce_id {
+ break 'l tx;
+ } else if bounced == send_min_id {
+ panic!("Bounced send min");
+ } else if bounced == bounce_min_id {
+ panic!("Bounced bounce min");
+ }
+ }
+ }
+ }
+ }
+ since = sync.lastblock;
+ rpc.wait_for_new_block().unwrap();
+ print_now(".");
+ };
+ println!("");
+ wait_for_pending(&mut since, &mut client_rpc, &mut wire_rpc);
+
+ println!("Check balance");
+ let new_client_balance = client_rpc.get_balance().unwrap();
+ let new_wire_balance = wire_rpc.get_balance().unwrap();
+ assert_eq!(
+ client_balance - client_sent_amount_cost - client_sent_fees_cost + unsigned(bounce.amount),
+ new_client_balance
+ );
+ assert_eq!(
+ wire_balance + test_amount + min_bounce_amount + min_send_amount + state.bounce_fee,
+ new_wire_balance
+ );
+
+ println!("Check history");
+ let history: IncomingHistory = ureq::get(&format!("{}/history/incoming", base_url))
+ .query("delta", "-5")
+ .call()
+ .unwrap()
+ .into_json()
+ .unwrap();
+ assert!(history
+ .incoming_transactions
+ .iter()
+ .find(|h| {
+ matches!(
+ h,
+ IncomingBankTransaction::IncomingReserveTransaction {
+ reserve_pub,
+ amount,
+ ..
+ } if reserve_pub == &Base32::from(reserve_pub_key) && amount == &taler_test_amount
+ )
+ })
+ .is_some());
+
+ println!("Get back some money");
+ let wtid = rand_slice();
+ ureq::post(&format!("{}/transfer", base_url))
+ .send_json(TransferRequest {
+ request_uid: Base32::from(rand_slice()),
+ amount: taler_test_amount.clone(),
+ exchange_base_url: state.config.base_url.clone(),
+ wtid: Base32::from(wtid),
+ credit_account: btc_payto_url(&client_addr),
+ })
+ .unwrap();
+
+ wait_for_pending(&mut since, &mut client_rpc, &mut wire_rpc);
+
+ println!("Check balances");
+ let last_client_balance = client_rpc.get_balance().unwrap();
+ assert_eq!(new_client_balance + test_amount, last_client_balance);
+
+ println!("Check history");
+ let history: OutgoingHistory = ureq::get(&format!("{}/history/outgoing", base_url))
+ .query("delta", "-5")
+ .call()
+ .unwrap()
+ .into_json()
+ .unwrap();
+ assert!(history
+ .outgoing_transactions
+ .iter()
+ .find(|h| {
+ h.wtid == Base32::from(wtid)
+ && h.exchange_base_url == state.config.base_url
+ && h.amount == taler_test_amount
+ })
+ .is_some());
+}
diff --git a/instrumentation/src/main.rs b/instrumentation/src/main.rs
@@ -14,32 +14,13 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-use std::{fmt::Display, io::Write, path::PathBuf, time::Duration};
+use std::{fmt::Display, io::Write, path::PathBuf};
-use bitcoin::{Amount, Network, SignedAmount};
-use btc_wire::{
- config::BitcoinConfig,
- config_bounce_fee,
- metadata::OutMetadata,
- rpc::{self, Category, ErrorCode, Rpc},
- rpc_utils::{self, default_data_dir},
- taler_utils::{btc_payto_url, btc_to_taler},
-};
+use btc::btc_test;
use clap::StructOpt;
-use common::{
- api_common::Base32,
- api_wire::{IncomingBankTransaction, IncomingHistory, OutgoingHistory, TransferRequest},
- config::{load_btc_config, Config, GatewayConfig},
- rand_slice,
- url::Url,
-};
+use common::config::{Config, GatewayConfig};
-pub const CLIENT: &str = "client";
-pub const WIRE: &str = "wire";
-
-fn unsigned(amount: SignedAmount) -> Amount {
- amount.abs().to_unsigned().unwrap()
-}
+mod btc;
fn print_now(disp: impl Display) {
print!("{}", disp);
@@ -61,232 +42,7 @@ pub fn main() {
let base_url = format!("http://localhost:{}", gateway_conf.port);
match gateway_conf.core.currency.as_str() {
- "BTC" => {
- let config = load_btc_config(args.config.as_deref());
- let data_dir = config
- .core
- .data_dir
- .as_ref()
- .cloned()
- .unwrap_or_else(default_data_dir);
- let btc_config = BitcoinConfig::load(&data_dir).unwrap();
-
- if btc_config.network == Network::Bitcoin {
- panic!("You should never run this test on a real bitcoin network");
- }
-
- let bounce_fee = config_bounce_fee(&config);
- let min_fund = Amount::from_sat(10_000);
-
- let mut rpc = Rpc::common(&btc_config).unwrap();
- // Load client
- match rpc.load_wallet(CLIENT) {
- Ok(_) => {}
- Err(rpc::Error::RPC {
- code: ErrorCode::RpcWalletNotFound,
- ..
- }) => {
- rpc.create_wallet(CLIENT, "").unwrap();
- }
- Err(rpc::Error::RPC {
- code: ErrorCode::RpcWalletError | ErrorCode::RpcWalletAlreadyLoaded,
- ..
- }) => {}
- Err(e) => panic!("{}", e),
- };
- let mut client_rpc = Rpc::wallet(&btc_config, CLIENT).unwrap();
- let client_addr = client_rpc.gen_addr().unwrap();
- if client_rpc.get_balance().unwrap() < min_fund {
- println!("Client need a minimum of {} BTC to run this test, send coins to this address: {}", min_fund.as_btc(), client_addr);
- print_now("Waiting for fund:");
- while client_rpc.get_balance().unwrap() < min_fund {
- client_rpc.wait_for_new_block().unwrap();
- print_now(".");
- }
- }
- let mut since = client_rpc.list_since_block(None, 1).unwrap().lastblock;
- // Load wire
- let mut wire_rpc = Rpc::wallet(&btc_config, WIRE).unwrap();
- let wire_addr = wire_rpc.gen_addr().unwrap();
-
- print_now("Wait for pending transactions mining:");
- 'l: loop {
- let sync = client_rpc.list_since_block(Some(&since), 1).unwrap();
- let sync2 = wire_rpc.list_since_block(Some(&since), 1).unwrap();
- since = sync.lastblock;
- for tx in sync
- .transactions
- .into_iter()
- .chain(sync2.transactions.into_iter())
- {
- if tx.confirmations == 0 {
- rpc.wait_for_new_block().unwrap();
- print_now(".");
- continue 'l;
- }
- }
- break;
- }
- println!("");
-
- // Load balances
- let client_balance = client_rpc.get_balance().unwrap();
- let wire_balance = wire_rpc.get_balance().unwrap();
- // Test amount
- let test_amount = Amount::from_sat(2000);
- let min_send_amount = rpc_utils::segwit_min_amount(); // To small to send back
- let min_bounce_amount = rpc_utils::segwit_min_amount() + Amount::from_sat(999); // To small after bounce fee
- let taler_test_amount = btc_to_taler(&test_amount.to_signed().unwrap());
-
- println!("Send transaction");
- let reserve_pub_key = rand_slice();
- let deposit_id = client_rpc
- .send_segwit_key(&wire_addr, &test_amount, &reserve_pub_key)
- .unwrap();
- let bounce_min_id = client_rpc
- .send(&wire_addr, &min_bounce_amount, None, false)
- .unwrap();
- let send_min_id = client_rpc
- .send(&wire_addr, &min_send_amount, None, false)
- .unwrap();
- let bounce_id = client_rpc
- .send(&wire_addr, &test_amount, None, false)
- .unwrap();
- let client_sent_amount_cost = test_amount
- + min_send_amount * 2
- + min_bounce_amount
- + min_send_amount
- + test_amount;
- let client_sent_fees_cost: Amount = [deposit_id, bounce_min_id, send_min_id, bounce_id]
- .into_iter()
- .map(|id| unsigned(client_rpc.get_tx(&id).unwrap().fee.unwrap()))
- .reduce(|acc, i| acc + i)
- .unwrap();
- let new_balance = client_rpc.get_balance().unwrap();
- assert_eq!(
- client_balance - client_sent_amount_cost - client_sent_fees_cost,
- new_balance
- );
-
- print_now("Wait for client mining and bounce:");
- let mut pending = true;
- let mut bounced_id = None;
- while pending || bounced_id.is_none() {
- pending = false;
- std::thread::sleep(Duration::from_secs(1));
- rpc.wait_for_new_block().unwrap();
- print_now(".");
- let sync = client_rpc.list_since_block(Some(&since), 1).unwrap();
- for tx in sync.transactions {
- if tx.confirmations == 0 {
- pending = true;
- } else if tx.category == Category::Receive {
- let (_, metadata) = client_rpc.get_tx_op_return(&tx.txid).unwrap();
- let metadata = OutMetadata::decode(&metadata).unwrap();
- match metadata {
- OutMetadata::Withdraw { .. } => {}
- OutMetadata::Bounce { bounced } => {
- if bounced == bounce_id {
- bounced_id.replace(tx.txid);
- } else if bounced == send_min_id {
- panic!("Bounced send min");
- } else if bounced == bounce_min_id {
- panic!("Bounced bounce min");
- }
- }
- }
- }
- }
- since = sync.lastblock;
- }
- println!("");
-
- println!("Check balance");
- let new_client_balance = client_rpc.get_balance().unwrap();
- let new_wire_balance = wire_rpc.get_balance().unwrap();
- let bounced = client_rpc.get_tx(&bounced_id.unwrap()).unwrap();
- assert_eq!(
- client_balance - client_sent_amount_cost - client_sent_fees_cost
- + unsigned(bounced.amount),
- new_client_balance
- );
- assert_eq!(
- wire_balance + test_amount + min_bounce_amount + min_send_amount + bounce_fee,
- new_wire_balance
- );
-
- println!("Check history");
- let history: IncomingHistory = ureq::get(&format!("{}/history/incoming", base_url))
- .query("delta", "-5")
- .call()
- .unwrap()
- .into_json()
- .unwrap();
- assert!(history
- .incoming_transactions
- .iter()
- .find(|h| {
- matches!(
- h,
- IncomingBankTransaction::IncomingReserveTransaction {
- reserve_pub,
- amount,
- ..
- } if reserve_pub == &Base32::from(reserve_pub_key) && amount == &taler_test_amount
- )
- })
- .is_some());
-
- println!("Get back some money");
- let url = Url::parse("ftp://example.com").unwrap();
- let wtid = rand_slice();
- ureq::post(&format!("{}/transfer", base_url))
- .send_json(TransferRequest {
- request_uid: Base32::from(rand_slice()),
- amount: taler_test_amount.clone(),
- exchange_base_url: url.clone(),
- wtid: Base32::from(wtid),
- credit_account: btc_payto_url(&client_addr),
- })
- .unwrap();
-
- print_now("Wait for wire mining:");
- std::thread::sleep(Duration::from_secs(1));
- 'l1: loop {
- let sync = wire_rpc.list_since_block(Some(&since), 1).unwrap();
- since = sync.lastblock;
- for tx in sync.transactions {
- if tx.confirmations == 0 {
- rpc.wait_for_new_block().unwrap();
- print_now(".");
- continue 'l1;
- }
- }
- break;
- }
- println!("");
-
- println!("Check balances");
- let last_client_balance = client_rpc.get_balance().unwrap();
- assert_eq!(new_client_balance + test_amount, last_client_balance);
-
- println!("Check history");
- let history: OutgoingHistory = ureq::get(&format!("{}/history/outgoing", base_url))
- .query("delta", "-5")
- .call()
- .unwrap()
- .into_json()
- .unwrap();
- assert!(history
- .outgoing_transactions
- .iter()
- .find(|h| {
- h.wtid == Base32::from(wtid)
- && h.exchange_base_url == url
- && h.amount == taler_test_amount
- })
- .is_some());
- }
+ "BTC" => btc_test(args.config.as_deref(), &base_url),
_ => unimplemented!(),
}
}