diff options
author | Antoine A <> | 2022-01-11 14:58:06 +0100 |
---|---|---|
committer | Antoine A <> | 2022-01-11 14:58:06 +0100 |
commit | 58b69e0cb114272dcbd31a8d43727169a6104560 (patch) | |
tree | 8ccf2b24adabd0dffdc5ac308e5e8ffe515f9237 | |
parent | 579cd18c2560716b4924a73ec097720731745502 (diff) | |
download | depolymerization-58b69e0cb114272dcbd31a8d43727169a6104560.tar.gz depolymerization-58b69e0cb114272dcbd31a8d43727169a6104560.tar.bz2 depolymerization-58b69e0cb114272dcbd31a8d43727169a6104560.zip |
btc-wire: support and test incoming transactions conflicting reorganisation
-rw-r--r-- | btc-wire/src/bin/btc-wire-cli.rs | 8 | ||||
-rw-r--r-- | btc-wire/src/main.rs | 74 | ||||
-rw-r--r-- | makefile | 1 | ||||
-rw-r--r-- | script/setup.sh | 33 | ||||
-rw-r--r-- | script/test_btc_conflict.sh | 4 | ||||
-rw-r--r-- | script/test_btc_hell.sh | 144 | ||||
-rw-r--r-- | script/test_btc_reconnect.sh | 2 | ||||
-rw-r--r-- | script/test_btc_reorg.sh | 4 |
8 files changed, 232 insertions, 38 deletions
diff --git a/btc-wire/src/bin/btc-wire-cli.rs b/btc-wire/src/bin/btc-wire-cli.rs index da0c93f..eab00db 100644 --- a/btc-wire/src/bin/btc-wire-cli.rs +++ b/btc-wire/src/bin/btc-wire-cli.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use bitcoin::{Address, Amount, Network};
use btc_wire::{
config::BitcoinConfig,
- rpc::{BtcRpc, Error, ErrorCode, Category},
+ rpc::{BtcRpc, Category, Error, ErrorCode},
rpc_utils::default_data_dir,
test::rand_key,
};
@@ -51,7 +51,7 @@ struct TransferCmd { #[argh(subcommand, name = "nblock")]
/// Wait or mine the next block
struct NextBlockCmd {
- #[argh(option, short = 't', default = "String::from(\"wire\")")]
+ #[argh(positional, default = "String::from(\"wire\")")]
/// receiver wallet
to: String,
}
@@ -60,7 +60,7 @@ struct NextBlockCmd { #[argh(subcommand, name = "abandon")]
/// Abandon all unconfirmed transaction
struct AbandonCmd {
- #[argh(option, short = 't', default = "String::from(\"wire\")")]
+ #[argh(positional, default = "String::from(\"wire\")")]
/// sender wallet
from: String,
}
@@ -133,7 +133,7 @@ fn main() { let (mut wire, _) = app.auto_wallet(&from);
let list = wire.list_since_block(None, 1, false).unwrap();
for tx in list.transactions {
- if tx.category == Category::Send && tx.confirmations == 0 {
+ if tx.category == Category::Send && tx.confirmations == 0 {
wire.abandon_tx(&tx.txid).unwrap();
}
}
diff --git a/btc-wire/src/main.rs b/btc-wire/src/main.rs index 22ba6bd..0368b70 100644 --- a/btc-wire/src/main.rs +++ b/btc-wire/src/main.rs @@ -3,7 +3,6 @@ use btc_wire::{ config::BitcoinConfig,
rpc::{self, BtcRpc, Category, ErrorCode},
rpc_utils::{default_data_dir, sender_address},
- segwit::DecodeSegWitErr,
GetOpReturnErr, GetSegwitErr,
};
use info::decode_info;
@@ -12,6 +11,7 @@ use rand::{rngs::OsRng, RngCore}; use reconnect::{AutoReconnectRPC, AutoReconnectSql};
use std::{
collections::{HashMap, HashSet},
+ fmt::Write,
process::exit,
str::FromStr,
time::{Duration, SystemTime},
@@ -235,23 +235,65 @@ fn sync_chain( };
// Check if a confirmed incoming transaction have been removed by a blockchain reorganisation
+
if !removed.is_empty() {
+ let mut blocking_receive = Vec::new();
+ let mut blocking_bounce = Vec::new();
for id in removed {
- if let Ok((full, key)) = rpc.get_tx_segwit_key(&id) {
- // If the removed tx is not in confirmed the txs list and the tx is stored in the database hard error
- if txs
- .get(&id)
- .map(|(_, confirmations)| *confirmations < min_confirmations as i32)
- .unwrap_or(true)
- && db
- .query_opt("SELECT 1 FROM tx_in WHERE reserve_pub=$1", &[&key.as_ref()])?
- .is_some()
- {
- let credit_addr = full.details[0].address.as_ref().unwrap();
- error!("Received transaction {} in {} from {} have been removed from the blockchain, bitcoin backing is compromised until the transaction reappear", base32(&key), id, credit_addr);
- exit(1);
+ match rpc.get_tx_segwit_key(&id) {
+ Ok((full, key)) => {
+ // If the removed tx is not in confirmed the txs list and the tx is stored in the database hard error
+ if txs
+ .get(&id)
+ .map(|(_, confirmations)| *confirmations < min_confirmations as i32)
+ .unwrap_or(true)
+ && db
+ .query_opt(
+ "SELECT 1 FROM tx_in WHERE reserve_pub=$1",
+ &[&key.as_ref()],
+ )?
+ .is_some()
+ {
+ let debit_addr = sender_address(rpc, &full)?;
+ blocking_receive.push((key, id, debit_addr));
+ }
}
+ Err(err) => {
+ match err {
+ GetSegwitErr::Decode(_) => {
+ if let Some(row) = db.query_opt(
+ "SELECT txid FROM bounce WHERE bounced=$1 AND txid IS NOT NULL",
+ &[&id.as_ref()],
+ )? {
+ let txid = Txid::from_slice(row.get(0)).unwrap();
+ blocking_bounce.push((txid, id));
+ } else {
+ db.execute("DELETE FROM bounce WHERE bounced=$1", &[&id.as_ref()])?;
+ }
+ }
+ _ => { /* ignore already caught error */ }
+ }
+ }
+ }
+ }
+
+ if !blocking_bounce.is_empty() || !blocking_receive.is_empty() {
+ let mut buf = "The following transaction have been removed from the blockchain, bitcoin backing is compromised until the transaction reappear:".to_string();
+ for (key, id, addr) in blocking_receive {
+ write!(
+ &mut buf,
+ "\n\treceived {} in {} from {}",
+ base32(&key),
+ id,
+ addr
+ )
+ .unwrap();
+ }
+ for (id, bounced) in blocking_bounce {
+ write!(&mut buf, "\n\tbounced {} in {}", id, bounced).unwrap();
}
+ error!("{}", buf);
+ exit(1);
}
}
@@ -404,9 +446,7 @@ fn sync_chain( }
}
Err(err) => match err {
- GetSegwitErr::Decode(
- DecodeSegWitErr::MissingSegWitAddress | DecodeSegWitErr::NoMagicIdMatch,
- ) => {
+ GetSegwitErr::Decode(_) => {
// Request a bounce
db.execute("INSERT INTO bounce (bounced) VALUES ($1) ON CONFLICT (bounced) DO NOTHING", &[&id.as_ref()])?;
}
@@ -13,5 +13,6 @@ test_btc: script/test_btc_stress.sh
script/test_btc_conflict.sh
script/test_btc_reorg.sh
+ script/test_btc_hell.sh
test: test_gateway test_btc
\ No newline at end of file diff --git a/script/setup.sh b/script/setup.sh index 7d9d7a8..d43a49d 100644 --- a/script/setup.sh +++ b/script/setup.sh @@ -27,6 +27,9 @@ for dir in $BTC_DIR $BTC_DIR2 $DB_DIR log; do mkdir -p $dir done +# Clear logs +rm log/* + # Setup command helpers BTC_CLI="bitcoin-cli -datadir=$BTC_DIR" BTC_CLI2="bitcoin-cli -datadir=$BTC_DIR2" @@ -78,7 +81,7 @@ function reset_db() { # Start a bitcoind regtest node, generate money, wallet and addresses function init_btc() { cp ${BASH_SOURCE%/*}/conf/bitcoin.conf $BTC_DIR/bitcoin.conf - bitcoind -datadir=$BTC_DIR $* &> log/btc.log & + bitcoind -datadir=$BTC_DIR $* &>> log/btc.log & BTC_PID="$!" # Wait for RPC server to be online $BTC_CLI -rpcwait getnetworkinfo > /dev/null @@ -99,7 +102,7 @@ function init_btc() { # Start a second bitcoind regtest node connected to the first one function init_btc2() { cp ${BASH_SOURCE%/*}/conf/bitcoin2.conf $BTC_DIR2/bitcoin.conf - bitcoind -datadir=$BTC_DIR2 $* &> log/btc2.log & + bitcoind -datadir=$BTC_DIR2 $* &>> log/btc2.log & $BTC_CLI2 -rpcwait getnetworkinfo > /dev/null $BTC_CLI addnode 127.0.0.1:8346 onetry } @@ -116,21 +119,31 @@ function btc2_fork() { sleep 1 } -# Start a bitcoind regest server in a previously created temporary directory and load wallets -function restart_btc() { +# Restart a bitcoind regest server in a previously created temporary directory and load wallets +function resume_btc() { + # Restart node bitcoind -datadir=$BTC_DIR $* &>> log/btc.log & BTC_PID="$!" + # Wait for RPC server to be online $BTC_CLI -rpcwait getnetworkinfo > /dev/null + # Load wallets for wallet in wire client reserve; do $BTC_CLI loadwallet $wallet > /dev/null done + # Connect second node + $BTC_CLI addnode 127.0.0.1:8346 onetry } function stop_btc() { - kill $BTC_PID + kill $BTC_PID wait $BTC_PID } +function restart_btc() { + stop_btc + resume_btc $* +} + # Mine blocks function mine_btc() { $BTC_CLI generatetoaddress "${1:-1}" $RESERVE > /dev/null @@ -171,22 +184,22 @@ function check_balance() { # Start btc_wire function btc_wire() { cargo build --bin btc-wire --release &> /dev/null - target/release/btc-wire $CONF &> log/btc_wire.log & + target/release/btc-wire $CONF &>> log/btc_wire.log & WIRE_PID="$!" } # Start btc_wire with random failures function fail_btc_wire() { cargo build --bin btc-wire --release --features fail &> /dev/null - target/release/btc-wire $CONF &> log/btc_wire.log & + target/release/btc-wire $CONF &>> log/btc_wire.log & WIRE_PID="$!" } # Start multiple btc_wire in parallel function stressed_btc_wire() { cargo build --bin btc-wire --release &> /dev/null - target/release/btc-wire $CONF &> log/btc_wire.log & - target/release/btc-wire $CONF &> log/btc_wire1.log & + target/release/btc-wire $CONF &>> log/btc_wire.log & + target/release/btc-wire $CONF &>> log/btc_wire1.log & } # ----- Gateway ------ # @@ -194,7 +207,7 @@ function stressed_btc_wire() { # Start wire_gateway in test mode function gateway() { cargo build --bin wire-gateway --release --features test &> /dev/null - target/release/wire-gateway $CONF &> log/gateway.log & + target/release/wire-gateway $CONF &>> log/gateway.log & GATEWAY_PID="$!" for n in `seq 1 50`; do echo -n "." diff --git a/script/test_btc_conflict.sh b/script/test_btc_conflict.sh index 8ed968f..bb8b4dd 100644 --- a/script/test_btc_conflict.sh +++ b/script/test_btc_conflict.sh @@ -40,7 +40,6 @@ check_balance 9.95799209 0.03799801 echo " OK" echo -n "Abandon pending transaction:" -stop_btc restart_btc -minrelaytxfee=0.0001 btc-wire-cli -d $BTC_DIR abandon check_balance 9.95799209 0.04200000 @@ -52,7 +51,6 @@ taler-exchange-wire-gateway-client \ -C payto://bitcoin/$CLIENT \ -a BTC:0.005 > /dev/null sleep 1 -stop_btc restart_btc mine_btc check_balance 9.96299209 0.03698010 @@ -93,7 +91,6 @@ check_balance 9.95999859 0.00001000 echo " OK" echo -n "Abandon pending transaction:" -stop_btc restart_btc -minrelaytxfee=0.0001 btc-wire-cli -d $BTC_DIR abandon check_balance 9.95999859 0.04000000 @@ -103,7 +100,6 @@ echo -n "Generate conflict:" $BTC_CLI -rpcwallet=client sendtoaddress $WIRE 0.05 > /dev/null mine_btc $CONFIRMATION sleep 1 -stop_btc restart_btc mine_btc check_balance 9.95994929 0.04001000 diff --git a/script/test_btc_hell.sh b/script/test_btc_hell.sh new file mode 100644 index 0000000..0211295 --- /dev/null +++ b/script/test_btc_hell.sh @@ -0,0 +1,144 @@ +#!/bin/bash + +## Test btc_wire correctness when a blockchain reorganisation occurs leading to past incoming transaction conflict + +set -eu + +source "${BASH_SOURCE%/*}/setup.sh" +SCHEMA=btc.sql + +echo "----- Setup -----" +echo "Load config file" +load_config +echo "Start database" +setup_db +echo "Start bitcoin node" +init_btc +echo "Start second bitcoin node" +init_btc2 +echo "Start btc-wire" +btc_wire +echo "Start gateway" +gateway +echo "" + +# Check btc-wire is running +function up() { + check_up $WIRE_PID btc_wire +} +# Check btc-wire is not running +function down() { + check_down $WIRE_PID btc_wire +} + +echo "----- Handle reorg conflicting incoming receive -----" + +echo "Loose second bitcoin node" +btc2_deco + +echo -n "Gen incoming transactions:" +btc-wire-cli -d $BTC_DIR 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:" +up +btc2_fork +check_balance 9.99579209 0.00000000 +down +echo " OK" + +echo -n "Check btc-wire hard error on restart:" +btc_wire +sleep 1 +down +echo " OK" + +echo -n "Generate conflict:" +restart_btc -minrelaytxfee=0.0001 +btc-wire-cli -d $BTC_DIR abandon client +btc-wire-cli -d $BTC_DIR transfer 0.0054 > /dev/null +next_btc +check_balance 9.99457382 0.00540000 +echo " OK" + +echo -n "Check btc-wire never heal on restart:" +btc_wire +sleep 1 +down +check_balance 9.99457382 0.00540000 +echo " OK" + +echo -n "Check btc-wire have not read the conflicting transaction:" +check_delta "incoming" "" +echo " OK" + +# Recover by paying for the customer ? + +echo "----- Reset -----" +echo "Cleanup" +cleanup +source "${BASH_SOURCE%/*}/setup.sh" +echo "Load config file" +load_config +echo "Start database" +setup_db +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" +btc2_deco + +echo -n "Generate bounce:" +$BTC_CLI -rpcwallet=client sendtoaddress $WIRE 0.042 > /dev/null +next_btc +sleep 1 +check_balance 9.99998674 0.00001000 +echo " OK" + +echo -n "Perform fork and check btc-wire hard error:" +up +btc2_fork +check_balance 9.95799859 0.00000000 +down +echo " OK" + +echo -n "Check btc-wire hard error on restart:" +btc_wire +sleep 1 +down +echo " OK" + +echo -n "Generate conflict:" +restart_btc -minrelaytxfee=0.0001 +btc-wire-cli -d $BTC_DIR abandon client +btc-wire-cli -d $BTC_DIR transfer 0.054 > /dev/null +next_btc +check_balance 9.94597382 0.05400000 +echo " OK" + +sleep 5 + +echo -n "Check btc-wire never heal on restart:" +btc_wire +sleep 1 +down +check_balance 9.94597382 0.05400000 +echo " OK" + +echo -n "Check btc-wire have not read the conflicting transaction:" +check_delta "incoming" "" +echo " OK" + + +echo "All tests passed"
\ No newline at end of file diff --git a/script/test_btc_reconnect.sh b/script/test_btc_reconnect.sh index 5bad9a1..09734b4 100644 --- a/script/test_btc_reconnect.sh +++ b/script/test_btc_reconnect.sh @@ -49,7 +49,7 @@ echo "----- Reconnect DB -----" echo "Start database" pg_ctl start -D $DB_DIR > /dev/null echo "Start bitcoin node" -restart_btc +resume_btc sleep 6 # Wait for connection to be available echo -n "Requesting exchange incoming transaction list:" taler-exchange-wire-gateway-client -b $BANK_ENDPOINT -i | grep BTC:0.00004 > /dev/null && echo " OK" || echo " Failed" diff --git a/script/test_btc_reorg.sh b/script/test_btc_reorg.sh index 18bb89f..d6f260e 100644 --- a/script/test_btc_reorg.sh +++ b/script/test_btc_reorg.sh @@ -119,11 +119,11 @@ sleep 1 check_balance "*" 0.00011000 echo " OK" -echo -n "Perform fork and check btc-wire still up:" +echo -n "Perform fork and check btc-wire hard error:" up btc2_fork check_balance "*" 0.00000000 -up +down echo " OK" echo -n "Recover orphaned transactions:" |