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"
num-traits = "0.2"
serde_json = "1.0.64"
thiserror = "1.0.26"
# 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/>.
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<Rialto>,
sub_initial_header: Option<Vec<u8>>,
) -> Result<(RialtoHeaderId, Vec<u8>), String> {
) -> Result<(RialtoHeaderId, Vec<u8>)> {
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<Rialto>,
sub_initial_header_hash: rialto_runtime::Hash,
sub_initial_authorities_set: Option<Vec<u8>>,
) -> Result<OpaqueGrandpaAuthoritiesSet, String> {
) -> Result<OpaqueGrandpaAuthoritiesSet> {
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<u8>,
initial_set_id: u64,
initial_authorities: Vec<u8>,
) -> 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))
}
@@ -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(
@@ -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(&eth_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)
}
+31 -29
View File
@@ -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<EthereumConnectionParams, String> {
) -> anyhow::Result<EthereumConnectionParams> {
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<EthereumSigningParams, String> {
fn ethereum_signing_params(matches: &clap::ArgMatches) -> anyhow::Result<EthereumSigningParams> {
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::<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)
}
fn substrate_connection_params(
matches: &clap::ArgMatches,
) -> Result<SubstrateConnectionParams, String> {
) -> anyhow::Result<SubstrateConnectionParams> {
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<RialtoSigningParams, String> {
fn rialto_signing_params(matches: &clap::ArgMatches) -> anyhow::Result<RialtoSigningParams> {
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<EthereumSyncParams, String> {
fn ethereum_sync_params(matches: &clap::ArgMatches) -> anyhow::Result<EthereumSyncParams> {
use crate::ethereum_sync_loop::consts::*;
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;
},
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,
}
@@ -226,12 +228,12 @@ fn ethereum_sync_params(matches: &clap::ArgMatches) -> Result<EthereumSyncParams
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::*;
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<SubstrateSyncPara
fn ethereum_deploy_contract_params(
matches: &clap::ArgMatches,
) -> Result<EthereumDeployContractParams, String> {
) -> anyhow::Result<EthereumDeployContractParams> {
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<EthereumExchangeSubmitParams, String> {
) -> anyhow::Result<EthereumExchangeSubmitParams> {
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<EthereumExchangeParams, String> {
fn ethereum_exchange_params(matches: &clap::ArgMatches) -> anyhow::Result<EthereumExchangeParams> {
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<EthereumExchan
.map(|eth_start_with_block| {
eth_start_with_block
.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()?,
),
@@ -380,7 +382,7 @@ fn ethereum_exchange_params(matches: &clap::ArgMatches) -> Result<EthereumExchan
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") {
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") {
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<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") {
match instance.to_lowercase().as_str() {
"rialto" => Arc::new(RialtoPoA) as Arc<dyn BridgeInstance>,
"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<Arc<dyn BridgeInstance>
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) {
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),
}
}
+10 -28
View File
@@ -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<RpcError> 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<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)
format!("{}", err)
}
}