diff --git a/bridges/relays/bin-ethereum/Cargo.toml b/bridges/relays/bin-ethereum/Cargo.toml index 080608a26f8c6..610dee2c3ce99 100644 --- a/bridges/relays/bin-ethereum/Cargo.toml +++ b/bridges/relays/bin-ethereum/Cargo.toml @@ -21,6 +21,7 @@ libsecp256k1 = { version = "0.7", default-features = false, features = ["hmac"] log = "0.4.14" num-traits = "0.2" serde_json = "1.0.64" +thiserror = "1.0.26" # Bridge dependencies diff --git a/bridges/relays/bin-ethereum/src/error.rs b/bridges/relays/bin-ethereum/src/error.rs new file mode 100644 index 0000000000000..61ae2a9a498dc --- /dev/null +++ b/bridges/relays/bin-ethereum/src/error.rs @@ -0,0 +1,38 @@ +// Copyright 2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +use crate::rpc_errors::RpcError; +use thiserror::Error; + +/// Result type used by PoA relay. +pub type Result = std::result::Result; + +/// Ethereum PoA relay errors. +#[derive(Error, Debug)] +pub enum Error { + /// Failed to decode initial header. + #[error("Error decoding initial header: {0}")] + DecodeInitialHeader(codec::Error), + /// RPC error. + #[error("{0}")] + Rpc(#[from] RpcError), + /// Failed to read genesis header. + #[error("Error reading Substrate genesis header: {0:?}")] + ReadGenesisHeader(relay_substrate_client::Error), + /// Failed to read initial GRANDPA authorities. + #[error("Error reading GRANDPA authorities set: {0:?}")] + ReadAuthorities(relay_substrate_client::Error), + /// Failed to deploy bridge contract to Ethereum chain. + #[error("Error deploying contract: {0:?}")] + DeployContract(RpcError), +} diff --git a/bridges/relays/bin-ethereum/src/ethereum_deploy_contract.rs b/bridges/relays/bin-ethereum/src/ethereum_deploy_contract.rs index 9eecc1a12f517..cbe2762263a6f 100644 --- a/bridges/relays/bin-ethereum/src/ethereum_deploy_contract.rs +++ b/bridges/relays/bin-ethereum/src/ethereum_deploy_contract.rs @@ -15,6 +15,7 @@ // along with Parity Bridges Common. If not, see . use crate::{ + error::{Error, Result}, ethereum_client::{bridge_contract, EthereumHighLevelRpc}, rpc_errors::RpcError, }; @@ -104,20 +105,20 @@ pub async fn run(params: EthereumDeployContractParams) { async fn prepare_initial_header( sub_client: &SubstrateClient, sub_initial_header: Option>, -) -> Result<(RialtoHeaderId, Vec), String> { +) -> Result<(RialtoHeaderId, Vec)> { match sub_initial_header { Some(raw_initial_header) => { match rialto_runtime::Header::decode(&mut &raw_initial_header[..]) { Ok(initial_header) => Ok((HeaderId(initial_header.number, initial_header.hash()), raw_initial_header)), - Err(error) => Err(format!("Error decoding initial header: {}", error)), + Err(error) => Err(Error::DecodeInitialHeader(error)), } }, None => { let initial_header = sub_client.header_by_number(Zero::zero()).await; initial_header .map(|header| (HeaderId(Zero::zero(), header.hash()), header.encode())) - .map_err(|error| format!("Error reading Substrate genesis header: {:?}", error)) + .map_err(|error| Error::ReadGenesisHeader(error)) }, } } @@ -127,14 +128,13 @@ async fn prepare_initial_authorities_set( sub_client: &SubstrateClient, sub_initial_header_hash: rialto_runtime::Hash, sub_initial_authorities_set: Option>, -) -> Result { +) -> Result { let initial_authorities_set = match sub_initial_authorities_set { Some(initial_authorities_set) => Ok(initial_authorities_set), None => sub_client.grandpa_authorities_set(sub_initial_header_hash).await, }; - initial_authorities_set - .map_err(|error| format!("Error reading GRANDPA authorities set: {:?}", error)) + initial_authorities_set.map_err(|error| Error::ReadAuthorities(error)) } /// Deploy bridge contract to Ethereum chain. @@ -145,7 +145,7 @@ async fn deploy_bridge_contract( initial_header: Vec, initial_set_id: u64, initial_authorities: Vec, -) -> Result<(), String> { +) -> Result<()> { eth_client .submit_ethereum_transaction( params, @@ -160,5 +160,5 @@ async fn deploy_bridge_contract( ), ) .await - .map_err(|error| format!("Error deploying contract: {:?}", error)) + .map_err(|error| Error::DeployContract(error)) } diff --git a/bridges/relays/bin-ethereum/src/ethereum_exchange.rs b/bridges/relays/bin-ethereum/src/ethereum_exchange.rs index 6262c44c18d2c..90d9a23835d46 100644 --- a/bridges/relays/bin-ethereum/src/ethereum_exchange.rs +++ b/bridges/relays/bin-ethereum/src/ethereum_exchange.rs @@ -339,7 +339,7 @@ pub async fn run(params: EthereumExchangeParams) { async fn run_single_transaction_relay( params: EthereumExchangeParams, eth_tx_hash: H256, -) -> Result<(), String> { +) -> anyhow::Result<()> { let EthereumExchangeParams { eth_params, sub_params, sub_sign, instance, .. } = params; let eth_client = EthereumClient::try_connect(eth_params).await.map_err(RpcError::Ethereum)?; @@ -354,7 +354,9 @@ async fn run_single_transaction_relay( bridge_instance: instance, }; - relay_single_transaction_proof(&source, &target, eth_tx_hash).await + relay_single_transaction_proof(&source, &target, eth_tx_hash) + .await + .map_err(Into::into) } async fn run_auto_transactions_relay_loop( diff --git a/bridges/relays/bin-ethereum/src/ethereum_exchange_submit.rs b/bridges/relays/bin-ethereum/src/ethereum_exchange_submit.rs index 75bdf0e577aa2..f68a21e594e06 100644 --- a/bridges/relays/bin-ethereum/src/ethereum_exchange_submit.rs +++ b/bridges/relays/bin-ethereum/src/ethereum_exchange_submit.rs @@ -16,6 +16,7 @@ //! Submitting Ethereum -> Substrate exchange transactions. +use anyhow::anyhow; use bp_eth_poa::{ signatures::{secret_to_address, SignTransaction}, UnsignedTransaction, @@ -47,10 +48,10 @@ pub async fn run(params: EthereumExchangeSubmitParams) { let EthereumExchangeSubmitParams { eth_params, eth_sign, eth_nonce, eth_amount, sub_recipient } = params; - let result: Result<_, String> = async move { + let result: anyhow::Result<_> = async move { let eth_client = EthereumClient::try_connect(eth_params) .await - .map_err(|err| format!("error connecting to Ethereum node: {:?}", err))?; + .map_err(|err| anyhow!("error connecting to Ethereum node: {:?}", err))?; let eth_signer_address = secret_to_address(ð_sign.signer); let sub_recipient_encoded = sub_recipient; @@ -59,7 +60,7 @@ pub async fn run(params: EthereumExchangeSubmitParams) { None => eth_client .account_nonce(eth_signer_address) .await - .map_err(|err| format!("error fetching acount nonce: {:?}", err))?, + .map_err(|err| anyhow!("error fetching acount nonce: {:?}", err))?, }; let gas = eth_client .estimate_gas(CallRequest { @@ -70,7 +71,7 @@ pub async fn run(params: EthereumExchangeSubmitParams) { ..Default::default() }) .await - .map_err(|err| format!("error estimating gas requirements: {:?}", err))?; + .map_err(|err| anyhow!("error estimating gas requirements: {:?}", err))?; let eth_tx_unsigned = UnsignedTransaction { nonce, gas_price: eth_sign.gas_price, @@ -84,7 +85,7 @@ pub async fn run(params: EthereumExchangeSubmitParams) { eth_client .submit_transaction(eth_tx_signed) .await - .map_err(|err| format!("error submitting transaction: {:?}", err))?; + .map_err(|err| anyhow!("error submitting transaction: {:?}", err))?; Ok(eth_tx_unsigned) } diff --git a/bridges/relays/bin-ethereum/src/main.rs b/bridges/relays/bin-ethereum/src/main.rs index 86b749872913e..99e1b48968d79 100644 --- a/bridges/relays/bin-ethereum/src/main.rs +++ b/bridges/relays/bin-ethereum/src/main.rs @@ -16,6 +16,7 @@ #![recursion_limit = "1024"] +mod error; mod ethereum_client; mod ethereum_deploy_contract; mod ethereum_exchange; @@ -27,6 +28,7 @@ mod rpc_errors; mod substrate_sync_loop; mod substrate_types; +use anyhow::anyhow; use ethereum_deploy_contract::EthereumDeployContractParams; use ethereum_exchange::EthereumExchangeParams; use ethereum_exchange_submit::EthereumExchangeSubmitParams; @@ -136,58 +138,58 @@ async fn run_command(matches: &clap::ArgMatches<'_>) { fn ethereum_connection_params( matches: &clap::ArgMatches, -) -> Result { +) -> anyhow::Result { let mut params = EthereumConnectionParams::default(); if let Some(eth_host) = matches.value_of("eth-host") { params.host = eth_host.into(); } if let Some(eth_port) = matches.value_of("eth-port") { - params.port = eth_port.parse().map_err(|e| format!("Failed to parse eth-port: {}", e))?; + params.port = eth_port.parse().map_err(|e| anyhow!("Failed to parse eth-port: {}", e))?; } Ok(params) } -fn ethereum_signing_params(matches: &clap::ArgMatches) -> Result { +fn ethereum_signing_params(matches: &clap::ArgMatches) -> anyhow::Result { let mut params = EthereumSigningParams::default(); if let Some(eth_signer) = matches.value_of("eth-signer") { params.signer = SecretKey::parse_slice( - &hex::decode(eth_signer).map_err(|e| format!("Failed to parse eth-signer: {}", e))?, + &hex::decode(eth_signer).map_err(|e| anyhow!("Failed to parse eth-signer: {}", e))?, ) - .map_err(|e| format!("Invalid eth-signer: {}", e))?; + .map_err(|e| anyhow!("Invalid eth-signer: {}", e))?; } if let Some(eth_chain_id) = matches.value_of("eth-chain-id") { params.chain_id = eth_chain_id .parse::() - .map_err(|e| format!("Failed to parse eth-chain-id: {}", e))?; + .map_err(|e| anyhow!("Failed to parse eth-chain-id: {}", e))?; } Ok(params) } fn substrate_connection_params( matches: &clap::ArgMatches, -) -> Result { +) -> anyhow::Result { let mut params = SubstrateConnectionParams::default(); if let Some(sub_host) = matches.value_of("sub-host") { params.host = sub_host.into(); } if let Some(sub_port) = matches.value_of("sub-port") { - params.port = sub_port.parse().map_err(|e| format!("Failed to parse sub-port: {}", e))?; + params.port = sub_port.parse().map_err(|e| anyhow!("Failed to parse sub-port: {}", e))?; } Ok(params) } -fn rialto_signing_params(matches: &clap::ArgMatches) -> Result { +fn rialto_signing_params(matches: &clap::ArgMatches) -> anyhow::Result { let mut params = sp_keyring::AccountKeyring::Alice.pair(); if let Some(sub_signer) = matches.value_of("sub-signer") { let sub_signer_password = matches.value_of("sub-signer-password"); params = sp_core::sr25519::Pair::from_string(sub_signer, sub_signer_password) - .map_err(|e| format!("Failed to parse sub-signer: {:?}", e))?; + .map_err(|e| anyhow!("Failed to parse sub-signer: {:?}", e))?; } Ok(params) } -fn ethereum_sync_params(matches: &clap::ArgMatches) -> Result { +fn ethereum_sync_params(matches: &clap::ArgMatches) -> anyhow::Result { use crate::ethereum_sync_loop::consts::*; let mut sync_params = HeadersSyncParams { @@ -208,7 +210,7 @@ fn ethereum_sync_params(matches: &clap::ArgMatches) -> Result sync_params.target_tx_mode = TargetTransactionMode::Backup, - Some(mode) => return Err(format!("Invalid sub-tx-mode: {}", mode)), + Some(mode) => return Err(anyhow!("Invalid sub-tx-mode: {}", mode)), None => sync_params.target_tx_mode = TargetTransactionMode::Signed, } @@ -226,12 +228,12 @@ fn ethereum_sync_params(matches: &clap::ArgMatches) -> Result Result { +fn substrate_sync_params(matches: &clap::ArgMatches) -> anyhow::Result { use crate::substrate_sync_loop::consts::*; let eth_contract_address: relay_ethereum_client::types::Address = if let Some(eth_contract) = matches.value_of("eth-contract") { - eth_contract.parse().map_err(|e| format!("{}", e))? + eth_contract.parse()? } else { "731a10897d267e19b34503ad902d0a29173ba4b1" .parse() @@ -261,7 +263,7 @@ fn substrate_sync_params(matches: &clap::ArgMatches) -> Result Result { +) -> anyhow::Result { let eth_contract_code = parse_hex_argument(matches, "eth-contract-code")?.unwrap_or_else(|| { hex::decode(include_str!("../res/substrate-bridge-bytecode.hex")) @@ -271,7 +273,7 @@ fn ethereum_deploy_contract_params( .value_of("sub-authorities-set-id") .map(|set| { set.parse() - .map_err(|e| format!("Failed to parse sub-authorities-set-id: {}", e)) + .map_err(|e| anyhow!("Failed to parse sub-authorities-set-id: {}", e)) }) .transpose()?; let sub_initial_authorities_set = parse_hex_argument(matches, "sub-authorities-set")?; @@ -294,19 +296,19 @@ fn ethereum_deploy_contract_params( fn ethereum_exchange_submit_params( matches: &clap::ArgMatches, -) -> Result { +) -> anyhow::Result { let eth_nonce = matches .value_of("eth-nonce") .map(|eth_nonce| { relay_ethereum_client::types::U256::from_dec_str(eth_nonce) - .map_err(|e| format!("Failed to parse eth-nonce: {}", e)) + .map_err(|e| anyhow!("Failed to parse eth-nonce: {}", e)) }) .transpose()?; let eth_amount = matches .value_of("eth-amount") .map(|eth_amount| { - eth_amount.parse().map_err(|e| format!("Failed to parse eth-amount: {}", e)) + eth_amount.parse().map_err(|e| anyhow!("Failed to parse eth-amount: {}", e)) }) .transpose()? .unwrap_or_else(|| { @@ -331,7 +333,7 @@ fn ethereum_exchange_submit_params( Ok(sub_recipient) } }) - .map_err(|e| format!("Failed to parse sub-recipient: {}", e))? + .map_err(|e| anyhow!("Failed to parse sub-recipient: {}", e))? } else { default_recepient }; @@ -349,10 +351,10 @@ fn ethereum_exchange_submit_params( Ok(params) } -fn ethereum_exchange_params(matches: &clap::ArgMatches) -> Result { +fn ethereum_exchange_params(matches: &clap::ArgMatches) -> anyhow::Result { let mode = match matches.value_of("eth-tx-hash") { Some(eth_tx_hash) => ethereum_exchange::ExchangeRelayMode::Single( - eth_tx_hash.parse().map_err(|e| format!("Failed to parse eth-tx-hash: {}", e))?, + eth_tx_hash.parse().map_err(|e| anyhow!("Failed to parse eth-tx-hash: {}", e))?, ), None => ethereum_exchange::ExchangeRelayMode::Auto( matches @@ -360,7 +362,7 @@ fn ethereum_exchange_params(matches: &clap::ArgMatches) -> Result Result Result { +fn metrics_params(matches: &clap::ArgMatches) -> anyhow::Result { if matches.is_present("no-prometheus") { return Ok(None.into()) } @@ -393,18 +395,18 @@ fn metrics_params(matches: &clap::ArgMatches) -> Result { if let Some(prometheus_port) = matches.value_of("prometheus-port") { metrics_params.port = prometheus_port .parse() - .map_err(|e| format!("Failed to parse prometheus-port: {}", e))?; + .map_err(|e| anyhow!("Failed to parse prometheus-port: {}", e))?; } Ok(Some(metrics_params).into()) } -fn instance_params(matches: &clap::ArgMatches) -> Result, String> { +fn instance_params(matches: &clap::ArgMatches) -> anyhow::Result> { let instance = if let Some(instance) = matches.value_of("sub-pallet-instance") { match instance.to_lowercase().as_str() { "rialto" => Arc::new(RialtoPoA) as Arc, "kovan" => Arc::new(Kovan), - _ => return Err("Unsupported bridge pallet instance".to_string()), + _ => return Err(anyhow!("Unsupported bridge pallet instance")), } } else { unreachable!("CLI config enforces a default instance, can never be None") @@ -413,10 +415,10 @@ fn instance_params(matches: &clap::ArgMatches) -> Result Ok(instance) } -fn parse_hex_argument(matches: &clap::ArgMatches, arg: &str) -> Result>, String> { +fn parse_hex_argument(matches: &clap::ArgMatches, arg: &str) -> anyhow::Result>> { match matches.value_of(arg) { Some(value) => - Ok(Some(hex::decode(value).map_err(|e| format!("Failed to parse {}: {}", arg, e))?)), + Ok(Some(hex::decode(value).map_err(|e| anyhow!("Failed to parse {}: {}", arg, e))?)), None => Ok(None), } } diff --git a/bridges/relays/bin-ethereum/src/rpc_errors.rs b/bridges/relays/bin-ethereum/src/rpc_errors.rs index f0f28b0c4062d..e91bc363839b6 100644 --- a/bridges/relays/bin-ethereum/src/rpc_errors.rs +++ b/bridges/relays/bin-ethereum/src/rpc_errors.rs @@ -17,48 +17,30 @@ use relay_ethereum_client::Error as EthereumNodeError; use relay_substrate_client::Error as SubstrateNodeError; use relay_utils::MaybeConnectionError; +use thiserror::Error; /// Contains common errors that can occur when /// interacting with a Substrate or Ethereum node /// through RPC. -#[derive(Debug)] +#[derive(Debug, Error)] pub enum RpcError { /// The arguments to the RPC method failed to serialize. - Serialization(serde_json::Error), + #[error("RPC arguments serialization failed: {0}")] + Serialization(#[from] serde_json::Error), /// An error occurred when interacting with an Ethereum node. - Ethereum(EthereumNodeError), + #[error("Ethereum node error: {0}")] + Ethereum(#[from] EthereumNodeError), /// An error occurred when interacting with a Substrate node. - Substrate(SubstrateNodeError), + #[error("Substrate node error: {0}")] + Substrate(#[from] SubstrateNodeError), /// Error running relay loop. + #[error("{0}")] SyncLoop(String), } impl From for String { fn from(err: RpcError) -> Self { - match err { - RpcError::Serialization(e) => e.to_string(), - RpcError::Ethereum(e) => e.to_string(), - RpcError::Substrate(e) => e.to_string(), - RpcError::SyncLoop(e) => e, - } - } -} - -impl From for RpcError { - fn from(err: serde_json::Error) -> Self { - Self::Serialization(err) - } -} - -impl From for RpcError { - fn from(err: EthereumNodeError) -> Self { - Self::Ethereum(err) - } -} - -impl From for RpcError { - fn from(err: SubstrateNodeError) -> Self { - Self::Substrate(err) + format!("{}", err) } } diff --git a/bridges/relays/bin-substrate/src/chains/kusama.rs b/bridges/relays/bin-substrate/src/chains/kusama.rs index f0c11f6abb83f..9e5351672dada 100644 --- a/bridges/relays/bin-substrate/src/chains/kusama.rs +++ b/bridges/relays/bin-substrate/src/chains/kusama.rs @@ -97,7 +97,7 @@ impl CliChain for Kusama { fn encode_message( _message: encode_message::MessagePayload, - ) -> Result { - Err("Sending messages from Kusama is not yet supported.".into()) + ) -> anyhow::Result { + anyhow::bail!("Sending messages from Kusama is not yet supported.") } } diff --git a/bridges/relays/bin-substrate/src/chains/kusama_messages_to_polkadot.rs b/bridges/relays/bin-substrate/src/chains/kusama_messages_to_polkadot.rs index e006532d978a4..40e03ef266847 100644 --- a/bridges/relays/bin-substrate/src/chains/kusama_messages_to_polkadot.rs +++ b/bridges/relays/bin-substrate/src/chains/kusama_messages_to_polkadot.rs @@ -256,6 +256,7 @@ pub async fn run( futures::future::pending(), ) .await + .map_err(Into::into) } /// Add standalone metrics for the Kusama -> Polkadot messages loop. diff --git a/bridges/relays/bin-substrate/src/chains/millau.rs b/bridges/relays/bin-substrate/src/chains/millau.rs index 004e4c740a473..55b16b7786b73 100644 --- a/bridges/relays/bin-substrate/src/chains/millau.rs +++ b/bridges/relays/bin-substrate/src/chains/millau.rs @@ -23,6 +23,7 @@ use crate::cli::{ send_message::{self, DispatchFeePayment}, CliChain, }; +use anyhow::anyhow; use bp_message_dispatch::{CallOrigin, MessagePayload}; use codec::Decode; use frame_support::weights::{DispatchInfo, GetDispatchInfo, Weight}; @@ -87,10 +88,10 @@ impl CliChain for Millau { // TODO [#854|#843] support multiple bridges? fn encode_message( message: encode_message::MessagePayload, - ) -> Result { + ) -> anyhow::Result { match message { encode_message::MessagePayload::Raw { data } => MessagePayload::decode(&mut &*data.0) - .map_err(|e| format!("Failed to decode Millau's MessagePayload: {:?}", e)), + .map_err(|e| anyhow!("Failed to decode Millau's MessagePayload: {:?}", e)), encode_message::MessagePayload::Call { mut call, mut sender } => { type Source = Millau; type Target = relay_rialto_client::Rialto; @@ -102,7 +103,7 @@ impl CliChain for Millau { &mut call, bridge::MILLAU_TO_RIALTO_INDEX, ); - let call = Target::encode_call(&call).map_err(|e| e.to_string())?; + let call = Target::encode_call(&call)?; let weight = call.get_dispatch_info().weight; Ok(send_message::message_payload( diff --git a/bridges/relays/bin-substrate/src/chains/millau_messages_to_rialto.rs b/bridges/relays/bin-substrate/src/chains/millau_messages_to_rialto.rs index 7682f32e608e4..9c56e8bd3d404 100644 --- a/bridges/relays/bin-substrate/src/chains/millau_messages_to_rialto.rs +++ b/bridges/relays/bin-substrate/src/chains/millau_messages_to_rialto.rs @@ -250,6 +250,7 @@ pub async fn run( futures::future::pending(), ) .await + .map_err(Into::into) } /// Add standalone metrics for the Millau -> Rialto messages loop. diff --git a/bridges/relays/bin-substrate/src/chains/polkadot.rs b/bridges/relays/bin-substrate/src/chains/polkadot.rs index 6baeb50c14a35..55d17e46f13bb 100644 --- a/bridges/relays/bin-substrate/src/chains/polkadot.rs +++ b/bridges/relays/bin-substrate/src/chains/polkadot.rs @@ -97,7 +97,7 @@ impl CliChain for Polkadot { fn encode_message( _message: encode_message::MessagePayload, - ) -> Result { - Err("Sending messages from Polkadot is not yet supported.".into()) + ) -> anyhow::Result { + anyhow::bail!("Sending messages from Polkadot is not yet supported.") } } diff --git a/bridges/relays/bin-substrate/src/chains/polkadot_messages_to_kusama.rs b/bridges/relays/bin-substrate/src/chains/polkadot_messages_to_kusama.rs index 71943af44da36..31e8b710e348e 100644 --- a/bridges/relays/bin-substrate/src/chains/polkadot_messages_to_kusama.rs +++ b/bridges/relays/bin-substrate/src/chains/polkadot_messages_to_kusama.rs @@ -255,6 +255,7 @@ pub async fn run( futures::future::pending(), ) .await + .map_err(Into::into) } /// Add standalone metrics for the Polkadot -> Kusama messages loop. diff --git a/bridges/relays/bin-substrate/src/chains/rialto.rs b/bridges/relays/bin-substrate/src/chains/rialto.rs index 0575896d97fdb..837957bb87a1b 100644 --- a/bridges/relays/bin-substrate/src/chains/rialto.rs +++ b/bridges/relays/bin-substrate/src/chains/rialto.rs @@ -23,6 +23,7 @@ use crate::cli::{ send_message::{self, DispatchFeePayment}, CliChain, }; +use anyhow::anyhow; use bp_message_dispatch::{CallOrigin, MessagePayload}; use codec::Decode; use frame_support::weights::{DispatchInfo, GetDispatchInfo, Weight}; @@ -86,10 +87,10 @@ impl CliChain for Rialto { fn encode_message( message: encode_message::MessagePayload, - ) -> Result { + ) -> anyhow::Result { match message { encode_message::MessagePayload::Raw { data } => MessagePayload::decode(&mut &*data.0) - .map_err(|e| format!("Failed to decode Rialto's MessagePayload: {:?}", e)), + .map_err(|e| anyhow!("Failed to decode Rialto's MessagePayload: {:?}", e)), encode_message::MessagePayload::Call { mut call, mut sender } => { type Source = Rialto; type Target = relay_millau_client::Millau; @@ -101,7 +102,7 @@ impl CliChain for Rialto { &mut call, bridge::RIALTO_TO_MILLAU_INDEX, ); - let call = Target::encode_call(&call).map_err(|e| e.to_string())?; + let call = Target::encode_call(&call)?; let weight = call.get_dispatch_info().weight; Ok(send_message::message_payload( diff --git a/bridges/relays/bin-substrate/src/chains/rialto_messages_to_millau.rs b/bridges/relays/bin-substrate/src/chains/rialto_messages_to_millau.rs index 529f14b84a3da..99960cd00f5a0 100644 --- a/bridges/relays/bin-substrate/src/chains/rialto_messages_to_millau.rs +++ b/bridges/relays/bin-substrate/src/chains/rialto_messages_to_millau.rs @@ -249,6 +249,7 @@ pub async fn run( futures::future::pending(), ) .await + .map_err(Into::into) } /// Add standalone metrics for the Rialto -> Millau messages loop. diff --git a/bridges/relays/bin-substrate/src/chains/rialto_parachain.rs b/bridges/relays/bin-substrate/src/chains/rialto_parachain.rs index f81bc61b859a8..4c9f63bbe41f6 100644 --- a/bridges/relays/bin-substrate/src/chains/rialto_parachain.rs +++ b/bridges/relays/bin-substrate/src/chains/rialto_parachain.rs @@ -76,7 +76,7 @@ impl CliChain for RialtoParachain { fn encode_message( _message: encode_message::MessagePayload, - ) -> Result { - Err("Not supported".into()) + ) -> anyhow::Result { + anyhow::bail!("Not supported") } } diff --git a/bridges/relays/bin-substrate/src/chains/rococo.rs b/bridges/relays/bin-substrate/src/chains/rococo.rs index ddd0b6cc13b12..4df60f89faa21 100644 --- a/bridges/relays/bin-substrate/src/chains/rococo.rs +++ b/bridges/relays/bin-substrate/src/chains/rococo.rs @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . +use anyhow::anyhow; use codec::Decode; use frame_support::weights::{DispatchClass, DispatchInfo, Pays, Weight}; use relay_rococo_client::Rococo; @@ -94,7 +95,7 @@ impl CliChain for Rococo { fn encode_message( _message: encode_message::MessagePayload, - ) -> Result { - Err("Sending messages from Rococo is not yet supported.".into()) + ) -> anyhow::Result { + Err(anyhow!("Sending messages from Rococo is not yet supported.")) } } diff --git a/bridges/relays/bin-substrate/src/chains/rococo_messages_to_wococo.rs b/bridges/relays/bin-substrate/src/chains/rococo_messages_to_wococo.rs index e26adcb9a27b1..65eda19c6ef18 100644 --- a/bridges/relays/bin-substrate/src/chains/rococo_messages_to_wococo.rs +++ b/bridges/relays/bin-substrate/src/chains/rococo_messages_to_wococo.rs @@ -253,6 +253,7 @@ pub async fn run( futures::future::pending(), ) .await + .map_err(Into::into) } /// Add standalone metrics for the Rococo -> Wococo messages loop. diff --git a/bridges/relays/bin-substrate/src/chains/westend.rs b/bridges/relays/bin-substrate/src/chains/westend.rs index 586e6ffd95760..a42e4805512ca 100644 --- a/bridges/relays/bin-substrate/src/chains/westend.rs +++ b/bridges/relays/bin-substrate/src/chains/westend.rs @@ -17,6 +17,7 @@ //! Westend chain specification for CLI. use crate::cli::{encode_message, CliChain}; +use anyhow::anyhow; use frame_support::weights::Weight; use relay_westend_client::Westend; use sp_version::RuntimeVersion; @@ -37,7 +38,7 @@ impl CliChain for Westend { fn encode_message( _message: encode_message::MessagePayload, - ) -> Result { - Err("Sending messages from Westend is not yet supported.".into()) + ) -> anyhow::Result { + Err(anyhow!("Sending messages from Westend is not yet supported.")) } } diff --git a/bridges/relays/bin-substrate/src/chains/wococo.rs b/bridges/relays/bin-substrate/src/chains/wococo.rs index fbdbcf868c7c4..328397d14ba7c 100644 --- a/bridges/relays/bin-substrate/src/chains/wococo.rs +++ b/bridges/relays/bin-substrate/src/chains/wococo.rs @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . +use anyhow::anyhow; use codec::Decode; use frame_support::weights::{DispatchClass, DispatchInfo, Pays, Weight}; use relay_wococo_client::Wococo; @@ -88,7 +89,7 @@ impl CliChain for Wococo { fn encode_message( _message: encode_message::MessagePayload, - ) -> Result { - Err("Sending messages from Wococo is not yet supported.".into()) + ) -> anyhow::Result { + Err(anyhow!("Sending messages from Wococo is not yet supported.")) } } diff --git a/bridges/relays/bin-substrate/src/chains/wococo_messages_to_rococo.rs b/bridges/relays/bin-substrate/src/chains/wococo_messages_to_rococo.rs index 4d1fc4f0d815b..d380e6c29a4cb 100644 --- a/bridges/relays/bin-substrate/src/chains/wococo_messages_to_rococo.rs +++ b/bridges/relays/bin-substrate/src/chains/wococo_messages_to_rococo.rs @@ -252,6 +252,7 @@ pub async fn run( futures::future::pending(), ) .await + .map_err(Into::into) } /// Add standalone metrics for the Wococo -> Rococo messages loop. diff --git a/bridges/relays/bin-substrate/src/cli/mod.rs b/bridges/relays/bin-substrate/src/cli/mod.rs index c35c2b87dd8fb..f1180059b9949 100644 --- a/bridges/relays/bin-substrate/src/cli/mod.rs +++ b/bridges/relays/bin-substrate/src/cli/mod.rs @@ -257,7 +257,7 @@ pub trait CliChain: relay_substrate_client::Chain { /// Construct message payload to be sent over the bridge. fn encode_message( message: crate::cli::encode_message::MessagePayload, - ) -> Result; + ) -> anyhow::Result; /// Maximal extrinsic weight (from the runtime). fn max_extrinsic_weight() -> Weight; diff --git a/bridges/relays/bin-substrate/src/cli/resubmit_transactions.rs b/bridges/relays/bin-substrate/src/cli/resubmit_transactions.rs index d20cff17f6331..1091048b3ba00 100644 --- a/bridges/relays/bin-substrate/src/cli/resubmit_transactions.rs +++ b/bridges/relays/bin-substrate/src/cli/resubmit_transactions.rs @@ -141,6 +141,7 @@ impl ResubmitTransactions { ) }) .await + .map_err(Into::into) }) } } diff --git a/bridges/relays/client-ethereum/Cargo.toml b/bridges/relays/client-ethereum/Cargo.toml index 4263055d01b4e..171988a32533b 100644 --- a/bridges/relays/client-ethereum/Cargo.toml +++ b/bridges/relays/client-ethereum/Cargo.toml @@ -17,3 +17,4 @@ log = "0.4.11" relay-utils = { path = "../utils" } tokio = "1.8" web3 = { git = "https://github.com/svyatonik/rust-web3.git", branch = "bump-deps" } +thiserror = "1.0.26" diff --git a/bridges/relays/client-ethereum/src/error.rs b/bridges/relays/client-ethereum/src/error.rs index 23a9138e12085..6323b708fc024 100644 --- a/bridges/relays/client-ethereum/src/error.rs +++ b/bridges/relays/client-ethereum/src/error.rs @@ -20,49 +20,47 @@ use crate::types::U256; use jsonrpsee_ws_client::types::Error as RpcError; use relay_utils::MaybeConnectionError; +use thiserror::Error; /// Result type used by Ethereum client. pub type Result = std::result::Result; /// Errors that can occur only when interacting with /// an Ethereum node through RPC. -#[derive(Debug)] +#[derive(Debug, Error)] pub enum Error { /// IO error. - Io(std::io::Error), + #[error("IO error: {0}")] + Io(#[from] std::io::Error), /// An error that can occur when making an HTTP request to /// an JSON-RPC client. - RpcError(RpcError), + #[error("RPC error: {0}")] + RpcError(#[from] RpcError), /// Failed to parse response. + #[error("Response parse failed: {0}")] ResponseParseFailed(String), /// We have received a header with missing fields. + #[error("Incomplete Ethereum Header Received (missing some of required fields - hash, number, logs_bloom).")] IncompleteHeader, /// We have received a transaction missing a `raw` field. + #[error("Incomplete Ethereum Transaction (missing required field - raw).")] IncompleteTransaction, /// An invalid Substrate block number was received from /// an Ethereum node. + #[error("Received an invalid Substrate block from Ethereum Node.")] InvalidSubstrateBlockNumber, /// An invalid index has been received from an Ethereum node. + #[error("Received an invalid incomplete index from Ethereum Node.")] InvalidIncompleteIndex, /// The client we're connected to is not synced, so we can't rely on its state. Contains /// number of unsynced headers. + #[error("Ethereum client is not synced: syncing {0} headers.")] ClientNotSynced(U256), /// Custom logic error. + #[error("{0}")] Custom(String), } -impl From for Error { - fn from(error: RpcError) -> Self { - Error::RpcError(error) - } -} - -impl From for Error { - fn from(error: std::io::Error) -> Self { - Error::Io(error) - } -} - impl From for Error { fn from(error: tokio::task::JoinError) -> Self { Error::Custom(format!("Failed to wait tokio task: {}", error)) @@ -82,24 +80,3 @@ impl MaybeConnectionError for Error { ) } } - -impl ToString for Error { - fn to_string(&self) -> String { - match self { - Self::Io(e) => e.to_string(), - Self::RpcError(e) => e.to_string(), - Self::ResponseParseFailed(e) => e.to_string(), - Self::IncompleteHeader => { - "Incomplete Ethereum Header Received (missing some of required fields - hash, number, logs_bloom)" - .to_string() - } - Self::IncompleteTransaction => "Incomplete Ethereum Transaction (missing required field - raw)".to_string(), - Self::InvalidSubstrateBlockNumber => "Received an invalid Substrate block from Ethereum Node".to_string(), - Self::InvalidIncompleteIndex => "Received an invalid incomplete index from Ethereum Node".to_string(), - Self::ClientNotSynced(missing_headers) => { - format!("Ethereum client is not synced: syncing {} headers", missing_headers) - } - Self::Custom(ref e) => e.clone(), - } - } -} diff --git a/bridges/relays/client-substrate/Cargo.toml b/bridges/relays/client-substrate/Cargo.toml index 7d82e365e2f17..6a1173581e7a7 100644 --- a/bridges/relays/client-substrate/Cargo.toml +++ b/bridges/relays/client-substrate/Cargo.toml @@ -15,6 +15,7 @@ log = "0.4.11" num-traits = "0.2" rand = "0.7" tokio = "1.8" +thiserror = "1.0.26" # Bridge dependencies diff --git a/bridges/relays/client-substrate/src/error.rs b/bridges/relays/client-substrate/src/error.rs index c3cd236b948c5..33b9b22a03efe 100644 --- a/bridges/relays/client-substrate/src/error.rs +++ b/bridges/relays/client-substrate/src/error.rs @@ -20,78 +20,54 @@ use jsonrpsee_ws_client::types::Error as RpcError; use relay_utils::MaybeConnectionError; use sc_rpc_api::system::Health; use sp_runtime::transaction_validity::TransactionValidityError; +use thiserror::Error; /// Result type used by Substrate client. pub type Result = std::result::Result; /// Errors that can occur only when interacting with /// a Substrate node through RPC. -#[derive(Debug)] +#[derive(Error, Debug)] pub enum Error { /// IO error. - Io(std::io::Error), + #[error("IO error: {0}")] + Io(#[from] std::io::Error), /// An error that can occur when making a request to /// an JSON-RPC server. - RpcError(RpcError), + #[error("RPC error: {0}")] + RpcError(#[from] RpcError), /// The response from the server could not be SCALE decoded. - ResponseParseFailed(codec::Error), + #[error("Response parse failed: {0}")] + ResponseParseFailed(#[from] codec::Error), /// The Substrate bridge pallet has not yet been initialized. + #[error("The Substrate bridge pallet has not been initialized yet.")] UninitializedBridgePallet, /// Account does not exist on the chain. + #[error("Account does not exist on the chain.")] AccountDoesNotExist, /// Runtime storage is missing mandatory ":code:" entry. + #[error("Mandatory :code: entry is missing from runtime storage.")] MissingMandatoryCodeEntry, /// The client we're connected to is not synced, so we can't rely on its state. + #[error("Substrate client is not synced {0}.")] ClientNotSynced(Health), /// An error has happened when we have tried to parse storage proof. + #[error("Error when parsing storage proof: {0:?}.")] StorageProofError(bp_runtime::StorageProofError), /// The Substrate transaction is invalid. - TransactionInvalid(TransactionValidityError), + #[error("Substrate transaction is invalid: {0:?}")] + TransactionInvalid(#[from] TransactionValidityError), /// Custom logic error. + #[error("{0}")] Custom(String), } -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::Io(ref e) => Some(e), - Self::RpcError(ref e) => Some(e), - Self::ResponseParseFailed(ref e) => Some(e), - Self::UninitializedBridgePallet => None, - Self::AccountDoesNotExist => None, - Self::MissingMandatoryCodeEntry => None, - Self::ClientNotSynced(_) => None, - Self::StorageProofError(_) => None, - Self::TransactionInvalid(_) => None, - Self::Custom(_) => None, - } - } -} - -impl From for Error { - fn from(error: RpcError) -> Self { - Error::RpcError(error) - } -} - -impl From for Error { - fn from(error: std::io::Error) -> Self { - Error::Io(error) - } -} - impl From for Error { fn from(error: tokio::task::JoinError) -> Self { Error::Custom(format!("Failed to wait tokio task: {}", error)) } } -impl From for Error { - fn from(error: TransactionValidityError) -> Self { - Error::TransactionInvalid(error) - } -} - impl MaybeConnectionError for Error { fn is_connection_error(&self) -> bool { matches!( @@ -105,30 +81,3 @@ impl MaybeConnectionError for Error { ) } } - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - let s = match self { - Self::Io(e) => e.to_string(), - Self::RpcError(e) => e.to_string(), - Self::ResponseParseFailed(e) => e.to_string(), - Self::UninitializedBridgePallet => - "The Substrate bridge pallet has not been initialized yet.".into(), - Self::AccountDoesNotExist => "Account does not exist on the chain".into(), - Self::MissingMandatoryCodeEntry => - "Mandatory :code: entry is missing from runtime storage".into(), - Self::StorageProofError(e) => format!("Error when parsing storage proof: {:?}", e), - Self::ClientNotSynced(health) => format!("Substrate client is not synced: {}", health), - Self::TransactionInvalid(e) => format!("Substrate transaction is invalid: {:?}", e), - Self::Custom(e) => e.clone(), - }; - - write!(f, "{}", s) - } -} - -impl From for String { - fn from(error: Error) -> String { - error.to_string() - } -} diff --git a/bridges/relays/client-substrate/src/guard.rs b/bridges/relays/client-substrate/src/guard.rs index b6ec83f98408c..a064e36234007 100644 --- a/bridges/relays/client-substrate/src/guard.rs +++ b/bridges/relays/client-substrate/src/guard.rs @@ -17,23 +17,30 @@ //! Pallet provides a set of guard functions that are running in background threads //! and are aborting process if some condition fails. -use crate::{Chain, ChainWithBalances, Client}; +use crate::{error::Error, Chain, ChainWithBalances, Client}; use async_trait::async_trait; use num_traits::CheckedSub; use sp_version::RuntimeVersion; use std::{ collections::VecDeque, + fmt::Display, time::{Duration, Instant}, }; /// Guards environment. #[async_trait] pub trait Environment: Send + Sync + 'static { + /// Error type. + type Error: Display + Send + Sync + 'static; + /// Return current runtime version. - async fn runtime_version(&mut self) -> Result; + async fn runtime_version(&mut self) -> Result; /// Return free native balance of the account on the chain. - async fn free_native_balance(&mut self, account: C::AccountId) -> Result; + async fn free_native_balance( + &mut self, + account: C::AccountId, + ) -> Result; /// Return current time. fn now(&self) -> Instant { @@ -74,7 +81,7 @@ pub fn abort_on_spec_version_change( }, Err(error) => log::warn!( target: "bridge-guard", - "Failed to read {} runtime version: {:?}. Relay may need to be stopped manually", + "Failed to read {} runtime version: {}. Relay may need to be stopped manually", C::NAME, error, ), @@ -137,7 +144,7 @@ pub fn abort_when_account_balance_decreased( Err(error) => { log::warn!( target: "bridge-guard", - "Failed to read {} account {:?} balance: {:?}. Relay may need to be stopped manually", + "Failed to read {} account {:?} balance: {}. Relay may need to be stopped manually", C::NAME, account_id, error, @@ -157,12 +164,17 @@ fn conditions_check_delay() -> Duration { #[async_trait] impl Environment for Client { - async fn runtime_version(&mut self) -> Result { - Client::::runtime_version(self).await.map_err(|e| e.to_string()) + type Error = Error; + + async fn runtime_version(&mut self) -> Result { + Client::::runtime_version(self).await } - async fn free_native_balance(&mut self, account: C::AccountId) -> Result { - Client::::free_native_balance(self, account).await.map_err(|e| e.to_string()) + async fn free_native_balance( + &mut self, + account: C::AccountId, + ) -> Result { + Client::::free_native_balance(self, account).await } } @@ -220,11 +232,13 @@ mod tests { #[async_trait] impl Environment for TestEnvironment { - async fn runtime_version(&mut self) -> Result { + type Error = Error; + + async fn runtime_version(&mut self) -> Result { Ok(self.runtime_version_rx.next().await.unwrap_or_default()) } - async fn free_native_balance(&mut self, _account: u32) -> Result { + async fn free_native_balance(&mut self, _account: u32) -> Result { Ok(self.free_native_balance_rx.next().await.unwrap_or_default()) } diff --git a/bridges/relays/exchange/Cargo.toml b/bridges/relays/exchange/Cargo.toml index b8bb8481f616d..f08c40325ec71 100644 --- a/bridges/relays/exchange/Cargo.toml +++ b/bridges/relays/exchange/Cargo.toml @@ -15,3 +15,4 @@ log = "0.4.11" num-traits = "0.2" parking_lot = "0.11.0" relay-utils = { path = "../utils" } +thiserror = "1.0.26" diff --git a/bridges/relays/exchange/src/error.rs b/bridges/relays/exchange/src/error.rs new file mode 100644 index 0000000000000..aa5c427a9efbd --- /dev/null +++ b/bridges/relays/exchange/src/error.rs @@ -0,0 +1,66 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Exchange-relay errors. + +use crate::exchange::{BlockHashOf, BlockNumberOf, TransactionHashOf}; + +use relay_utils::MaybeConnectionError; +use std::fmt::{Debug, Display}; +use thiserror::Error; + +/// Error type given pipeline. +pub type ErrorOf

= Error, BlockNumberOf

, TransactionHashOf

>; + +/// Exchange-relay error type. +#[derive(Error, Debug)] +pub enum Error { + /// Failed to check finality of the requested header on the target node. + #[error("Failed to check finality of header {0}/{1} on {2} node: {3:?}")] + Finality(HeaderNumber, Hash, &'static str, anyhow::Error), + /// Error retrieving block from the source node. + #[error("Error retrieving block {0} from {1} node: {2:?}")] + RetrievingBlock(Hash, &'static str, anyhow::Error), + /// Error retrieving transaction from the source node. + #[error("Error retrieving transaction {0} from {1} node: {2:?}")] + RetrievingTransaction(SourceTxHash, &'static str, anyhow::Error), + /// Failed to check existence of header from the target node. + #[error("Failed to check existence of header {0}/{1} on {2} node: {3:?}")] + CheckHeaderExistence(HeaderNumber, Hash, &'static str, anyhow::Error), + /// Failed to prepare proof for the transaction from the source node. + #[error("Error building transaction {0} proof on {1} node: {2:?}")] + BuildTransactionProof(String, &'static str, anyhow::Error, bool), + /// Failed to submit the transaction proof to the target node. + #[error("Error submitting transaction {0} proof to {1} node: {2:?}")] + SubmitTransactionProof(String, &'static str, anyhow::Error, bool), + /// Transaction filtering failed. + #[error("Transaction filtering has failed with {0:?}")] + TransactionFiltering(anyhow::Error, bool), + /// Utilities/metrics error. + #[error("{0}")] + Utils(#[from] relay_utils::Error), +} + +impl MaybeConnectionError for Error { + fn is_connection_error(&self) -> bool { + match *self { + Self::BuildTransactionProof(_, _, _, b) => b, + Self::SubmitTransactionProof(_, _, _, b) => b, + Self::TransactionFiltering(_, b) => b, + _ => false, + } + } +} diff --git a/bridges/relays/exchange/src/exchange.rs b/bridges/relays/exchange/src/exchange.rs index b48a094ee3d5d..b4538d2636cec 100644 --- a/bridges/relays/exchange/src/exchange.rs +++ b/bridges/relays/exchange/src/exchange.rs @@ -16,11 +16,11 @@ //! Relaying proofs of exchange transaction. +use crate::error::{Error, ErrorOf}; + +use anyhow::anyhow; use async_trait::async_trait; -use relay_utils::{ - relay_loop::Client as RelayClient, FailedClient, MaybeConnectionError, - StringifiedMaybeConnectionError, -}; +use relay_utils::{relay_loop::Client as RelayClient, FailedClient, MaybeConnectionError}; use std::{ fmt::{Debug, Display}, string::ToString, @@ -67,7 +67,7 @@ pub trait SourceBlock: 'static + Send + Sync { /// Transaction that is participating in exchange. pub trait SourceTransaction: 'static + Send { /// Transaction hash type. - type Hash: Debug + Display; + type Hash: Debug + Display + Clone; /// Return transaction hash. fn hash(&self) -> Self::Hash; @@ -117,7 +117,7 @@ pub trait TargetClient: RelayClient { /// Sleep until exchange-related data is (probably) updated. async fn tick(&self); /// Returns `Ok(true)` if header is known to the target node. - async fn is_header_known(&self, id: &HeaderId

) -> Result; + async fn is_header_known(&self, id: &HeaderId

) -> std::result::Result; /// Returns `Ok(true)` if header is finalized by the target node. async fn is_header_finalized(&self, id: &HeaderId

) -> Result; /// Returns best finalized header id. @@ -178,9 +178,9 @@ pub async fn relay_block_transactions( target_client.filter_transaction_proof(&source_tx_proof).await.map_err(|err| { ( FailedClient::Target, - StringifiedMaybeConnectionError::new( + Error::TransactionFiltering( + anyhow!("{:?}", err), err.is_connection_error(), - format!("Transaction filtering has failed with {:?}", err), ), ) })?; @@ -256,20 +256,14 @@ pub async fn relay_single_transaction_proof( source_client: &impl SourceClient

