Unify error enums in substrate and ethereum clients with thiserror (#1094)

* Unify error enums in substrate and ethereum clients with `thiserror`

Related to https://github.com/paritytech/parity-bridges-common/issues/857

* Add license pre-amble

* rustfmt

* Fix spelling
This commit is contained in:
Vladislav
2021-10-22 13:15:50 +03:00
committed by Bastian Köcher
parent 7b4f1c2236
commit 5842968273
48 changed files with 482 additions and 381 deletions
+1
View File
@@ -21,6 +21,7 @@ libsecp256k1 = { version = "0.7", default-features = false, features = ["hmac"]
log = "0.4.14" log = "0.4.14"
num-traits = "0.2" num-traits = "0.2"
serde_json = "1.0.64" serde_json = "1.0.64"
thiserror = "1.0.26"
# Bridge dependencies # Bridge dependencies
+38
View File
@@ -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 <http://www.gnu.org/licenses/>.
use crate::rpc_errors::RpcError;
use thiserror::Error;
/// Result type used by PoA relay.
pub type Result<T> = std::result::Result<T, Error>;
/// 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),
}
@@ -15,6 +15,7 @@
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>. // along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use crate::{ use crate::{
error::{Error, Result},
ethereum_client::{bridge_contract, EthereumHighLevelRpc}, ethereum_client::{bridge_contract, EthereumHighLevelRpc},
rpc_errors::RpcError, rpc_errors::RpcError,
}; };
@@ -104,20 +105,20 @@ pub async fn run(params: EthereumDeployContractParams) {
async fn prepare_initial_header( async fn prepare_initial_header(
sub_client: &SubstrateClient<Rialto>, sub_client: &SubstrateClient<Rialto>,
sub_initial_header: Option<Vec<u8>>, sub_initial_header: Option<Vec<u8>>,
) -> Result<(RialtoHeaderId, Vec<u8>), String> { ) -> Result<(RialtoHeaderId, Vec<u8>)> {
match sub_initial_header { match sub_initial_header {
Some(raw_initial_header) => { Some(raw_initial_header) => {
match rialto_runtime::Header::decode(&mut &raw_initial_header[..]) { match rialto_runtime::Header::decode(&mut &raw_initial_header[..]) {
Ok(initial_header) => Ok(initial_header) =>
Ok((HeaderId(initial_header.number, initial_header.hash()), raw_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 => { None => {
let initial_header = sub_client.header_by_number(Zero::zero()).await; let initial_header = sub_client.header_by_number(Zero::zero()).await;
initial_header initial_header
.map(|header| (HeaderId(Zero::zero(), header.hash()), header.encode())) .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<Rialto>, sub_client: &SubstrateClient<Rialto>,
sub_initial_header_hash: rialto_runtime::Hash, sub_initial_header_hash: rialto_runtime::Hash,
sub_initial_authorities_set: Option<Vec<u8>>, sub_initial_authorities_set: Option<Vec<u8>>,
) -> Result<OpaqueGrandpaAuthoritiesSet, String> { ) -> Result<OpaqueGrandpaAuthoritiesSet> {
let initial_authorities_set = match sub_initial_authorities_set { let initial_authorities_set = match sub_initial_authorities_set {
Some(initial_authorities_set) => Ok(initial_authorities_set), Some(initial_authorities_set) => Ok(initial_authorities_set),
None => sub_client.grandpa_authorities_set(sub_initial_header_hash).await, None => sub_client.grandpa_authorities_set(sub_initial_header_hash).await,
}; };
initial_authorities_set initial_authorities_set.map_err(|error| Error::ReadAuthorities(error))
.map_err(|error| format!("Error reading GRANDPA authorities set: {:?}", error))
} }
/// Deploy bridge contract to Ethereum chain. /// Deploy bridge contract to Ethereum chain.
@@ -145,7 +145,7 @@ async fn deploy_bridge_contract(
initial_header: Vec<u8>, initial_header: Vec<u8>,
initial_set_id: u64, initial_set_id: u64,
initial_authorities: Vec<u8>, initial_authorities: Vec<u8>,
) -> Result<(), String> { ) -> Result<()> {
eth_client eth_client
.submit_ethereum_transaction( .submit_ethereum_transaction(
params, params,
@@ -160,5 +160,5 @@ async fn deploy_bridge_contract(
), ),
) )
.await .await
.map_err(|error| format!("Error deploying contract: {:?}", error)) .map_err(|error| Error::DeployContract(error))
} }
@@ -339,7 +339,7 @@ pub async fn run(params: EthereumExchangeParams) {
async fn run_single_transaction_relay( async fn run_single_transaction_relay(
params: EthereumExchangeParams, params: EthereumExchangeParams,
eth_tx_hash: H256, eth_tx_hash: H256,
) -> Result<(), String> { ) -> anyhow::Result<()> {
let EthereumExchangeParams { eth_params, sub_params, sub_sign, instance, .. } = params; let EthereumExchangeParams { eth_params, sub_params, sub_sign, instance, .. } = params;
let eth_client = EthereumClient::try_connect(eth_params).await.map_err(RpcError::Ethereum)?; 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, 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( async fn run_auto_transactions_relay_loop(
@@ -16,6 +16,7 @@
//! Submitting Ethereum -> Substrate exchange transactions. //! Submitting Ethereum -> Substrate exchange transactions.
use anyhow::anyhow;
use bp_eth_poa::{ use bp_eth_poa::{
signatures::{secret_to_address, SignTransaction}, signatures::{secret_to_address, SignTransaction},
UnsignedTransaction, UnsignedTransaction,
@@ -47,10 +48,10 @@ pub async fn run(params: EthereumExchangeSubmitParams) {
let EthereumExchangeSubmitParams { eth_params, eth_sign, eth_nonce, eth_amount, sub_recipient } = let EthereumExchangeSubmitParams { eth_params, eth_sign, eth_nonce, eth_amount, sub_recipient } =
params; params;
let result: Result<_, String> = async move { let result: anyhow::Result<_> = async move {
let eth_client = EthereumClient::try_connect(eth_params) let eth_client = EthereumClient::try_connect(eth_params)
.await .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(&eth_sign.signer); let eth_signer_address = secret_to_address(&eth_sign.signer);
let sub_recipient_encoded = sub_recipient; let sub_recipient_encoded = sub_recipient;
@@ -59,7 +60,7 @@ pub async fn run(params: EthereumExchangeSubmitParams) {
None => eth_client None => eth_client
.account_nonce(eth_signer_address) .account_nonce(eth_signer_address)
.await .await
.map_err(|err| format!("error fetching acount nonce: {:?}", err))?, .map_err(|err| anyhow!("error fetching acount nonce: {:?}", err))?,
}; };
let gas = eth_client let gas = eth_client
.estimate_gas(CallRequest { .estimate_gas(CallRequest {
@@ -70,7 +71,7 @@ pub async fn run(params: EthereumExchangeSubmitParams) {
..Default::default() ..Default::default()
}) })
.await .await
.map_err(|err| format!("error estimating gas requirements: {:?}", err))?; .map_err(|err| anyhow!("error estimating gas requirements: {:?}", err))?;
let eth_tx_unsigned = UnsignedTransaction { let eth_tx_unsigned = UnsignedTransaction {
nonce, nonce,
gas_price: eth_sign.gas_price, gas_price: eth_sign.gas_price,
@@ -84,7 +85,7 @@ pub async fn run(params: EthereumExchangeSubmitParams) {
eth_client eth_client
.submit_transaction(eth_tx_signed) .submit_transaction(eth_tx_signed)
.await .await
.map_err(|err| format!("error submitting transaction: {:?}", err))?; .map_err(|err| anyhow!("error submitting transaction: {:?}", err))?;
Ok(eth_tx_unsigned) Ok(eth_tx_unsigned)
} }
+31 -29
View File
@@ -16,6 +16,7 @@
#![recursion_limit = "1024"] #![recursion_limit = "1024"]
mod error;
mod ethereum_client; mod ethereum_client;
mod ethereum_deploy_contract; mod ethereum_deploy_contract;
mod ethereum_exchange; mod ethereum_exchange;
@@ -27,6 +28,7 @@ mod rpc_errors;
mod substrate_sync_loop; mod substrate_sync_loop;
mod substrate_types; mod substrate_types;
use anyhow::anyhow;
use ethereum_deploy_contract::EthereumDeployContractParams; use ethereum_deploy_contract::EthereumDeployContractParams;
use ethereum_exchange::EthereumExchangeParams; use ethereum_exchange::EthereumExchangeParams;
use ethereum_exchange_submit::EthereumExchangeSubmitParams; use ethereum_exchange_submit::EthereumExchangeSubmitParams;
@@ -136,58 +138,58 @@ async fn run_command(matches: &clap::ArgMatches<'_>) {
fn ethereum_connection_params( fn ethereum_connection_params(
matches: &clap::ArgMatches, matches: &clap::ArgMatches,
) -> Result<EthereumConnectionParams, String> { ) -> anyhow::Result<EthereumConnectionParams> {
let mut params = EthereumConnectionParams::default(); let mut params = EthereumConnectionParams::default();
if let Some(eth_host) = matches.value_of("eth-host") { if let Some(eth_host) = matches.value_of("eth-host") {
params.host = eth_host.into(); params.host = eth_host.into();
} }
if let Some(eth_port) = matches.value_of("eth-port") { 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) Ok(params)
} }
fn ethereum_signing_params(matches: &clap::ArgMatches) -> Result<EthereumSigningParams, String> { fn ethereum_signing_params(matches: &clap::ArgMatches) -> anyhow::Result<EthereumSigningParams> {
let mut params = EthereumSigningParams::default(); let mut params = EthereumSigningParams::default();
if let Some(eth_signer) = matches.value_of("eth-signer") { if let Some(eth_signer) = matches.value_of("eth-signer") {
params.signer = SecretKey::parse_slice( 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") { if let Some(eth_chain_id) = matches.value_of("eth-chain-id") {
params.chain_id = eth_chain_id params.chain_id = eth_chain_id
.parse::<u64>() .parse::<u64>()
.map_err(|e| format!("Failed to parse eth-chain-id: {}", e))?; .map_err(|e| anyhow!("Failed to parse eth-chain-id: {}", e))?;
} }
Ok(params) Ok(params)
} }
fn substrate_connection_params( fn substrate_connection_params(
matches: &clap::ArgMatches, matches: &clap::ArgMatches,
) -> Result<SubstrateConnectionParams, String> { ) -> anyhow::Result<SubstrateConnectionParams> {
let mut params = SubstrateConnectionParams::default(); let mut params = SubstrateConnectionParams::default();
if let Some(sub_host) = matches.value_of("sub-host") { if let Some(sub_host) = matches.value_of("sub-host") {
params.host = sub_host.into(); params.host = sub_host.into();
} }
if let Some(sub_port) = matches.value_of("sub-port") { 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) Ok(params)
} }
fn rialto_signing_params(matches: &clap::ArgMatches) -> Result<RialtoSigningParams, String> { fn rialto_signing_params(matches: &clap::ArgMatches) -> anyhow::Result<RialtoSigningParams> {
let mut params = sp_keyring::AccountKeyring::Alice.pair(); let mut params = sp_keyring::AccountKeyring::Alice.pair();
if let Some(sub_signer) = matches.value_of("sub-signer") { if let Some(sub_signer) = matches.value_of("sub-signer") {
let sub_signer_password = matches.value_of("sub-signer-password"); let sub_signer_password = matches.value_of("sub-signer-password");
params = sp_core::sr25519::Pair::from_string(sub_signer, 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) Ok(params)
} }
fn ethereum_sync_params(matches: &clap::ArgMatches) -> Result<EthereumSyncParams, String> { fn ethereum_sync_params(matches: &clap::ArgMatches) -> anyhow::Result<EthereumSyncParams> {
use crate::ethereum_sync_loop::consts::*; use crate::ethereum_sync_loop::consts::*;
let mut sync_params = HeadersSyncParams { let mut sync_params = HeadersSyncParams {
@@ -208,7 +210,7 @@ fn ethereum_sync_params(matches: &clap::ArgMatches) -> Result<EthereumSyncParams
sync_params.max_headers_in_submitted_status = 10; sync_params.max_headers_in_submitted_status = 10;
}, },
Some("backup") => sync_params.target_tx_mode = TargetTransactionMode::Backup, Some("backup") => 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, None => sync_params.target_tx_mode = TargetTransactionMode::Signed,
} }
@@ -226,12 +228,12 @@ fn ethereum_sync_params(matches: &clap::ArgMatches) -> Result<EthereumSyncParams
Ok(params) Ok(params)
} }
fn substrate_sync_params(matches: &clap::ArgMatches) -> Result<SubstrateSyncParams, String> { fn substrate_sync_params(matches: &clap::ArgMatches) -> anyhow::Result<SubstrateSyncParams> {
use crate::substrate_sync_loop::consts::*; use crate::substrate_sync_loop::consts::*;
let eth_contract_address: relay_ethereum_client::types::Address = let eth_contract_address: relay_ethereum_client::types::Address =
if let Some(eth_contract) = matches.value_of("eth-contract") { if let Some(eth_contract) = matches.value_of("eth-contract") {
eth_contract.parse().map_err(|e| format!("{}", e))? eth_contract.parse()?
} else { } else {
"731a10897d267e19b34503ad902d0a29173ba4b1" "731a10897d267e19b34503ad902d0a29173ba4b1"
.parse() .parse()
@@ -261,7 +263,7 @@ fn substrate_sync_params(matches: &clap::ArgMatches) -> Result<SubstrateSyncPara
fn ethereum_deploy_contract_params( fn ethereum_deploy_contract_params(
matches: &clap::ArgMatches, matches: &clap::ArgMatches,
) -> Result<EthereumDeployContractParams, String> { ) -> anyhow::Result<EthereumDeployContractParams> {
let eth_contract_code = let eth_contract_code =
parse_hex_argument(matches, "eth-contract-code")?.unwrap_or_else(|| { parse_hex_argument(matches, "eth-contract-code")?.unwrap_or_else(|| {
hex::decode(include_str!("../res/substrate-bridge-bytecode.hex")) hex::decode(include_str!("../res/substrate-bridge-bytecode.hex"))
@@ -271,7 +273,7 @@ fn ethereum_deploy_contract_params(
.value_of("sub-authorities-set-id") .value_of("sub-authorities-set-id")
.map(|set| { .map(|set| {
set.parse() 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()?; .transpose()?;
let sub_initial_authorities_set = parse_hex_argument(matches, "sub-authorities-set")?; 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( fn ethereum_exchange_submit_params(
matches: &clap::ArgMatches, matches: &clap::ArgMatches,
) -> Result<EthereumExchangeSubmitParams, String> { ) -> anyhow::Result<EthereumExchangeSubmitParams> {
let eth_nonce = matches let eth_nonce = matches
.value_of("eth-nonce") .value_of("eth-nonce")
.map(|eth_nonce| { .map(|eth_nonce| {
relay_ethereum_client::types::U256::from_dec_str(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()?; .transpose()?;
let eth_amount = matches let eth_amount = matches
.value_of("eth-amount") .value_of("eth-amount")
.map(|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()? .transpose()?
.unwrap_or_else(|| { .unwrap_or_else(|| {
@@ -331,7 +333,7 @@ fn ethereum_exchange_submit_params(
Ok(sub_recipient) Ok(sub_recipient)
} }
}) })
.map_err(|e| format!("Failed to parse sub-recipient: {}", e))? .map_err(|e| anyhow!("Failed to parse sub-recipient: {}", e))?
} else { } else {
default_recepient default_recepient
}; };
@@ -349,10 +351,10 @@ fn ethereum_exchange_submit_params(
Ok(params) Ok(params)
} }
fn ethereum_exchange_params(matches: &clap::ArgMatches) -> Result<EthereumExchangeParams, String> { fn ethereum_exchange_params(matches: &clap::ArgMatches) -> anyhow::Result<EthereumExchangeParams> {
let mode = match matches.value_of("eth-tx-hash") { let mode = match matches.value_of("eth-tx-hash") {
Some(eth_tx_hash) => ethereum_exchange::ExchangeRelayMode::Single( 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( None => ethereum_exchange::ExchangeRelayMode::Auto(
matches matches
@@ -360,7 +362,7 @@ fn ethereum_exchange_params(matches: &clap::ArgMatches) -> Result<EthereumExchan
.map(|eth_start_with_block| { .map(|eth_start_with_block| {
eth_start_with_block eth_start_with_block
.parse() .parse()
.map_err(|e| format!("Failed to parse eth-start-with-block: {}", e)) .map_err(|e| anyhow!("Failed to parse eth-start-with-block: {}", e))
}) })
.transpose()?, .transpose()?,
), ),
@@ -380,7 +382,7 @@ fn ethereum_exchange_params(matches: &clap::ArgMatches) -> Result<EthereumExchan
Ok(params) Ok(params)
} }
fn metrics_params(matches: &clap::ArgMatches) -> Result<MetricsParams, String> { fn metrics_params(matches: &clap::ArgMatches) -> anyhow::Result<MetricsParams> {
if matches.is_present("no-prometheus") { if matches.is_present("no-prometheus") {
return Ok(None.into()) return Ok(None.into())
} }
@@ -393,18 +395,18 @@ fn metrics_params(matches: &clap::ArgMatches) -> Result<MetricsParams, String> {
if let Some(prometheus_port) = matches.value_of("prometheus-port") { if let Some(prometheus_port) = matches.value_of("prometheus-port") {
metrics_params.port = prometheus_port metrics_params.port = prometheus_port
.parse() .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()) Ok(Some(metrics_params).into())
} }
fn instance_params(matches: &clap::ArgMatches) -> Result<Arc<dyn BridgeInstance>, String> { fn instance_params(matches: &clap::ArgMatches) -> anyhow::Result<Arc<dyn BridgeInstance>> {
let instance = if let Some(instance) = matches.value_of("sub-pallet-instance") { let instance = if let Some(instance) = matches.value_of("sub-pallet-instance") {
match instance.to_lowercase().as_str() { match instance.to_lowercase().as_str() {
"rialto" => Arc::new(RialtoPoA) as Arc<dyn BridgeInstance>, "rialto" => Arc::new(RialtoPoA) as Arc<dyn BridgeInstance>,
"kovan" => Arc::new(Kovan), "kovan" => Arc::new(Kovan),
_ => return Err("Unsupported bridge pallet instance".to_string()), _ => return Err(anyhow!("Unsupported bridge pallet instance")),
} }
} else { } else {
unreachable!("CLI config enforces a default instance, can never be None") unreachable!("CLI config enforces a default instance, can never be None")
@@ -413,10 +415,10 @@ fn instance_params(matches: &clap::ArgMatches) -> Result<Arc<dyn BridgeInstance>
Ok(instance) Ok(instance)
} }
fn parse_hex_argument(matches: &clap::ArgMatches, arg: &str) -> Result<Option<Vec<u8>>, String> { fn parse_hex_argument(matches: &clap::ArgMatches, arg: &str) -> anyhow::Result<Option<Vec<u8>>> {
match matches.value_of(arg) { match matches.value_of(arg) {
Some(value) => 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), None => Ok(None),
} }
} }
+10 -28
View File
@@ -17,48 +17,30 @@
use relay_ethereum_client::Error as EthereumNodeError; use relay_ethereum_client::Error as EthereumNodeError;
use relay_substrate_client::Error as SubstrateNodeError; use relay_substrate_client::Error as SubstrateNodeError;
use relay_utils::MaybeConnectionError; use relay_utils::MaybeConnectionError;
use thiserror::Error;
/// Contains common errors that can occur when /// Contains common errors that can occur when
/// interacting with a Substrate or Ethereum node /// interacting with a Substrate or Ethereum node
/// through RPC. /// through RPC.
#[derive(Debug)] #[derive(Debug, Error)]
pub enum RpcError { pub enum RpcError {
/// The arguments to the RPC method failed to serialize. /// 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. /// 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. /// An error occurred when interacting with a Substrate node.
Substrate(SubstrateNodeError), #[error("Substrate node error: {0}")]
Substrate(#[from] SubstrateNodeError),
/// Error running relay loop. /// Error running relay loop.
#[error("{0}")]
SyncLoop(String), SyncLoop(String),
} }
impl From<RpcError> for String { impl From<RpcError> for String {
fn from(err: RpcError) -> Self { fn from(err: RpcError) -> Self {
match err { format!("{}", 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<serde_json::Error> for RpcError {
fn from(err: serde_json::Error) -> Self {
Self::Serialization(err)
}
}
impl From<EthereumNodeError> for RpcError {
fn from(err: EthereumNodeError) -> Self {
Self::Ethereum(err)
}
}
impl From<SubstrateNodeError> for RpcError {
fn from(err: SubstrateNodeError) -> Self {
Self::Substrate(err)
} }
} }
@@ -97,7 +97,7 @@ impl CliChain for Kusama {
fn encode_message( fn encode_message(
_message: encode_message::MessagePayload, _message: encode_message::MessagePayload,
) -> Result<Self::MessagePayload, String> { ) -> anyhow::Result<Self::MessagePayload> {
Err("Sending messages from Kusama is not yet supported.".into()) anyhow::bail!("Sending messages from Kusama is not yet supported.")
} }
} }
@@ -256,6 +256,7 @@ pub async fn run(
futures::future::pending(), futures::future::pending(),
) )
.await .await
.map_err(Into::into)
} }
/// Add standalone metrics for the Kusama -> Polkadot messages loop. /// Add standalone metrics for the Kusama -> Polkadot messages loop.
@@ -23,6 +23,7 @@ use crate::cli::{
send_message::{self, DispatchFeePayment}, send_message::{self, DispatchFeePayment},
CliChain, CliChain,
}; };
use anyhow::anyhow;
use bp_message_dispatch::{CallOrigin, MessagePayload}; use bp_message_dispatch::{CallOrigin, MessagePayload};
use codec::Decode; use codec::Decode;
use frame_support::weights::{DispatchInfo, GetDispatchInfo, Weight}; use frame_support::weights::{DispatchInfo, GetDispatchInfo, Weight};
@@ -87,10 +88,10 @@ impl CliChain for Millau {
// TODO [#854|#843] support multiple bridges? // TODO [#854|#843] support multiple bridges?
fn encode_message( fn encode_message(
message: encode_message::MessagePayload, message: encode_message::MessagePayload,
) -> Result<Self::MessagePayload, String> { ) -> anyhow::Result<Self::MessagePayload> {
match message { match message {
encode_message::MessagePayload::Raw { data } => MessagePayload::decode(&mut &*data.0) 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 } => { encode_message::MessagePayload::Call { mut call, mut sender } => {
type Source = Millau; type Source = Millau;
type Target = relay_rialto_client::Rialto; type Target = relay_rialto_client::Rialto;
@@ -102,7 +103,7 @@ impl CliChain for Millau {
&mut call, &mut call,
bridge::MILLAU_TO_RIALTO_INDEX, 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; let weight = call.get_dispatch_info().weight;
Ok(send_message::message_payload( Ok(send_message::message_payload(
@@ -250,6 +250,7 @@ pub async fn run(
futures::future::pending(), futures::future::pending(),
) )
.await .await
.map_err(Into::into)
} }
/// Add standalone metrics for the Millau -> Rialto messages loop. /// Add standalone metrics for the Millau -> Rialto messages loop.
@@ -97,7 +97,7 @@ impl CliChain for Polkadot {
fn encode_message( fn encode_message(
_message: encode_message::MessagePayload, _message: encode_message::MessagePayload,
) -> Result<Self::MessagePayload, String> { ) -> anyhow::Result<Self::MessagePayload> {
Err("Sending messages from Polkadot is not yet supported.".into()) anyhow::bail!("Sending messages from Polkadot is not yet supported.")
} }
} }
@@ -255,6 +255,7 @@ pub async fn run(
futures::future::pending(), futures::future::pending(),
) )
.await .await
.map_err(Into::into)
} }
/// Add standalone metrics for the Polkadot -> Kusama messages loop. /// Add standalone metrics for the Polkadot -> Kusama messages loop.
@@ -23,6 +23,7 @@ use crate::cli::{
send_message::{self, DispatchFeePayment}, send_message::{self, DispatchFeePayment},
CliChain, CliChain,
}; };
use anyhow::anyhow;
use bp_message_dispatch::{CallOrigin, MessagePayload}; use bp_message_dispatch::{CallOrigin, MessagePayload};
use codec::Decode; use codec::Decode;
use frame_support::weights::{DispatchInfo, GetDispatchInfo, Weight}; use frame_support::weights::{DispatchInfo, GetDispatchInfo, Weight};
@@ -86,10 +87,10 @@ impl CliChain for Rialto {
fn encode_message( fn encode_message(
message: encode_message::MessagePayload, message: encode_message::MessagePayload,
) -> Result<Self::MessagePayload, String> { ) -> anyhow::Result<Self::MessagePayload> {
match message { match message {
encode_message::MessagePayload::Raw { data } => MessagePayload::decode(&mut &*data.0) 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 } => { encode_message::MessagePayload::Call { mut call, mut sender } => {
type Source = Rialto; type Source = Rialto;
type Target = relay_millau_client::Millau; type Target = relay_millau_client::Millau;
@@ -101,7 +102,7 @@ impl CliChain for Rialto {
&mut call, &mut call,
bridge::RIALTO_TO_MILLAU_INDEX, 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; let weight = call.get_dispatch_info().weight;
Ok(send_message::message_payload( Ok(send_message::message_payload(
@@ -249,6 +249,7 @@ pub async fn run(
futures::future::pending(), futures::future::pending(),
) )
.await .await
.map_err(Into::into)
} }
/// Add standalone metrics for the Rialto -> Millau messages loop. /// Add standalone metrics for the Rialto -> Millau messages loop.
@@ -76,7 +76,7 @@ impl CliChain for RialtoParachain {
fn encode_message( fn encode_message(
_message: encode_message::MessagePayload, _message: encode_message::MessagePayload,
) -> Result<Self::MessagePayload, String> { ) -> anyhow::Result<Self::MessagePayload> {
Err("Not supported".into()) anyhow::bail!("Not supported")
} }
} }
@@ -14,6 +14,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>. // along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use anyhow::anyhow;
use codec::Decode; use codec::Decode;
use frame_support::weights::{DispatchClass, DispatchInfo, Pays, Weight}; use frame_support::weights::{DispatchClass, DispatchInfo, Pays, Weight};
use relay_rococo_client::Rococo; use relay_rococo_client::Rococo;
@@ -94,7 +95,7 @@ impl CliChain for Rococo {
fn encode_message( fn encode_message(
_message: encode_message::MessagePayload, _message: encode_message::MessagePayload,
) -> Result<Self::MessagePayload, String> { ) -> anyhow::Result<Self::MessagePayload> {
Err("Sending messages from Rococo is not yet supported.".into()) Err(anyhow!("Sending messages from Rococo is not yet supported."))
} }
} }
@@ -253,6 +253,7 @@ pub async fn run(
futures::future::pending(), futures::future::pending(),
) )
.await .await
.map_err(Into::into)
} }
/// Add standalone metrics for the Rococo -> Wococo messages loop. /// Add standalone metrics for the Rococo -> Wococo messages loop.
@@ -17,6 +17,7 @@
//! Westend chain specification for CLI. //! Westend chain specification for CLI.
use crate::cli::{encode_message, CliChain}; use crate::cli::{encode_message, CliChain};
use anyhow::anyhow;
use frame_support::weights::Weight; use frame_support::weights::Weight;
use relay_westend_client::Westend; use relay_westend_client::Westend;
use sp_version::RuntimeVersion; use sp_version::RuntimeVersion;
@@ -37,7 +38,7 @@ impl CliChain for Westend {
fn encode_message( fn encode_message(
_message: encode_message::MessagePayload, _message: encode_message::MessagePayload,
) -> Result<Self::MessagePayload, String> { ) -> anyhow::Result<Self::MessagePayload> {
Err("Sending messages from Westend is not yet supported.".into()) Err(anyhow!("Sending messages from Westend is not yet supported."))
} }
} }
@@ -14,6 +14,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>. // along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use anyhow::anyhow;
use codec::Decode; use codec::Decode;
use frame_support::weights::{DispatchClass, DispatchInfo, Pays, Weight}; use frame_support::weights::{DispatchClass, DispatchInfo, Pays, Weight};
use relay_wococo_client::Wococo; use relay_wococo_client::Wococo;
@@ -88,7 +89,7 @@ impl CliChain for Wococo {
fn encode_message( fn encode_message(
_message: encode_message::MessagePayload, _message: encode_message::MessagePayload,
) -> Result<Self::MessagePayload, String> { ) -> anyhow::Result<Self::MessagePayload> {
Err("Sending messages from Wococo is not yet supported.".into()) Err(anyhow!("Sending messages from Wococo is not yet supported."))
} }
} }
@@ -252,6 +252,7 @@ pub async fn run(
futures::future::pending(), futures::future::pending(),
) )
.await .await
.map_err(Into::into)
} }
/// Add standalone metrics for the Wococo -> Rococo messages loop. /// Add standalone metrics for the Wococo -> Rococo messages loop.
+1 -1
View File
@@ -257,7 +257,7 @@ pub trait CliChain: relay_substrate_client::Chain {
/// Construct message payload to be sent over the bridge. /// Construct message payload to be sent over the bridge.
fn encode_message( fn encode_message(
message: crate::cli::encode_message::MessagePayload, message: crate::cli::encode_message::MessagePayload,
) -> Result<Self::MessagePayload, String>; ) -> anyhow::Result<Self::MessagePayload>;
/// Maximal extrinsic weight (from the runtime). /// Maximal extrinsic weight (from the runtime).
fn max_extrinsic_weight() -> Weight; fn max_extrinsic_weight() -> Weight;
@@ -141,6 +141,7 @@ impl ResubmitTransactions {
) )
}) })
.await .await
.map_err(Into::into)
}) })
} }
} }
@@ -17,3 +17,4 @@ log = "0.4.11"
relay-utils = { path = "../utils" } relay-utils = { path = "../utils" }
tokio = "1.8" tokio = "1.8"
web3 = { git = "https://github.com/svyatonik/rust-web3.git", branch = "bump-deps" } web3 = { git = "https://github.com/svyatonik/rust-web3.git", branch = "bump-deps" }
thiserror = "1.0.26"
+13 -36
View File
@@ -20,49 +20,47 @@ use crate::types::U256;
use jsonrpsee_ws_client::types::Error as RpcError; use jsonrpsee_ws_client::types::Error as RpcError;
use relay_utils::MaybeConnectionError; use relay_utils::MaybeConnectionError;
use thiserror::Error;
/// Result type used by Ethereum client. /// Result type used by Ethereum client.
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;
/// Errors that can occur only when interacting with /// Errors that can occur only when interacting with
/// an Ethereum node through RPC. /// an Ethereum node through RPC.
#[derive(Debug)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
/// IO 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 error that can occur when making an HTTP request to
/// an JSON-RPC client. /// an JSON-RPC client.
RpcError(RpcError), #[error("RPC error: {0}")]
RpcError(#[from] RpcError),
/// Failed to parse response. /// Failed to parse response.
#[error("Response parse failed: {0}")]
ResponseParseFailed(String), ResponseParseFailed(String),
/// We have received a header with missing fields. /// We have received a header with missing fields.
#[error("Incomplete Ethereum Header Received (missing some of required fields - hash, number, logs_bloom).")]
IncompleteHeader, IncompleteHeader,
/// We have received a transaction missing a `raw` field. /// We have received a transaction missing a `raw` field.
#[error("Incomplete Ethereum Transaction (missing required field - raw).")]
IncompleteTransaction, IncompleteTransaction,
/// An invalid Substrate block number was received from /// An invalid Substrate block number was received from
/// an Ethereum node. /// an Ethereum node.
#[error("Received an invalid Substrate block from Ethereum Node.")]
InvalidSubstrateBlockNumber, InvalidSubstrateBlockNumber,
/// An invalid index has been received from an Ethereum node. /// An invalid index has been received from an Ethereum node.
#[error("Received an invalid incomplete index from Ethereum Node.")]
InvalidIncompleteIndex, InvalidIncompleteIndex,
/// The client we're connected to is not synced, so we can't rely on its state. Contains /// The client we're connected to is not synced, so we can't rely on its state. Contains
/// number of unsynced headers. /// number of unsynced headers.
#[error("Ethereum client is not synced: syncing {0} headers.")]
ClientNotSynced(U256), ClientNotSynced(U256),
/// Custom logic error. /// Custom logic error.
#[error("{0}")]
Custom(String), Custom(String),
} }
impl From<RpcError> for Error {
fn from(error: RpcError) -> Self {
Error::RpcError(error)
}
}
impl From<std::io::Error> for Error {
fn from(error: std::io::Error) -> Self {
Error::Io(error)
}
}
impl From<tokio::task::JoinError> for Error { impl From<tokio::task::JoinError> for Error {
fn from(error: tokio::task::JoinError) -> Self { fn from(error: tokio::task::JoinError) -> Self {
Error::Custom(format!("Failed to wait tokio task: {}", error)) 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(),
}
}
}
@@ -15,6 +15,7 @@ log = "0.4.11"
num-traits = "0.2" num-traits = "0.2"
rand = "0.7" rand = "0.7"
tokio = "1.8" tokio = "1.8"
thiserror = "1.0.26"
# Bridge dependencies # Bridge dependencies
+16 -67
View File
@@ -20,78 +20,54 @@ use jsonrpsee_ws_client::types::Error as RpcError;
use relay_utils::MaybeConnectionError; use relay_utils::MaybeConnectionError;
use sc_rpc_api::system::Health; use sc_rpc_api::system::Health;
use sp_runtime::transaction_validity::TransactionValidityError; use sp_runtime::transaction_validity::TransactionValidityError;
use thiserror::Error;
/// Result type used by Substrate client. /// Result type used by Substrate client.
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;
/// Errors that can occur only when interacting with /// Errors that can occur only when interacting with
/// a Substrate node through RPC. /// a Substrate node through RPC.
#[derive(Debug)] #[derive(Error, Debug)]
pub enum Error { pub enum Error {
/// IO 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 error that can occur when making a request to
/// an JSON-RPC server. /// an JSON-RPC server.
RpcError(RpcError), #[error("RPC error: {0}")]
RpcError(#[from] RpcError),
/// The response from the server could not be SCALE decoded. /// 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. /// The Substrate bridge pallet has not yet been initialized.
#[error("The Substrate bridge pallet has not been initialized yet.")]
UninitializedBridgePallet, UninitializedBridgePallet,
/// Account does not exist on the chain. /// Account does not exist on the chain.
#[error("Account does not exist on the chain.")]
AccountDoesNotExist, AccountDoesNotExist,
/// Runtime storage is missing mandatory ":code:" entry. /// Runtime storage is missing mandatory ":code:" entry.
#[error("Mandatory :code: entry is missing from runtime storage.")]
MissingMandatoryCodeEntry, MissingMandatoryCodeEntry,
/// The client we're connected to is not synced, so we can't rely on its state. /// 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), ClientNotSynced(Health),
/// An error has happened when we have tried to parse storage proof. /// An error has happened when we have tried to parse storage proof.
#[error("Error when parsing storage proof: {0:?}.")]
StorageProofError(bp_runtime::StorageProofError), StorageProofError(bp_runtime::StorageProofError),
/// The Substrate transaction is invalid. /// The Substrate transaction is invalid.
TransactionInvalid(TransactionValidityError), #[error("Substrate transaction is invalid: {0:?}")]
TransactionInvalid(#[from] TransactionValidityError),
/// Custom logic error. /// Custom logic error.
#[error("{0}")]
Custom(String), 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<RpcError> for Error {
fn from(error: RpcError) -> Self {
Error::RpcError(error)
}
}
impl From<std::io::Error> for Error {
fn from(error: std::io::Error) -> Self {
Error::Io(error)
}
}
impl From<tokio::task::JoinError> for Error { impl From<tokio::task::JoinError> for Error {
fn from(error: tokio::task::JoinError) -> Self { fn from(error: tokio::task::JoinError) -> Self {
Error::Custom(format!("Failed to wait tokio task: {}", error)) Error::Custom(format!("Failed to wait tokio task: {}", error))
} }
} }
impl From<TransactionValidityError> for Error {
fn from(error: TransactionValidityError) -> Self {
Error::TransactionInvalid(error)
}
}
impl MaybeConnectionError for Error { impl MaybeConnectionError for Error {
fn is_connection_error(&self) -> bool { fn is_connection_error(&self) -> bool {
matches!( 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<Error> for String {
fn from(error: Error) -> String {
error.to_string()
}
}
+25 -11
View File
@@ -17,23 +17,30 @@
//! Pallet provides a set of guard functions that are running in background threads //! Pallet provides a set of guard functions that are running in background threads
//! and are aborting process if some condition fails. //! 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 async_trait::async_trait;
use num_traits::CheckedSub; use num_traits::CheckedSub;
use sp_version::RuntimeVersion; use sp_version::RuntimeVersion;
use std::{ use std::{
collections::VecDeque, collections::VecDeque,
fmt::Display,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
/// Guards environment. /// Guards environment.
#[async_trait] #[async_trait]
pub trait Environment<C: ChainWithBalances>: Send + Sync + 'static { pub trait Environment<C: ChainWithBalances>: Send + Sync + 'static {
/// Error type.
type Error: Display + Send + Sync + 'static;
/// Return current runtime version. /// Return current runtime version.
async fn runtime_version(&mut self) -> Result<RuntimeVersion, String>; async fn runtime_version(&mut self) -> Result<RuntimeVersion, Self::Error>;
/// Return free native balance of the account on the chain. /// Return free native balance of the account on the chain.
async fn free_native_balance(&mut self, account: C::AccountId) -> Result<C::Balance, String>; async fn free_native_balance(
&mut self,
account: C::AccountId,
) -> Result<C::Balance, Self::Error>;
/// Return current time. /// Return current time.
fn now(&self) -> Instant { fn now(&self) -> Instant {
@@ -74,7 +81,7 @@ pub fn abort_on_spec_version_change<C: ChainWithBalances>(
}, },
Err(error) => log::warn!( Err(error) => log::warn!(
target: "bridge-guard", 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, C::NAME,
error, error,
), ),
@@ -137,7 +144,7 @@ pub fn abort_when_account_balance_decreased<C: ChainWithBalances>(
Err(error) => { Err(error) => {
log::warn!( log::warn!(
target: "bridge-guard", 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, C::NAME,
account_id, account_id,
error, error,
@@ -157,12 +164,17 @@ fn conditions_check_delay<C: Chain>() -> Duration {
#[async_trait] #[async_trait]
impl<C: ChainWithBalances> Environment<C> for Client<C> { impl<C: ChainWithBalances> Environment<C> for Client<C> {
async fn runtime_version(&mut self) -> Result<RuntimeVersion, String> { type Error = Error;
Client::<C>::runtime_version(self).await.map_err(|e| e.to_string())
async fn runtime_version(&mut self) -> Result<RuntimeVersion, Self::Error> {
Client::<C>::runtime_version(self).await
} }
async fn free_native_balance(&mut self, account: C::AccountId) -> Result<C::Balance, String> { async fn free_native_balance(
Client::<C>::free_native_balance(self, account).await.map_err(|e| e.to_string()) &mut self,
account: C::AccountId,
) -> Result<C::Balance, Self::Error> {
Client::<C>::free_native_balance(self, account).await
} }
} }
@@ -220,11 +232,13 @@ mod tests {
#[async_trait] #[async_trait]
impl Environment<TestChain> for TestEnvironment { impl Environment<TestChain> for TestEnvironment {
async fn runtime_version(&mut self) -> Result<RuntimeVersion, String> { type Error = Error;
async fn runtime_version(&mut self) -> Result<RuntimeVersion, Self::Error> {
Ok(self.runtime_version_rx.next().await.unwrap_or_default()) Ok(self.runtime_version_rx.next().await.unwrap_or_default())
} }
async fn free_native_balance(&mut self, _account: u32) -> Result<u32, String> { async fn free_native_balance(&mut self, _account: u32) -> Result<u32, Self::Error> {
Ok(self.free_native_balance_rx.next().await.unwrap_or_default()) Ok(self.free_native_balance_rx.next().await.unwrap_or_default())
} }
+1
View File
@@ -15,3 +15,4 @@ log = "0.4.11"
num-traits = "0.2" num-traits = "0.2"
parking_lot = "0.11.0" parking_lot = "0.11.0"
relay-utils = { path = "../utils" } relay-utils = { path = "../utils" }
thiserror = "1.0.26"
+66
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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<P> = Error<BlockHashOf<P>, BlockNumberOf<P>, TransactionHashOf<P>>;
/// Exchange-relay error type.
#[derive(Error, Debug)]
pub enum Error<Hash: Display, HeaderNumber: Display, SourceTxHash: Display> {
/// 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<T: Display, U: Display, V: Display> MaybeConnectionError for Error<T, U, V> {
fn is_connection_error(&self) -> bool {
match *self {
Self::BuildTransactionProof(_, _, _, b) => b,
Self::SubmitTransactionProof(_, _, _, b) => b,
Self::TransactionFiltering(_, b) => b,
_ => false,
}
}
}
+39 -57
View File
@@ -16,11 +16,11 @@
//! Relaying proofs of exchange transaction. //! Relaying proofs of exchange transaction.
use crate::error::{Error, ErrorOf};
use anyhow::anyhow;
use async_trait::async_trait; use async_trait::async_trait;
use relay_utils::{ use relay_utils::{relay_loop::Client as RelayClient, FailedClient, MaybeConnectionError};
relay_loop::Client as RelayClient, FailedClient, MaybeConnectionError,
StringifiedMaybeConnectionError,
};
use std::{ use std::{
fmt::{Debug, Display}, fmt::{Debug, Display},
string::ToString, string::ToString,
@@ -67,7 +67,7 @@ pub trait SourceBlock: 'static + Send + Sync {
/// Transaction that is participating in exchange. /// Transaction that is participating in exchange.
pub trait SourceTransaction: 'static + Send { pub trait SourceTransaction: 'static + Send {
/// Transaction hash type. /// Transaction hash type.
type Hash: Debug + Display; type Hash: Debug + Display + Clone;
/// Return transaction hash. /// Return transaction hash.
fn hash(&self) -> Self::Hash; fn hash(&self) -> Self::Hash;
@@ -117,7 +117,7 @@ pub trait TargetClient<P: TransactionProofPipeline>: RelayClient {
/// Sleep until exchange-related data is (probably) updated. /// Sleep until exchange-related data is (probably) updated.
async fn tick(&self); async fn tick(&self);
/// Returns `Ok(true)` if header is known to the target node. /// Returns `Ok(true)` if header is known to the target node.
async fn is_header_known(&self, id: &HeaderId<P>) -> Result<bool, Self::Error>; async fn is_header_known(&self, id: &HeaderId<P>) -> std::result::Result<bool, Self::Error>;
/// Returns `Ok(true)` if header is finalized by the target node. /// Returns `Ok(true)` if header is finalized by the target node.
async fn is_header_finalized(&self, id: &HeaderId<P>) -> Result<bool, Self::Error>; async fn is_header_finalized(&self, id: &HeaderId<P>) -> Result<bool, Self::Error>;
/// Returns best finalized header id. /// Returns best finalized header id.
@@ -178,9 +178,9 @@ pub async fn relay_block_transactions<P: TransactionProofPipeline>(
target_client.filter_transaction_proof(&source_tx_proof).await.map_err(|err| { target_client.filter_transaction_proof(&source_tx_proof).await.map_err(|err| {
( (
FailedClient::Target, FailedClient::Target,
StringifiedMaybeConnectionError::new( Error::TransactionFiltering(
anyhow!("{:?}", err),
err.is_connection_error(), err.is_connection_error(),
format!("Transaction filtering has failed with {:?}", err),
), ),
) )
})?; })?;
@@ -256,20 +256,14 @@ pub async fn relay_single_transaction_proof<P: TransactionProofPipeline>(
source_client: &impl SourceClient<P>, source_client: &impl SourceClient<P>,
target_client: &impl TargetClient<P>, target_client: &impl TargetClient<P>,
source_tx_hash: TransactionHashOf<P>, source_tx_hash: TransactionHashOf<P>,
) -> Result<(), String> { ) -> Result<(), ErrorOf<P>> {
// wait for transaction and header on source node // wait for transaction and header on source node
let (source_header_id, source_tx_index) = let (source_header_id, source_tx_index) =
wait_transaction_mined(source_client, &source_tx_hash).await?; 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_client.block_by_hash(source_header_id.1.clone()).await;
let source_block = source_block.map_err(|err| { let source_block = source_block.map_err(|err| {
format!( Error::RetrievingBlock(source_header_id.1.clone(), P::SOURCE_NAME, anyhow!("{:?}", err))
"Error retrieving block {} from {} node: {:?}",
source_header_id.1,
P::SOURCE_NAME,
err,
)
})?; })?;
// wait for transaction and header on target node // wait for transaction and header on target node
wait_header_imported(target_client, &source_header_id).await?; wait_header_imported(target_client, &source_header_id).await?;
wait_header_finalized(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<P: TransactionProofPipeline>(
target_client, target_client,
&source_tx_id, &source_tx_id,
prepare_transaction_proof(source_client, &source_tx_id, &source_block, source_tx_index) prepare_transaction_proof(source_client, &source_tx_id, &source_block, source_tx_index)
.await .await?,
.map_err(|err| err.to_string())?,
) )
.await .await
.map_err(|err| err.to_string()) .map_err(Into::into)
} }
/// Prepare transaction proof. /// Prepare transaction proof.
@@ -293,19 +286,16 @@ async fn prepare_transaction_proof<P: TransactionProofPipeline>(
source_tx_id: &str, source_tx_id: &str,
source_block: &P::Block, source_block: &P::Block,
source_tx_index: usize, source_tx_index: usize,
) -> Result<P::TransactionProof, StringifiedMaybeConnectionError> { ) -> Result<P::TransactionProof, ErrorOf<P>> {
source_client source_client
.transaction_proof(source_block, source_tx_index) .transaction_proof(source_block, source_tx_index)
.await .await
.map_err(|err| { .map_err(|err| {
StringifiedMaybeConnectionError::new( Error::BuildTransactionProof(
err.is_connection_error(), source_tx_id.to_owned(),
format!(
"Error building transaction {} proof on {} node: {:?}",
source_tx_id,
P::SOURCE_NAME, P::SOURCE_NAME,
err, anyhow!("{:?}", err),
), err.is_connection_error(),
) )
}) })
} }
@@ -315,16 +305,13 @@ async fn relay_ready_transaction_proof<P: TransactionProofPipeline>(
target_client: &impl TargetClient<P>, target_client: &impl TargetClient<P>,
source_tx_id: &str, source_tx_id: &str,
source_tx_proof: P::TransactionProof, source_tx_proof: P::TransactionProof,
) -> Result<(), StringifiedMaybeConnectionError> { ) -> Result<(), ErrorOf<P>> {
target_client.submit_transaction_proof(source_tx_proof).await.map_err(|err| { target_client.submit_transaction_proof(source_tx_proof).await.map_err(|err| {
StringifiedMaybeConnectionError::new( Error::SubmitTransactionProof(
err.is_connection_error(), source_tx_id.to_owned(),
format!(
"Error submitting transaction {} proof to {} node: {:?}",
source_tx_id,
P::TARGET_NAME, P::TARGET_NAME,
err, anyhow!("{:?}", err),
), err.is_connection_error(),
) )
}) })
} }
@@ -333,15 +320,14 @@ async fn relay_ready_transaction_proof<P: TransactionProofPipeline>(
async fn wait_transaction_mined<P: TransactionProofPipeline>( async fn wait_transaction_mined<P: TransactionProofPipeline>(
source_client: &impl SourceClient<P>, source_client: &impl SourceClient<P>,
source_tx_hash: &TransactionHashOf<P>, source_tx_hash: &TransactionHashOf<P>,
) -> Result<(HeaderId<P>, usize), String> { ) -> Result<(HeaderId<P>, usize), ErrorOf<P>> {
loop { loop {
let source_header_and_tx = let source_header_and_tx =
source_client.transaction_block(source_tx_hash).await.map_err(|err| { source_client.transaction_block(source_tx_hash).await.map_err(|err| {
format!( Error::RetrievingTransaction(
"Error retrieving transaction {} from {} node: {:?}", source_tx_hash.clone(),
source_tx_hash,
P::SOURCE_NAME, P::SOURCE_NAME,
err, anyhow!("{:?}", err),
) )
})?; })?;
match source_header_and_tx { match source_header_and_tx {
@@ -373,16 +359,15 @@ async fn wait_transaction_mined<P: TransactionProofPipeline>(
async fn wait_header_imported<P: TransactionProofPipeline>( async fn wait_header_imported<P: TransactionProofPipeline>(
target_client: &impl TargetClient<P>, target_client: &impl TargetClient<P>,
source_header_id: &HeaderId<P>, source_header_id: &HeaderId<P>,
) -> Result<(), String> { ) -> Result<(), ErrorOf<P>> {
loop { loop {
let is_header_known = let is_header_known =
target_client.is_header_known(source_header_id).await.map_err(|err| { target_client.is_header_known(source_header_id).await.map_err(|err| {
format!( Error::CheckHeaderExistence(
"Failed to check existence of header {}/{} on {} node: {:?}",
source_header_id.0, source_header_id.0,
source_header_id.1, source_header_id.1.clone(),
P::TARGET_NAME, P::TARGET_NAME,
err, anyhow!("{:?}", err),
) )
})?; })?;
match is_header_known { match is_header_known {
@@ -416,16 +401,15 @@ async fn wait_header_imported<P: TransactionProofPipeline>(
async fn wait_header_finalized<P: TransactionProofPipeline>( async fn wait_header_finalized<P: TransactionProofPipeline>(
target_client: &impl TargetClient<P>, target_client: &impl TargetClient<P>,
source_header_id: &HeaderId<P>, source_header_id: &HeaderId<P>,
) -> Result<(), String> { ) -> Result<(), ErrorOf<P>> {
loop { loop {
let is_header_finalized = let is_header_finalized =
target_client.is_header_finalized(source_header_id).await.map_err(|err| { target_client.is_header_finalized(source_header_id).await.map_err(|err| {
format!( Error::Finality(
"Failed to check finality of header {}/{} on {} node: {:?}",
source_header_id.0, source_header_id.0,
source_header_id.1, source_header_id.1.clone(),
P::TARGET_NAME, P::TARGET_NAME,
err, anyhow!("{:?}", err),
) )
})?; })?;
match is_header_finalized { match is_header_finalized {
@@ -691,14 +675,12 @@ pub(crate) mod tests {
source: &TestTransactionsSource, source: &TestTransactionsSource,
target: &TestTransactionsTarget, target: &TestTransactionsTarget,
) { ) {
assert_eq!( assert!(async_std::task::block_on(relay_single_transaction_proof(
async_std::task::block_on(relay_single_transaction_proof(
source, source,
target, target,
test_transaction_hash(0), test_transaction_hash(0)
)), ))
Ok(()), .is_ok());
);
assert_eq!( assert_eq!(
target.data.lock().submitted_proofs, target.data.lock().submitted_proofs,
vec![TestTransactionProof(test_transaction_hash(0))], vec![TestTransactionProof(test_transaction_hash(0))],
@@ -711,7 +693,7 @@ pub(crate) mod tests {
&target, &target,
test_transaction_hash(0), test_transaction_hash(0),
)) ))
.is_err(),); .is_err());
assert!(target.data.lock().submitted_proofs.is_empty()); assert!(target.data.lock().submitted_proofs.is_empty());
} }
+4 -1
View File
@@ -17,6 +17,7 @@
//! Relaying proofs of exchange transactions. //! Relaying proofs of exchange transactions.
use crate::{ use crate::{
error::Error,
exchange::{ exchange::{
relay_block_transactions, BlockNumberOf, RelayedBlockTransactions, SourceClient, relay_block_transactions, BlockNumberOf, RelayedBlockTransactions, SourceClient,
TargetClient, TransactionProofPipeline, TargetClient, TransactionProofPipeline,
@@ -24,6 +25,7 @@ use crate::{
exchange_loop_metrics::ExchangeLoopMetrics, exchange_loop_metrics::ExchangeLoopMetrics,
}; };
use crate::error::ErrorOf;
use backoff::backoff::Backoff; use backoff::backoff::Backoff;
use futures::{future::FutureExt, select}; use futures::{future::FutureExt, select};
use num_traits::One; use num_traits::One;
@@ -92,7 +94,7 @@ pub async fn run<P: TransactionProofPipeline>(
target_client: impl TargetClient<P>, target_client: impl TargetClient<P>,
metrics_params: MetricsParams, metrics_params: MetricsParams,
exit_signal: impl Future<Output = ()> + 'static + Send, exit_signal: impl Future<Output = ()> + 'static + Send,
) -> anyhow::Result<()> { ) -> Result<(), ErrorOf<P>> {
let exit_signal = exit_signal.shared(); let exit_signal = exit_signal.shared();
relay_utils::relay_loop(source_client, target_client) relay_utils::relay_loop(source_client, target_client)
@@ -111,6 +113,7 @@ pub async fn run<P: TransactionProofPipeline>(
) )
}) })
.await .await
.map_err(Error::Utils)
} }
/// Run proofs synchronization. /// Run proofs synchronization.
+1
View File
@@ -21,6 +21,7 @@
#![warn(missing_docs)] #![warn(missing_docs)]
pub mod error;
pub mod exchange; pub mod exchange;
pub mod exchange_loop; pub mod exchange_loop;
pub mod exchange_loop_metrics; pub mod exchange_loop_metrics;
-1
View File
@@ -7,7 +7,6 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
description = "Finality proofs relay" description = "Finality proofs relay"
[dependencies] [dependencies]
anyhow = "1.0"
async-std = "1.6.5" async-std = "1.6.5"
async-trait = "0.1.40" async-trait = "0.1.40"
backoff = "0.2" backoff = "0.2"
+1 -1
View File
@@ -110,7 +110,7 @@ pub async fn run<P: FinalitySyncPipeline>(
sync_params: FinalitySyncParams, sync_params: FinalitySyncParams,
metrics_params: MetricsParams, metrics_params: MetricsParams,
exit_signal: impl Future<Output = ()> + 'static + Send, exit_signal: impl Future<Output = ()> + 'static + Send,
) -> anyhow::Result<()> { ) -> Result<(), relay_utils::Error> {
let exit_signal = exit_signal.shared(); let exit_signal = exit_signal.shared();
relay_utils::relay_loop(source_client, target_client) relay_utils::relay_loop(source_client, target_client)
.with_metrics(Some(metrics_prefix::<P>()), metrics_params) .with_metrics(Some(metrics_prefix::<P>()), metrics_params)
-1
View File
@@ -6,7 +6,6 @@ edition = "2018"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0" license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies] [dependencies]
anyhow = "1.0"
async-std = "1.6.5" async-std = "1.6.5"
async-trait = "0.1.40" async-trait = "0.1.40"
backoff = "0.2" backoff = "0.2"
+1 -1
View File
@@ -143,7 +143,7 @@ pub async fn run<P: HeadersSyncPipeline, TC: TargetClient<P>>(
sync_params: HeadersSyncParams, sync_params: HeadersSyncParams,
metrics_params: MetricsParams, metrics_params: MetricsParams,
exit_signal: impl Future<Output = ()> + 'static + Send, exit_signal: impl Future<Output = ()> + 'static + Send,
) -> anyhow::Result<()> { ) -> Result<(), relay_utils::Error> {
let exit_signal = exit_signal.shared(); let exit_signal = exit_signal.shared();
relay_utils::relay_loop(source_client, target_client) relay_utils::relay_loop(source_client, target_client)
.with_metrics(Some(metrics_prefix::<P>()), metrics_params) .with_metrics(Some(metrics_prefix::<P>()), metrics_params)
@@ -7,6 +7,7 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
thiserror = "1.0.26"
async-std = "1.9.0" async-std = "1.9.0"
async-trait = "0.1.42" async-trait = "0.1.42"
codec = { package = "parity-scale-codec", version = "2.2.0" } codec = { package = "parity-scale-codec", version = "2.2.0" }
@@ -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 <http://www.gnu.org/licenses/>.
//! 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<Hash: Debug + MaybeDisplay, HeaderNumber: Debug + MaybeDisplay> {
/// 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),
}
@@ -21,6 +21,8 @@
//! and authorities set from source to target chain. The headers sync starts //! and authorities set from source to target chain. The headers sync starts
//! with this header. //! with this header.
use crate::error::Error;
use bp_header_chain::{ use bp_header_chain::{
find_grandpa_authorities_scheduled_change, find_grandpa_authorities_scheduled_change,
justification::{verify_justification, GrandpaJustification}, justification::{verify_justification, GrandpaJustification},
@@ -32,7 +34,7 @@ use num_traits::{One, Zero};
use relay_substrate_client::{Chain, Client}; use relay_substrate_client::{Chain, Client};
use sp_core::Bytes; use sp_core::Bytes;
use sp_finality_grandpa::AuthorityList as GrandpaAuthoritiesSet; 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. /// Submit headers-bridge initialization transaction.
pub async fn initialize<SourceChain: Chain, TargetChain: Chain>( pub async fn initialize<SourceChain: Chain, TargetChain: Chain>(
@@ -77,7 +79,7 @@ async fn do_initialize<SourceChain: Chain, TargetChain: Chain>(
prepare_initialize_transaction: impl FnOnce(TargetChain::Index, InitializationData<SourceChain::Header>) -> Bytes prepare_initialize_transaction: impl FnOnce(TargetChain::Index, InitializationData<SourceChain::Header>) -> Bytes
+ Send + Send
+ 'static, + 'static,
) -> Result<TargetChain::Hash, String> { ) -> Result<TargetChain::Hash, Error<SourceChain::Hash, <SourceChain::Header as Header>::Number>> {
let initialization_data = prepare_initialization_data(source_client).await?; let initialization_data = prepare_initialization_data(source_client).await?;
log::info!( log::info!(
target: "bridge", target: "bridge",
@@ -92,41 +94,40 @@ async fn do_initialize<SourceChain: Chain, TargetChain: Chain>(
prepare_initialize_transaction(transaction_nonce, initialization_data) prepare_initialize_transaction(transaction_nonce, initialization_data)
}) })
.await .await
.map_err(|err| format!("Failed to submit {} transaction: {:?}", TargetChain::NAME, err))?; .map_err(|err| Error::SubmitTransaction(TargetChain::NAME, err))?;
Ok(initialization_tx_hash) Ok(initialization_tx_hash)
} }
/// Prepare initialization data for the GRANDPA verifier pallet. /// Prepare initialization data for the GRANDPA verifier pallet.
async fn prepare_initialization_data<SourceChain: Chain>( async fn prepare_initialization_data<SourceChain: Chain>(
source_client: Client<SourceChain>, source_client: Client<SourceChain>,
) -> Result<InitializationData<SourceChain::Header>, String> { ) -> Result<
InitializationData<SourceChain::Header>,
Error<SourceChain::Hash, <SourceChain::Header as Header>::Number>,
> {
// In ideal world we just need to get best finalized header and then to read GRANDPA authorities // 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. // set (`pallet_grandpa::CurrentSetId` + `GrandpaApi::grandpa_authorities()`) at this header.
// //
// But now there are problems with this approach - `CurrentSetId` may return invalid value. So // 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 // here we're waiting for the next justification, read the authorities set and then try to
// figure out the set id with bruteforce. // figure out the set id with bruteforce.
let justifications = source_client.subscribe_justifications().await.map_err(|err| { let justifications = source_client
format!("Failed to subscribe to {} justifications: {:?}", SourceChain::NAME, err) .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. // Read next justification - the header that it finalizes will be used as initial header.
let justification = justifications let justification = justifications
.next() .next()
.await .await
.map_err(|err| err.to_string()) .map_err(|e| Error::ReadJustification(SourceChain::NAME, e))
.and_then(|justification| { .and_then(|justification| {
justification.ok_or_else(|| "stream has ended unexpectedly".into()) justification.ok_or_else(|| Error::ReadJustificationStreamEnded(SourceChain::NAME))
})
.map_err(|err| {
format!("Failed to read {} justification from the stream: {}", SourceChain::NAME, err,)
})?; })?;
// Read initial header. // Read initial header.
let justification: GrandpaJustification<SourceChain::Header> = let justification: GrandpaJustification<SourceChain::Header> =
Decode::decode(&mut &justification.0[..]).map_err(|err| { Decode::decode(&mut &justification.0[..])
format!("Failed to decode {} justification: {:?}", SourceChain::NAME, err) .map_err(|err| Error::DecodeJustification(SourceChain::NAME, err))?;
})?;
let (initial_header_hash, initial_header_number) = let (initial_header_hash, initial_header_number) =
(justification.commit.target_hash, justification.commit.target_number); (justification.commit.target_hash, justification.commit.target_number);
@@ -174,11 +175,7 @@ async fn prepare_initialization_data<SourceChain: Chain>(
let mut min_possible_block_number = SourceChain::BlockNumber::zero(); let mut min_possible_block_number = SourceChain::BlockNumber::zero();
let authorities_for_verification = VoterSet::new(authorities_for_verification.clone()) let authorities_for_verification = VoterSet::new(authorities_for_verification.clone())
.ok_or_else(|| { .ok_or_else(|| {
format!( Error::ReadInvalidAuthorities(SourceChain::NAME, authorities_for_verification)
"Read invalid {} authorities set: {:?}",
SourceChain::NAME,
authorities_for_verification,
)
})?; })?;
loop { loop {
log::trace!( log::trace!(
@@ -205,12 +202,7 @@ async fn prepare_initialization_data<SourceChain: Chain>(
// there can't be more authorities set changes than headers => if we have reached // 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_block_number` and still have not found correct value of
// `initial_authorities_set_id`, then something else is broken => fail // `initial_authorities_set_id`, then something else is broken => fail
return Err(format!( return Err(Error::GuessInitialAuthorities(SourceChain::NAME, initial_header_number))
"Failed to guess initial {} GRANDPA authorities set id: checked all\
possible ids in range [0; {}]",
SourceChain::NAME,
initial_header_number
))
} }
} }
@@ -230,37 +222,24 @@ async fn prepare_initialization_data<SourceChain: Chain>(
async fn source_header<SourceChain: Chain>( async fn source_header<SourceChain: Chain>(
source_client: &Client<SourceChain>, source_client: &Client<SourceChain>,
header_hash: SourceChain::Hash, header_hash: SourceChain::Hash,
) -> Result<SourceChain::Header, String> { ) -> Result<SourceChain::Header, Error<SourceChain::Hash, <SourceChain::Header as Header>::Number>>
source_client.header_by_hash(header_hash).await.map_err(|err| { {
format!( source_client
"Failed to retrive {} header with hash {}: {:?}", .header_by_hash(header_hash)
SourceChain::NAME, .await
header_hash, .map_err(|err| Error::RetrieveHeader(SourceChain::NAME, header_hash, err))
err,
)
})
} }
/// Read GRANDPA authorities set at given header. /// Read GRANDPA authorities set at given header.
async fn source_authorities_set<SourceChain: Chain>( async fn source_authorities_set<SourceChain: Chain>(
source_client: &Client<SourceChain>, source_client: &Client<SourceChain>,
header_hash: SourceChain::Hash, header_hash: SourceChain::Hash,
) -> Result<GrandpaAuthoritiesSet, String> { ) -> Result<GrandpaAuthoritiesSet, Error<SourceChain::Hash, <SourceChain::Header as Header>::Number>>
let raw_authorities_set = {
source_client.grandpa_authorities_set(header_hash).await.map_err(|err| { let raw_authorities_set = source_client
format!( .grandpa_authorities_set(header_hash)
"Failed to retrive {} GRANDPA authorities set at header {}: {:?}", .await
SourceChain::NAME, .map_err(|err| Error::RetrieveAuthorities(SourceChain::NAME, header_hash, err))?;
header_hash, GrandpaAuthoritiesSet::decode(&mut &raw_authorities_set[..])
err, .map_err(|err| Error::DecodeAuthorities(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,
)
})
} }
@@ -19,6 +19,7 @@
#![warn(missing_docs)] #![warn(missing_docs)]
pub mod conversion_rate_update; pub mod conversion_rate_update;
pub mod error;
pub mod finality_pipeline; pub mod finality_pipeline;
pub mod finality_target; pub mod finality_target;
pub mod headers_initialize; pub mod headers_initialize;
-1
View File
@@ -6,7 +6,6 @@ edition = "2018"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0" license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies] [dependencies]
anyhow = "1.0"
async-std = { version = "1.6.5", features = ["attributes"] } async-std = { version = "1.6.5", features = ["attributes"] }
async-trait = "0.1.40" async-trait = "0.1.40"
futures = "0.3.5" futures = "0.3.5"
@@ -263,7 +263,7 @@ pub async fn run<P: MessageLane>(
target_client: impl TargetClient<P>, target_client: impl TargetClient<P>,
metrics_params: MetricsParams, metrics_params: MetricsParams,
exit_signal: impl Future<Output = ()> + Send + 'static, exit_signal: impl Future<Output = ()> + Send + 'static,
) -> anyhow::Result<()> { ) -> Result<(), relay_utils::Error> {
let exit_signal = exit_signal.shared(); let exit_signal = exit_signal.shared();
relay_utils::relay_loop(source_client, target_client) relay_utils::relay_loop(source_client, target_client)
.reconnect_delay(params.reconnect_delay) .reconnect_delay(params.reconnect_delay)
+1
View File
@@ -20,6 +20,7 @@ num-traits = "0.2"
serde_json = "1.0" serde_json = "1.0"
sysinfo = "0.15" sysinfo = "0.15"
time = "0.2" time = "0.2"
thiserror = "1.0.26"
# Substrate dependencies # Substrate dependencies
+46
View File
@@ -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 <http://www.gnu.org/licenses/>.
use std::net::AddrParseError;
use thiserror::Error;
/// Result type used by relay utilities.
pub type Result<T> = std::result::Result<T, Error>;
/// 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),
}
+6 -10
View File
@@ -16,11 +16,13 @@
//! Utilities used by different relays. //! Utilities used by different relays.
pub use error::Error;
pub use relay_loop::{relay_loop, relay_metrics}; pub use relay_loop::{relay_loop, relay_metrics};
use backoff::{backoff::Backoff, ExponentialBackoff}; use backoff::{backoff::Backoff, ExponentialBackoff};
use futures::future::FutureExt; use futures::future::FutureExt;
use std::time::Duration; use std::time::Duration;
use thiserror::Error;
/// Max delay after connection-unrelated error happened before we'll try the /// Max delay after connection-unrelated error happened before we'll try the
/// same request again. /// same request again.
@@ -29,6 +31,7 @@ pub const MAX_BACKOFF_INTERVAL: Duration = Duration::from_secs(60);
/// reconnection again. /// reconnection again.
pub const CONNECTION_ERROR_DELAY: Duration = Duration::from_secs(10); pub const CONNECTION_ERROR_DELAY: Duration = Duration::from_secs(10);
pub mod error;
pub mod initialize; pub mod initialize;
pub mod metrics; pub mod metrics;
pub mod relay_loop; pub mod relay_loop;
@@ -111,11 +114,13 @@ pub trait MaybeConnectionError {
} }
/// Stringified error that may be either connection-related or not. /// Stringified error that may be either connection-related or not.
#[derive(Debug)] #[derive(Error, Debug)]
pub enum StringifiedMaybeConnectionError { pub enum StringifiedMaybeConnectionError {
/// The error is connection-related error. /// The error is connection-related error.
#[error("{0}")]
Connection(String), Connection(String),
/// The error is connection-unrelated error. /// The error is connection-unrelated error.
#[error("{0}")]
NonConnection(String), 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. /// Exponential backoff for connection-unrelated errors retries.
pub fn retry_backoff() -> ExponentialBackoff { pub fn retry_backoff() -> ExponentialBackoff {
ExponentialBackoff { ExponentialBackoff {
@@ -14,8 +14,12 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>. // along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use crate::metrics::{ use crate::{
metric_name, register, F64SharedRef, Gauge, PrometheusError, Registry, StandaloneMetrics, F64, error::{self, Error},
metrics::{
metric_name, register, F64SharedRef, Gauge, PrometheusError, Registry, StandaloneMetrics,
F64,
},
}; };
use async_std::sync::{Arc, RwLock}; use async_std::sync::{Arc, RwLock};
@@ -61,27 +65,18 @@ impl FloatJsonValueMetric {
self.shared_value_ref.clone() self.shared_value_ref.clone()
} }
/// Read value from HTTP service. /// Request value from HTTP service.
async fn read_value(&self) -> Result<f64, String> { async fn request_value(&self) -> anyhow::Result<String> {
use isahc::{AsyncReadResponseExt, HttpClient, Request}; use isahc::{AsyncReadResponseExt, HttpClient, Request};
fn map_isahc_err(err: impl std::fmt::Display) -> String { let request = Request::get(&self.url).header("Accept", "application/json").body(())?;
format!("Failed to fetch token price from remote server: {}", err) let raw_response = HttpClient::new()?.send_async(request).await?.text().await?;
Ok(raw_response)
} }
let request = Request::get(&self.url) /// Read value from HTTP service.
.header("Accept", "application/json") async fn read_value(&self) -> error::Result<f64> {
.body(()) let raw_response = self.request_value().await.map_err(Error::FetchTokenPrice)?;
.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)?;
parse_service_response(&self.json_path, &raw_response) parse_service_response(&self.json_path, &raw_response)
} }
} }
@@ -94,30 +89,26 @@ impl StandaloneMetrics for FloatJsonValueMetric {
async fn update(&self) { async fn update(&self) {
let value = self.read_value().await; let value = self.read_value().await;
crate::metrics::set_gauge_value(&self.metric, value.clone().map(Some)); let maybe_ok = value.as_ref().ok().copied();
*self.shared_value_ref.write().await = value.ok(); crate::metrics::set_gauge_value(&self.metric, value.map(Some));
*self.shared_value_ref.write().await = maybe_ok;
} }
} }
/// Parse HTTP service response. /// Parse HTTP service response.
fn parse_service_response(json_path: &str, response: &str) -> Result<f64, String> { fn parse_service_response(json_path: &str, response: &str) -> error::Result<f64> {
let json = serde_json::from_str(response).map_err(|err| { let json =
format!("Failed to parse HTTP service response: {:?}. Response: {:?}", err, response,) serde_json::from_str(response).map_err(|err| Error::ParseHttp(err, response.to_owned()))?;
})?;
let mut selector = jsonpath_lib::selector(&json); let mut selector = jsonpath_lib::selector(&json);
let maybe_selected_value = selector(json_path).map_err(|err| { let maybe_selected_value =
format!("Failed to select value from response: {:?}. Response: {:?}", err, response,) selector(json_path).map_err(|err| Error::SelectResponseValue(err, response.to_owned()))?;
})?;
let selected_value = maybe_selected_value let selected_value = maybe_selected_value
.first() .first()
.and_then(|v| v.as_f64()) .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 { if !selected_value.is_normal() || selected_value < 0.0 {
return Err(format!( return Err(Error::ParseFloat(selected_value))
"Failed to parse float value {:?} from response. It is assumed to be positive and normal",
selected_value,
))
} }
Ok(selected_value) Ok(selected_value)
+10 -12
View File
@@ -15,6 +15,7 @@
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>. // along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use crate::{ use crate::{
error::Error,
metrics::{Metrics, MetricsAddress, MetricsParams, PrometheusError, StandaloneMetrics}, metrics::{Metrics, MetricsAddress, MetricsParams, PrometheusError, StandaloneMetrics},
FailedClient, MaybeConnectionError, FailedClient, MaybeConnectionError,
}; };
@@ -116,7 +117,7 @@ impl<SC, TC, LM> Loop<SC, TC, LM> {
/// This function represents an outer loop, which in turn calls provided `run_loop` function to /// 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, /// do actual job. When `run_loop` returns, this outer loop reconnects to failed client (source,
/// target or both) and calls `run_loop` again. /// target or both) and calls `run_loop` again.
pub async fn run<R, F>(mut self, loop_name: String, run_loop: R) -> anyhow::Result<()> pub async fn run<R, F>(mut self, loop_name: String, run_loop: R) -> Result<(), Error>
where where
R: 'static + Send + Fn(SC, TC, Option<LM>) -> F, R: 'static + Send + Fn(SC, TC, Option<LM>) -> F,
F: 'static + Send + Future<Output = Result<(), FailedClient>>, F: 'static + Send + Future<Output = Result<(), FailedClient>>,
@@ -162,7 +163,7 @@ impl<SC, TC, LM> LoopMetrics<SC, TC, LM> {
pub fn loop_metric<NewLM: Metrics>( pub fn loop_metric<NewLM: Metrics>(
self, self,
create_metric: impl FnOnce(&Registry, Option<&str>) -> Result<NewLM, PrometheusError>, create_metric: impl FnOnce(&Registry, Option<&str>) -> Result<NewLM, PrometheusError>,
) -> anyhow::Result<LoopMetrics<SC, TC, NewLM>> { ) -> Result<LoopMetrics<SC, TC, NewLM>, Error> {
let loop_metric = create_metric(&self.registry, self.metrics_prefix.as_deref())?; let loop_metric = create_metric(&self.registry, self.metrics_prefix.as_deref())?;
Ok(LoopMetrics { Ok(LoopMetrics {
@@ -178,13 +179,13 @@ impl<SC, TC, LM> LoopMetrics<SC, TC, LM> {
pub fn standalone_metric<M: StandaloneMetrics>( pub fn standalone_metric<M: StandaloneMetrics>(
self, self,
create_metric: impl FnOnce(&Registry, Option<&str>) -> Result<M, PrometheusError>, create_metric: impl FnOnce(&Registry, Option<&str>) -> Result<M, PrometheusError>,
) -> anyhow::Result<Self> { ) -> Result<Self, Error> {
// since standalone metrics are updating themselves, we may just ignore the fact that the // 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 // same standalone metric is exposed by several loops && only spawn single metric
match create_metric(&self.registry, self.metrics_prefix.as_deref()) { match create_metric(&self.registry, self.metrics_prefix.as_deref()) {
Ok(standalone_metrics) => standalone_metrics.spawn(), Ok(standalone_metrics) => standalone_metrics.spawn(),
Err(PrometheusError::AlreadyReg) => (), Err(PrometheusError::AlreadyReg) => (),
Err(e) => anyhow::bail!(e), Err(e) => return Err(e.into()),
} }
Ok(self) Ok(self)
@@ -202,16 +203,13 @@ impl<SC, TC, LM> LoopMetrics<SC, TC, LM> {
/// Expose metrics using address passed at creation. /// Expose metrics using address passed at creation.
/// ///
/// If passed `address` is `None`, metrics are not exposed. /// If passed `address` is `None`, metrics are not exposed.
pub async fn expose(self) -> anyhow::Result<Loop<SC, TC, LM>> { pub async fn expose(self) -> Result<Loop<SC, TC, LM>, Error> {
if let Some(address) = self.address { if let Some(address) = self.address {
let socket_addr = SocketAddr::new( let socket_addr = SocketAddr::new(
address.host.parse().map_err(|err| { address
anyhow::format_err!( .host
"Invalid host {} is used to expose Prometheus metrics: {}", .parse()
address.host, .map_err(|err| Error::ExposingMetricsInvalidHost(address.host.clone(), err))?,
err,
)
})?,
address.port, address.port,
); );