// Copyright 2019-2020 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 . //! Substrate -> Ethereum synchronization. use crate::ethereum_client::{self, EthereumConnectionParams, EthereumSigningParams}; use crate::ethereum_types::Address; use crate::substrate_client::{self, SubstrateConnectionParams}; use crate::substrate_types::{ GrandpaJustification, Hash, Header, Number, QueuedSubstrateHeader, SubstrateHeaderId, SubstrateHeadersSyncPipeline, }; use crate::sync::{HeadersSyncParams, TargetTransactionMode}; use crate::sync_loop::{OwnedSourceFutureOutput, OwnedTargetFutureOutput, SourceClient, TargetClient}; use crate::sync_types::SourceHeader; use async_trait::async_trait; use futures::future::FutureExt; use std::{collections::HashSet, time::Duration}; /// Interval at which we check new Substrate headers when we are synced/almost synced. const SUBSTRATE_TICK_INTERVAL: Duration = Duration::from_secs(10); /// Interval at which we check new Ethereum blocks. 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; /// Max depth of in-memory headers in all states. Past this depth they will be forgotten (pruned). const PRUNE_DEPTH: u32 = 256; /// Substrate synchronization parameters. #[derive(Debug)] pub struct SubstrateSyncParams { /// Ethereum connection params. pub eth: EthereumConnectionParams, /// Ethereum signing params. pub eth_sign: EthereumSigningParams, /// Ethereum bridge contract address. pub eth_contract_address: Address, /// Substrate connection params. pub sub: SubstrateConnectionParams, /// Synchronization parameters. pub sync_params: HeadersSyncParams, } impl Default for SubstrateSyncParams { fn default() -> Self { SubstrateSyncParams { eth: Default::default(), eth_sign: Default::default(), // the address 0x731a10897d267e19b34503ad902d0a29173ba4b1 is the address // of the contract that is deployed by default signer and 0 nonce eth_contract_address: "731a10897d267e19b34503ad902d0a29173ba4b1" .parse() .expect("address is hardcoded, thus valid; qed"), sub: Default::default(), sync_params: HeadersSyncParams { max_future_headers_to_download: MAX_FUTURE_HEADERS_TO_DOWNLOAD, max_headers_in_submitted_status: MAX_SUBMITTED_HEADERS, // since we always have single Substrate header in separate Ethereum transaction, // all max_**_in_single_submit aren't important here max_headers_in_single_submit: 4, max_headers_size_in_single_submit: std::usize::MAX, prune_depth: PRUNE_DEPTH, target_tx_mode: TargetTransactionMode::Signed, }, } } } /// Substrate client as headers source. struct SubstrateHeadersSource { /// Substrate node client. client: substrate_client::Client, } type SubstrateFutureOutput = OwnedSourceFutureOutput; #[async_trait] impl SourceClient for SubstrateHeadersSource { type Error = substrate_client::Error; async fn best_block_number(self) -> SubstrateFutureOutput { substrate_client::best_header(self.client) .map(|(client, result)| (SubstrateHeadersSource { client }, result.map(|header| header.number))) .await } async fn header_by_hash(self, hash: Hash) -> SubstrateFutureOutput
{ substrate_client::header_by_hash(self.client, hash) .map(|(client, result)| (SubstrateHeadersSource { client }, result)) .await } async fn header_by_number(self, number: Number) -> SubstrateFutureOutput
{ substrate_client::header_by_number(self.client, number) .map(|(client, result)| (SubstrateHeadersSource { client }, result)) .await } async fn header_completion( self, id: SubstrateHeaderId, ) -> SubstrateFutureOutput<(SubstrateHeaderId, Option)> { substrate_client::grandpa_justification(self.client, id) .map(|(client, result)| (SubstrateHeadersSource { client }, result)) .await } async fn header_extra( self, id: SubstrateHeaderId, _header: QueuedSubstrateHeader, ) -> SubstrateFutureOutput<(SubstrateHeaderId, ())> { (self, Ok((id, ()))) } } /// Ethereum client as Substrate headers target. struct EthereumHeadersTarget { /// Ethereum node client. client: ethereum_client::Client, /// Bridge contract address. contract: Address, /// Ethereum signing params. sign_params: EthereumSigningParams, } type EthereumFutureOutput = OwnedTargetFutureOutput; #[async_trait] impl TargetClient for EthereumHeadersTarget { type Error = ethereum_client::Error; async fn best_header_id(self) -> EthereumFutureOutput { let (contract, sign_params) = (self.contract, self.sign_params); ethereum_client::best_substrate_block(self.client, contract) .map(move |(client, result)| { ( EthereumHeadersTarget { client, contract, sign_params, }, result, ) }) .await } async fn is_known_header(self, id: SubstrateHeaderId) -> EthereumFutureOutput<(SubstrateHeaderId, bool)> { let (contract, sign_params) = (self.contract, self.sign_params); ethereum_client::substrate_header_known(self.client, contract, id) .map(move |(client, result)| { ( EthereumHeadersTarget { client, contract, sign_params, }, result, ) }) .await } async fn submit_headers(self, headers: Vec) -> EthereumFutureOutput> { let (contract, sign_params) = (self.contract, self.sign_params); ethereum_client::submit_substrate_headers(self.client, sign_params.clone(), contract, headers) .map(move |(client, result)| { ( EthereumHeadersTarget { client, contract, sign_params, }, result, ) }) .await } async fn incomplete_headers_ids(self) -> EthereumFutureOutput> { let (contract, sign_params) = (self.contract, self.sign_params); ethereum_client::incomplete_substrate_headers(self.client, contract) .map(move |(client, result)| { ( EthereumHeadersTarget { client, contract, sign_params, }, result, ) }) .await } async fn complete_header( self, id: SubstrateHeaderId, completion: GrandpaJustification, ) -> EthereumFutureOutput { let (contract, sign_params) = (self.contract, self.sign_params); ethereum_client::complete_substrate_header(self.client, sign_params.clone(), contract, id, completion) .map(move |(client, result)| { ( EthereumHeadersTarget { client, contract, sign_params, }, result, ) }) .await } async fn requires_extra(self, header: QueuedSubstrateHeader) -> EthereumFutureOutput<(SubstrateHeaderId, bool)> { (self, Ok((header.header().id(), false))) } } /// Run Substrate headers synchronization. pub fn run(params: SubstrateSyncParams) { let eth_client = ethereum_client::client(params.eth); let sub_client = substrate_client::client(params.sub); crate::sync_loop::run( SubstrateHeadersSource { client: sub_client }, SUBSTRATE_TICK_INTERVAL, EthereumHeadersTarget { client: eth_client, contract: params.eth_contract_address, sign_params: params.eth_sign, }, ETHEREUM_TICK_INTERVAL, params.sync_params, ); }