, target_client: &impl TargetClient

, source_tx_hash: TransactionHashOf

, -) -> Result<(), String> { +) -> Result<(), ErrorOf

> { // wait for transaction and header on source node let (source_header_id, source_tx_index) = wait_transaction_mined(source_client, &source_tx_hash).await?; let source_block = source_client.block_by_hash(source_header_id.1.clone()).await; let source_block = source_block.map_err(|err| { - format!( - "Error retrieving block {} from {} node: {:?}", - source_header_id.1, - P::SOURCE_NAME, - err, - ) + Error::RetrievingBlock(source_header_id.1.clone(), P::SOURCE_NAME, anyhow!("{:?}", err)) })?; - // wait for transaction and header on target node wait_header_imported(target_client, &source_header_id).await?; wait_header_finalized(target_client, &source_header_id).await?; @@ -280,11 +274,10 @@ pub async fn relay_single_transaction_proof( target_client, &source_tx_id, prepare_transaction_proof(source_client, &source_tx_id, &source_block, source_tx_index) - .await - .map_err(|err| err.to_string())?, + .await?, ) .await - .map_err(|err| err.to_string()) + .map_err(Into::into) } /// Prepare transaction proof. @@ -293,19 +286,16 @@ async fn prepare_transaction_proof( source_tx_id: &str, source_block: &P::Block, source_tx_index: usize, -) -> Result { +) -> Result> { source_client .transaction_proof(source_block, source_tx_index) .await .map_err(|err| { - StringifiedMaybeConnectionError::new( + Error::BuildTransactionProof( + source_tx_id.to_owned(), + P::SOURCE_NAME, + anyhow!("{:?}", err), err.is_connection_error(), - format!( - "Error building transaction {} proof on {} node: {:?}", - source_tx_id, - P::SOURCE_NAME, - err, - ), ) }) } @@ -315,16 +305,13 @@ async fn relay_ready_transaction_proof( target_client: &impl TargetClient

, source_tx_id: &str, source_tx_proof: P::TransactionProof, -) -> Result<(), StringifiedMaybeConnectionError> { +) -> Result<(), ErrorOf

