summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntoine A <>2022-01-11 14:58:06 +0100
committerAntoine A <>2022-01-11 14:58:06 +0100
commit58b69e0cb114272dcbd31a8d43727169a6104560 (patch)
tree8ccf2b24adabd0dffdc5ac308e5e8ffe515f9237
parent579cd18c2560716b4924a73ec097720731745502 (diff)
downloaddepolymerization-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.rs8
-rw-r--r--btc-wire/src/main.rs74
-rw-r--r--makefile1
-rw-r--r--script/setup.sh33
-rw-r--r--script/test_btc_conflict.sh4
-rw-r--r--script/test_btc_hell.sh144
-rw-r--r--script/test_btc_reconnect.sh2
-rw-r--r--script/test_btc_reorg.sh4
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()])?;
}
diff --git a/makefile b/makefile
index 3829f74..956b71a 100644
--- a/makefile
+++ b/makefile
@@ -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:"