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:
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