> { target_client.submit_transaction_proof(source_tx_proof).await.map_err(|err| { - StringifiedMaybeConnectionError::new( + Error::SubmitTransactionProof( + source_tx_id.to_owned(), + P::TARGET_NAME, + anyhow!("{:?}", err), err.is_connection_error(), - format!( - "Error submitting transaction {} proof to {} node: {:?}", - source_tx_id, - P::TARGET_NAME, - err, - ), ) }) } @@ -333,15 +320,14 @@ async fn relay_ready_transaction_proof( async fn wait_transaction_mined( source_client: &impl SourceClient

, source_tx_hash: &TransactionHashOf

, -) -> Result<(HeaderId

, usize), String> { +) -> Result<(HeaderId

, usize), ErrorOf

> { loop { let source_header_and_tx = source_client.transaction_block(source_tx_hash).await.map_err(|err| { - format!( - "Error retrieving transaction {} from {} node: {:?}", - source_tx_hash, + Error::RetrievingTransaction( + source_tx_hash.clone(), P::SOURCE_NAME, - err, + anyhow!("{:?}", err), ) })?; match source_header_and_tx { @@ -373,16 +359,15 @@ async fn wait_transaction_mined( async fn wait_header_imported( target_client: &impl TargetClient

, source_header_id: &HeaderId

, -) -> Result<(), String> { +) -> Result<(), ErrorOf

> { loop { let is_header_known = target_client.is_header_known(source_header_id).await.map_err(|err| { - format!( - "Failed to check existence of header {}/{} on {} node: {:?}", + Error::CheckHeaderExistence( source_header_id.0, - source_header_id.1, + source_header_id.1.clone(), P::TARGET_NAME, - err, + anyhow!("{:?}", err), ) })?; match is_header_known { @@ -416,16 +401,15 @@ async fn wait_header_imported( async fn wait_header_finalized( target_client: &impl TargetClient

, source_header_id: &HeaderId

, -) -> Result<(), String> { +) -> Result<(), ErrorOf

> { loop { let is_header_finalized = target_client.is_header_finalized(source_header_id).await.map_err(|err| { - format!( - "Failed to check finality of header {}/{} on {} node: {:?}", + Error::Finality( source_header_id.0, - source_header_id.1, + source_header_id.1.clone(), P::TARGET_NAME, - err, + anyhow!("{:?}", err), ) })?; match is_header_finalized { @@ -691,14 +675,12 @@ pub(crate) mod tests { source: &TestTransactionsSource, target: &TestTransactionsTarget, ) { - assert_eq!( - async_std::task::block_on(relay_single_transaction_proof( - source, - target, - test_transaction_hash(0), - )), - Ok(()), - ); + assert!(async_std::task::block_on(relay_single_transaction_proof( + source, + target, + test_transaction_hash(0) + )) + .is_ok()); assert_eq!( target.data.lock().submitted_proofs, vec![TestTransactionProof(test_transaction_hash(0))], @@ -711,7 +693,7 @@ pub(crate) mod tests { &target, test_transaction_hash(0), )) - .is_err(),); + .is_err()); assert!(target.data.lock().submitted_proofs.is_empty()); } diff --git a/bridges/relays/exchange/src/exchange_loop.rs b/bridges/relays/exchange/src/exchange_loop.rs index 2b19a30e21244..84f4f9261163c 100644 --- a/bridges/relays/exchange/src/exchange_loop.rs +++ b/bridges/relays/exchange/src/exchange_loop.rs @@ -17,6 +17,7 @@ //! Relaying proofs of exchange transactions. use crate::{ + error::Error, exchange::{ relay_block_transactions, BlockNumberOf, RelayedBlockTransactions, SourceClient, TargetClient, TransactionProofPipeline, @@ -24,6 +25,7 @@ use crate::{ exchange_loop_metrics::ExchangeLoopMetrics, }; +use crate::error::ErrorOf; use backoff::backoff::Backoff; use futures::{future::FutureExt, select}; use num_traits::One; @@ -92,7 +94,7 @@ pub async fn run( target_client: impl TargetClient

, metrics_params: MetricsParams, exit_signal: impl Future + 'static + Send, -) -> anyhow::Result<()> { +) -> Result<(), ErrorOf

> { let exit_signal = exit_signal.shared(); relay_utils::relay_loop(source_client, target_client) @@ -111,6 +113,7 @@ pub async fn run( ) }) .await + .map_err(Error::Utils) } /// Run proofs synchronization. diff --git a/bridges/relays/exchange/src/lib.rs b/bridges/relays/exchange/src/lib.rs index 370f085b4bf7c..d167e5aa398ee 100644 --- a/bridges/relays/exchange/src/lib.rs +++ b/bridges/relays/exchange/src/lib.rs @@ -21,6 +21,7 @@ #![warn(missing_docs)] +pub mod error; pub mod exchange; pub mod exchange_loop; pub mod exchange_loop_metrics; diff --git a/bridges/relays/finality/Cargo.toml b/bridges/relays/finality/Cargo.toml index 3ecda234db3ef..944da9837ffcb 100644 --- a/bridges/relays/finality/Cargo.toml +++ b/bridges/relays/finality/Cargo.toml @@ -7,7 +7,6 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0" description = "Finality proofs relay" [dependencies] -anyhow = "1.0" async-std = "1.6.5" async-trait = "0.1.40" backoff = "0.2" diff --git a/bridges/relays/finality/src/finality_loop.rs b/bridges/relays/finality/src/finality_loop.rs index c64c9d629bf05..1b1327ffde590 100644 --- a/bridges/relays/finality/src/finality_loop.rs +++ b/bridges/relays/finality/src/finality_loop.rs @@ -110,7 +110,7 @@ pub async fn run( sync_params: FinalitySyncParams, metrics_params: MetricsParams, exit_signal: impl Future + 'static + Send, -) -> anyhow::Result<()> { +) -> Result<(), relay_utils::Error> { let exit_signal = exit_signal.shared(); relay_utils::relay_loop(source_client, target_client) .with_metrics(Some(metrics_prefix::

()), metrics_params) diff --git a/bridges/relays/headers/Cargo.toml b/bridges/relays/headers/Cargo.toml index 8eda40e9356f8..31d3166a99781 100644 --- a/bridges/relays/headers/Cargo.toml +++ b/bridges/relays/headers/Cargo.toml @@ -6,7 +6,6 @@ edition = "2018" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" [dependencies] -anyhow = "1.0" async-std = "1.6.5" async-trait = "0.1.40" backoff = "0.2" diff --git a/bridges/relays/headers/src/sync_loop.rs b/bridges/relays/headers/src/sync_loop.rs index d54a445d7f0e5..419211835aa8e 100644 --- a/bridges/relays/headers/src/sync_loop.rs +++ b/bridges/relays/headers/src/sync_loop.rs @@ -143,7 +143,7 @@ pub async fn run>( sync_params: HeadersSyncParams, metrics_params: MetricsParams, exit_signal: impl Future + 'static + Send, -) -> anyhow::Result<()> { +) -> Result<(), relay_utils::Error> { let exit_signal = exit_signal.shared(); relay_utils::relay_loop(source_client, target_client) .with_metrics(Some(metrics_prefix::

()), metrics_params) diff --git a/bridges/relays/lib-substrate-relay/Cargo.toml b/bridges/relays/lib-substrate-relay/Cargo.toml index 7ab81b786b5d0..5be28bccfb3bb 100644 --- a/bridges/relays/lib-substrate-relay/Cargo.toml +++ b/bridges/relays/lib-substrate-relay/Cargo.toml @@ -7,6 +7,7 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0" [dependencies] anyhow = "1.0" +thiserror = "1.0.26" async-std = "1.9.0" async-trait = "0.1.42" codec = { package = "parity-scale-codec", version = "2.2.0" } diff --git a/bridges/relays/lib-substrate-relay/src/error.rs b/bridges/relays/lib-substrate-relay/src/error.rs new file mode 100644 index 0000000000000..802499503563d --- /dev/null +++ b/bridges/relays/lib-substrate-relay/src/error.rs @@ -0,0 +1,58 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! Relay errors. + +use relay_substrate_client as client; +use sp_finality_grandpa::AuthorityList; +use sp_runtime::traits::MaybeDisplay; +use std::fmt::Debug; +use thiserror::Error; + +/// Relay errors. +#[derive(Error, Debug)] +pub enum Error { + /// Failed to submit signed extrinsic from to the target chain. + #[error("Failed to submit {0} transaction: {1:?}")] + SubmitTransaction(&'static str, client::Error), + /// Failed subscribe to justification stream of the source chain. + #[error("Failed to subscribe to {0} justifications: {1:?}")] + Subscribe(&'static str, client::Error), + /// Failed subscribe to read justification from the source chain (client error). + #[error("Failed to read {0} justification from the stream: {1}")] + ReadJustification(&'static str, client::Error), + /// Failed subscribe to read justification from the source chain (stream ended). + #[error("Failed to read {0} justification from the stream: stream has ended unexpectedly")] + ReadJustificationStreamEnded(&'static str), + /// Failed subscribe to decode justification from the source chain. + #[error("Failed to decode {0} justification: {1:?}")] + DecodeJustification(&'static str, codec::Error), + /// GRANDPA authorities read from the source chain are invalid. + #[error("Read invalid {0} authorities set: {1:?}")] + ReadInvalidAuthorities(&'static str, AuthorityList), + /// Failed to guess initial GRANDPA authorities at the given header of the source chain. + #[error("Failed to guess initial {0} GRANDPA authorities set id: checked all possible ids in range [0; {1}]")] + GuessInitialAuthorities(&'static str, HeaderNumber), + /// Failed to retrieve GRANDPA authorities at the given header from the source chain. + #[error("Failed to retrive {0} GRANDPA authorities set at header {1}: {2:?}")] + RetrieveAuthorities(&'static str, Hash, client::Error), + /// Failed to decode GRANDPA authorities at the given header of the source chain. + #[error("Failed to decode {0} GRANDPA authorities set at header {1}: {2:?}")] + DecodeAuthorities(&'static str, Hash, codec::Error), + /// Failed to retrieve header by the hash from the source chain. + #[error("Failed to retrieve {0} header with hash {1}: {:?}")] + RetrieveHeader(&'static str, Hash, client::Error), +} diff --git a/bridges/relays/lib-substrate-relay/src/headers_initialize.rs b/bridges/relays/lib-substrate-relay/src/headers_initialize.rs index 00397434a0d7e..ca82f50942ab3 100644 --- a/bridges/relays/lib-substrate-relay/src/headers_initialize.rs +++ b/bridges/relays/lib-substrate-relay/src/headers_initialize.rs @@ -21,6 +21,8 @@ //! and authorities set from source to target chain. The headers sync starts //! with this header. +use crate::error::Error; + use bp_header_chain::{ find_grandpa_authorities_scheduled_change, justification::{verify_justification, GrandpaJustification}, @@ -32,7 +34,7 @@ use num_traits::{One, Zero}; use relay_substrate_client::{Chain, Client}; use sp_core::Bytes; use sp_finality_grandpa::AuthorityList as GrandpaAuthoritiesSet; -use sp_runtime::traits::Header as HeaderT; +use sp_runtime::traits::{Header as HeaderT, Header}; /// Submit headers-bridge initialization transaction. pub async fn initialize( @@ -77,7 +79,7 @@ async fn do_initialize( prepare_initialize_transaction: impl FnOnce(TargetChain::Index, InitializationData) -> Bytes + Send + 'static, -) -> Result { +) -> Result::Number>> { let initialization_data = prepare_initialization_data(source_client).await?; log::info!( target: "bridge", @@ -92,41 +94,40 @@ async fn do_initialize( prepare_initialize_transaction(transaction_nonce, initialization_data) }) .await - .map_err(|err| format!("Failed to submit {} transaction: {:?}", TargetChain::NAME, err))?; + .map_err(|err| Error::SubmitTransaction(TargetChain::NAME, err))?; Ok(initialization_tx_hash) } /// Prepare initialization data for the GRANDPA verifier pallet. async fn prepare_initialization_data( source_client: Client, -) -> Result, String> { +) -> Result< + InitializationData, + Error::Number>, +> { // In ideal world we just need to get best finalized header and then to read GRANDPA authorities // set (`pallet_grandpa::CurrentSetId` + `GrandpaApi::grandpa_authorities()`) at this header. // // But now there are problems with this approach - `CurrentSetId` may return invalid value. So // here we're waiting for the next justification, read the authorities set and then try to // figure out the set id with bruteforce. - let justifications = source_client.subscribe_justifications().await.map_err(|err| { - format!("Failed to subscribe to {} justifications: {:?}", SourceChain::NAME, err) - })?; - + let justifications = source_client + .subscribe_justifications() + .await + .map_err(|err| Error::Subscribe(SourceChain::NAME, err))?; // Read next justification - the header that it finalizes will be used as initial header. let justification = justifications .next() .await - .map_err(|err| err.to_string()) + .map_err(|e| Error::ReadJustification(SourceChain::NAME, e)) .and_then(|justification| { - justification.ok_or_else(|| "stream has ended unexpectedly".into()) - }) - .map_err(|err| { - format!("Failed to read {} justification from the stream: {}", SourceChain::NAME, err,) + justification.ok_or_else(|| Error::ReadJustificationStreamEnded(SourceChain::NAME)) })?; // Read initial header. let justification: GrandpaJustification = - Decode::decode(&mut &justification.0[..]).map_err(|err| { - format!("Failed to decode {} justification: {:?}", SourceChain::NAME, err) - })?; + Decode::decode(&mut &justification.0[..]) + .map_err(|err| Error::DecodeJustification(SourceChain::NAME, err))?; let (initial_header_hash, initial_header_number) = (justification.commit.target_hash, justification.commit.target_number); @@ -174,11 +175,7 @@ async fn prepare_initialization_data( let mut min_possible_block_number = SourceChain::BlockNumber::zero(); let authorities_for_verification = VoterSet::new(authorities_for_verification.clone()) .ok_or_else(|| { - format!( - "Read invalid {} authorities set: {:?}", - SourceChain::NAME, - authorities_for_verification, - ) + Error::ReadInvalidAuthorities(SourceChain::NAME, authorities_for_verification) })?; loop { log::trace!( @@ -205,12 +202,7 @@ async fn prepare_initialization_data( // there can't be more authorities set changes than headers => if we have reached // `initial_block_number` and still have not found correct value of // `initial_authorities_set_id`, then something else is broken => fail - return Err(format!( - "Failed to guess initial {} GRANDPA authorities set id: checked all\ - possible ids in range [0; {}]", - SourceChain::NAME, - initial_header_number - )) + return Err(Error::GuessInitialAuthorities(SourceChain::NAME, initial_header_number)) } } @@ -230,37 +222,24 @@ async fn prepare_initialization_data( async fn source_header( source_client: &Client, header_hash: SourceChain::Hash, -) -> Result { - source_client.header_by_hash(header_hash).await.map_err(|err| { - format!( - "Failed to retrive {} header with hash {}: {:?}", - SourceChain::NAME, - header_hash, - err, - ) - }) +) -> Result::Number>> +{ + source_client + .header_by_hash(header_hash) + .await + .map_err(|err| Error::RetrieveHeader(SourceChain::NAME, header_hash, err)) } /// Read GRANDPA authorities set at given header. async fn source_authorities_set( source_client: &Client, header_hash: SourceChain::Hash, -) -> Result { - let raw_authorities_set = - source_client.grandpa_authorities_set(header_hash).await.map_err(|err| { - format!( - "Failed to retrive {} GRANDPA authorities set at header {}: {:?}", - SourceChain::NAME, - header_hash, - err, - ) - })?; - GrandpaAuthoritiesSet::decode(&mut &raw_authorities_set[..]).map_err(|err| { - format!( - "Failed to decode {} GRANDPA authorities set at header {}: {:?}", - SourceChain::NAME, - header_hash, - err, - ) - }) +) -> Result::Number>> +{ + let raw_authorities_set = source_client + .grandpa_authorities_set(header_hash) + .await + .map_err(|err| Error::RetrieveAuthorities(SourceChain::NAME, header_hash, err))?; + GrandpaAuthoritiesSet::decode(&mut &raw_authorities_set[..]) + .map_err(|err| Error::DecodeAuthorities(SourceChain::NAME, header_hash, err)) } diff --git a/bridges/relays/lib-substrate-relay/src/lib.rs b/bridges/relays/lib-substrate-relay/src/lib.rs index 380bcfef2db5f..dac6b8caa8e5c 100644 --- a/bridges/relays/lib-substrate-relay/src/lib.rs +++ b/bridges/relays/lib-substrate-relay/src/lib.rs @@ -19,6 +19,7 @@ #![warn(missing_docs)] pub mod conversion_rate_update; +pub mod error; pub mod finality_pipeline; pub mod finality_target; pub mod headers_initialize; diff --git a/bridges/relays/messages/Cargo.toml b/bridges/relays/messages/Cargo.toml index 8fc0e7c4f19e6..ea5d46845c5a3 100644 --- a/bridges/relays/messages/Cargo.toml +++ b/bridges/relays/messages/Cargo.toml @@ -6,7 +6,6 @@ edition = "2018" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" [dependencies] -anyhow = "1.0" async-std = { version = "1.6.5", features = ["attributes"] } async-trait = "0.1.40" futures = "0.3.5" diff --git a/bridges/relays/messages/src/message_lane_loop.rs b/bridges/relays/messages/src/message_lane_loop.rs index 595d241bf3014..bfd1a2b94082b 100644 --- a/bridges/relays/messages/src/message_lane_loop.rs +++ b/bridges/relays/messages/src/message_lane_loop.rs @@ -263,7 +263,7 @@ pub async fn run( target_client: impl TargetClient

, metrics_params: MetricsParams, exit_signal: impl Future + Send + 'static, -) -> anyhow::Result<()> { +) -> Result<(), relay_utils::Error> { let exit_signal = exit_signal.shared(); relay_utils::relay_loop(source_client, target_client) .reconnect_delay(params.reconnect_delay) diff --git a/bridges/relays/utils/Cargo.toml b/bridges/relays/utils/Cargo.toml index ea5f33b77e85b..652ff54002833 100644 --- a/bridges/relays/utils/Cargo.toml +++ b/bridges/relays/utils/Cargo.toml @@ -20,6 +20,7 @@ num-traits = "0.2" serde_json = "1.0" sysinfo = "0.15" time = "0.2" +thiserror = "1.0.26" # Substrate dependencies diff --git a/bridges/relays/utils/src/error.rs b/bridges/relays/utils/src/error.rs new file mode 100644 index 0000000000000..2e4128feb072a --- /dev/null +++ b/bridges/relays/utils/src/error.rs @@ -0,0 +1,46 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +use std::net::AddrParseError; +use thiserror::Error; + +/// Result type used by relay utilities. +pub type Result = std::result::Result; + +/// Relay utilities errors. +#[derive(Error, Debug)] +pub enum Error { + /// Failed to request a float value from HTTP service. + #[error("Failed to fetch token price from remote server: {0}")] + FetchTokenPrice(#[source] anyhow::Error), + /// Failed to parse the response from HTTP service. + #[error("Failed to parse HTTP service response: {0:?}. Response: {1:?}")] + ParseHttp(serde_json::Error, String), + /// Failed to select response value from the JSON response. + #[error("Failed to select value from response: {0:?}. Response: {1:?}")] + SelectResponseValue(jsonpath_lib::JsonPathError, String), + /// Failed to parse float value from the selected value. + #[error( + "Failed to parse float value {0:?} from response. It is assumed to be positive and normal" + )] + ParseFloat(f64), + /// Couldn't found value in the JSON response. + #[error("Missing required value from response: {0:?}")] + MissingResponseValue(String), + /// Invalid host address was used for exposing Prometheus metrics. + #[error("Invalid host {0} is used to expose Prometheus metrics: {1}")] + ExposingMetricsInvalidHost(String, AddrParseError), + /// Prometheus error. + #[error("{0}")] + Prometheus(#[from] substrate_prometheus_endpoint::prometheus::Error), +} diff --git a/bridges/relays/utils/src/lib.rs b/bridges/relays/utils/src/lib.rs index deec1d688a7de..936e9ba68156a 100644 --- a/bridges/relays/utils/src/lib.rs +++ b/bridges/relays/utils/src/lib.rs @@ -16,11 +16,13 @@ //! Utilities used by different relays. +pub use error::Error; pub use relay_loop::{relay_loop, relay_metrics}; use backoff::{backoff::Backoff, ExponentialBackoff}; use futures::future::FutureExt; use std::time::Duration; +use thiserror::Error; /// Max delay after connection-unrelated error happened before we'll try the /// same request again. @@ -29,6 +31,7 @@ pub const MAX_BACKOFF_INTERVAL: Duration = Duration::from_secs(60); /// reconnection again. pub const CONNECTION_ERROR_DELAY: Duration = Duration::from_secs(10); +pub mod error; pub mod initialize; pub mod metrics; pub mod relay_loop; @@ -111,11 +114,13 @@ pub trait MaybeConnectionError { } /// Stringified error that may be either connection-related or not. -#[derive(Debug)] +#[derive(Error, Debug)] pub enum StringifiedMaybeConnectionError { /// The error is connection-related error. + #[error("{0}")] Connection(String), /// The error is connection-unrelated error. + #[error("{0}")] NonConnection(String), } @@ -139,15 +144,6 @@ impl MaybeConnectionError for StringifiedMaybeConnectionError { } } -impl ToString for StringifiedMaybeConnectionError { - fn to_string(&self) -> String { - match *self { - StringifiedMaybeConnectionError::Connection(ref err) => err.clone(), - StringifiedMaybeConnectionError::NonConnection(ref err) => err.clone(), - } - } -} - /// Exponential backoff for connection-unrelated errors retries. pub fn retry_backoff() -> ExponentialBackoff { ExponentialBackoff { diff --git a/bridges/relays/utils/src/metrics/float_json_value.rs b/bridges/relays/utils/src/metrics/float_json_value.rs index a57cc5a77f579..9404695c1c305 100644 --- a/bridges/relays/utils/src/metrics/float_json_value.rs +++ b/bridges/relays/utils/src/metrics/float_json_value.rs @@ -14,8 +14,12 @@ // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . -use crate::metrics::{ - metric_name, register, F64SharedRef, Gauge, PrometheusError, Registry, StandaloneMetrics, F64, +use crate::{ + error::{self, Error}, + metrics::{ + metric_name, register, F64SharedRef, Gauge, PrometheusError, Registry, StandaloneMetrics, + F64, + }, }; use async_std::sync::{Arc, RwLock}; @@ -61,27 +65,18 @@ impl FloatJsonValueMetric { self.shared_value_ref.clone() } - /// Read value from HTTP service. - async fn read_value(&self) -> Result { + /// Request value from HTTP service. + async fn request_value(&self) -> anyhow::Result { use isahc::{AsyncReadResponseExt, HttpClient, Request}; - fn map_isahc_err(err: impl std::fmt::Display) -> String { - format!("Failed to fetch token price from remote server: {}", err) - } - - let request = Request::get(&self.url) - .header("Accept", "application/json") - .body(()) - .map_err(map_isahc_err)?; - let raw_response = HttpClient::new() - .map_err(map_isahc_err)? - .send_async(request) - .await - .map_err(map_isahc_err)? - .text() - .await - .map_err(map_isahc_err)?; + let request = Request::get(&self.url).header("Accept", "application/json").body(())?; + let raw_response = HttpClient::new()?.send_async(request).await?.text().await?; + Ok(raw_response) + } + /// Read value from HTTP service. + async fn read_value(&self) -> error::Result { + let raw_response = self.request_value().await.map_err(Error::FetchTokenPrice)?; parse_service_response(&self.json_path, &raw_response) } } @@ -94,30 +89,26 @@ impl StandaloneMetrics for FloatJsonValueMetric { async fn update(&self) { let value = self.read_value().await; - crate::metrics::set_gauge_value(&self.metric, value.clone().map(Some)); - *self.shared_value_ref.write().await = value.ok(); + let maybe_ok = value.as_ref().ok().copied(); + crate::metrics::set_gauge_value(&self.metric, value.map(Some)); + *self.shared_value_ref.write().await = maybe_ok; } } /// Parse HTTP service response. -fn parse_service_response(json_path: &str, response: &str) -> Result { - let json = serde_json::from_str(response).map_err(|err| { - format!("Failed to parse HTTP service response: {:?}. Response: {:?}", err, response,) - })?; +fn parse_service_response(json_path: &str, response: &str) -> error::Result { + let json = + serde_json::from_str(response).map_err(|err| Error::ParseHttp(err, response.to_owned()))?; let mut selector = jsonpath_lib::selector(&json); - let maybe_selected_value = selector(json_path).map_err(|err| { - format!("Failed to select value from response: {:?}. Response: {:?}", err, response,) - })?; + let maybe_selected_value = + selector(json_path).map_err(|err| Error::SelectResponseValue(err, response.to_owned()))?; let selected_value = maybe_selected_value .first() .and_then(|v| v.as_f64()) - .ok_or_else(|| format!("Missing required value from response: {:?}", response,))?; + .ok_or_else(|| Error::MissingResponseValue(response.to_owned()))?; if !selected_value.is_normal() || selected_value < 0.0 { - return Err(format!( - "Failed to parse float value {:?} from response. It is assumed to be positive and normal", - selected_value, - )) + return Err(Error::ParseFloat(selected_value)) } Ok(selected_value) diff --git a/bridges/relays/utils/src/relay_loop.rs b/bridges/relays/utils/src/relay_loop.rs index 49047a8810d6f..4898185a150b8 100644 --- a/bridges/relays/utils/src/relay_loop.rs +++ b/bridges/relays/utils/src/relay_loop.rs @@ -15,6 +15,7 @@ // along with Parity Bridges Common. If not, see . use crate::{ + error::Error, metrics::{Metrics, MetricsAddress, MetricsParams, PrometheusError, StandaloneMetrics}, FailedClient, MaybeConnectionError, }; @@ -116,7 +117,7 @@ impl Loop { /// This function represents an outer loop, which in turn calls provided `run_loop` function to /// do actual job. When `run_loop` returns, this outer loop reconnects to failed client (source, /// target or both) and calls `run_loop` again. - pub async fn run(mut self, loop_name: String, run_loop: R) -> anyhow::Result<()> + pub async fn run(mut self, loop_name: String, run_loop: R) -> Result<(), Error> where R: 'static + Send + Fn(SC, TC, Option) -> F, F: 'static + Send + Future>, @@ -162,7 +163,7 @@ impl LoopMetrics { pub fn loop_metric( self, create_metric: impl FnOnce(&Registry, Option<&str>) -> Result, - ) -> anyhow::Result> { + ) -> Result, Error> { let loop_metric = create_metric(&self.registry, self.metrics_prefix.as_deref())?; Ok(LoopMetrics { @@ -178,13 +179,13 @@ impl LoopMetrics { pub fn standalone_metric( self, create_metric: impl FnOnce(&Registry, Option<&str>) -> Result, - ) -> anyhow::Result { + ) -> Result { // since standalone metrics are updating themselves, we may just ignore the fact that the // same standalone metric is exposed by several loops && only spawn single metric match create_metric(&self.registry, self.metrics_prefix.as_deref()) { Ok(standalone_metrics) => standalone_metrics.spawn(), Err(PrometheusError::AlreadyReg) => (), - Err(e) => anyhow::bail!(e), + Err(e) => return Err(e.into()), } Ok(self) @@ -202,16 +203,13 @@ impl LoopMetrics { /// Expose metrics using address passed at creation. /// /// If passed `address` is `None`, metrics are not exposed. - pub async fn expose(self) -> anyhow::Result> { + pub async fn expose(self) -> Result, Error> { if let Some(address) = self.address { let socket_addr = SocketAddr::new( - address.host.parse().map_err(|err| { - anyhow::format_err!( - "Invalid host {} is used to expose Prometheus metrics: {}", - address.host, - err, - ) - })?, + address + .host + .parse() + .map_err(|err| Error::ExposingMetricsInvalidHost(address.host.clone(), err))?, address.port, );