depolymerization

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

commit cc676a94ff5ed6917afbd9021d59f5c1622b3167
parent d38811ca846872386824a3f053d018727bfa4e20
Author: Antoine A <>
Date:   Wed,  9 Feb 2022 14:30:10 +0100

eth-wire: zero-cost bounce and handle attacks

Diffstat:
Meth-wire/src/bin/eth-wire-utils.rs | 31++++++++++++++++++++++++++++++-
Meth-wire/src/lib.rs | 21++++++++++++++++++---
Meth-wire/src/loops/worker.rs | 26++++++++++++--------------
Meth-wire/src/rpc.rs | 27++++++++++++++++++++++++++-
Mtest/btc/analysis.sh | 4++--
Mtest/btc/bumpfee.sh | 2+-
Mtest/btc/conflict.sh | 6+++---
Mtest/btc/hell.sh | 16+++++++---------
Mtest/btc/lifetime.sh | 2+-
Mtest/btc/maxfee.sh | 2+-
Mtest/btc/reconnect.sh | 4++--
Mtest/btc/reorg.sh | 2+-
Mtest/btc/stress.sh | 2+-
Mtest/btc/wire.sh | 2+-
Atest/eth/hell.sh | 107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/eth/lifetime.sh | 2+-
Mtest/eth/reorg.sh | 4++--
Mtest/eth/stress.sh | 4++--
Mtest/eth/wire.sh | 4++--
19 files changed, 220 insertions(+), 48 deletions(-)

