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)
}
}
@@ -97,7 +97,7 @@ impl CliChain for Kusama {
fn encode_message(
_message: encode_message::MessagePayload,
) -> Result<Self::MessagePayload, String> {
Err("Sending messages from Kusama is not yet supported.".into())
) -> anyhow::Result<Self::MessagePayload> {
anyhow::bail!("Sending messages from Kusama is not yet supported.")
}
}
@@ -256,6 +256,7 @@ pub async fn run(
futures::future::pending(),
)
.await
.map_err(Into::into)
}
/// Add standalone metrics for the Kusama -> Polkadot messages loop.
@@ -23,6 +23,7 @@ use crate::cli::{
send_message::{self, DispatchFeePayment},
CliChain,
};
use anyhow::anyhow;
use bp_message_dispatch::{CallOrigin, MessagePayload};
use codec::Decode;
use frame_support::weights::{DispatchInfo, GetDispatchInfo, Weight};
@@ -87,10 +88,10 @@ impl CliChain for Millau {
// TODO [#854|#843] support multiple bridges?
fn encode_message(
message: encode_message::MessagePayload,
) -> Result<Self::MessagePayload, String> {
) -> anyhow::Result<Self::MessagePayload> {
match message {
encode_message::MessagePayload::Raw { data } => MessagePayload::decode(&mut &*data.0)
.map_err(|e| format!("Failed to decode Millau's MessagePayload: {:?}", e)),
.map_err(|e| anyhow!("Failed to decode Millau's MessagePayload: {:?}", e)),
encode_message::MessagePayload::Call { mut call, mut sender } => {
type Source = Millau;
type Target = relay_rialto_client::Rialto;
@@ -102,7 +103,7 @@ impl CliChain for Millau {
&mut call,
bridge::MILLAU_TO_RIALTO_INDEX,
);
let call = Target::encode_call(&call).map_err(|e| e.to_string())?;
let call = Target::encode_call(&call)?;
let weight = call.get_dispatch_info().weight;
Ok(send_message::message_payload(
@@ -250,6 +250,7 @@ pub async fn run(
futures::future::pending(),
)
.await
.map_err(Into::into)
}
/// Add standalone metrics for the Millau -> Rialto messages loop.
@@ -97,7 +97,7 @@ impl CliChain for Polkadot {
fn encode_message(
_message: encode_message::MessagePayload,
) -> Result<Self::MessagePayload, String> {
Err("Sending messages from Polkadot is not yet supported.".into())
) -> anyhow::Result<Self::MessagePayload> {
anyhow::bail!("Sending messages from Polkadot is not yet supported.")
}
}
@@ -255,6 +255,7 @@ pub async fn run(
futures::future::pending(),
)
.await
.map_err(Into::into)
}
/// Add standalone metrics for the Polkadot -> Kusama messages loop.
@@ -23,6 +23,7 @@ use crate::cli::{
send_message::{self, DispatchFeePayment},
CliChain,
};
use anyhow::anyhow;
use bp_message_dispatch::{CallOrigin, MessagePayload};
use codec::Decode;
use frame_support::weights::{DispatchInfo, GetDispatchInfo, Weight};
@@ -86,10 +87,10 @@ impl CliChain for Rialto {
fn encode_message(
message: encode_message::MessagePayload,
) -> Result<Self::MessagePayload, String> {
) -> anyhow::Result<Self::MessagePayload> {
match message {
encode_message::MessagePayload::Raw { data } => MessagePayload::decode(&mut &*data.0)
.map_err(|e| format!("Failed to decode Rialto's MessagePayload: {:?}", e)),
.map_err(|e| anyhow!("Failed to decode Rialto's MessagePayload: {:?}", e)),
encode_message::MessagePayload::Call { mut call, mut sender } => {
type Source = Rialto;
type Target = relay_millau_client::Millau;
@@ -101,7 +102,7 @@ impl CliChain for Rialto {
&mut call,
bridge::RIALTO_TO_MILLAU_INDEX,
);
let call = Target::encode_call(&call).map_err(|e| e.to_string())?;
let call = Target::encode_call(&call)?;
let weight = call.get_dispatch_info().weight;
Ok(send_message::message_payload(
@@ -249,6 +249,7 @@ pub async fn run(
futures::future::pending(),
)
.await
.map_err(Into::into)
}
/// Add standalone metrics for the Rialto -> Millau messages loop.
@@ -76,7 +76,7 @@ impl CliChain for RialtoParachain {
fn encode_message(
_message: encode_message::MessagePayload,
) -> Result<Self::MessagePayload, String> {
Err("Not supported".into())
) -> anyhow::Result<Self::MessagePayload> {
anyhow::bail!("Not supported")
}
}
@@ -14,6 +14,7 @@
// 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 anyhow::anyhow;
use codec::Decode;
use frame_support::weights::{DispatchClass, DispatchInfo, Pays, Weight};
use relay_rococo_client::Rococo;
@@ -94,7 +95,7 @@ impl CliChain for Rococo {
fn encode_message(
_message: encode_message::MessagePayload,
) -> Result<Self::MessagePayload, String> {
Err("Sending messages from Rococo is not yet supported.".into())
) -> anyhow::Result<Self::MessagePayload> {
Err(anyhow!("Sending messages from Rococo is not yet supported."))
}
}
@@ -253,6 +253,7 @@ pub async fn run(
futures::future::pending(),
)
.await
.map_err(Into::into)
}
/// Add standalone metrics for the Rococo -> Wococo messages loop.
@@ -17,6 +17,7 @@
//! Westend chain specification for CLI.
use crate::cli::{encode_message, CliChain};
use anyhow::anyhow;
use frame_support::weights::Weight;
use relay_westend_client::Westend;
use sp_version::RuntimeVersion;
@@ -37,7 +38,7 @@ impl CliChain for Westend {
fn encode_message(
_message: encode_message::MessagePayload,
) -> Result<Self::MessagePayload, String> {
Err("Sending messages from Westend is not yet supported.".into())
) -> anyhow::Result<Self::MessagePayload> {
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
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use anyhow::anyhow;
use codec::Decode;
use frame_support::weights::{DispatchClass, DispatchInfo, Pays, Weight};
use relay_wococo_client::Wococo;
@@ -88,7 +89,7 @@ impl CliChain for Wococo {
fn encode_message(
_message: encode_message::MessagePayload,
) -> Result<Self::MessagePayload, String> {
Err("Sending messages from Wococo is not yet supported.".into())
) -> anyhow::Result<Self::MessagePayload> {
Err(anyhow!("Sending messages from Wococo is not yet supported."))
}
}
@@ -252,6 +252,7 @@ pub async fn run(
futures::future::pending(),
)
.await
.map_err(Into::into)
}
/// Add standalone metrics for the Wococo -> Rococo messages loop.
+1 -1
View File
@@ -257,7 +257,7 @@ pub trait CliChain: relay_substrate_client::Chain {
/// Construct message payload to be sent over the bridge.
fn encode_message(
message: crate::cli::encode_message::MessagePayload,
) -> Result<Self::MessagePayload, String>;
) -> anyhow::Result<Self::MessagePayload>;
/// Maximal extrinsic weight (from the runtime).
fn max_extrinsic_weight() -> Weight;
@@ -141,6 +141,7 @@ impl ResubmitTransactions {
)
})
.await
.map_err(Into::into)
})
}
}
@@ -17,3 +17,4 @@ log = "0.4.11"
relay-utils = { path = "../utils" }
tokio = "1.8"
web3 = { git = "https://github.com/svyatonik/rust-web3.git", branch = "bump-deps" }
thiserror = "1.0.26"
+13 -36
View File
@@ -20,49 +20,47 @@ use crate::types::U256;
use jsonrpsee_ws_client::types::Error as RpcError;
use relay_utils::MaybeConnectionError;
use thiserror::Error;
/// Result type used by Ethereum client.
pub type Result<T> = std::result::Result<T, Error>;
/// Errors that can occur only when interacting with
/// an Ethereum node through RPC.
#[derive(Debug)]
#[derive(Debug, Error)]
pub enum Error {
/// IO error.
Io(std::io::Error),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
/// An error that can occur when making an HTTP request to
/// an JSON-RPC client.
RpcError(RpcError),
#[error("RPC error: {0}")]
RpcError(#[from] RpcError),
/// Failed to parse response.
#[error("Response parse failed: {0}")]
ResponseParseFailed(String),
/// We have received a header with missing fields.
#[error("Incomplete Ethereum Header Received (missing some of required fields - hash, number, logs_bloom).")]
IncompleteHeader,
/// We have received a transaction missing a `raw` field.
#[error("Incomplete Ethereum Transaction (missing required field - raw).")]
IncompleteTransaction,
/// An invalid Substrate block number was received from
/// an Ethereum node.
#[error("Received an invalid Substrate block from Ethereum Node.")]
InvalidSubstrateBlockNumber,
/// An invalid index has been received from an Ethereum node.
#[error("Received an invalid incomplete index from Ethereum Node.")]
InvalidIncompleteIndex,
/// The client we're connected to is not synced, so we can't rely on its state. Contains
/// number of unsynced headers.
#[error("Ethereum client is not synced: syncing {0} headers.")]
ClientNotSynced(U256),
/// Custom logic error.
#[error("{0}")]
Custom(String),
}
impl From<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 {
fn from(error: tokio::task::JoinError) -> Self {
Error::Custom(format!("Failed to wait tokio task: {}", error))
@@ -82,24 +80,3 @@ impl MaybeConnectionError for Error {
)
}
}
impl ToString for Error {
fn to_string(&self) -> String {
match self {
Self::Io(e) => e.to_string(),
Self::RpcError(e) => e.to_string(),
Self::ResponseParseFailed(e) => e.to_string(),
Self::IncompleteHeader => {
"Incomplete Ethereum Header Received (missing some of required fields - hash, number, logs_bloom)"
.to_string()
}
Self::IncompleteTransaction => "Incomplete Ethereum Transaction (missing required field - raw)".to_string(),
Self::InvalidSubstrateBlockNumber => "Received an invalid Substrate block from Ethereum Node".to_string(),
Self::InvalidIncompleteIndex => "Received an invalid incomplete index from Ethereum Node".to_string(),
Self::ClientNotSynced(missing_headers) => {
format!("Ethereum client is not synced: syncing {} headers", missing_headers)
}
Self::Custom(ref e) => e.clone(),
}
}
}
@@ -15,6 +15,7 @@ log = "0.4.11"
num-traits = "0.2"
rand = "0.7"
tokio = "1.8"
thiserror = "1.0.26"
# Bridge dependencies
+16 -67
View File
@@ -20,78 +20,54 @@ use jsonrpsee_ws_client::types::Error as RpcError;
use relay_utils::MaybeConnectionError;
use sc_rpc_api::system::Health;
use sp_runtime::transaction_validity::TransactionValidityError;
use thiserror::Error;
/// Result type used by Substrate client.
pub type Result<T> = std::result::Result<T, Error>;
/// Errors that can occur only when interacting with
/// a Substrate node through RPC.
#[derive(Debug)]
#[derive(Error, Debug)]
pub enum Error {
/// IO error.
Io(std::io::Error),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
/// An error that can occur when making a request to
/// an JSON-RPC server.
RpcError(RpcError),
#[error("RPC error: {0}")]
RpcError(#[from] RpcError),
/// The response from the server could not be SCALE decoded.
ResponseParseFailed(codec::Error),
#[error("Response parse failed: {0}")]
ResponseParseFailed(#[from] codec::Error),
/// The Substrate bridge pallet has not yet been initialized.
#[error("The Substrate bridge pallet has not been initialized yet.")]
UninitializedBridgePallet,
/// Account does not exist on the chain.
#[error("Account does not exist on the chain.")]
AccountDoesNotExist,
/// Runtime storage is missing mandatory ":code:" entry.
#[error("Mandatory :code: entry is missing from runtime storage.")]
MissingMandatoryCodeEntry,
/// The client we're connected to is not synced, so we can't rely on its state.
#[error("Substrate client is not synced {0}.")]
ClientNotSynced(Health),
/// An error has happened when we have tried to parse storage proof.
#[error("Error when parsing storage proof: {0:?}.")]
StorageProofError(bp_runtime::StorageProofError),
/// The Substrate transaction is invalid.
TransactionInvalid(TransactionValidityError),
#[error("Substrate transaction is invalid: {0:?}")]
TransactionInvalid(#[from] TransactionValidityError),
/// Custom logic error.
#[error("{0}")]
Custom(String),
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Io(ref e) => Some(e),
Self::RpcError(ref e) => Some(e),
Self::ResponseParseFailed(ref e) => Some(e),
Self::UninitializedBridgePallet => None,
Self::AccountDoesNotExist => None,
Self::MissingMandatoryCodeEntry => None,
Self::ClientNotSynced(_) => None,
Self::StorageProofError(_) => None,
Self::TransactionInvalid(_) => None,
Self::Custom(_) => None,
}
}
}
impl From<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 {
fn from(error: tokio::task::JoinError) -> Self {
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 {
fn is_connection_error(&self) -> bool {
matches!(
@@ -105,30 +81,3 @@ impl MaybeConnectionError for Error {
)
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let s = match self {
Self::Io(e) => e.to_string(),
Self::RpcError(e) => e.to_string(),
Self::ResponseParseFailed(e) => e.to_string(),
Self::UninitializedBridgePallet =>
"The Substrate bridge pallet has not been initialized yet.".into(),
Self::AccountDoesNotExist => "Account does not exist on the chain".into(),
Self::MissingMandatoryCodeEntry =>
"Mandatory :code: entry is missing from runtime storage".into(),
Self::StorageProofError(e) => format!("Error when parsing storage proof: {:?}", e),
Self::ClientNotSynced(health) => format!("Substrate client is not synced: {}", health),
Self::TransactionInvalid(e) => format!("Substrate transaction is invalid: {:?}", e),
Self::Custom(e) => e.clone(),
};
write!(f, "{}", s)
}
}
impl From<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
//! and are aborting process if some condition fails.
use crate::{Chain, ChainWithBalances, Client};
use crate::{error::Error, Chain, ChainWithBalances, Client};
use async_trait::async_trait;
use num_traits::CheckedSub;
use sp_version::RuntimeVersion;
use std::{
collections::VecDeque,
fmt::Display,
time::{Duration, Instant},
};
/// Guards environment.
#[async_trait]
pub trait Environment<C: ChainWithBalances>: Send + Sync + 'static {
/// Error type.
type Error: Display + Send + Sync + 'static;
/// 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.
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.
fn now(&self) -> Instant {
@@ -74,7 +81,7 @@ pub fn abort_on_spec_version_change<C: ChainWithBalances>(
},
Err(error) => log::warn!(
target: "bridge-guard",
"Failed to read {} runtime version: {:?}. Relay may need to be stopped manually",
"Failed to read {} runtime version: {}. Relay may need to be stopped manually",
C::NAME,
error,
),
@@ -137,7 +144,7 @@ pub fn abort_when_account_balance_decreased<C: ChainWithBalances>(
Err(error) => {
log::warn!(
target: "bridge-guard",
"Failed to read {} account {:?} balance: {:?}. Relay may need to be stopped manually",
"Failed to read {} account {:?} balance: {}. Relay may need to be stopped manually",
C::NAME,
account_id,
error,
@@ -157,12 +164,17 @@ fn conditions_check_delay<C: Chain>() -> Duration {
#[async_trait]
impl<C: ChainWithBalances> Environment<C> for Client<C> {
async fn runtime_version(&mut self) -> Result<RuntimeVersion, String> {
Client::<C>::runtime_version(self).await.map_err(|e| e.to_string())
type Error = Error;
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> {
Client::<C>::free_native_balance(self, account).await.map_err(|e| e.to_string())
async fn free_native_balance(
&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]
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())
}
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())
}
+1
View File
@@ -15,3 +15,4 @@ log = "0.4.11"
num-traits = "0.2"
parking_lot = "0.11.0"
relay-utils = { path = "../utils" }
thiserror = "1.0.26"
+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,
}
}
}
+41 -59
View File
@@ -16,11 +16,11 @@
//! Relaying proofs of exchange transaction.
use crate::error::{Error, ErrorOf};
use anyhow::anyhow;
use async_trait::async_trait;
use relay_utils::{
relay_loop::Client as RelayClient, FailedClient, MaybeConnectionError,
StringifiedMaybeConnectionError,
};
use relay_utils::{relay_loop::Client as RelayClient, FailedClient, MaybeConnectionError};
use std::{
fmt::{Debug, Display},
string::ToString,
@@ -67,7 +67,7 @@ pub trait SourceBlock: 'static + Send + Sync {
/// Transaction that is participating in exchange.
pub trait SourceTransaction: 'static + Send {
/// Transaction hash type.
type Hash: Debug + Display;
type Hash: Debug + Display + Clone;
/// Return transaction hash.
fn hash(&self) -> Self::Hash;
@@ -117,7 +117,7 @@ pub trait TargetClient<P: TransactionProofPipeline>: RelayClient {
/// Sleep until exchange-related data is (probably) updated.
async fn tick(&self);
/// Returns `Ok(true)` if header is known to the target node.
async fn is_header_known(&self, id: &HeaderId<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.
async fn is_header_finalized(&self, id: &HeaderId<P>) -> Result<bool, Self::Error>;
/// 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| {
(
FailedClient::Target,
StringifiedMaybeConnectionError::new(
Error::TransactionFiltering(
anyhow!("{:?}", err),
err.is_connection_error(),
format!("Transaction filtering has failed with {:?}", err),
),
)
})?;
@@ -256,20 +256,14 @@ pub async fn relay_single_transaction_proof<P: TransactionProofPipeline>(
source_client: &impl SourceClient<P>,
target_client: &impl TargetClient<P>,
source_tx_hash: TransactionHashOf<P>,
) -> Result<(), String> {
) -> Result<(), ErrorOf<P>> {
// wait for transaction and header on source node
let (source_header_id, source_tx_index) =
wait_transaction_mined(source_client, &source_tx_hash).await?;
let source_block = source_client.block_by_hash(source_header_id.1.clone()).await;
let source_block = source_block.map_err(|err| {
format!(
"Error retrieving block {} from {} node: {:?}",
source_header_id.1,
P::SOURCE_NAME,
err,
)
Error::RetrievingBlock(source_header_id.1.clone(), P::SOURCE_NAME, anyhow!("{:?}", err))
})?;
// wait for transaction and header on target node
wait_header_imported(target_client, &source_header_id).await?;
wait_header_finalized(target_client, &source_header_id).await?;
@@ -280,11 +274,10 @@ pub async fn relay_single_transaction_proof<P: TransactionProofPipeline>(
target_client,
&source_tx_id,
prepare_transaction_proof(source_client, &source_tx_id, &source_block, source_tx_index)
.await
.map_err(|err| err.to_string())?,
.await?,
)
.await
.map_err(|err| err.to_string())
.map_err(Into::into)
}
/// Prepare transaction proof.
@@ -293,19 +286,16 @@ async fn prepare_transaction_proof<P: TransactionProofPipeline>(
source_tx_id: &str,
source_block: &P::Block,
source_tx_index: usize,
) -> Result<P::TransactionProof, StringifiedMaybeConnectionError> {
) -> Result<P::TransactionProof, ErrorOf<P>> {
source_client
.transaction_proof(source_block, source_tx_index)
.await
.map_err(|err| {
StringifiedMaybeConnectionError::new(
Error::BuildTransactionProof(
source_tx_id.to_owned(),
P::SOURCE_NAME,
anyhow!("{:?}", err),
err.is_connection_error(),
format!(
"Error building transaction {} proof on {} node: {:?}",
source_tx_id,
P::SOURCE_NAME,
err,
),
)
})
}
@@ -315,16 +305,13 @@ async fn relay_ready_transaction_proof<P: TransactionProofPipeline>(
target_client: &impl TargetClient<P>,
source_tx_id: &str,
source_tx_proof: P::TransactionProof,
) -> Result<(), StringifiedMaybeConnectionError> {
) -> Result<(), ErrorOf<P>> {
target_client.submit_transaction_proof(source_tx_proof).await.map_err(|err| {
StringifiedMaybeConnectionError::new(
Error::SubmitTransactionProof(
source_tx_id.to_owned(),
P::TARGET_NAME,
anyhow!("{:?}", err),
err.is_connection_error(),
format!(
"Error submitting transaction {} proof to {} node: {:?}",
source_tx_id,
P::TARGET_NAME,
err,
),
)
})
}
@@ -333,15 +320,14 @@ async fn relay_ready_transaction_proof<P: TransactionProofPipeline>(
async fn wait_transaction_mined<P: TransactionProofPipeline>(
source_client: &impl SourceClient<P>,
source_tx_hash: &TransactionHashOf<P>,
) -> Result<(HeaderId<P>, usize), String> {
) -> Result<(HeaderId<P>, usize), ErrorOf<P>> {
loop {
let source_header_and_tx =
source_client.transaction_block(source_tx_hash).await.map_err(|err| {
format!(
"Error retrieving transaction {} from {} node: {:?}",
source_tx_hash,
Error::RetrievingTransaction(
source_tx_hash.clone(),
P::SOURCE_NAME,
err,
anyhow!("{:?}", err),
)
})?;
match source_header_and_tx {
@@ -373,16 +359,15 @@ async fn wait_transaction_mined<P: TransactionProofPipeline>(
async fn wait_header_imported<P: TransactionProofPipeline>(
target_client: &impl TargetClient<P>,
source_header_id: &HeaderId<P>,
) -> Result<(), String> {
) -> Result<(), ErrorOf<P>> {
loop {
let is_header_known =
target_client.is_header_known(source_header_id).await.map_err(|err| {
format!(
"Failed to check existence of header {}/{} on {} node: {:?}",
Error::CheckHeaderExistence(
source_header_id.0,
source_header_id.1,
source_header_id.1.clone(),
P::TARGET_NAME,
err,
anyhow!("{:?}", err),
)
})?;
match is_header_known {
@@ -416,16 +401,15 @@ async fn wait_header_imported<P: TransactionProofPipeline>(
async fn wait_header_finalized<P: TransactionProofPipeline>(
target_client: &impl TargetClient<P>,
source_header_id: &HeaderId<P>,
) -> Result<(), String> {
) -> Result<(), ErrorOf<P>> {
loop {
let is_header_finalized =
target_client.is_header_finalized(source_header_id).await.map_err(|err| {
format!(
"Failed to check finality of header {}/{} on {} node: {:?}",
Error::Finality(
source_header_id.0,
source_header_id.1,
source_header_id.1.clone(),
P::TARGET_NAME,
err,
anyhow!("{:?}", err),
)
})?;
match is_header_finalized {
@@ -691,14 +675,12 @@ pub(crate) mod tests {
source: &TestTransactionsSource,
target: &TestTransactionsTarget,
) {
assert_eq!(
async_std::task::block_on(relay_single_transaction_proof(
source,
target,
test_transaction_hash(0),
)),
Ok(()),
);
assert!(async_std::task::block_on(relay_single_transaction_proof(
source,
target,
test_transaction_hash(0)
))
.is_ok());
assert_eq!(
target.data.lock().submitted_proofs,
vec![TestTransactionProof(test_transaction_hash(0))],
@@ -711,7 +693,7 @@ pub(crate) mod tests {
&target,
test_transaction_hash(0),
))
.is_err(),);
.is_err());
assert!(target.data.lock().submitted_proofs.is_empty());
}
+4 -1
View File
@@ -17,6 +17,7 @@
//! Relaying proofs of exchange transactions.
use crate::{
error::Error,
exchange::{
relay_block_transactions, BlockNumberOf, RelayedBlockTransactions, SourceClient,
TargetClient, TransactionProofPipeline,
@@ -24,6 +25,7 @@ use crate::{
exchange_loop_metrics::ExchangeLoopMetrics,
};
use crate::error::ErrorOf;
use backoff::backoff::Backoff;
use futures::{future::FutureExt, select};
use num_traits::One;
@@ -92,7 +94,7 @@ pub async fn run<P: TransactionProofPipeline>(
target_client: impl TargetClient<P>,
metrics_params: MetricsParams,
exit_signal: impl Future<Output = ()> + 'static + Send,
) -> anyhow::Result<()> {
) -> Result<(), ErrorOf<P>> {
let exit_signal = exit_signal.shared();
relay_utils::relay_loop(source_client, target_client)
@@ -111,6 +113,7 @@ pub async fn run<P: TransactionProofPipeline>(
)
})
.await
.map_err(Error::Utils)
}
/// Run proofs synchronization.
+1
View File
@@ -21,6 +21,7 @@
#![warn(missing_docs)]
pub mod error;
pub mod exchange;
pub mod exchange_loop;
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"
[dependencies]
anyhow = "1.0"
async-std = "1.6.5"
async-trait = "0.1.40"
backoff = "0.2"
+1 -1
View File
@@ -110,7 +110,7 @@ pub async fn run<P: FinalitySyncPipeline>(
sync_params: FinalitySyncParams,
metrics_params: MetricsParams,
exit_signal: impl Future<Output = ()> + 'static + Send,
) -> anyhow::Result<()> {
) -> Result<(), relay_utils::Error> {
let exit_signal = exit_signal.shared();
relay_utils::relay_loop(source_client, target_client)
.with_metrics(Some(metrics_prefix::<P>()), metrics_params)
-1
View File
@@ -6,7 +6,6 @@ edition = "2018"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
anyhow = "1.0"
async-std = "1.6.5"
async-trait = "0.1.40"
backoff = "0.2"
+1 -1
View File
@@ -143,7 +143,7 @@ pub async fn run<P: HeadersSyncPipeline, TC: TargetClient<P>>(
sync_params: HeadersSyncParams,
metrics_params: MetricsParams,
exit_signal: impl Future<Output = ()> + 'static + Send,
) -> anyhow::Result<()> {
) -> Result<(), relay_utils::Error> {
let exit_signal = exit_signal.shared();
relay_utils::relay_loop(source_client, target_client)
.with_metrics(Some(metrics_prefix::<P>()), metrics_params)
@@ -7,6 +7,7 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
anyhow = "1.0"
thiserror = "1.0.26"
async-std = "1.9.0"
async-trait = "0.1.42"
codec = { package = "parity-scale-codec", version = "2.2.0" }
@@ -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
//! with this header.
use crate::error::Error;
use bp_header_chain::{
find_grandpa_authorities_scheduled_change,
justification::{verify_justification, GrandpaJustification},
@@ -32,7 +34,7 @@ use num_traits::{One, Zero};
use relay_substrate_client::{Chain, Client};
use sp_core::Bytes;
use sp_finality_grandpa::AuthorityList as GrandpaAuthoritiesSet;
use sp_runtime::traits::Header as HeaderT;
use sp_runtime::traits::{Header as HeaderT, Header};
/// Submit headers-bridge initialization transaction.
pub async fn initialize<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
+ Send
+ '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?;
log::info!(
target: "bridge",
@@ -92,41 +94,40 @@ async fn do_initialize<SourceChain: Chain, TargetChain: Chain>(
prepare_initialize_transaction(transaction_nonce, initialization_data)
})
.await
.map_err(|err| format!("Failed to submit {} transaction: {:?}", TargetChain::NAME, err))?;
.map_err(|err| Error::SubmitTransaction(TargetChain::NAME, err))?;
Ok(initialization_tx_hash)
}
/// Prepare initialization data for the GRANDPA verifier pallet.
async fn prepare_initialization_data<SourceChain: Chain>(
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
// set (`pallet_grandpa::CurrentSetId` + `GrandpaApi::grandpa_authorities()`) at this header.
//
// But now there are problems with this approach - `CurrentSetId` may return invalid value. So
// here we're waiting for the next justification, read the authorities set and then try to
// figure out the set id with bruteforce.
let justifications = source_client.subscribe_justifications().await.map_err(|err| {
format!("Failed to subscribe to {} justifications: {:?}", SourceChain::NAME, err)
})?;
let justifications = source_client
.subscribe_justifications()
.await
.map_err(|err| Error::Subscribe(SourceChain::NAME, err))?;
// Read next justification - the header that it finalizes will be used as initial header.
let justification = justifications
.next()
.await
.map_err(|err| err.to_string())
.map_err(|e| Error::ReadJustification(SourceChain::NAME, e))
.and_then(|justification| {
justification.ok_or_else(|| "stream has ended unexpectedly".into())
})
.map_err(|err| {
format!("Failed to read {} justification from the stream: {}", SourceChain::NAME, err,)
justification.ok_or_else(|| Error::ReadJustificationStreamEnded(SourceChain::NAME))
})?;
// Read initial header.
let justification: GrandpaJustification<SourceChain::Header> =
Decode::decode(&mut &justification.0[..]).map_err(|err| {
format!("Failed to decode {} justification: {:?}", SourceChain::NAME, err)
})?;
Decode::decode(&mut &justification.0[..])
.map_err(|err| Error::DecodeJustification(SourceChain::NAME, err))?;
let (initial_header_hash, initial_header_number) =
(justification.commit.target_hash, justification.commit.target_number);
@@ -174,11 +175,7 @@ async fn prepare_initialization_data<SourceChain: Chain>(
let mut min_possible_block_number = SourceChain::BlockNumber::zero();
let authorities_for_verification = VoterSet::new(authorities_for_verification.clone())
.ok_or_else(|| {
format!(
"Read invalid {} authorities set: {:?}",
SourceChain::NAME,
authorities_for_verification,
)
Error::ReadInvalidAuthorities(SourceChain::NAME, authorities_for_verification)
})?;
loop {
log::trace!(
@@ -205,12 +202,7 @@ async fn prepare_initialization_data<SourceChain: Chain>(
// there can't be more authorities set changes than headers => if we have reached
// `initial_block_number` and still have not found correct value of
// `initial_authorities_set_id`, then something else is broken => fail
return Err(format!(
"Failed to guess initial {} GRANDPA authorities set id: checked all\
possible ids in range [0; {}]",
SourceChain::NAME,
initial_header_number
))
return Err(Error::GuessInitialAuthorities(SourceChain::NAME, initial_header_number))
}
}
@@ -230,37 +222,24 @@ async fn prepare_initialization_data<SourceChain: Chain>(
async fn source_header<SourceChain: Chain>(
source_client: &Client<SourceChain>,
header_hash: SourceChain::Hash,
) -> Result<SourceChain::Header, String> {
source_client.header_by_hash(header_hash).await.map_err(|err| {
format!(
"Failed to retrive {} header with hash {}: {:?}",
SourceChain::NAME,
header_hash,
err,
)
})
) -> Result<SourceChain::Header, Error<SourceChain::Hash, <SourceChain::Header as Header>::Number>>
{
source_client
.header_by_hash(header_hash)
.await
.map_err(|err| Error::RetrieveHeader(SourceChain::NAME, header_hash, err))
}
/// Read GRANDPA authorities set at given header.
async fn source_authorities_set<SourceChain: Chain>(
source_client: &Client<SourceChain>,
header_hash: SourceChain::Hash,
) -> Result<GrandpaAuthoritiesSet, String> {
let raw_authorities_set =
source_client.grandpa_authorities_set(header_hash).await.map_err(|err| {
format!(
"Failed to retrive {} GRANDPA authorities set at header {}: {:?}",
SourceChain::NAME,
header_hash,
err,
)
})?;
GrandpaAuthoritiesSet::decode(&mut &raw_authorities_set[..]).map_err(|err| {
format!(
"Failed to decode {} GRANDPA authorities set at header {}: {:?}",
SourceChain::NAME,
header_hash,
err,
)
})
) -> Result<GrandpaAuthoritiesSet, Error<SourceChain::Hash, <SourceChain::Header as Header>::Number>>
{
let raw_authorities_set = source_client
.grandpa_authorities_set(header_hash)
.await
.map_err(|err| Error::RetrieveAuthorities(SourceChain::NAME, header_hash, err))?;
GrandpaAuthoritiesSet::decode(&mut &raw_authorities_set[..])
.map_err(|err| Error::DecodeAuthorities(SourceChain::NAME, header_hash, err))
}
@@ -19,6 +19,7 @@
#![warn(missing_docs)]
pub mod conversion_rate_update;
pub mod error;
pub mod finality_pipeline;
pub mod finality_target;
pub mod headers_initialize;
-1
View File
@@ -6,7 +6,6 @@ edition = "2018"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
anyhow = "1.0"
async-std = { version = "1.6.5", features = ["attributes"] }
async-trait = "0.1.40"
futures = "0.3.5"
@@ -263,7 +263,7 @@ pub async fn run<P: MessageLane>(
target_client: impl TargetClient<P>,
metrics_params: MetricsParams,
exit_signal: impl Future<Output = ()> + Send + 'static,
) -> anyhow::Result<()> {
) -> Result<(), relay_utils::Error> {
let exit_signal = exit_signal.shared();
relay_utils::relay_loop(source_client, target_client)
.reconnect_delay(params.reconnect_delay)
+1
View File
@@ -20,6 +20,7 @@ num-traits = "0.2"
serde_json = "1.0"
sysinfo = "0.15"
time = "0.2"
thiserror = "1.0.26"
# 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.
pub use error::Error;
pub use relay_loop::{relay_loop, relay_metrics};
use backoff::{backoff::Backoff, ExponentialBackoff};
use futures::future::FutureExt;
use std::time::Duration;
use thiserror::Error;
/// Max delay after connection-unrelated error happened before we'll try the
/// same request again.
@@ -29,6 +31,7 @@ pub const MAX_BACKOFF_INTERVAL: Duration = Duration::from_secs(60);
/// reconnection again.
pub const CONNECTION_ERROR_DELAY: Duration = Duration::from_secs(10);
pub mod error;
pub mod initialize;
pub mod metrics;
pub mod relay_loop;
@@ -111,11 +114,13 @@ pub trait MaybeConnectionError {
}
/// Stringified error that may be either connection-related or not.
#[derive(Debug)]
#[derive(Error, Debug)]
pub enum StringifiedMaybeConnectionError {
/// The error is connection-related error.
#[error("{0}")]
Connection(String),
/// The error is connection-unrelated error.
#[error("{0}")]
NonConnection(String),
}
@@ -139,15 +144,6 @@ impl MaybeConnectionError for StringifiedMaybeConnectionError {
}
}
impl ToString for StringifiedMaybeConnectionError {
fn to_string(&self) -> String {
match *self {
StringifiedMaybeConnectionError::Connection(ref err) => err.clone(),
StringifiedMaybeConnectionError::NonConnection(ref err) => err.clone(),
}
}
}
/// Exponential backoff for connection-unrelated errors retries.
pub fn retry_backoff() -> ExponentialBackoff {
ExponentialBackoff {
@@ -14,8 +14,12 @@
// 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::metrics::{
metric_name, register, F64SharedRef, Gauge, PrometheusError, Registry, StandaloneMetrics, F64,
use crate::{
error::{self, Error},
metrics::{
metric_name, register, F64SharedRef, Gauge, PrometheusError, Registry, StandaloneMetrics,
F64,
},
};
use async_std::sync::{Arc, RwLock};
@@ -61,27 +65,18 @@ impl FloatJsonValueMetric {
self.shared_value_ref.clone()
}
/// Read value from HTTP service.
async fn read_value(&self) -> Result<f64, String> {
/// Request value from HTTP service.
async fn request_value(&self) -> anyhow::Result<String> {
use isahc::{AsyncReadResponseExt, HttpClient, Request};
fn map_isahc_err(err: impl std::fmt::Display) -> String {
format!("Failed to fetch token price from remote server: {}", err)
}
let request = Request::get(&self.url)
.header("Accept", "application/json")
.body(())
.map_err(map_isahc_err)?;
let raw_response = HttpClient::new()
.map_err(map_isahc_err)?
.send_async(request)
.await
.map_err(map_isahc_err)?
.text()
.await
.map_err(map_isahc_err)?;
let request = Request::get(&self.url).header("Accept", "application/json").body(())?;
let raw_response = HttpClient::new()?.send_async(request).await?.text().await?;
Ok(raw_response)
}
/// Read value from HTTP service.
async fn read_value(&self) -> error::Result<f64> {
let raw_response = self.request_value().await.map_err(Error::FetchTokenPrice)?;
parse_service_response(&self.json_path, &raw_response)
}
}
@@ -94,30 +89,26 @@ impl StandaloneMetrics for FloatJsonValueMetric {
async fn update(&self) {
let value = self.read_value().await;
crate::metrics::set_gauge_value(&self.metric, value.clone().map(Some));
*self.shared_value_ref.write().await = value.ok();
let maybe_ok = value.as_ref().ok().copied();
crate::metrics::set_gauge_value(&self.metric, value.map(Some));
*self.shared_value_ref.write().await = maybe_ok;
}
}
/// Parse HTTP service response.
fn parse_service_response(json_path: &str, response: &str) -> Result<f64, String> {
let json = serde_json::from_str(response).map_err(|err| {
format!("Failed to parse HTTP service response: {:?}. Response: {:?}", err, response,)
})?;
fn parse_service_response(json_path: &str, response: &str) -> error::Result<f64> {
let json =
serde_json::from_str(response).map_err(|err| Error::ParseHttp(err, response.to_owned()))?;
let mut selector = jsonpath_lib::selector(&json);
let maybe_selected_value = selector(json_path).map_err(|err| {
format!("Failed to select value from response: {:?}. Response: {:?}", err, response,)
})?;
let maybe_selected_value =
selector(json_path).map_err(|err| Error::SelectResponseValue(err, response.to_owned()))?;
let selected_value = maybe_selected_value
.first()
.and_then(|v| v.as_f64())
.ok_or_else(|| format!("Missing required value from response: {:?}", response,))?;
.ok_or_else(|| Error::MissingResponseValue(response.to_owned()))?;
if !selected_value.is_normal() || selected_value < 0.0 {
return Err(format!(
"Failed to parse float value {:?} from response. It is assumed to be positive and normal",
selected_value,
))
return Err(Error::ParseFloat(selected_value))
}
Ok(selected_value)
+10 -12
View File
@@ -15,6 +15,7 @@
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use crate::{
error::Error,
metrics::{Metrics, MetricsAddress, MetricsParams, PrometheusError, StandaloneMetrics},
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
/// do actual job. When `run_loop` returns, this outer loop reconnects to failed client (source,
/// target or both) and calls `run_loop` again.
pub async fn run<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
R: 'static + Send + Fn(SC, TC, Option<LM>) -> F,
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>(
self,
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())?;
Ok(LoopMetrics {
@@ -178,13 +179,13 @@ impl<SC, TC, LM> LoopMetrics<SC, TC, LM> {
pub fn standalone_metric<M: StandaloneMetrics>(
self,
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
// same standalone metric is exposed by several loops && only spawn single metric
match create_metric(&self.registry, self.metrics_prefix.as_deref()) {
Ok(standalone_metrics) => standalone_metrics.spawn(),
Err(PrometheusError::AlreadyReg) => (),
Err(e) => anyhow::bail!(e),
Err(e) => return Err(e.into()),
}
Ok(self)
@@ -202,16 +203,13 @@ impl<SC, TC, LM> LoopMetrics<SC, TC, LM> {
/// Expose metrics using address passed at creation.
///
/// 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 {
let socket_addr = SocketAddr::new(
address.host.parse().map_err(|err| {
anyhow::format_err!(
"Invalid host {} is used to expose Prometheus metrics: {}",
address.host,
err,
)
})?,
address
.host
.parse()
.map_err(|err| Error::ExposingMetricsInvalidHost(address.host.clone(), err))?,
address.port,
);