commit 42b2bc5aa273b98b6c5062f6bfc7aa1005bad811
parent 0edd2ffc4c06ad18ea9dcfa2325a8ffb863474cb
Author: Antoine A <>
Date: Mon, 24 Jan 2022 15:11:39 +0100
Improve error handling
Diffstat:
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