diff --git a/eth-wire/src/bin/eth-wire-utils.rs b/eth-wire/src/bin/eth-wire-utils.rs @@ -27,7 +27,7 @@ use eth_wire::{ taler_util::taler_to_eth, SyncState, }; -use ethereum_types::H160; +use ethereum_types::{H160, U256}; #[derive(argh::FromArgs)] /// Euthereum wire test client @@ -49,6 +49,7 @@ enum Cmd { Balance(BalanceCmd), Connect(ConnectCmd), Disconnect(DisconnectCmd), + Abandon(AbandonCmd), } #[derive(argh::FromArgs)] @@ -141,6 +142,15 @@ struct DisconnectCmd { datadir: PathBuf, } +#[derive(argh::FromArgs)] +#[argh(subcommand, name = "abandon")] +/// Abandon all unconfirmed transaction +struct AbandonCmd { + #[argh(positional)] + /// sender address + from: String, +} + fn main() { init(); let args: Args = argh::from_env(); @@ -177,6 +187,7 @@ fn main() { from, to, value, + nonce: None, gas: None, gas_price: None, data: Hex(vec![]), @@ -240,5 +251,23 @@ fn main() { enode.set_host(Some("127.0.0.1")).unwrap(); assert!(rpc.remove_peer(&enode).unwrap()); } + Cmd::Abandon(AbandonCmd { from }) => { + let from = H160::from_str(&from).unwrap(); + rpc.unlock_account(&from, "password").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: None, + gas_price: Some(U256::from(1)), // Bigger gas price to replace fee + data: Hex(vec![]), + nonce: Some(tx.nonce), + }) + .unwrap(); + } + } } } diff --git a/eth-wire/src/lib.rs b/eth-wire/src/lib.rs @@ -23,6 +23,8 @@ 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, @@ -36,6 +38,7 @@ impl rpc::Rpc { from, to, value, + nonce: None, gas: None, gas_price: None, data: Hex(metadata.encode()), @@ -55,26 +58,38 @@ impl rpc::Rpc { from, to, value, + nonce: None, gas: None, gas_price: None, data: Hex(metadata.encode()), }) } - pub fn bounce(&mut self, hash: H256, bounce_fee: U256) -> rpc::Result<H256> { + pub fn bounce(&mut self, hash: H256, bounce_fee: U256) -> rpc::Result<Option<H256>> { let tx = self .get_transaction(&hash)? .expect("Cannot bounce a non existent transaction"); let bounce_value = tx.value.saturating_sub(bounce_fee); let metadata = OutMetadata::Bounce { bounced: hash }; - // TODO do not bounce empty amount - self.send_transaction(&rpc::TransactionRequest { + let mut request = rpc::TransactionRequest { from: tx.to.expect("Cannot bounce contract transaction"), to: tx.from.expect("Cannot bounce coinbase transaction"), value: bounce_value, + nonce: None, gas: None, gas_price: None, data: Hex(metadata.encode()), + }; + // Estimate fee price using node + let fill = self.fill_transaction(&request)?; + // Deduce fee price from bounced value + request.value = request + .value + .saturating_sub(fill.tx.gas * GWEI * fill.tx.gas_price); + Ok(if request.value.is_zero() { + None + } else { + Some(self.send_transaction(&request)?) }) } } diff --git a/eth-wire/src/loops/worker.rs b/eth-wire/src/loops/worker.rs @@ -499,25 +499,23 @@ fn bounce(db: &mut Client, rpc: &mut Rpc, fee: U256) -> LoopResult<bool> { let id: i32 = row.get(0); let bounced: H256 = sql_hash(row, 1); - match rpc.bounce(bounced, fee) { - Ok(it) => { + let bounce = rpc.bounce(bounced, fee)?; + match bounce { + Some(hash) => { fail_point("(injected) fail bounce", 0.3)?; db.execute( "UPDATE bounce SET txid=$1, status=$2 WHERE id=$3", - &[&it.as_ref(), &(BounceStatus::Sent as i16), &id], + &[&hash.as_ref(), &(BounceStatus::Sent as i16), &id], )?; - info!("|| {} in {}", hex::encode(&bounced), hex::encode(&it)); + info!("|| {} in {}", hex::encode(&bounced), hex::encode(&hash)); + } + None => { + db.execute( + "UPDATE bounce SET status=$1 WHERE id=$2", + &[&(BounceStatus::Ignored as i16), &id], + )?; + info!("|| (ignore) {} ", &bounced); } - Err(err) => match err { - rpc::Error::RPC { code, msg } => { - db.execute( - "UPDATE bounce SET status=$1 WHERE id=$2", - &[&(BounceStatus::Ignored as i16), &id], - )?; - info!("|| (ignore) {} because {} {}", &bounced, code, msg); - } - e => Err(e)?, - }, } } Ok(row.is_some()) diff --git a/eth-wire/src/rpc.rs b/eth-wire/src/rpc.rs @@ -22,7 +22,7 @@ use common::{log::log::error, reconnect::AutoReconnect, url::Url}; use ethereum_types::{Address, H256, U256, U64}; use serde::de::DeserializeOwned; -use serde_json::error::Category; +use serde_json::{error::Category, Value}; use std::{ fmt::Debug, io::{self, BufWriter, ErrorKind, Read, Write}, @@ -209,6 +209,10 @@ impl Rpc { } } + pub fn fill_transaction(&mut self, params: &TransactionRequest) -> Result<Filled> { + self.call("eth_fillTransaction", &[params]) + } + pub fn send_transaction(&mut self, params: &TransactionRequest) -> Result<H256> { self.call("eth_sendTransaction", &[params]) } @@ -333,6 +337,8 @@ pub struct BlockHead {} pub struct Transaction { /// Hash pub hash: H256, + /// None + pub nonce: U256, /// Sender address (None when coinbase) pub from: Option<Address>, /// Recipient address (None when contract creation) @@ -343,6 +349,22 @@ pub struct Transaction { pub input: Hex, } +/// Fill result +#[derive(Debug, serde::Deserialize)] +pub struct Filled { + pub tx: FilledGas, +} + +/// Filles gas +#[derive(Debug, serde::Deserialize)] +pub struct FilledGas { + /// Supplied gas + pub gas: U256, + /// Gas price + #[serde(rename = "gasPrice")] + pub gas_price: U256, +} + /// Send Transaction Parameters #[derive(Debug, serde::Serialize)] pub struct TransactionRequest { @@ -361,6 +383,9 @@ pub struct TransactionRequest { pub gas_price: Option<U256>, /// Transaction data pub data: Hex, + /// Transaction nonce (None for next available nonce) + #[serde(skip_serializing_if = "Option::is_none")] + pub nonce: Option<U256>, } #[derive(Debug, serde::Deserialize)] diff --git a/test/btc/analysis.sh b/test/btc/analysis.sh @@ -29,7 +29,7 @@ echo "Loose second bitcoin node" btc_deco echo -n "Making wire transfer to exchange:" -btc-wire-utils -d $WIRE_DIR transfer 0.042 > /dev/null +$WIRE_UTILS transfer 0.042 > /dev/null next_btc # Trigger btc-wire check_balance 9.95799209 0.04200000 echo " OK" @@ -51,7 +51,7 @@ echo "Loose second bitcoin node" btc_deco echo -n "Making wire transfer to exchange:" -btc-wire-utils -d $WIRE_DIR transfer 0.064 > /dev/null +$WIRE_UTILS transfer 0.064 > /dev/null next_btc 5 # More block needed to confirm check_balance 9.89398418 0.10600000 echo " OK" diff --git a/test/btc/bumpfee.sh b/test/btc/bumpfee.sh @@ -27,7 +27,7 @@ SEQ="seq 10 30" echo -n "Making wire transfer to exchange:" for n in `$SEQ`; do - btc-wire-utils -d $WIRE_DIR transfer 0.$n > /dev/null + $WIRE_UTILS transfer 0.$n > /dev/null mine_btc # Mine transactions done next_btc # Trigger btc-wire diff --git a/test/btc/conflict.sh b/test/btc/conflict.sh @@ -26,7 +26,7 @@ echo "" echo "----- Conflict send -----" echo -n "Making wire transfer to exchange:" -btc-wire-utils -d $WIRE_DIR transfer 0.042 > /dev/null +$WIRE_UTILS transfer 0.042 > /dev/null next_btc check_balance 9.95799209 0.04200000 echo " OK" @@ -42,7 +42,7 @@ echo " OK" echo -n "Abandon pending transaction:" restart_btc -minrelaytxfee=0.0001 -btc-wire-utils -d $WIRE_DIR abandon +$WIRE_UTILS abandon check_balance 9.95799209 0.04200000 echo " OK" @@ -93,7 +93,7 @@ echo " OK" echo -n "Abandon pending transaction:" restart_btc -minrelaytxfee=0.0001 -btc-wire-utils -d $WIRE_DIR abandon +$WIRE_UTILS abandon check_balance 9.95999859 0.04000000 echo " OK" diff --git a/test/btc/hell.sh b/test/btc/hell.sh @@ -23,13 +23,13 @@ echo "Start gateway" gateway echo "" -echo "----- Handle reorg conflicting incoming receive -----" +echo "----- Handle reorg conflicting incoming deposit -----" echo "Loose second bitcoin node" btc_deco echo -n "Gen incoming transactions:" -btc-wire-utils -d $WIRE_DIR transfer 0.0042 > /dev/null +$WIRE_UTILS transfer 0.0042 > /dev/null next_btc # Trigger btc-wire check_balance 9.99579209 0.00420000 echo " OK" @@ -43,17 +43,16 @@ echo " OK" echo -n "Generate conflict:" restart_btc -minrelaytxfee=0.0001 -btc-wire-utils -d $WIRE_DIR abandon client -btc-wire-utils -d $WIRE_DIR transfer 0.0054 > /dev/null +$WIRE_UTILS abandon client +$WIRE_UTILS transfer 0.0054 > /dev/null next_btc check_balance 9.99457382 0.00540000 echo " OK" echo -n "Check btc-wire have not read the conflicting transaction:" -sleep 5 # Wait for reconnection # Wait for reconnection +sleep 5 # Wait for reconnection gateway_down check_balance 9.99457382 0.00540000 -check_delta "incoming" "" echo " OK" # Recover by paying for the customer ? @@ -96,8 +95,8 @@ echo " OK" echo -n "Generate conflict:" restart_btc -minrelaytxfee=0.0001 -btc-wire-utils -d $WIRE_DIR abandon client -btc-wire-utils -d $WIRE_DIR transfer 0.054 > /dev/null +$WIRE_UTILS abandon client +$WIRE_UTILS transfer 0.054 > /dev/null next_btc check_balance 9.94597382 0.05400000 echo " OK" @@ -106,7 +105,6 @@ echo -n "Check btc-wire have not read the conflicting transaction:" sleep 5 # Wait for reconnection gateway_down check_balance 9.94597382 0.05400000 -check_delta "incoming" "" echo " OK" diff --git a/test/btc/lifetime.sh b/test/btc/lifetime.sh @@ -33,7 +33,7 @@ echo " OK" echo -n "Do some work:" for n in `$SEQ`; do - btc-wire-utils -d $WIRE_DIR transfer 0.000$n > /dev/null + $WIRE_UTILS transfer 0.000$n > /dev/null mine_btc # Mine transactions done next_btc # Trigger btc-wire diff --git a/test/btc/maxfee.sh b/test/btc/maxfee.sh @@ -27,7 +27,7 @@ SEQ="seq 10 30" echo -n "Making wire transfer to exchange:" for n in `$SEQ`; do - btc-wire-utils -d $WIRE_DIR transfer 0.$n > /dev/null + $WIRE_UTILS transfer 0.$n > /dev/null mine_btc # Mine transactions done next_btc # Trigger btc-wire diff --git a/test/btc/reconnect.sh b/test/btc/reconnect.sh @@ -23,7 +23,7 @@ echo "" echo "----- With DB -----" echo "Making wire transfer to exchange:" -btc-wire-utils -d $WIRE_DIR transfer 0.000042 > /dev/null +$WIRE_UTILS transfer 0.000042 > /dev/null next_btc check_balance 9.99995009 0.00004200 echo -n "Requesting exchange incoming transaction list:" @@ -37,7 +37,7 @@ 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:" -btc-wire-utils -d $WIRE_DIR transfer 0.00004 > /dev/null +$WIRE_UTILS transfer 0.00004 > /dev/null next_btc check_balance 9.99948077 0.00050200 echo " OK" diff --git a/test/btc/reorg.sh b/test/btc/reorg.sh @@ -32,7 +32,7 @@ btc_deco echo -n "Gen incoming transactions:" for n in `$SEQ`; do - btc-wire-utils -d $WIRE_DIR transfer 0.000$n > /dev/null + $WIRE_UTILS transfer 0.000$n > /dev/null mine_btc # Mine transactions done next_btc # Trigger btc-wire diff --git a/test/btc/stress.sh b/test/btc/stress.sh @@ -27,7 +27,7 @@ echo "----- Handle incoming -----" echo -n "Making wire transfer to exchange:" for n in `$SEQ`; do - btc-wire-utils -d $WIRE_DIR transfer 0.000$n > /dev/null + $WIRE_UTILS transfer 0.000$n > /dev/null mine_btc # Mine transactions done next_btc # Confirm all transactions diff --git a/test/btc/wire.sh b/test/btc/wire.sh @@ -27,7 +27,7 @@ echo "----- Receive -----" echo -n "Making wire transfer to exchange:" for n in `$SEQ`; do - btc-wire-utils -d $WIRE_DIR transfer 0.000$n > /dev/null + $WIRE_UTILS transfer 0.000$n > /dev/null mine_btc # Mine transactions done next_btc # Trigger btc-wire diff --git a/test/eth/hell.sh b/test/eth/hell.sh @@ -0,0 +1,106 @@ +#!/bin/bash + +## Test eth-wire correctness when a blockchain reorganisation occurs leading to past incoming transaction conflict + +set -eu + +source "${BASH_SOURCE%/*}/../common.sh" +SCHEMA=eth.sql +CONFIG=taler_eth.conf + +echo "----- Setup -----" +echo "Load config file" +load_config +echo "Start database" +setup_db +echo "Start ethereum node" +init_eth +echo "Start second ethereum node" +init_eth2 +echo "Start eth-wire" +eth_wire +echo "Start gateway" +gateway +echo "" + +echo "----- Handle reorg conflicting incoming deposit -----" + +echo "Loose second ethereum node" +eth_deco + +echo -n "Making wire transfer to exchange:" +$WIRE_UTILS deposit $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 have not read the conflicting transaction:" +gateway_down +check_balance_eth 999999999 0 +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 database" +setup_db +echo "Start ethereum node" +init_eth +echo "Start second ethereum node" +init_eth2 +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 +sleep 1 +next_eth 6 +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 have not read the conflicting transaction:" +gateway_down +check_balance_eth 999999999 0 +echo " OK" + + +echo "All tests passed!" +\ No newline at end of file diff --git a/test/eth/lifetime.sh b/test/eth/lifetime.sh @@ -32,7 +32,7 @@ check_up $GATEWAY_PID wire-gateway echo " OK" echo -n "Do some work:" -eth-wire-utils -d $WIRE_DIR deposit $CLIENT $WIRE 0.000 `$SEQ` > /dev/null +$WIRE_UTILS deposit $CLIENT $WIRE 0.000 `$SEQ` > /dev/null next_eth # Trigger eth-wire check_balance_eth 999835000 165000 for n in `$SEQ`; do diff --git a/test/eth/reorg.sh b/test/eth/reorg.sh @@ -31,7 +31,7 @@ echo "Loose second ethereum node" eth_deco echo -n "Making wire transfer to exchange:" -eth-wire-utils -d $WIRE_DIR deposit $CLIENT $WIRE 0.000 `$SEQ` +$WIRE_UTILS deposit $CLIENT $WIRE 0.000 `$SEQ` next_eth # Trigger eth-wire check_delta "incoming?delta=-100" "$SEQ" "0.000" check_balance_eth 999835000 165000 @@ -86,7 +86,7 @@ echo "Loose second ethereum node" eth_deco echo -n "Bounce:" -eth-wire-utils -d $WIRE_DIR send $CLIENT $WIRE 0.000 `$SEQ` +$WIRE_UTILS send $CLIENT $WIRE 0.000 `$SEQ` sleep 1 next_eth 6 check_balance_eth 999840500 159500 diff --git a/test/eth/stress.sh b/test/eth/stress.sh @@ -26,7 +26,7 @@ SEQ="seq 10 30" echo "----- Handle incoming -----" echo -n "Making wire transfer to exchange:" -eth-wire-utils -d $WIRE_DIR deposit $CLIENT $WIRE 0.000 `$SEQ` +$WIRE_UTILS deposit $CLIENT $WIRE 0.000 `$SEQ` next_eth # Trigger eth-wire echo " OK" @@ -81,7 +81,7 @@ echo " OK" echo "----- Handle bounce -----" echo -n "Bounce:" -eth-wire-utils -d $WIRE_DIR send $CLIENT $WIRE 0.000 `$SEQ` +$WIRE_UTILS send $CLIENT $WIRE 0.000 `$SEQ` sleep 1 next_eth echo " OK" diff --git a/test/eth/wire.sh b/test/eth/wire.sh @@ -26,7 +26,7 @@ SEQ="seq 10 99" echo "----- Receive -----" echo -n "Making wire transfer to exchange:" -eth-wire-utils -d $WIRE_DIR deposit $CLIENT $WIRE 0.000 `$SEQ` +$WIRE_UTILS deposit $CLIENT $WIRE 0.000 `$SEQ` next_eth # Trigger eth-wire check_balance_eth 995095000 4905000 echo " OK" @@ -56,7 +56,7 @@ echo " OK" echo "----- Bounce -----" echo -n "Bounce:" -eth-wire-utils -d $WIRE_DIR send $CLIENT $WIRE 0.000 `seq 10 40` +$WIRE_UTILS send $CLIENT $WIRE 0.000 `seq 10 40` sleep 1 next_eth check_balance_eth 995554500 4445500