depolymerization

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

commit 42b2bc5aa273b98b6c5062f6bfc7aa1005bad811
parent 0edd2ffc4c06ad18ea9dcfa2325a8ffb863474cb
Author: Antoine A <>
Date:   Mon, 24 Jan 2022 15:11:39 +0100

Improve error handling

Diffstat:
Mbtc-wire/src/fail_point.rs | 8++++++--
Mbtc-wire/src/loops.rs | 20++++++++++++++++++++
Mbtc-wire/src/loops/analysis.rs | 4+++-
Mbtc-wire/src/loops/listener.rs | 4+++-
Mbtc-wire/src/loops/worker.rs | 32++++++++++++++++++--------------
Mmakefile | 1+
Mtest/btc/bumpfee.sh | 1-
Atest/btc/maxfee.sh | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 121 insertions(+), 19 deletions(-)

diff --git a/btc-wire/src/fail_point.rs b/btc-wire/src/fail_point.rs @@ -13,12 +13,16 @@ You should have received a copy of the GNU Affero General Public License along with TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ +#[derive(Debug, thiserror::Error)] +#[error("{0}")] +pub struct Injected(&'static str); + /// Inject random failure when 'fail' feature is used #[allow(unused_variables)] -pub fn fail_point(msg: &'static str, prob: f32) -> Result<(), &'static str> { +pub fn fail_point(msg: &'static str, prob: f32) -> Result<(), Injected> { #[cfg(feature = "fail")] return if fastrand::f32() < prob { - Err(msg) + Err(Injected(msg)) } else { Ok(()) }; diff --git a/btc-wire/src/loops.rs b/btc-wire/src/loops.rs @@ -13,6 +13,25 @@ You should have received a copy of the GNU Affero General Public License along with TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ + +use btc_wire::rpc; + +use crate::fail_point::Injected; + pub mod analysis; pub mod listener; pub mod worker; + +#[derive(Debug, thiserror::Error)] +pub enum LoopError { + #[error(transparent)] + RPC(#[from] rpc::Error), + #[error(transparent)] + DB(#[from] postgres::Error), + #[error("Another btc_wire process is running concurrently")] + Concurrency, + #[error(transparent)] + Injected(#[from] Injected), +} + +pub type LoopResult<T> = Result<T, LoopError>; +\ No newline at end of file diff --git a/btc-wire/src/loops/analysis.rs b/btc-wire/src/loops/analysis.rs @@ -27,6 +27,8 @@ use crate::{ WireState, }; +use super::LoopResult; + /// Analyse blockchain behavior and adapt confirmations in real time pub fn analysis( mut rpc: AutoReconnectRPC, @@ -39,7 +41,7 @@ pub fn analysis( loop { let rpc = rpc.client(); let db = db.client(); - let result: Result<(), Box<dyn std::error::Error>> = (|| { + let result: LoopResult<()> = (|| { // Register as listener db.batch_execute("LISTEN new_block")?; loop { diff --git a/btc-wire/src/loops/listener.rs b/btc-wire/src/loops/listener.rs @@ -17,12 +17,14 @@ use taler_common::log::log::error; use crate::reconnect::{AutoReconnectRPC, AutoReconnectSql}; +use super::LoopResult; + /// Wait for new block and notify arrival with postgreSQL notifications pub fn block_listener(mut rpc: AutoReconnectRPC, mut db: AutoReconnectSql) { loop { let rpc = rpc.client(); let db = db.client(); - let result: Result<(), Box<dyn std::error::Error>> = (|| loop { + let result: LoopResult<()> = (|| loop { rpc.wait_for_new_block(0)?; db.execute("NOTIFY new_block", &[])?; })(); diff --git a/btc-wire/src/loops/worker.rs b/btc-wire/src/loops/worker.rs @@ -44,6 +44,8 @@ use crate::{ WireState, }; +use super::{LoopError, LoopResult}; + /// Listen for new proposed transactions and announce them on the bitcoin network pub fn worker( mut rpc: AutoReconnectRPC, @@ -71,7 +73,7 @@ pub fn worker( let rpc = rpc.client(); let db = db.client(); - let result: Result<(), Box<dyn std::error::Error>> = (|| { + let result: LoopResult<()> = (|| { // Listen to all channels db.batch_execute("LISTEN new_block; LISTEN new_tx")?; // Wait for the next notification @@ -98,7 +100,7 @@ pub fn worker( let row = db.query_one("SELECT pg_try_advisory_lock(42)", &[])?; let locked: bool = row.get(0); if !locked { - return Err("Another btc_wire process is running concurrently".into()); + return Err(LoopError::Concurrency); } // Sync chain @@ -121,8 +123,14 @@ pub fn worker( })(); if let Err(e) = result { error!("worker: {}", e); - // On failure retry without waiting for notifications - skip_notification = true; + // When we catch an error, we sometimes want to retry immediately (eg. reconnect to RPC or DB). + // Bitcoin error codes are generic. We need to match the msg to get precise ones. Some errors + // can resolve themselves when a new block is mined (new fees, new transactions). Our simple + // approach is to wait for the next loop when an RPC error is caught to prevent endless logged errors. + skip_notification = !matches!( + e, + LoopError::RPC(rpc::Error::RPC { .. } | rpc::Error::Bitcoin(_)) + ); } else { skip_notification = false; } @@ -130,11 +138,7 @@ pub fn worker( } /// Send a transaction on the blockchain, return true if more transactions with the same status remains -fn send( - db: &mut Client, - rpc: &mut BtcRpc, - status: TxStatus, -) -> Result<bool, Box<dyn std::error::Error>> { +fn send(db: &mut Client, rpc: &mut BtcRpc, status: TxStatus) -> LoopResult<bool> { assert!(status == TxStatus::Delayed || status == TxStatus::Requested); // We rely on the advisory lock to ensure we are the only one sending transactions let row = db.query_opt( @@ -178,7 +182,7 @@ fn bounce( rpc: &mut BtcRpc, status: BounceStatus, fee: &BtcAmount, -) -> Result<bool, Box<dyn std::error::Error>> { +) -> LoopResult<bool> { assert!(status == BounceStatus::Delayed || status == BounceStatus::Requested); // We rely on the advisory lock to ensure we are the only one sending transactions let row = db.query_opt( @@ -238,7 +242,7 @@ fn sync_chain( config: &Config, state: &WireState, status: &mut bool, -) -> Result<bool, Box<dyn std::error::Error>> { +) -> LoopResult<bool> { // Get stored last_hash let last_hash = last_hash(db)?; let min_confirmations = state.confirmation.load(Ordering::SeqCst); @@ -322,7 +326,7 @@ fn sync_chain_removed( rpc: &mut BtcRpc, db: &mut Client, min_confirmations: i32, -) -> Result<bool, Box<dyn std::error::Error>> { +) -> LoopResult<bool> { // Removed transactions are correctness issue in only two cases: // - An incoming valid transaction considered confirmed in the database // - An incoming invalid transactions already bounced @@ -394,7 +398,7 @@ fn sync_chain_outgoing( rpc: &mut BtcRpc, db: &mut Client, config: &Config, -) -> Result<(), Box<dyn std::error::Error>> { +) -> LoopResult<()> { match rpc .get_tx_op_return(id) .map(|(full, bytes)| (full, decode_info(&bytes))) @@ -572,7 +576,7 @@ fn sync_chain_incoming_confirmed( id: &Txid, rpc: &mut BtcRpc, db: &mut Client, -) -> Result<(), Box<dyn std::error::Error>> { +) -> Result<(), LoopError> { match rpc.get_tx_segwit_key(id) { Ok((full, reserve_pub)) => { // Store transactions in database diff --git a/makefile b/makefile @@ -15,6 +15,7 @@ test_btc: test/btc/hell.sh test/btc/analysis.sh test/btc/bumpfee.sh + test/btc/maxfee.sh test/btc/config.sh test: install test_gateway test_btc \ No newline at end of file diff --git a/test/btc/bumpfee.sh b/test/btc/bumpfee.sh @@ -7,7 +7,6 @@ set -eu source "${BASH_SOURCE%/*}/../common.sh" SCHEMA=btc.sql CONFIG=taler_bump.conf -RUST_BACKTRACE=full echo "----- Setup -----" echo "Load config file" diff --git a/test/btc/maxfee.sh b/test/btc/maxfee.sh @@ -0,0 +1,69 @@ +#!/bin/bash + +## Test btc_wire ability to handle stuck transaction correctly + +set -eu + +source "${BASH_SOURCE%/*}/../common.sh" +SCHEMA=btc.sql +CONFIG=taler_bump.conf + +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 "" + +SEQ="seq 10 30" + +echo -n "Making wire transfer to exchange:" +for n in `$SEQ`; do + btc-wire-utils -d $BTC_DIR transfer 0.$n > /dev/null + mine_btc # Mine transactions +done +next_btc # Trigger btc_wire +check_balance 5.79983389 4.20000000 +echo " OK" + +echo "----- Too high fees -----" + +echo -n "Set up node" +restart_btc -maxtxfee=0.0000001 -minrelaytxfee=0.0000001 +echo " OK" + +echo -n "Making wire transfer from exchange:" +for n in `$SEQ`; do + taler-exchange-wire-gateway-client \ + -b $BANK_ENDPOINT \ + -C payto://bitcoin/$CLIENT \ + -a BTC:0.0$n > /dev/null +done +sleep 5 +mine_btc +echo " OK" + +echo -n "Check no transaction have been made" +check_balance 5.79983389 4.20000000 +echo " OK" + +echo "----- Good fees -----" + +echo -n "Set up node" +restart_btc +echo " OK" + +echo -n "Check transaction have been made" +sleep 5 +check_balance 5.79983389 3.77995821 +echo " OK" + +echo "All tests passed!" +\ No newline at end of file