From b98b7d2e4305cf125d612d947a4d8c67054c552c Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 30 Jul 2020 18:05:50 +0300 Subject: [PATCH] Prepare for sub to eth relay (#248) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fixed PoA contract deploy (granda_authorities call) * pause if all submitted headers were rejected * give funds to Bertha and Carlos * max 1 active PoA transaction in headers sync :( * display initial header id when deploying PoA contract * cargo fmt + clipy * fix compilation * Update relays/ethereum/src/sync_types.rs Co-authored-by: Tomasz Drwięga * Update relays/ethereum/src/utils.rs Co-authored-by: Tomasz Drwięga Co-authored-by: Tomasz Drwięga --- bridges/bin/node/runtime/src/rialto.rs | 4 +- .../relays/ethereum/src/ethereum_client.rs | 3 +- .../ethereum/src/ethereum_deploy_contract.rs | 19 +++--- bridges/relays/ethereum/src/exchange.rs | 38 +---------- bridges/relays/ethereum/src/rpc_errors.rs | 2 +- .../relays/ethereum/src/substrate_client.rs | 4 +- .../ethereum/src/substrate_sync_loop.rs | 2 +- bridges/relays/ethereum/src/sync_loop.rs | 26 ++++---- .../relays/ethereum/src/sync_loop_tests.rs | 6 +- bridges/relays/ethereum/src/sync_types.rs | 22 +++++-- bridges/relays/ethereum/src/utils.rs | 63 +++++++++++++++++++ 11 files changed, 117 insertions(+), 72 deletions(-) diff --git a/bridges/bin/node/runtime/src/rialto.rs b/bridges/bin/node/runtime/src/rialto.rs index 195fed1693..1a46fae0b5 100644 --- a/bridges/bin/node/runtime/src/rialto.rs +++ b/bridges/bin/node/runtime/src/rialto.rs @@ -79,7 +79,7 @@ pub fn genesis_header() -> Header { transactions_root: hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421").into(), uncles_hash: hex!("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347").into(), extra_data: vec![], - state_root: hex!("d6368925ffd9acad81f411ce45891d3722e14355af2790391839488e23d74b0d").into(), + state_root: hex!("eccf6b74c2bcbe115c71116a23fe963c54406010c244d9650526028ad3e32cce").into(), receipts_root: hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421").into(), log_bloom: Default::default(), gas_used: Default::default(), @@ -134,7 +134,7 @@ mod tests { fn genesis_hash_matches() { assert_eq!( genesis_header().compute_hash(), - hex!("bc936e808b668546250ad43de5c0a95fe2a9644a850a2ff69b57f874e3e35644").into(), + hex!("9ff57c7fa155853586382022f0982b71c51fa313a0942f8c456300896643e890").into(), ); } diff --git a/bridges/relays/ethereum/src/ethereum_client.rs b/bridges/relays/ethereum/src/ethereum_client.rs index aba5d1ca74..df4945afca 100644 --- a/bridges/relays/ethereum/src/ethereum_client.rs +++ b/bridges/relays/ethereum/src/ethereum_client.rs @@ -21,7 +21,8 @@ use crate::ethereum_types::{ use crate::rpc::{Ethereum, EthereumRpc}; use crate::rpc_errors::{EthereumNodeError, RpcError}; use crate::substrate_types::{GrandpaJustification, Hash as SubstrateHash, QueuedSubstrateHeader, SubstrateHeaderId}; -use crate::sync_types::{HeaderId, MaybeConnectionError, SubmittedHeaders}; +use crate::sync_types::{HeaderId, SubmittedHeaders}; +use crate::utils::MaybeConnectionError; use async_trait::async_trait; use codec::{Decode, Encode}; diff --git a/bridges/relays/ethereum/src/ethereum_deploy_contract.rs b/bridges/relays/ethereum/src/ethereum_deploy_contract.rs index 0e573bda06..d46bd87944 100644 --- a/bridges/relays/ethereum/src/ethereum_deploy_contract.rs +++ b/bridges/relays/ethereum/src/ethereum_deploy_contract.rs @@ -19,7 +19,8 @@ use crate::ethereum_client::{ }; use crate::rpc::SubstrateRpc; use crate::substrate_client::{SubstrateConnectionParams, SubstrateRpcClient}; -use crate::substrate_types::{Hash as SubstrateHash, Header as SubstrateHeader}; +use crate::substrate_types::{Hash as SubstrateHash, Header as SubstrateHeader, SubstrateHeaderId}; +use crate::sync_types::HeaderId; use codec::{Decode, Encode}; use num_traits::Zero; @@ -66,18 +67,19 @@ pub fn run(params: EthereumDeployContractParams) { let eth_client = EthereumRpcClient::new(params.eth); let sub_client = SubstrateRpcClient::new(params.sub).await?; - let (initial_header_hash, initial_header) = prepare_initial_header(&sub_client, params.sub_initial_header).await?; + let (initial_header_id, initial_header) = prepare_initial_header(&sub_client, params.sub_initial_header).await?; let initial_set_id = params.sub_initial_authorities_set_id.unwrap_or(0); let initial_set = prepare_initial_authorities_set( &sub_client, - initial_header_hash, + initial_header_id.1, params.sub_initial_authorities_set, ).await?; log::info!( target: "bridge", - "Deploying Ethereum contract.\r\n\tInitial header: {:?}\r\n\tInitial header encoded: {}\r\n\tInitial authorities set ID: {}\r\n\tInitial authorities set: {}", + "Deploying Ethereum contract.\r\n\tInitial header: {:?}\r\n\tInitial header id: {:?}\r\n\tInitial header encoded: {}\r\n\tInitial authorities set ID: {}\r\n\tInitial authorities set: {}", initial_header, + initial_header_id, hex::encode(&initial_header), initial_set_id, hex::encode(&initial_set), @@ -102,16 +104,19 @@ pub fn run(params: EthereumDeployContractParams) { async fn prepare_initial_header( sub_client: &SubstrateRpcClient, sub_initial_header: Option>, -) -> Result<(SubstrateHash, Vec), String> { +) -> Result<(SubstrateHeaderId, Vec), String> { match sub_initial_header { Some(raw_initial_header) => match SubstrateHeader::decode(&mut &raw_initial_header[..]) { - Ok(initial_header) => Ok((initial_header.hash(), 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)), }, None => { let initial_header = sub_client.header_by_number(Zero::zero()).await; initial_header - .map(|header| (header.hash(), header.encode())) + .map(|header| (HeaderId(Zero::zero(), header.hash()), header.encode())) .map_err(|error| format!("Error reading Substrate genesis header: {:?}", error)) } } diff --git a/bridges/relays/ethereum/src/exchange.rs b/bridges/relays/ethereum/src/exchange.rs index 58ddcf6b66..89f30a2dae 100644 --- a/bridges/relays/ethereum/src/exchange.rs +++ b/bridges/relays/ethereum/src/exchange.rs @@ -16,7 +16,7 @@ //! Relaying proofs of exchange transaction. -use crate::sync_types::MaybeConnectionError; +use crate::utils::{MaybeConnectionError, StringifiedMaybeConnectionError}; use async_trait::async_trait; use std::{ @@ -135,14 +135,6 @@ pub struct RelayedBlockTransactions { pub failed: usize, } -/// Stringified error that may be either connection-related or not. -enum StringifiedMaybeConnectionError { - /// The error is connection-related error. - Connection(String), - /// The error is connection-unrelated error. - NonConnection(String), -} - /// Relay all suitable transactions from single block. /// /// If connection error occurs, returns Err with number of successfully processed transactions. @@ -448,34 +440,6 @@ async fn wait_header_finalized( } } -impl StringifiedMaybeConnectionError { - fn new(is_connection_error: bool, error: String) -> Self { - if is_connection_error { - StringifiedMaybeConnectionError::Connection(error) - } else { - StringifiedMaybeConnectionError::NonConnection(error) - } - } -} - -impl MaybeConnectionError for StringifiedMaybeConnectionError { - fn is_connection_error(&self) -> bool { - match *self { - StringifiedMaybeConnectionError::Connection(_) => true, - StringifiedMaybeConnectionError::NonConnection(_) => false, - } - } -} - -impl ToString for StringifiedMaybeConnectionError { - fn to_string(&self) -> String { - match *self { - StringifiedMaybeConnectionError::Connection(ref err) => err.clone(), - StringifiedMaybeConnectionError::NonConnection(ref err) => err.clone(), - } - } -} - #[cfg(test)] pub(crate) mod tests { use super::*; diff --git a/bridges/relays/ethereum/src/rpc_errors.rs b/bridges/relays/ethereum/src/rpc_errors.rs index 9739237520..dfc84192db 100644 --- a/bridges/relays/ethereum/src/rpc_errors.rs +++ b/bridges/relays/ethereum/src/rpc_errors.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . -use crate::sync_types::MaybeConnectionError; +use crate::utils::MaybeConnectionError; use jsonrpsee::client::RequestError; diff --git a/bridges/relays/ethereum/src/substrate_client.rs b/bridges/relays/ethereum/src/substrate_client.rs index c6e19cf110..bfb6c3c279 100644 --- a/bridges/relays/ethereum/src/substrate_client.rs +++ b/bridges/relays/ethereum/src/substrate_client.rs @@ -187,9 +187,9 @@ impl SubstrateRpc for SubstrateRpcClient { async fn grandpa_authorities_set(&self, block: Hash) -> Result { let call = SUB_API_GRANDPA_AUTHORITIES.to_string(); - let data = Bytes(block.as_bytes().to_vec()); + let data = Bytes(Vec::new()); - let encoded_response = Substrate::state_call(&self.client, call, data, None).await?; + let encoded_response = Substrate::state_call(&self.client, call, data, Some(block)).await?; let authority_list = encoded_response.0; Ok(authority_list) diff --git a/bridges/relays/ethereum/src/substrate_sync_loop.rs b/bridges/relays/ethereum/src/substrate_sync_loop.rs index 1d81e2e6bf..16afb163d4 100644 --- a/bridges/relays/ethereum/src/substrate_sync_loop.rs +++ b/bridges/relays/ethereum/src/substrate_sync_loop.rs @@ -42,7 +42,7 @@ const ETHEREUM_TICK_INTERVAL: Duration = Duration::from_secs(5); /// Max Ethereum headers we want to have in all 'before-submitted' states. const MAX_FUTURE_HEADERS_TO_DOWNLOAD: usize = 8; /// Max Ethereum headers count we want to have in 'submitted' state. -const MAX_SUBMITTED_HEADERS: usize = 4; +const MAX_SUBMITTED_HEADERS: usize = 1; /// Max depth of in-memory headers in all states. Past this depth they will be forgotten (pruned). const PRUNE_DEPTH: u32 = 256; diff --git a/bridges/relays/ethereum/src/sync_loop.rs b/bridges/relays/ethereum/src/sync_loop.rs index 9e5535f5c4..a62c712f22 100644 --- a/bridges/relays/ethereum/src/sync_loop.rs +++ b/bridges/relays/ethereum/src/sync_loop.rs @@ -17,10 +17,8 @@ use crate::metrics::{start as metrics_start, GlobalMetrics, MetricsParams}; use crate::sync::HeadersSyncParams; use crate::sync_loop_metrics::SyncLoopMetrics; -use crate::sync_types::{ - HeaderIdOf, HeaderStatus, HeadersSyncPipeline, MaybeConnectionError, QueuedHeader, SubmittedHeaders, -}; -use crate::utils::retry_backoff; +use crate::sync_types::{HeaderIdOf, HeaderStatus, HeadersSyncPipeline, QueuedHeader, SubmittedHeaders}; +use crate::utils::{format_ids, retry_backoff, MaybeConnectionError, StringifiedMaybeConnectionError}; use async_trait::async_trait; use backoff::{backoff::Backoff, ExponentialBackoff}; @@ -309,7 +307,16 @@ pub fn run>( submitted_headers = target_submit_header_future => { // following line helps Rust understand the type of `submitted_headers` :/ let submitted_headers: SubmittedHeaders, TC::Error> = submitted_headers; - let maybe_fatal_error = submitted_headers.fatal_error.map(Err).unwrap_or(Ok(())); + let submitted_headers_str = format!("{}", submitted_headers); + let maybe_fatal_error = match submitted_headers.fatal_error { + Some(fatal_error) => Err(StringifiedMaybeConnectionError::new( + fatal_error.is_connection_error(), + format!("{:?}", fatal_error), + )), + None if submitted_headers.submitted.is_empty() && submitted_headers.incomplete.is_empty() => + Err(StringifiedMaybeConnectionError::new(false, "All headers were rejected".into())), + None => Ok(()), + }; target_client_is_online = process_future_result( maybe_fatal_error, @@ -320,6 +327,8 @@ pub fn run>( || format!("Error submitting headers to {} node", P::TARGET_NAME), ); + log::debug!(target: "bridge", "Header submit result: {}", submitted_headers_str); + sync.headers_mut().headers_submitted(submitted_headers.submitted); sync.headers_mut().add_incomplete_headers(submitted_headers.incomplete); }, @@ -434,17 +443,12 @@ pub fn run>( } else if let Some(headers) = sync.select_headers_to_submit(last_update_time.elapsed() > BACKUP_STALL_SYNC_TIMEOUT) { - let ids = match headers.len() { - 1 => format!("{:?}", headers[0].id()), - 2 => format!("[{:?}, {:?}]", headers[0].id(), headers[1].id()), - len => format!("[{:?} ... {:?}]", headers[0].id(), headers[len - 1].id()), - }; log::debug!( target: "bridge", "Submitting {} header(s) to {} node: {:?}", headers.len(), P::TARGET_NAME, - ids, + format_ids(headers.iter().map(|header| header.id())), ); let headers = headers.into_iter().cloned().collect(); diff --git a/bridges/relays/ethereum/src/sync_loop_tests.rs b/bridges/relays/ethereum/src/sync_loop_tests.rs index 7b65f3d54a..13206f4ad2 100644 --- a/bridges/relays/ethereum/src/sync_loop_tests.rs +++ b/bridges/relays/ethereum/src/sync_loop_tests.rs @@ -17,10 +17,8 @@ #![cfg(test)] use crate::sync_loop::{process_future_result, run, SourceClient, TargetClient}; -use crate::sync_types::{ - HeaderId, HeadersSyncPipeline, MaybeConnectionError, QueuedHeader, SourceHeader, SubmittedHeaders, -}; -use crate::utils::retry_backoff; +use crate::sync_types::{HeaderId, HeadersSyncPipeline, QueuedHeader, SourceHeader, SubmittedHeaders}; +use crate::utils::{retry_backoff, MaybeConnectionError}; use async_trait::async_trait; use backoff::backoff::Backoff; diff --git a/bridges/relays/ethereum/src/sync_types.rs b/bridges/relays/ethereum/src/sync_types.rs index 1e2aca16f7..4f9dbb3e6b 100644 --- a/bridges/relays/ethereum/src/sync_types.rs +++ b/bridges/relays/ethereum/src/sync_types.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . +use crate::utils::format_ids; + use std::{ops::Deref, sync::Arc}; /// Ethereum header Id. @@ -43,12 +45,6 @@ pub enum HeaderStatus { Synced, } -/// Error type that can signal connection errors. -pub trait MaybeConnectionError { - /// Returns true if error (maybe) represents connection error. - fn is_connection_error(&self) -> bool; -} - /// Headers synchronization pipeline. pub trait HeadersSyncPipeline: Clone + Copy { /// Name of the headers source. @@ -192,3 +188,17 @@ impl Default for SubmittedHeaders { } } } + +impl std::fmt::Display for SubmittedHeaders { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let submitted = format_ids(self.submitted.iter()); + let incomplete = format_ids(self.incomplete.iter()); + let rejected = format_ids(self.rejected.iter()); + + write!( + f, + "Submitted: {}, Incomplete: {}, Rejected: {}", + submitted, incomplete, rejected + ) + } +} diff --git a/bridges/relays/ethereum/src/utils.rs b/bridges/relays/ethereum/src/utils.rs index e769e10782..dfe3b3966f 100644 --- a/bridges/relays/ethereum/src/utils.rs +++ b/bridges/relays/ethereum/src/utils.rs @@ -43,6 +43,50 @@ macro_rules! bail_on_arg_error { }; } +/// Error type that can signal connection errors. +pub trait MaybeConnectionError { + /// Returns true if error (maybe) represents connection error. + fn is_connection_error(&self) -> bool; +} + +/// Stringified error that may be either connection-related or not. +#[derive(Debug)] +pub enum StringifiedMaybeConnectionError { + /// The error is connection-related error. + Connection(String), + /// The error is connection-unrelated error. + NonConnection(String), +} + +impl StringifiedMaybeConnectionError { + /// Create new stringified connection error. + pub fn new(is_connection_error: bool, error: String) -> Self { + if is_connection_error { + StringifiedMaybeConnectionError::Connection(error) + } else { + StringifiedMaybeConnectionError::NonConnection(error) + } + } +} + +impl MaybeConnectionError for StringifiedMaybeConnectionError { + fn is_connection_error(&self) -> bool { + match *self { + StringifiedMaybeConnectionError::Connection(_) => true, + StringifiedMaybeConnectionError::NonConnection(_) => false, + } + } +} + +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 { let mut backoff = ExponentialBackoff::default(); @@ -51,3 +95,22 @@ pub fn retry_backoff() -> ExponentialBackoff { backoff.max_interval = MAX_BACKOFF_INTERVAL; backoff } + +/// Compact format of IDs vector. +pub fn format_ids(mut ids: impl ExactSizeIterator) -> String { + const NTH_PROOF: &str = "we have checked len; qed"; + match ids.len() { + 0 => "".into(), + 1 => format!("{:?}", ids.next().expect(NTH_PROOF)), + 2 => { + let id0 = ids.next().expect(NTH_PROOF); + let id1 = ids.next().expect(NTH_PROOF); + format!("[{:?}, {:?}]", id0, id1) + } + len => { + let id0 = ids.next().expect(NTH_PROOF); + let id_last = ids.last().expect(NTH_PROOF); + format!("{}:[{:?} ... {:?}]", len, id0, id_last) + } + } +}