commit f83ecda42ee694ade4e5f4e16ce019d92b3754f4
parent ec1d6043820b5a5d37e46fe2d26f228528663d60
Author: Antoine A <>
Date: Thu, 24 Feb 2022 17:19:11 +0100
Add eth instrumentation test and fix eth bounce
Diffstat:
12 files changed, 434 insertions(+), 198 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -893,6 +893,8 @@ dependencies = [
"clap 3.1.2",
"common",
"eth-wire",
+ "ethereum-types",
+ "hex",
"ureq",
]
diff --git a/btc-wire/src/rpc.rs b/btc-wire/src/rpc.rs
@@ -46,9 +46,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)
- .map_err(|err| error!("connect RPC: {}", err))
- .ok()?;
+ rpc.load_wallet(wallet).ok();
rpc.unlock_wallet(&password())
.map_err(|err| error!("connect RPC: {}", err))
.ok()?;
diff --git a/eth-wire/src/bin/eth-wire-utils.rs b/eth-wire/src/bin/eth-wire-utils.rs
@@ -108,7 +108,7 @@ fn main() {
.as_ref()
.and_then(|it| it.data_dir.as_deref())
.or(args.datadir.as_deref());
- let mut rpc = Rpc::new(data_dir.unwrap().join("geth.ipc")).unwrap();
+ let mut rpc = Rpc::new(data_dir.unwrap_or(&PathBuf::from("/tmp/")).join("geth.ipc")).unwrap();
let passwd = password();
match args.cmd {
Cmd::Deposit(TransactionCmd {
@@ -143,7 +143,6 @@ fn main() {
to,
value,
nonce: None,
- gas: None,
gas_price: None,
data: Hex(vec![]),
})
@@ -188,7 +187,7 @@ fn main() {
}
Cmd::Balance { addr } => {
let addr = H160::from_str(&addr).unwrap();
- let balance = rpc.balance(&addr).unwrap();
+ let balance = rpc.get_balance(&addr).unwrap();
println!("{}", (balance / 10_000_000_000u64).as_u64());
}
Cmd::Connect { datadir } => {
@@ -215,7 +214,6 @@ fn main() {
from,
to: tx.to.unwrap(),
value: U256::zero(),
- gas: None,
gas_price: Some(U256::from(1u8)), // Bigger gas price to replace fee
data: Hex(vec![]),
nonce: Some(tx.nonce),
diff --git a/eth-wire/src/lib.rs b/eth-wire/src/lib.rs
@@ -14,17 +14,18 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-use common::url::Url;
-use ethereum_types::{Address, H256, U256, U64};
+use std::{str::FromStr, sync::atomic::AtomicU16};
+
+use common::{api_common::Amount, config::EthConfig, url::Url};
+use ethereum_types::{Address, H160, H256, U256, U64};
use metadata::{InMetadata, OutMetadata};
-use rpc::hex::Hex;
+use rpc::{hex::Hex, Transaction};
+use taler_util::{eth_payto_addr, taler_to_eth};
pub mod metadata;
pub mod rpc;
pub mod taler_util;
-const GWEI: u64 = 1_000_000_000;
-
impl rpc::Rpc {
pub fn deposit(
&mut self,
@@ -39,7 +40,6 @@ impl rpc::Rpc {
to,
value,
nonce: None,
- gas: None,
gas_price: None,
data: Hex(metadata.encode()),
})
@@ -59,7 +59,6 @@ impl rpc::Rpc {
to,
value,
nonce: None,
- gas: None,
gas_price: None,
data: Hex(metadata.encode()),
})
@@ -76,7 +75,6 @@ impl rpc::Rpc {
to: tx.from.expect("Cannot bounce coinbase transaction"),
value: bounce_value,
nonce: None,
- gas: None,
gas_price: None,
data: Hex(metadata.encode()),
};
@@ -85,13 +83,80 @@ impl rpc::Rpc {
// Deduce fee price from bounced value
request.value = request
.value
- .saturating_sub(fill.tx.gas * GWEI * fill.tx.gas_price);
+ .saturating_sub(fill.tx.gas * fill.tx.gas_price.or(fill.tx.max_fee_per_gas).unwrap());
Ok(if request.value.is_zero() {
None
} else {
Some(self.send_transaction(&request)?)
})
}
+
+ /// List new and removed transaction since the last sync state, returning a new sync state
+ pub fn list_since_sync_state(
+ &mut self,
+ address: &Address,
+ state: SyncState,
+ min_confirmation: u16,
+ ) -> rpc::Result<(Vec<(Transaction, u16)>, Vec<(Transaction, u16)>, SyncState)> {
+ let match_tx = |txs: Vec<Transaction>, conf: u16| -> Vec<(Transaction, u16)> {
+ txs.into_iter()
+ .filter_map(|tx| {
+ (tx.from == Some(*address) || tx.to == Some(*address)).then(|| (tx, conf))
+ })
+ .collect()
+ };
+
+ let mut txs = Vec::new();
+ let mut removed = Vec::new();
+
+ // Add pending transaction
+ txs.extend(match_tx(self.pending_transactions()?, 0));
+
+ let latest = self.latest_block()?;
+
+ let mut confirmation = 1;
+ let mut chain_cursor = latest.clone();
+
+ // Move until tip height
+ while chain_cursor.number.unwrap() != state.tip_height {
+ txs.extend(match_tx(chain_cursor.transactions, confirmation));
+ chain_cursor = self.block(&chain_cursor.parent_hash)?.unwrap();
+ confirmation += 1;
+ }
+
+ // Check if fork
+ if chain_cursor.hash.unwrap() != state.tip_hash {
+ let mut fork_cursor = self.block(&state.tip_hash)?.unwrap();
+ // Move until found common parent
+ while fork_cursor.hash != chain_cursor.hash {
+ txs.extend(match_tx(chain_cursor.transactions, confirmation));
+ removed.extend(match_tx(fork_cursor.transactions, confirmation));
+ chain_cursor = self.block(&chain_cursor.parent_hash)?.unwrap();
+ fork_cursor = self.block(&fork_cursor.parent_hash)?.unwrap();
+ confirmation += 1;
+ }
+ }
+
+ // Move until last conf
+ while chain_cursor.number.unwrap() > state.conf_height {
+ txs.extend(match_tx(chain_cursor.transactions, confirmation));
+ chain_cursor = self.block(&chain_cursor.parent_hash)?.unwrap();
+ confirmation += 1;
+ }
+
+ Ok((
+ txs,
+ removed,
+ SyncState {
+ tip_hash: latest.hash.unwrap(),
+ tip_height: latest.number.unwrap(),
+ conf_height: latest
+ .number
+ .unwrap()
+ .saturating_sub(U64::from(min_confirmation)),
+ },
+ ))
+ }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -119,6 +184,37 @@ impl SyncState {
}
}
+const DEFAULT_CONFIRMATION: u16 = 24;
+const DEFAULT_BOUNCE_FEE: &'static str = "ETH:0.00001";
+pub struct WireState {
+ pub confirmation: AtomicU16,
+ pub max_confirmations: u16,
+ pub address: H160,
+ pub config: EthConfig,
+ pub bounce_fee: U256,
+}
+
+impl WireState {
+ pub fn from_taler_config(config: EthConfig) -> Self {
+ let init_confirmation = config.confirmation.unwrap_or(DEFAULT_CONFIRMATION);
+ Self {
+ confirmation: AtomicU16::new(init_confirmation),
+ max_confirmations: init_confirmation * 2,
+ address: eth_payto_addr(&config.payto).unwrap(),
+ bounce_fee: config_bounce_fee(&config),
+ config,
+ }
+ }
+}
+
+fn config_bounce_fee(config: &EthConfig) -> U256 {
+ let config = config.bounce_fee.as_deref().unwrap_or(DEFAULT_BOUNCE_FEE);
+ Amount::from_str(&config)
+ .ok()
+ .and_then(|a| taler_to_eth(&a).ok())
+ .expect("config value BOUNCE_FEE is no a valid ethereum amount")
+}
+
#[cfg(test)]
mod test {
use common::{rand::random, rand_slice};
diff --git a/eth-wire/src/loops/worker.rs b/eth-wire/src/loops/worker.rs
@@ -29,7 +29,7 @@ use eth_wire::{
taler_util::{eth_payto_url, eth_to_taler},
SyncState,
};
-use ethereum_types::{Address, H256, U256, U64};
+use ethereum_types::{Address, H256, U256};
use crate::{
fail_point::fail_point,
@@ -119,73 +119,6 @@ pub fn worker(mut rpc: AutoRpcWallet, mut db: AutoReconnectDb, state: &WireState
}
}
-/// List new and removed transaction since the last sync state, returning a new sync state
-fn list_since_block_state(
- rpc: &mut Rpc,
- address: &Address,
- state: SyncState,
- min_confirmation: u16,
-) -> LoopResult<(Vec<(Transaction, u16)>, Vec<(Transaction, u16)>, SyncState)> {
- let match_tx = |txs: Vec<Transaction>, conf: u16| -> Vec<(Transaction, u16)> {
- txs.into_iter()
- .filter_map(|tx| {
- (tx.from == Some(*address) || tx.to == Some(*address)).then(|| (tx, conf))
- })
- .collect()
- };
-
- let mut txs = Vec::new();
- let mut removed = Vec::new();
-
- // Add pending transaction
- txs.extend(match_tx(rpc.pending_transactions()?, 0));
-
- let latest = rpc.latest_block()?;
-
- let mut confirmation = 1;
- let mut chain_cursor = latest.clone();
-
- // Move until tip height
- while chain_cursor.number.unwrap() != state.tip_height {
- txs.extend(match_tx(chain_cursor.transactions, confirmation));
- chain_cursor = rpc.block(&chain_cursor.parent_hash)?.unwrap();
- confirmation += 1;
- }
-
- // Check if fork
- if chain_cursor.hash.unwrap() != state.tip_hash {
- let mut fork_cursor = rpc.block(&state.tip_hash)?.unwrap();
- // Move until found common parent
- while fork_cursor.hash != chain_cursor.hash {
- txs.extend(match_tx(chain_cursor.transactions, confirmation));
- removed.extend(match_tx(fork_cursor.transactions, confirmation));
- chain_cursor = rpc.block(&chain_cursor.parent_hash)?.unwrap();
- fork_cursor = rpc.block(&fork_cursor.parent_hash)?.unwrap();
- confirmation += 1;
- }
- }
-
- // Move until last conf
- while chain_cursor.number.unwrap() > state.conf_height {
- txs.extend(match_tx(chain_cursor.transactions, confirmation));
- chain_cursor = rpc.block(&chain_cursor.parent_hash)?.unwrap();
- confirmation += 1;
- }
-
- Ok((
- txs,
- removed,
- SyncState {
- tip_hash: latest.hash.unwrap(),
- tip_height: latest.number.unwrap(),
- conf_height: latest
- .number
- .unwrap()
- .saturating_sub(U64::from(min_confirmation)),
- },
- ))
-}
-
/// Parse new transactions, return true if the database is up to date with the latest mined block
fn sync_chain(
rpc: &mut Rpc,
@@ -199,7 +132,7 @@ fn sync_chain(
let min_confirmations = state.confirmation.load(Ordering::SeqCst);
let (txs, removed, next_state) =
- list_since_block_state(rpc, &state.address, block, min_confirmations)?;
+ rpc.list_since_sync_state(&state.address, block, min_confirmations)?;
// Check if a confirmed incoming transaction have been removed by a blockchain reorganisation
let new_status = sync_chain_removed(&txs, &removed, db, &state.address, min_confirmations)?;
@@ -520,7 +453,7 @@ fn bounce(db: &mut Client, rpc: &mut Rpc, fee: U256) -> LoopResult<bool> {
"UPDATE bounce SET status=$1 WHERE id=$2",
&[&(BounceStatus::Ignored as i16), &id],
)?;
- info!("|| (ignore) {} ", &bounced);
+ info!("|| (ignore) {} ", hex::encode(bounced));
}
}
}
diff --git a/eth-wire/src/main.rs b/eth-wire/src/main.rs
@@ -14,47 +14,26 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-use std::{path::PathBuf, str::FromStr, sync::atomic::AtomicU16};
+use std::path::PathBuf;
use clap::StructOpt;
use common::{
- api_common::Amount,
- config::{load_eth_config, Config, CoreConfig, EthConfig},
+ config::{load_eth_config, Config, CoreConfig},
named_spawn, password,
postgres::{Client, NoTls},
reconnect::auto_reconnect_db,
};
use eth_wire::{
rpc::{auto_rpc_common, auto_rpc_wallet, Rpc},
- taler_util::{eth_payto_addr, taler_to_eth},
- SyncState,
+ SyncState, WireState,
};
-use ethereum_types::{H160, U256};
+use ethereum_types::H160;
use loops::{analysis::analysis, watcher::watcher, worker::worker};
mod fail_point;
mod loops;
mod sql;
-const DEFAULT_CONFIRMATION: u16 = 24;
-const DEFAULT_BOUNCE_FEE: &'static str = "ETH:0.00001";
-
-pub struct WireState {
- confirmation: AtomicU16,
- max_confirmations: u16,
- address: H160,
- config: EthConfig,
- bounce_fee: U256,
-}
-
-fn config_bounce_fee(config: &EthConfig) -> U256 {
- let config = config.bounce_fee.as_deref().unwrap_or(DEFAULT_BOUNCE_FEE);
- Amount::from_str(&config)
- .ok()
- .and_then(|a| taler_to_eth(&a).ok())
- .expect("config value BOUNCE_FEE is no a valid ethereum amount")
-}
-
/// Taler wire for geth
#[derive(clap::Parser, Debug)]
struct Args {
@@ -89,8 +68,13 @@ fn init(config: Option<PathBuf>, init: Init) {
// Connect to database
let mut db = Client::connect(&config.db_url, NoTls).expect("Failed to connect to database");
// Connect to ethereum node
- let mut rpc = Rpc::new(config.data_dir.unwrap().join("geth.ipc"))
- .expect("Failed to connect to ethereum RPC server");
+ let mut rpc = Rpc::new(
+ config
+ .data_dir
+ .unwrap_or(PathBuf::from("/tmp/"))
+ .join("geth.ipc"),
+ )
+ .expect("Failed to connect to ethereum RPC server");
match init {
Init::Initdb => {
@@ -111,12 +95,11 @@ fn init(config: Option<PathBuf>, init: Init) {
tip_height: block.number.unwrap(),
conf_height: block.number.unwrap(),
};
- db
- .execute(
- "INSERT INTO state (name, value) VALUES ('sync', $1) ON CONFLICT (name) DO NOTHING",
- &[&state.to_bytes().as_ref()],
- )
- .expect("Failed to update database state");
+ db.execute(
+ "INSERT INTO state (name, value) VALUES ('sync', $1) ON CONFLICT (name) DO NOTHING",
+ &[&state.to_bytes().as_ref()],
+ )
+ .expect("Failed to update database state");
println!("Database initialised");
}
Init::Initwallet => {
@@ -172,18 +155,33 @@ fn init(config: Option<PathBuf>, init: Init) {
fn run(config: Option<PathBuf>) {
let config = load_eth_config(config.as_deref());
- let init_confirmation = config.confirmation.unwrap_or(DEFAULT_CONFIRMATION);
- let state: &'static WireState = Box::leak(Box::new(WireState {
- confirmation: AtomicU16::new(init_confirmation),
- max_confirmations: init_confirmation * 2,
- address: eth_payto_addr(&config.payto).unwrap(),
- bounce_fee: config_bounce_fee(&config),
- config,
- }));
-
- let rpc_worker = auto_rpc_wallet(state.config.core.data_dir.clone().unwrap(), state.address);
- let rpc_analysis = auto_rpc_common(state.config.core.data_dir.clone().unwrap());
- let rpc_watcher = auto_rpc_common(state.config.core.data_dir.clone().unwrap());
+ let state: &'static _ = Box::leak(Box::new(WireState::from_taler_config(config)));
+
+ let rpc_worker = auto_rpc_wallet(
+ state
+ .config
+ .core
+ .data_dir
+ .clone()
+ .unwrap_or(PathBuf::from("/tmp/")),
+ state.address,
+ );
+ let rpc_analysis = auto_rpc_common(
+ state
+ .config
+ .core
+ .data_dir
+ .clone()
+ .unwrap_or(PathBuf::from("/tmp/")),
+ );
+ let rpc_watcher = auto_rpc_common(
+ state
+ .config
+ .core
+ .data_dir
+ .clone()
+ .unwrap_or(PathBuf::from("/tmp/")),
+ );
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/eth-wire/src/rpc.rs b/eth-wire/src/rpc.rs
@@ -224,6 +224,13 @@ impl Rpc {
}
}
+ pub fn get_transaction_receipt(&mut self, hash: &H256) -> Result<Option<TransactionReceipt>> {
+ match self.call("eth_getTransactionReceipt", &[hash]) {
+ Err(Error::Null) => Ok(None),
+ r => r,
+ }
+ }
+
pub fn fill_transaction(&mut self, params: &TransactionRequest) -> Result<Filled> {
self.call("eth_fillTransaction", &[params])
}
@@ -271,7 +278,7 @@ impl Rpc {
self.call("eth_getBlockByNumber", &("earliest", &true))
}
- pub fn balance(&mut self, addr: &Address) -> Result<U256> {
+ pub fn get_balance(&mut self, addr: &Address) -> Result<U256> {
self.call("eth_getBalance", &(addr, "latest"))
}
@@ -364,6 +371,17 @@ pub struct Transaction {
pub input: Hex,
}
+/// Description of a Transaction, pending or in the chain.
+#[derive(Debug, Clone, serde::Deserialize)]
+pub struct TransactionReceipt {
+ /// Gas used by this transaction alone.
+ #[serde(rename = "gasUsed")]
+ pub gas_used: U256,
+ /// Effective gas price
+ #[serde(rename = "effectiveGasPrice")]
+ pub effective_gas_price: Option<U256>,
+}
+
/// Fill result
#[derive(Debug, serde::Deserialize)]
pub struct Filled {
@@ -377,7 +395,10 @@ pub struct FilledGas {
pub gas: U256,
/// Gas price
#[serde(rename = "gasPrice")]
- pub gas_price: U256,
+ pub gas_price: Option<U256>,
+ /// Max fee per gas
+ #[serde(rename = "maxFeePerGas")]
+ pub max_fee_per_gas: Option<U256>,
}
/// Send Transaction Parameters
@@ -389,11 +410,7 @@ pub struct TransactionRequest {
pub to: Address,
/// Transferred value
pub value: U256,
- /// Supplied gas (None for sensible default)
- #[serde(skip_serializing_if = "Option::is_none")]
- pub gas: Option<U256>,
/// Gas price (None for sensible default)
- #[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "gasPrice")]
pub gas_price: Option<U256>,
/// Transaction data
diff --git a/eth-wire/src/taler_util.rs b/eth-wire/src/taler_util.rs
@@ -18,8 +18,8 @@ use std::str::FromStr;
use ethereum_types::{Address, U256};
use common::{api_common::Amount, url::Url};
-const WEI: u64 = 1_000_000_000_000_000_000;
-const TRUNC: u64 = 10_000_000_000;
+pub const WEI: u64 = 1_000_000_000_000_000_000;
+pub const TRUNC: u64 = 10_000_000_000;
/// Generate a payto uri from an eth address
pub fn eth_payto_url(addr: &Address) -> Url {
diff --git a/instrumentation/Cargo.toml b/instrumentation/Cargo.toml
@@ -11,11 +11,10 @@ clap = { version = "3.1.1", features = ["derive"] }
common = { path = "../common" }
# Bitcoin
btc-wire = { path = "../btc-wire" }
-bitcoin = { version = "0.27.1", features = [
- "std",
- "use-serde",
-], default-features = false }
+bitcoin = { version = "0.27.1", default-features = false }
# Ethereum
eth-wire = { path = "../eth-wire" }
+ethereum-types = { version = "0.13.0", default-features = false }
+hex = "0.4.3"
# Wire Gateway
ureq = { version = "2.4.0", features = ["json"] }
diff --git a/instrumentation/src/btc.rs b/instrumentation/src/btc.rs
@@ -24,14 +24,9 @@ use btc_wire::{
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 common::{config::load_btc_config, rand_slice};
-use crate::print_now;
+use crate::{check_incoming, check_outgoing, print_now, transfer};
pub const CLIENT: &str = "client";
pub const WIRE: &str = "wire";
@@ -187,60 +182,22 @@ pub fn btc_test(config: Option<&Path>, base_url: &str) {
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());
+ check_incoming(base_url, &reserve_pub_key, &taler_test_amount);
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();
-
+ transfer(
+ base_url,
+ &wtid,
+ &state.config.base_url,
+ btc_payto_url(&client_addr),
+ &taler_test_amount,
+ );
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());
+ check_outgoing(base_url, &wtid, &state.config.base_url, &taler_test_amount);
}
diff --git a/instrumentation/src/eth.rs b/instrumentation/src/eth.rs
@@ -0,0 +1,176 @@
+use std::{
+ path::{Path, PathBuf},
+ time::Duration,
+};
+
+use common::{config::load_eth_config, rand_slice};
+use eth_wire::{
+ metadata::OutMetadata,
+ rpc::{hex::Hex, Rpc, TransactionRequest},
+ taler_util::{eth_to_taler, TRUNC, eth_payto_url},
+ SyncState, WireState,
+};
+use ethereum_types::U256;
+
+use crate::{check_incoming, check_outgoing, print_now, transfer};
+
+fn wait_for_pending(rpc: &mut Rpc) {
+ print_now("Wait for pending transactions mining:");
+ let mut notifier = rpc.subscribe_new_head().unwrap();
+ std::thread::sleep(Duration::from_secs(1)); // Wait for eth-wire to act
+ while !rpc.pending_transactions().unwrap().is_empty() {
+ notifier.next().unwrap();
+ print_now(".");
+ std::thread::sleep(Duration::from_secs(1)); // Wait for eth-wire to act
+ }
+ println!("");
+}
+
+pub fn eth_test(config: Option<&Path>, base_url: &str) {
+ let config = load_eth_config(config);
+ let state = WireState::from_taler_config(config);
+ // TODO eth network check
+
+ let min_fund = U256::from(10_000 * TRUNC);
+ let test_amount = U256::from(2_000 * TRUNC);
+ let taler_test_amount = eth_to_taler(&test_amount);
+
+ let mut rpc = Rpc::new(
+ state
+ .config
+ .core
+ .data_dir
+ .unwrap_or_else(|| PathBuf::from("/tmp/geth.ipc")),
+ )
+ .unwrap();
+
+ // Load client
+ let client = rpc
+ .list_accounts()
+ .unwrap()
+ .into_iter()
+ .skip(1) // Skip etherbase if dev network
+ .filter(|addr| addr != &state.address) // Skip wire
+ .next();
+ let client_addr = match client {
+ Some(addr) => addr,
+ None => rpc.new_account("password").unwrap(),
+ };
+ rpc.unlock_account(&client_addr, "password").unwrap();
+ if rpc.get_balance(&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(),
+ hex::encode(client_addr)
+ );
+ print_now("Waiting for fund:");
+ let mut notifier = rpc.subscribe_new_head().unwrap();
+ while rpc.get_balance(&client_addr).unwrap() < min_fund {
+ notifier.next().unwrap();
+ print_now(".");
+ }
+ 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();
+ // Start sync state
+ let latest = rpc.latest_block().unwrap();
+ let mut sync_state = SyncState {
+ tip_hash: latest.hash.unwrap(),
+ tip_height: latest.number.unwrap(),
+ conf_height: latest.number.unwrap(),
+ };
+
+ println!("Send transaction");
+ let reserve_pub_key = rand_slice();
+ let deposit_id = rpc
+ .deposit(client_addr, state.address, test_amount, reserve_pub_key)
+ .unwrap();
+ let zero_id = rpc
+ .send_transaction(&TransactionRequest {
+ from: client_addr,
+ to: state.address,
+ value: U256::zero(),
+ gas_price: None,
+ data: Hex(vec![]),
+ nonce: None,
+ })
+ .unwrap();
+ let bounce_id = rpc
+ .send_transaction(&TransactionRequest {
+ from: client_addr,
+ to: state.address,
+ value: test_amount,
+ gas_price: None,
+ data: Hex(vec![]),
+ nonce: None,
+ })
+ .unwrap();
+ print_now("Wait for bounce:");
+ let bounce = {
+ let mut notifier = rpc.subscribe_new_head().unwrap();
+ 'l: loop {
+ let (transactions, _, new_state) = rpc
+ .list_since_sync_state(&state.address, sync_state, 0)
+ .unwrap();
+ sync_state = new_state;
+ for (tx, _) in transactions {
+ if tx.to.unwrap() == client_addr && tx.from.unwrap() == state.address {
+ let metadata = OutMetadata::decode(&tx.input).unwrap();
+ match metadata {
+ OutMetadata::Withdraw { .. } => {}
+ OutMetadata::Bounce { bounced } => {
+ if bounced == bounce_id {
+ break 'l tx;
+ } else if bounced == zero_id {
+ panic!("Bounced zero");
+ }
+ }
+ }
+ }
+ }
+ notifier.next().unwrap();
+ print_now(".");
+ }
+ };
+ 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 client_sent_amount_cost = test_amount * U256::from(2u8);
+ let client_sent_fees_cost = [deposit_id, zero_id, bounce_id]
+ .into_iter()
+ .map(|id| {
+ let receipt = rpc.get_transaction_receipt(&id).unwrap().unwrap();
+ receipt.gas_used * receipt.effective_gas_price.unwrap()
+ })
+ .reduce(|acc, i| acc + i)
+ .unwrap();
+ assert_eq!(
+ client_balance - client_sent_amount_cost - client_sent_fees_cost + bounce.value,
+ new_client_balance
+ );
+ let receipt = rpc.get_transaction_receipt(&bounce.hash).unwrap().unwrap();
+ let bounced_fee = receipt.gas_used * receipt.effective_gas_price.unwrap();
+ assert_eq!(
+ wire_balance + test_amount + (test_amount - bounce.value - bounced_fee),
+ new_wire_balance
+ );
+
+ check_incoming(base_url, &reserve_pub_key, &taler_test_amount);
+
+ let wtid = rand_slice();
+ transfer(base_url, &wtid, &state.config.base_url, eth_payto_url(&client_addr), &taler_test_amount);
+ wait_for_pending(&mut rpc);
+
+ println!("Check balances");
+ let last_client_balance = rpc.get_balance(&client_addr).unwrap();
+ assert_eq!(new_client_balance + test_amount, last_client_balance);
+
+ check_outgoing(base_url, &wtid, &state.config.base_url, &taler_test_amount);
+}
diff --git a/instrumentation/src/main.rs b/instrumentation/src/main.rs
@@ -18,15 +18,76 @@ use std::{fmt::Display, io::Write, path::PathBuf};
use btc::btc_test;
use clap::StructOpt;
-use common::config::{Config, GatewayConfig};
+use common::{
+ api_common::{Amount, Base32},
+ api_wire::{IncomingBankTransaction, IncomingHistory, OutgoingHistory, TransferRequest},
+ config::{Config, GatewayConfig},
+ url::Url, rand_slice,
+};
+use eth::eth_test;
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()
+ .find(|h| {
+ matches!(
+ h,
+ IncomingBankTransaction::IncomingReserveTransaction {
+ reserve_pub,
+ amount,
+ ..
+ } if reserve_pub == &Base32::from(*reserve_pub_key) && amount == taler_amount
+ )
+ })
+ .is_some());
+}
+
+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()
+ .find(|h| {
+ h.wtid == Base32::from(*wtid) && &h.exchange_base_url == url && &h.amount == amount
+ })
+ .is_some());
+}
+
/// Depolymerizer instrumentation test
#[derive(clap::Parser, Debug)]
struct Args {
@@ -43,6 +104,7 @@ pub fn main() {
match gateway_conf.core.currency.as_str() {
"BTC" => btc_test(args.config.as_deref(), &base_url),
+ "ETH" => eth_test(args.config.as_deref(), &base_url),
_ => unimplemented!(),
}
}