Adding Bridges code as git subtree. (#2515)

* Add instructions.

* Squashed 'bridges/' content from commit 345e84a21

git-subtree-dir: bridges
git-subtree-split: 345e84a2146b56628e9888c9f5e129cb40e868a9

* Remove bridges workspace file to avoid confusing Cargo.

* Add some bridges primitives to Polkadot workspace.

* Improve docs.
This commit is contained in:
Tomasz Drwięga
2021-03-01 22:33:16 +01:00
committed by GitHub
parent 7a2c7aa3fe
commit 5169155f94
291 changed files with 64249 additions and 0 deletions
@@ -0,0 +1,17 @@
[package]
name = "relay-ethereum-client"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
bp-eth-poa = { path = "../../primitives/ethereum-poa" }
codec = { package = "parity-scale-codec", version = "2.0.0" }
headers-relay = { path = "../headers-relay" }
hex-literal = "0.3"
jsonrpsee = { git = "https://github.com/svyatonik/jsonrpsee.git", branch = "shared-client-in-rpc-api", default-features = false, features = ["http"] }
libsecp256k1 = { version = "0.3.4", default-features = false, features = ["hmac"] }
log = "0.4.11"
relay-utils = { path = "../utils" }
web3 = { version = "0.15", git = "https://github.com/tomusdrw/rust-web3.git", branch ="td-ethabi", default-features = false }
@@ -0,0 +1,172 @@
// 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 <http://www.gnu.org/licenses/>.
use crate::rpc::Ethereum;
use crate::types::{
Address, Bytes, CallRequest, Header, HeaderWithTransactions, Receipt, SignedRawTx, SyncState, Transaction,
TransactionHash, H256, U256,
};
use crate::{ConnectionParams, Error, Result};
use jsonrpsee::raw::RawClient;
use jsonrpsee::transport::http::HttpTransportClient;
use jsonrpsee::Client as RpcClient;
/// Number of headers missing from the Ethereum node for us to consider node not synced.
const MAJOR_SYNC_BLOCKS: u64 = 5;
/// The client used to interact with an Ethereum node through RPC.
#[derive(Clone)]
pub struct Client {
params: ConnectionParams,
client: RpcClient,
}
impl Client {
/// Create a new Ethereum RPC Client.
pub fn new(params: ConnectionParams) -> Self {
Self {
client: Self::build_client(&params),
params,
}
}
/// Build client to use in connection.
fn build_client(params: &ConnectionParams) -> RpcClient {
let uri = format!("http://{}:{}", params.host, params.port);
let transport = HttpTransportClient::new(&uri);
let raw_client = RawClient::new(transport);
raw_client.into()
}
/// Reopen client connection.
pub fn reconnect(&mut self) {
self.client = Self::build_client(&self.params);
}
}
impl Client {
/// Returns true if client is connected to at least one peer and is in synced state.
pub async fn ensure_synced(&self) -> Result<()> {
match Ethereum::syncing(&self.client).await? {
SyncState::NotSyncing => Ok(()),
SyncState::Syncing(syncing) => {
let missing_headers = syncing.highest_block.saturating_sub(syncing.current_block);
if missing_headers > MAJOR_SYNC_BLOCKS.into() {
return Err(Error::ClientNotSynced(missing_headers));
}
Ok(())
}
}
}
/// Estimate gas usage for the given call.
pub async fn estimate_gas(&self, call_request: CallRequest) -> Result<U256> {
Ok(Ethereum::estimate_gas(&self.client, call_request).await?)
}
/// Retrieve number of the best known block from the Ethereum node.
pub async fn best_block_number(&self) -> Result<u64> {
Ok(Ethereum::block_number(&self.client).await?.as_u64())
}
/// Retrieve number of the best known block from the Ethereum node.
pub async fn header_by_number(&self, block_number: u64) -> Result<Header> {
let get_full_tx_objects = false;
let header = Ethereum::get_block_by_number(&self.client, block_number, get_full_tx_objects).await?;
match header.number.is_some() && header.hash.is_some() && header.logs_bloom.is_some() {
true => Ok(header),
false => Err(Error::IncompleteHeader),
}
}
/// Retrieve block header by its hash from Ethereum node.
pub async fn header_by_hash(&self, hash: H256) -> Result<Header> {
let get_full_tx_objects = false;
let header = Ethereum::get_block_by_hash(&self.client, hash, get_full_tx_objects).await?;
match header.number.is_some() && header.hash.is_some() && header.logs_bloom.is_some() {
true => Ok(header),
false => Err(Error::IncompleteHeader),
}
}
/// Retrieve block header and its transactions by its number from Ethereum node.
pub async fn header_by_number_with_transactions(&self, number: u64) -> Result<HeaderWithTransactions> {
let get_full_tx_objects = true;
let header = Ethereum::get_block_by_number_with_transactions(&self.client, number, get_full_tx_objects).await?;
let is_complete_header = header.number.is_some() && header.hash.is_some() && header.logs_bloom.is_some();
if !is_complete_header {
return Err(Error::IncompleteHeader);
}
let is_complete_transactions = header.transactions.iter().all(|tx| tx.raw.is_some());
if !is_complete_transactions {
return Err(Error::IncompleteTransaction);
}
Ok(header)
}
/// Retrieve block header and its transactions by its hash from Ethereum node.
pub async fn header_by_hash_with_transactions(&self, hash: H256) -> Result<HeaderWithTransactions> {
let get_full_tx_objects = true;
let header = Ethereum::get_block_by_hash_with_transactions(&self.client, hash, get_full_tx_objects).await?;
let is_complete_header = header.number.is_some() && header.hash.is_some() && header.logs_bloom.is_some();
if !is_complete_header {
return Err(Error::IncompleteHeader);
}
let is_complete_transactions = header.transactions.iter().all(|tx| tx.raw.is_some());
if !is_complete_transactions {
return Err(Error::IncompleteTransaction);
}
Ok(header)
}
/// Retrieve transaction by its hash from Ethereum node.
pub async fn transaction_by_hash(&self, hash: H256) -> Result<Option<Transaction>> {
Ok(Ethereum::transaction_by_hash(&self.client, hash).await?)
}
/// Retrieve transaction receipt by transaction hash.
pub async fn transaction_receipt(&self, transaction_hash: H256) -> Result<Receipt> {
Ok(Ethereum::get_transaction_receipt(&self.client, transaction_hash).await?)
}
/// Get the nonce of the given account.
pub async fn account_nonce(&self, address: Address) -> Result<U256> {
Ok(Ethereum::get_transaction_count(&self.client, address).await?)
}
/// Submit an Ethereum transaction.
///
/// The transaction must already be signed before sending it through this method.
pub async fn submit_transaction(&self, signed_raw_tx: SignedRawTx) -> Result<TransactionHash> {
let transaction = Bytes(signed_raw_tx);
let tx_hash = Ethereum::submit_transaction(&self.client, transaction).await?;
log::trace!(target: "bridge", "Sent transaction to Ethereum node: {:?}", tx_hash);
Ok(tx_hash)
}
/// Call Ethereum smart contract.
pub async fn eth_call(&self, call_transaction: CallRequest) -> Result<Bytes> {
Ok(Ethereum::call(&self.client, call_transaction).await?)
}
}
@@ -0,0 +1,82 @@
// 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 <http://www.gnu.org/licenses/>.
//! Ethereum node RPC errors.
use crate::types::U256;
use jsonrpsee::client::RequestError;
use relay_utils::MaybeConnectionError;
/// 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)]
pub enum Error {
/// An error that can occur when making an HTTP request to
/// an JSON-RPC client.
Request(RequestError),
/// Failed to parse response.
ResponseParseFailed(String),
/// We have received a header with missing fields.
IncompleteHeader,
/// We have received a transaction missing a `raw` field.
IncompleteTransaction,
/// An invalid Substrate block number was received from
/// an Ethereum node.
InvalidSubstrateBlockNumber,
/// An invalid index has been received from an 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.
ClientNotSynced(U256),
}
impl From<RequestError> for Error {
fn from(error: RequestError) -> Self {
Error::Request(error)
}
}
impl MaybeConnectionError for Error {
fn is_connection_error(&self) -> bool {
matches!(
*self,
Error::Request(RequestError::TransportError(_)) | Error::ClientNotSynced(_),
)
}
}
impl ToString for Error {
fn to_string(&self) -> String {
match self {
Self::Request(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)
}
}
}
}
@@ -0,0 +1,48 @@
// 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 <http://www.gnu.org/licenses/>.
//! Tools to interact with (Open) Ethereum node using RPC methods.
#![warn(missing_docs)]
mod client;
mod error;
mod rpc;
mod sign;
pub use crate::client::Client;
pub use crate::error::{Error, Result};
pub use crate::sign::{sign_and_submit_transaction, SigningParams};
pub mod types;
/// Ethereum connection params.
#[derive(Debug, Clone)]
pub struct ConnectionParams {
/// Ethereum RPC host.
pub host: String,
/// Ethereum RPC port.
pub port: u16,
}
impl Default for ConnectionParams {
fn default() -> Self {
ConnectionParams {
host: "localhost".into(),
port: 8545,
}
}
}
@@ -0,0 +1,56 @@
// 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 <http://www.gnu.org/licenses/>.
//! Ethereum node RPC interface.
// The compiler doesn't think we're using the
// code from rpc_api!
#![allow(dead_code)]
#![allow(unused_variables)]
use crate::types::{
Address, Bytes, CallRequest, Header, HeaderWithTransactions, Receipt, SyncState, Transaction, TransactionHash,
H256, U256, U64,
};
jsonrpsee::rpc_api! {
pub(crate) Ethereum {
#[rpc(method = "eth_syncing", positional_params)]
fn syncing() -> SyncState;
#[rpc(method = "eth_estimateGas", positional_params)]
fn estimate_gas(call_request: CallRequest) -> U256;
#[rpc(method = "eth_blockNumber", positional_params)]
fn block_number() -> U64;
#[rpc(method = "eth_getBlockByNumber", positional_params)]
fn get_block_by_number(block_number: U64, full_tx_objs: bool) -> Header;
#[rpc(method = "eth_getBlockByHash", positional_params)]
fn get_block_by_hash(hash: H256, full_tx_objs: bool) -> Header;
#[rpc(method = "eth_getBlockByNumber", positional_params)]
fn get_block_by_number_with_transactions(number: U64, full_tx_objs: bool) -> HeaderWithTransactions;
#[rpc(method = "eth_getBlockByHash", positional_params)]
fn get_block_by_hash_with_transactions(hash: H256, full_tx_objs: bool) -> HeaderWithTransactions;
#[rpc(method = "eth_getTransactionByHash", positional_params)]
fn transaction_by_hash(hash: H256) -> Option<Transaction>;
#[rpc(method = "eth_getTransactionReceipt", positional_params)]
fn get_transaction_receipt(transaction_hash: H256) -> Receipt;
#[rpc(method = "eth_getTransactionCount", positional_params)]
fn get_transaction_count(address: Address) -> U256;
#[rpc(method = "eth_submitTransaction", positional_params)]
fn submit_transaction(transaction: Bytes) -> TransactionHash;
#[rpc(method = "eth_call", positional_params)]
fn call(transaction_call: CallRequest) -> Bytes;
}
}
@@ -0,0 +1,85 @@
// 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 <http://www.gnu.org/licenses/>.
use crate::types::{Address, CallRequest, U256};
use crate::{Client, Result};
use bp_eth_poa::signatures::{secret_to_address, SignTransaction};
use hex_literal::hex;
use secp256k1::SecretKey;
/// Ethereum signing params.
#[derive(Clone, Debug)]
pub struct SigningParams {
/// Ethereum chain id.
pub chain_id: u64,
/// Ethereum transactions signer.
pub signer: SecretKey,
/// Gas price we agree to pay.
pub gas_price: U256,
}
impl Default for SigningParams {
fn default() -> Self {
SigningParams {
chain_id: 0x11, // Parity dev chain
// account that has a lot of ether when we run instant seal engine
// address: 0x00a329c0648769a73afac7f9381e08fb43dbea72
// secret: 0x4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7
signer: SecretKey::parse(&hex!(
"4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7"
))
.expect("secret is hardcoded, thus valid; qed"),
gas_price: 8_000_000_000u64.into(), // 8 Gwei
}
}
}
/// Sign and submit tranaction using given Ethereum client.
pub async fn sign_and_submit_transaction(
client: &Client,
params: &SigningParams,
contract_address: Option<Address>,
nonce: Option<U256>,
double_gas: bool,
encoded_call: Vec<u8>,
) -> Result<()> {
let nonce = if let Some(n) = nonce {
n
} else {
let address: Address = secret_to_address(&params.signer);
client.account_nonce(address).await?
};
let call_request = CallRequest {
to: contract_address,
data: Some(encoded_call.clone().into()),
..Default::default()
};
let gas = client.estimate_gas(call_request).await?;
let raw_transaction = bp_eth_poa::UnsignedTransaction {
nonce,
to: contract_address,
value: U256::zero(),
gas: if double_gas { gas.saturating_mul(2.into()) } else { gas },
gas_price: params.gas_price,
payload: encoded_call,
}
.sign_by(&params.signer, Some(params.chain_id));
let _ = client.submit_transaction(raw_transaction).await?;
Ok(())
}
@@ -0,0 +1,80 @@
// 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 <http://www.gnu.org/licenses/>.
//! Common types that are used in relay <-> Ethereum node communications.
use headers_relay::sync_types::SourceHeader;
pub use web3::types::{Address, Bytes, CallRequest, SyncState, H256, U128, U256, U64};
/// When header is just received from the Ethereum node, we check that it has
/// both number and hash fields filled.
pub const HEADER_ID_PROOF: &str = "checked on retrieval; qed";
/// Ethereum transaction hash type.
pub type HeaderHash = H256;
/// Ethereum transaction hash type.
pub type TransactionHash = H256;
/// Ethereum transaction type.
pub type Transaction = web3::types::Transaction;
/// Ethereum header type.
pub type Header = web3::types::Block<H256>;
/// Ethereum header type used in headers sync.
#[derive(Clone, Debug, PartialEq)]
pub struct SyncHeader(Header);
impl std::ops::Deref for SyncHeader {
type Target = Header;
fn deref(&self) -> &Self::Target {
&self.0
}
}
/// Ethereum header with transactions type.
pub type HeaderWithTransactions = web3::types::Block<Transaction>;
/// Ethereum transaction receipt type.
pub type Receipt = web3::types::TransactionReceipt;
/// Ethereum header ID.
pub type HeaderId = relay_utils::HeaderId<H256, u64>;
/// A raw Ethereum transaction that's been signed.
pub type SignedRawTx = Vec<u8>;
impl From<Header> for SyncHeader {
fn from(header: Header) -> Self {
Self(header)
}
}
impl SourceHeader<H256, u64> for SyncHeader {
fn id(&self) -> HeaderId {
relay_utils::HeaderId(
self.number.expect(HEADER_ID_PROOF).as_u64(),
self.hash.expect(HEADER_ID_PROOF),
)
}
fn parent_id(&self) -> HeaderId {
relay_utils::HeaderId(self.number.expect(HEADER_ID_PROOF).as_u64() - 1, self.parent_hash)
}
}
@@ -0,0 +1,48 @@
[package]
name = "ethereum-poa-relay"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
ansi_term = "0.12"
async-std = "1.9.0"
async-trait = "0.1.42"
clap = { version = "2.33.3", features = ["yaml"] }
codec = { package = "parity-scale-codec", version = "2.0.0" }
env_logger = "0.8.3"
ethabi = { git = "https://github.com/paritytech/ethabi.git", branch = "td-eth-types-11" }
ethabi-contract = { git = "https://github.com/paritytech/ethabi.git", branch = "td-eth-types-11" }
ethabi-derive = { git = "https://github.com/paritytech/ethabi.git", branch = "td-eth-types-11" }
futures = "0.3.12"
hex = "0.4"
hex-literal = "0.3"
libsecp256k1 = { version = "0.3.4", default-features = false, features = ["hmac"] }
log = "0.4.14"
num-traits = "0.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.62"
time = "0.2"
# Bridge dependencies
bp-currency-exchange = { path = "../../primitives/currency-exchange" }
bp-eth-poa = { path = "../../primitives/ethereum-poa" }
exchange-relay = { path = "../exchange-relay" }
headers-relay = { path = "../headers-relay" }
messages-relay = { path = "../messages-relay" }
relay-ethereum-client = { path = "../ethereum-client" }
relay-rialto-client = { path = "../rialto-client" }
relay-substrate-client = { path = "../substrate-client" }
relay-utils = { path = "../utils" }
rialto-runtime = { path = "../../bin/rialto/runtime" }
# Substrate Dependencies
frame-system = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
pallet-transaction-payment = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
sp-core = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
sp-keyring = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
sp-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
substrate-prometheus-endpoint = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
@@ -0,0 +1,7 @@
# PoA <> Substrate Bridge
**DISCLAIMER:** *we recommend not using the bridge in "production" (to bridge significant amounts) just yet.
it's missing a code audit and should still be considered alpha. we can't rule out that there are bugs that might result in loss of the bridged amounts.
we'll update this disclaimer once that changes*
These docs are very incomplete yet. Describe high-level goals here in the (near) future.
@@ -0,0 +1,167 @@
[
{
"inputs": [
{
"internalType": "bytes",
"name": "rawInitialHeader",
"type": "bytes"
},
{
"internalType": "uint64",
"name": "initialValidatorsSetId",
"type": "uint64"
},
{
"internalType": "bytes",
"name": "initialValidatorsSet",
"type": "bytes"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"stateMutability": "nonpayable",
"type": "fallback"
},
{
"inputs": [],
"name": "bestKnownHeader",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
},
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "finalityTargetNumber",
"type": "uint256"
},
{
"internalType": "bytes32",
"name": "finalityTargetHash",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "rawFinalityProof",
"type": "bytes"
}
],
"name": "importFinalityProof",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "rawHeader1",
"type": "bytes"
},
{
"internalType": "bytes",
"name": "rawHeader2",
"type": "bytes"
},
{
"internalType": "bytes",
"name": "rawHeader3",
"type": "bytes"
},
{
"internalType": "bytes",
"name": "rawHeader4",
"type": "bytes"
}
],
"name": "importHeaders",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "incompleteHeaders",
"outputs": [
{
"internalType": "uint256[]",
"name": "",
"type": "uint256[]"
},
{
"internalType": "bytes32[]",
"name": "",
"type": "bytes32[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "rawHeader1",
"type": "bytes"
},
{
"internalType": "bytes",
"name": "rawHeader2",
"type": "bytes"
},
{
"internalType": "bytes",
"name": "rawHeader3",
"type": "bytes"
},
{
"internalType": "bytes",
"name": "rawHeader4",
"type": "bytes"
}
],
"name": "isIncompleteHeaders",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "headerHash",
"type": "bytes32"
}
],
"name": "isKnownHeader",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
}
]
File diff suppressed because one or more lines are too long
@@ -0,0 +1,5 @@
Last Change Date: 2020-07-30
Solc version: 0.6.6+commit.6c089d02.Linux.g++
Source hash (keccak256): 0xea5d6d744f69157adc2857166792aca139c0b5b186ba89c1011358fbcad90d7e
Source gist: https://github.com/svyatonik/substrate-bridge-sol/blob/6456d3e016c95cd5e6d5e817c23e9e69e739aa78/substrate-bridge.sol
Compiler flags used (command to produce the file): `docker run -i ethereum/solc:0.6.6 --optimize --bin - < substrate-bridge.sol`
@@ -0,0 +1,166 @@
name: ethsub-bridge
version: "0.1.0"
author: Parity Technologies <admin@parity.io>
about: Parity Ethereum (PoA) <-> Substrate bridge
subcommands:
- eth-to-sub:
about: Synchronize headers from Ethereum node to Substrate node.
args:
- eth-host: &eth-host
long: eth-host
value_name: ETH_HOST
help: Connect to Ethereum node at given host.
takes_value: true
- eth-port: &eth-port
long: eth-port
value_name: ETH_PORT
help: Connect to Ethereum node at given port.
takes_value: true
- sub-host: &sub-host
long: sub-host
value_name: SUB_HOST
help: Connect to Substrate node at given host.
takes_value: true
- sub-port: &sub-port
long: sub-port
value_name: SUB_PORT
help: Connect to Substrate node websocket server at given port.
takes_value: true
- sub-tx-mode:
long: sub-tx-mode
value_name: MODE
help: Submit headers using signed (default) or unsigned transactions. Third mode - backup - submits signed transactions only when we believe that sync has stalled.
takes_value: true
possible_values:
- signed
- unsigned
- backup
- sub-signer: &sub-signer
long: sub-signer
value_name: SUB_SIGNER
help: The SURI of secret key to use when transactions are submitted to the Substrate node.
- sub-signer-password: &sub-signer-password
long: sub-signer-password
value_name: SUB_SIGNER_PASSWORD
help: The password for the SURI of secret key to use when transactions are submitted to the Substrate node.
- sub-pallet-instance: &sub-pallet-instance
long: instance
short: i
value_name: PALLET_INSTANCE
help: The instance of the bridge pallet the relay should follow.
takes_value: true
case_insensitive: true
possible_values:
- Rialto
- Kovan
default_value: Rialto
- no-prometheus: &no-prometheus
long: no-prometheus
help: Do not expose a Prometheus metric endpoint.
- prometheus-host: &prometheus-host
long: prometheus-host
value_name: PROMETHEUS_HOST
help: Expose Prometheus endpoint at given interface.
- prometheus-port: &prometheus-port
long: prometheus-port
value_name: PROMETHEUS_PORT
help: Expose Prometheus endpoint at given port.
- sub-to-eth:
about: Synchronize headers from Substrate node to Ethereum node.
args:
- eth-host: *eth-host
- eth-port: *eth-port
- eth-contract:
long: eth-contract
value_name: ETH_CONTRACT
help: Address of deployed bridge contract.
takes_value: true
- eth-chain-id: &eth-chain-id
long: eth-chain-id
value_name: ETH_CHAIN_ID
help: Chain ID to use for signing.
- eth-signer: &eth-signer
long: eth-signer
value_name: ETH_SIGNER
help: Hex-encoded secret to use when transactions are submitted to the Ethereum node.
- sub-host: *sub-host
- sub-port: *sub-port
- no-prometheus: *no-prometheus
- prometheus-host: *prometheus-host
- prometheus-port: *prometheus-port
- eth-deploy-contract:
about: Deploy Bridge contract on Ethereum node.
args:
- eth-host: *eth-host
- eth-port: *eth-port
- eth-signer: *eth-signer
- eth-chain-id: *eth-chain-id
- eth-contract-code:
long: eth-contract-code
value_name: ETH_CONTRACT_CODE
help: Bytecode of bridge contract.
takes_value: true
- sub-host: *sub-host
- sub-port: *sub-port
- sub-authorities-set-id:
long: sub-authorities-set-id
value_name: SUB_AUTHORITIES_SET_ID
help: ID of initial GRANDPA authorities set.
takes_value: true
- sub-authorities-set:
long: sub-authorities-set
value_name: SUB_AUTHORITIES_SET
help: Encoded initial GRANDPA authorities set.
takes_value: true
- sub-initial-header:
long: sub-initial-header
value_name: SUB_INITIAL_HEADER
help: Encoded initial Substrate header.
takes_value: true
- eth-submit-exchange-tx:
about: Submit lock funds transaction to Ethereum node.
args:
- eth-host: *eth-host
- eth-port: *eth-port
- eth-nonce:
long: eth-nonce
value_name: ETH_NONCE
help: Nonce that have to be used when building transaction. If not specified, read from PoA node.
takes_value: true
- eth-signer: *eth-signer
- eth-chain-id: *eth-chain-id
- eth-amount:
long: eth-amount
value_name: ETH_AMOUNT
help: Amount of ETH to lock (in wei).
takes_value: true
- sub-recipient:
long: sub-recipient
value_name: SUB_RECIPIENT
help: Hex-encoded Public key of funds recipient in Substrate chain.
takes_value: true
- eth-exchange-sub:
about: Submit proof of PoA lock funds transaction to Substrate node.
args:
- eth-host: *eth-host
- eth-port: *eth-port
- eth-start-with-block:
long: eth-start-with-block
value_name: ETH_START_WITH_BLOCK
help: Auto-relay transactions starting with given block number. If not specified, starts with best finalized Ethereum block (known to Substrate node) transactions.
takes_value: true
conflicts_with:
- eth-tx-hash
- eth-tx-hash:
long: eth-tx-hash
value_name: ETH_TX_HASH
help: Hash of the lock funds transaction.
takes_value: true
- sub-host: *sub-host
- sub-port: *sub-port
- sub-signer: *sub-signer
- sub-signer-password: *sub-signer-password
- sub-pallet-instance: *sub-pallet-instance
- no-prometheus: *no-prometheus
- prometheus-host: *prometheus-host
- prometheus-port: *prometheus-port
@@ -0,0 +1,653 @@
// 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 <http://www.gnu.org/licenses/>.
use crate::rpc_errors::RpcError;
use crate::substrate_sync_loop::QueuedRialtoHeader;
use async_trait::async_trait;
use bp_eth_poa::signatures::secret_to_address;
use codec::{Decode, Encode};
use ethabi::FunctionOutputDecoder;
use headers_relay::sync_types::SubmittedHeaders;
use relay_ethereum_client::{
sign_and_submit_transaction,
types::{Address, CallRequest, HeaderId as EthereumHeaderId, Receipt, H256, U256},
Client as EthereumClient, Error as EthereumNodeError, SigningParams as EthereumSigningParams,
};
use relay_rialto_client::HeaderId as RialtoHeaderId;
use relay_utils::{HeaderId, MaybeConnectionError};
use sp_runtime::Justification;
use std::collections::HashSet;
// to encode/decode contract calls
ethabi_contract::use_contract!(bridge_contract, "res/substrate-bridge-abi.json");
type RpcResult<T> = std::result::Result<T, RpcError>;
/// A trait which contains methods that work by using multiple low-level RPCs, or more complicated
/// interactions involving, for example, an Ethereum contract.
#[async_trait]
pub trait EthereumHighLevelRpc {
/// Returns best Substrate block that PoA chain knows of.
async fn best_substrate_block(&self, contract_address: Address) -> RpcResult<RialtoHeaderId>;
/// Returns true if Substrate header is known to Ethereum node.
async fn substrate_header_known(
&self,
contract_address: Address,
id: RialtoHeaderId,
) -> RpcResult<(RialtoHeaderId, bool)>;
/// Submits Substrate headers to Ethereum contract.
async fn submit_substrate_headers(
&self,
params: EthereumSigningParams,
contract_address: Address,
headers: Vec<QueuedRialtoHeader>,
) -> SubmittedHeaders<RialtoHeaderId, RpcError>;
/// Returns ids of incomplete Substrate headers.
async fn incomplete_substrate_headers(&self, contract_address: Address) -> RpcResult<HashSet<RialtoHeaderId>>;
/// Complete Substrate header.
async fn complete_substrate_header(
&self,
params: EthereumSigningParams,
contract_address: Address,
id: RialtoHeaderId,
justification: Justification,
) -> RpcResult<RialtoHeaderId>;
/// Submit ethereum transaction.
async fn submit_ethereum_transaction(
&self,
params: &EthereumSigningParams,
contract_address: Option<Address>,
nonce: Option<U256>,
double_gas: bool,
encoded_call: Vec<u8>,
) -> RpcResult<()>;
/// Retrieve transactions receipts for given block.
async fn transaction_receipts(
&self,
id: EthereumHeaderId,
transactions: Vec<H256>,
) -> RpcResult<(EthereumHeaderId, Vec<Receipt>)>;
}
#[async_trait]
impl EthereumHighLevelRpc for EthereumClient {
async fn best_substrate_block(&self, contract_address: Address) -> RpcResult<RialtoHeaderId> {
let (encoded_call, call_decoder) = bridge_contract::functions::best_known_header::call();
let call_request = CallRequest {
to: Some(contract_address),
data: Some(encoded_call.into()),
..Default::default()
};
let call_result = self.eth_call(call_request).await?;
let (number, raw_hash) = call_decoder.decode(&call_result.0)?;
let hash = rialto_runtime::Hash::decode(&mut &raw_hash[..])?;
if number != number.low_u32().into() {
return Err(RpcError::Ethereum(EthereumNodeError::InvalidSubstrateBlockNumber));
}
Ok(HeaderId(number.low_u32(), hash))
}
async fn substrate_header_known(
&self,
contract_address: Address,
id: RialtoHeaderId,
) -> RpcResult<(RialtoHeaderId, bool)> {
let (encoded_call, call_decoder) = bridge_contract::functions::is_known_header::call(id.1);
let call_request = CallRequest {
to: Some(contract_address),
data: Some(encoded_call.into()),
..Default::default()
};
let call_result = self.eth_call(call_request).await?;
let is_known_block = call_decoder.decode(&call_result.0)?;
Ok((id, is_known_block))
}
async fn submit_substrate_headers(
&self,
params: EthereumSigningParams,
contract_address: Address,
headers: Vec<QueuedRialtoHeader>,
) -> SubmittedHeaders<RialtoHeaderId, RpcError> {
// read nonce of signer
let address: Address = secret_to_address(&params.signer);
let nonce = match self.account_nonce(address).await {
Ok(nonce) => nonce,
Err(error) => {
return SubmittedHeaders {
submitted: Vec::new(),
incomplete: Vec::new(),
rejected: headers.iter().rev().map(|header| header.id()).collect(),
fatal_error: Some(error.into()),
}
}
};
// submit headers. Note that we're cloning self here. It is ok, because
// cloning `jsonrpsee::Client` only clones reference to background threads
submit_substrate_headers(
EthereumHeadersSubmitter {
client: self.clone(),
params,
contract_address,
nonce,
},
headers,
)
.await
}
async fn incomplete_substrate_headers(&self, contract_address: Address) -> RpcResult<HashSet<RialtoHeaderId>> {
let (encoded_call, call_decoder) = bridge_contract::functions::incomplete_headers::call();
let call_request = CallRequest {
to: Some(contract_address),
data: Some(encoded_call.into()),
..Default::default()
};
let call_result = self.eth_call(call_request).await?;
// Q: Is is correct to call these "incomplete_ids"?
let (incomplete_headers_numbers, incomplete_headers_hashes) = call_decoder.decode(&call_result.0)?;
let incomplete_ids = incomplete_headers_numbers
.into_iter()
.zip(incomplete_headers_hashes)
.filter_map(|(number, hash)| {
if number != number.low_u32().into() {
return None;
}
Some(HeaderId(number.low_u32(), hash))
})
.collect();
Ok(incomplete_ids)
}
async fn complete_substrate_header(
&self,
params: EthereumSigningParams,
contract_address: Address,
id: RialtoHeaderId,
justification: Justification,
) -> RpcResult<RialtoHeaderId> {
let _ = self
.submit_ethereum_transaction(
&params,
Some(contract_address),
None,
false,
bridge_contract::functions::import_finality_proof::encode_input(id.0, id.1, justification),
)
.await?;
Ok(id)
}
async fn submit_ethereum_transaction(
&self,
params: &EthereumSigningParams,
contract_address: Option<Address>,
nonce: Option<U256>,
double_gas: bool,
encoded_call: Vec<u8>,
) -> RpcResult<()> {
sign_and_submit_transaction(self, params, contract_address, nonce, double_gas, encoded_call)
.await
.map_err(Into::into)
}
async fn transaction_receipts(
&self,
id: EthereumHeaderId,
transactions: Vec<H256>,
) -> RpcResult<(EthereumHeaderId, Vec<Receipt>)> {
let mut transaction_receipts = Vec::with_capacity(transactions.len());
for transaction in transactions {
let transaction_receipt = self.transaction_receipt(transaction).await?;
transaction_receipts.push(transaction_receipt);
}
Ok((id, transaction_receipts))
}
}
/// Max number of headers which can be sent to Solidity contract.
pub const HEADERS_BATCH: usize = 4;
/// Substrate headers to send to the Ethereum light client.
///
/// The Solidity contract can only accept a fixed number of headers in one go.
/// This struct is meant to encapsulate this limitation.
#[derive(Debug)]
#[cfg_attr(test, derive(Clone))]
pub struct HeadersBatch {
pub header1: QueuedRialtoHeader,
pub header2: Option<QueuedRialtoHeader>,
pub header3: Option<QueuedRialtoHeader>,
pub header4: Option<QueuedRialtoHeader>,
}
impl HeadersBatch {
/// Create new headers from given header & ids collections.
///
/// This method will pop `HEADERS_BATCH` items from both collections
/// and construct `Headers` object and a vector of `RialtoHeaderId`s.
pub fn pop_from(
headers: &mut Vec<QueuedRialtoHeader>,
ids: &mut Vec<RialtoHeaderId>,
) -> Result<(Self, Vec<RialtoHeaderId>), ()> {
if headers.len() != ids.len() {
log::error!(target: "bridge", "Collection size mismatch ({} vs {})", headers.len(), ids.len());
return Err(());
}
let header1 = headers.pop().ok_or(())?;
let header2 = headers.pop();
let header3 = headers.pop();
let header4 = headers.pop();
let mut submitting_ids = Vec::with_capacity(HEADERS_BATCH);
for _ in 0..HEADERS_BATCH {
submitting_ids.extend(ids.pop().iter());
}
Ok((
Self {
header1,
header2,
header3,
header4,
},
submitting_ids,
))
}
/// Returns unified array of headers.
///
/// The first element is always `Some`.
fn headers(&self) -> [Option<&QueuedRialtoHeader>; HEADERS_BATCH] {
[
Some(&self.header1),
self.header2.as_ref(),
self.header3.as_ref(),
self.header4.as_ref(),
]
}
/// Encodes all headers. If header is not present an empty vector will be returned.
pub fn encode(&self) -> [Vec<u8>; HEADERS_BATCH] {
let encode = |h: &QueuedRialtoHeader| h.header().encode();
let headers = self.headers();
[
headers[0].map(encode).unwrap_or_default(),
headers[1].map(encode).unwrap_or_default(),
headers[2].map(encode).unwrap_or_default(),
headers[3].map(encode).unwrap_or_default(),
]
}
/// Returns number of contained headers.
pub fn len(&self) -> usize {
let is_set = |h: &Option<&QueuedRialtoHeader>| if h.is_some() { 1 } else { 0 };
self.headers().iter().map(is_set).sum()
}
/// Remove headers starting from `idx` (0-based) from this collection.
///
/// The collection will be left with `[0, idx)` headers.
/// Returns `Err` when `idx == 0`, since `Headers` must contain at least one header,
/// or when `idx > HEADERS_BATCH`.
pub fn split_off(&mut self, idx: usize) -> Result<(), ()> {
if idx == 0 || idx > HEADERS_BATCH {
return Err(());
}
let mut vals: [_; HEADERS_BATCH] = [&mut None, &mut self.header2, &mut self.header3, &mut self.header4];
for val in vals.iter_mut().skip(idx) {
**val = None;
}
Ok(())
}
}
/// Substrate headers submitter API.
#[async_trait]
trait HeadersSubmitter {
/// Returns Ok(0) if all given not-yet-imported headers are complete.
/// Returns Ok(index != 0) where index is 1-based index of first header that is incomplete.
///
/// Returns Err(()) if contract has rejected headers. This means that the contract is
/// unable to import first header (e.g. it may already be imported).
async fn is_headers_incomplete(&self, headers: &HeadersBatch) -> RpcResult<usize>;
/// Submit given headers to Ethereum node.
async fn submit_headers(&mut self, headers: HeadersBatch) -> RpcResult<()>;
}
/// Implementation of Substrate headers submitter that sends headers to running Ethereum node.
struct EthereumHeadersSubmitter {
client: EthereumClient,
params: EthereumSigningParams,
contract_address: Address,
nonce: U256,
}
#[async_trait]
impl HeadersSubmitter for EthereumHeadersSubmitter {
async fn is_headers_incomplete(&self, headers: &HeadersBatch) -> RpcResult<usize> {
let [h1, h2, h3, h4] = headers.encode();
let (encoded_call, call_decoder) = bridge_contract::functions::is_incomplete_headers::call(h1, h2, h3, h4);
let call_request = CallRequest {
to: Some(self.contract_address),
data: Some(encoded_call.into()),
..Default::default()
};
let call_result = self.client.eth_call(call_request).await?;
let incomplete_index: U256 = call_decoder.decode(&call_result.0)?;
if incomplete_index > HEADERS_BATCH.into() {
return Err(RpcError::Ethereum(EthereumNodeError::InvalidIncompleteIndex));
}
Ok(incomplete_index.low_u32() as _)
}
async fn submit_headers(&mut self, headers: HeadersBatch) -> RpcResult<()> {
let [h1, h2, h3, h4] = headers.encode();
let result = self
.client
.submit_ethereum_transaction(
&self.params,
Some(self.contract_address),
Some(self.nonce),
false,
bridge_contract::functions::import_headers::encode_input(h1, h2, h3, h4),
)
.await;
if result.is_ok() {
self.nonce += U256::one();
}
result
}
}
/// Submit multiple Substrate headers.
async fn submit_substrate_headers(
mut header_submitter: impl HeadersSubmitter,
mut headers: Vec<QueuedRialtoHeader>,
) -> SubmittedHeaders<RialtoHeaderId, RpcError> {
let mut submitted_headers = SubmittedHeaders::default();
let mut ids = headers.iter().map(|header| header.id()).rev().collect::<Vec<_>>();
headers.reverse();
while !headers.is_empty() {
let (headers, submitting_ids) =
HeadersBatch::pop_from(&mut headers, &mut ids).expect("Headers and ids are not empty; qed");
submitted_headers.fatal_error =
submit_substrate_headers_batch(&mut header_submitter, &mut submitted_headers, submitting_ids, headers)
.await;
if submitted_headers.fatal_error.is_some() {
ids.reverse();
submitted_headers.rejected.extend(ids);
break;
}
}
submitted_headers
}
/// Submit 4 Substrate headers in single PoA transaction.
async fn submit_substrate_headers_batch(
header_submitter: &mut impl HeadersSubmitter,
submitted_headers: &mut SubmittedHeaders<RialtoHeaderId, RpcError>,
mut ids: Vec<RialtoHeaderId>,
mut headers: HeadersBatch,
) -> Option<RpcError> {
debug_assert_eq!(ids.len(), headers.len(),);
// if parent of first header is either incomplete, or rejected, we assume that contract
// will reject this header as well
let parent_id = headers.header1.parent_id();
if submitted_headers.rejected.contains(&parent_id) || submitted_headers.incomplete.contains(&parent_id) {
submitted_headers.rejected.extend(ids);
return None;
}
// check if headers are incomplete
let incomplete_header_index = match header_submitter.is_headers_incomplete(&headers).await {
// All headers valid
Ok(0) => None,
Ok(incomplete_header_index) => Some(incomplete_header_index),
Err(error) => {
// contract has rejected all headers => we do not want to submit it
submitted_headers.rejected.extend(ids);
if error.is_connection_error() {
return Some(error);
} else {
return None;
}
}
};
// Modify `ids` and `headers` to only contain values that are going to be accepted.
let rejected = if let Some(idx) = incomplete_header_index {
let len = std::cmp::min(idx, ids.len());
headers
.split_off(len)
.expect("len > 0, the case where all headers are valid is converted to None; qed");
ids.split_off(len)
} else {
Vec::new()
};
let submitted = ids;
let submit_result = header_submitter.submit_headers(headers).await;
match submit_result {
Ok(_) => {
if incomplete_header_index.is_some() {
submitted_headers.incomplete.extend(submitted.iter().last().cloned());
}
submitted_headers.submitted.extend(submitted);
submitted_headers.rejected.extend(rejected);
None
}
Err(error) => {
submitted_headers.rejected.extend(submitted);
submitted_headers.rejected.extend(rejected);
Some(error)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use sp_runtime::traits::Header;
struct TestHeadersSubmitter {
incomplete: Vec<RialtoHeaderId>,
failed: Vec<RialtoHeaderId>,
}
#[async_trait]
impl HeadersSubmitter for TestHeadersSubmitter {
async fn is_headers_incomplete(&self, headers: &HeadersBatch) -> RpcResult<usize> {
if self.incomplete.iter().any(|i| i.0 == headers.header1.id().0) {
Ok(1)
} else {
Ok(0)
}
}
async fn submit_headers(&mut self, headers: HeadersBatch) -> RpcResult<()> {
if self.failed.iter().any(|i| i.0 == headers.header1.id().0) {
Err(RpcError::Ethereum(EthereumNodeError::InvalidSubstrateBlockNumber))
} else {
Ok(())
}
}
}
fn header(number: rialto_runtime::BlockNumber) -> QueuedRialtoHeader {
QueuedRialtoHeader::new(
rialto_runtime::Header::new(
number,
Default::default(),
Default::default(),
if number == 0 {
Default::default()
} else {
header(number - 1).id().1
},
Default::default(),
)
.into(),
)
}
#[test]
fn descendants_of_incomplete_headers_are_not_submitted() {
let submitted_headers = async_std::task::block_on(submit_substrate_headers(
TestHeadersSubmitter {
incomplete: vec![header(5).id()],
failed: vec![],
},
vec![header(5), header(6)],
));
assert_eq!(submitted_headers.submitted, vec![header(5).id()]);
assert_eq!(submitted_headers.incomplete, vec![header(5).id()]);
assert_eq!(submitted_headers.rejected, vec![header(6).id()]);
assert!(submitted_headers.fatal_error.is_none());
}
#[test]
fn headers_after_fatal_error_are_not_submitted() {
let submitted_headers = async_std::task::block_on(submit_substrate_headers(
TestHeadersSubmitter {
incomplete: vec![],
failed: vec![header(9).id()],
},
vec![
header(5),
header(6),
header(7),
header(8),
header(9),
header(10),
header(11),
],
));
assert_eq!(
submitted_headers.submitted,
vec![header(5).id(), header(6).id(), header(7).id(), header(8).id()]
);
assert_eq!(submitted_headers.incomplete, vec![]);
assert_eq!(
submitted_headers.rejected,
vec![header(9).id(), header(10).id(), header(11).id(),]
);
assert!(submitted_headers.fatal_error.is_some());
}
fn headers_batch() -> HeadersBatch {
let mut init_headers = vec![header(1), header(2), header(3), header(4), header(5)];
init_headers.reverse();
let mut init_ids = init_headers.iter().map(|h| h.id()).collect();
let (headers, ids) = HeadersBatch::pop_from(&mut init_headers, &mut init_ids).unwrap();
assert_eq!(init_headers, vec![header(5)]);
assert_eq!(init_ids, vec![header(5).id()]);
assert_eq!(
ids,
vec![header(1).id(), header(2).id(), header(3).id(), header(4).id()]
);
headers
}
#[test]
fn headers_batch_len() {
let headers = headers_batch();
assert_eq!(headers.len(), 4);
}
#[test]
fn headers_batch_encode() {
let headers = headers_batch();
assert_eq!(
headers.encode(),
[
header(1).header().encode(),
header(2).header().encode(),
header(3).header().encode(),
header(4).header().encode(),
]
);
}
#[test]
fn headers_batch_split_off() {
// given
let mut headers = headers_batch();
// when
assert!(headers.split_off(0).is_err());
assert_eq!(headers.header1, header(1));
assert!(headers.header2.is_some());
assert!(headers.header3.is_some());
assert!(headers.header4.is_some());
// when
let mut h = headers.clone();
h.split_off(1).unwrap();
assert!(h.header2.is_none());
assert!(h.header3.is_none());
assert!(h.header4.is_none());
// when
let mut h = headers.clone();
h.split_off(2).unwrap();
assert!(h.header2.is_some());
assert!(h.header3.is_none());
assert!(h.header4.is_none());
// when
let mut h = headers.clone();
h.split_off(3).unwrap();
assert!(h.header2.is_some());
assert!(h.header3.is_some());
assert!(h.header4.is_none());
// when
let mut h = headers;
h.split_off(4).unwrap();
assert!(h.header2.is_some());
assert!(h.header3.is_some());
assert!(h.header4.is_some());
}
}
@@ -0,0 +1,156 @@
// 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 <http://www.gnu.org/licenses/>.
use crate::ethereum_client::{bridge_contract, EthereumHighLevelRpc};
use crate::rpc_errors::RpcError;
use codec::{Decode, Encode};
use num_traits::Zero;
use relay_ethereum_client::{
Client as EthereumClient, ConnectionParams as EthereumConnectionParams, SigningParams as EthereumSigningParams,
};
use relay_rialto_client::{HeaderId as RialtoHeaderId, Rialto};
use relay_substrate_client::{
Client as SubstrateClient, ConnectionParams as SubstrateConnectionParams, OpaqueGrandpaAuthoritiesSet,
};
use relay_utils::HeaderId;
/// Ethereum synchronization parameters.
#[derive(Debug)]
pub struct EthereumDeployContractParams {
/// Ethereum connection params.
pub eth_params: EthereumConnectionParams,
/// Ethereum signing params.
pub eth_sign: EthereumSigningParams,
/// Ethereum contract bytecode.
pub eth_contract_code: Vec<u8>,
/// Substrate connection params.
pub sub_params: SubstrateConnectionParams,
/// Initial authorities set id.
pub sub_initial_authorities_set_id: Option<u64>,
/// Initial authorities set.
pub sub_initial_authorities_set: Option<Vec<u8>>,
/// Initial header.
pub sub_initial_header: Option<Vec<u8>>,
}
/// Deploy Bridge contract on Ethereum chain.
pub fn run(params: EthereumDeployContractParams) {
let mut local_pool = futures::executor::LocalPool::new();
let EthereumDeployContractParams {
eth_params,
eth_sign,
sub_params,
sub_initial_authorities_set_id,
sub_initial_authorities_set,
sub_initial_header,
eth_contract_code,
} = params;
let result = local_pool.run_until(async move {
let eth_client = EthereumClient::new(eth_params);
let sub_client = SubstrateClient::<Rialto>::new(sub_params).await.map_err(RpcError::Substrate)?;
let (initial_header_id, initial_header) = prepare_initial_header(&sub_client, sub_initial_header).await?;
let initial_set_id = sub_initial_authorities_set_id.unwrap_or(0);
let initial_set = prepare_initial_authorities_set(
&sub_client,
initial_header_id.1,
sub_initial_authorities_set,
).await?;
log::info!(
target: "bridge",
"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),
);
deploy_bridge_contract(
&eth_client,
&eth_sign,
eth_contract_code,
initial_header,
initial_set_id,
initial_set,
).await
});
if let Err(error) = result {
log::error!(target: "bridge", "{}", error);
}
}
/// Prepare initial header.
async fn prepare_initial_header(
sub_client: &SubstrateClient<Rialto>,
sub_initial_header: Option<Vec<u8>>,
) -> Result<(RialtoHeaderId, Vec<u8>), String> {
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)),
},
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))
}
}
}
/// Prepare initial GRANDPA authorities set.
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> {
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))
}
/// Deploy bridge contract to Ethereum chain.
async fn deploy_bridge_contract(
eth_client: &EthereumClient,
params: &EthereumSigningParams,
contract_code: Vec<u8>,
initial_header: Vec<u8>,
initial_set_id: u64,
initial_authorities: Vec<u8>,
) -> Result<(), String> {
eth_client
.submit_ethereum_transaction(
params,
None,
None,
false,
bridge_contract::constructor(contract_code, initial_header, initial_set_id, initial_authorities),
)
.await
.map_err(|error| format!("Error deploying contract: {:?}", error))
}
@@ -0,0 +1,394 @@
// 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 <http://www.gnu.org/licenses/>.
//! Relaying proofs of PoA -> Substrate exchange transactions.
use crate::instances::BridgeInstance;
use crate::rialto_client::{SubmitEthereumExchangeTransactionProof, SubstrateHighLevelRpc};
use crate::rpc_errors::RpcError;
use crate::substrate_types::into_substrate_ethereum_receipt;
use async_trait::async_trait;
use bp_currency_exchange::MaybeLockFundsTransaction;
use exchange_relay::exchange::{
relay_single_transaction_proof, SourceBlock, SourceClient, SourceTransaction, TargetClient,
TransactionProofPipeline,
};
use exchange_relay::exchange_loop::{run as run_loop, InMemoryStorage};
use relay_ethereum_client::{
types::{
HeaderId as EthereumHeaderId, HeaderWithTransactions as EthereumHeaderWithTransactions,
Transaction as EthereumTransaction, TransactionHash as EthereumTransactionHash, H256, HEADER_ID_PROOF,
},
Client as EthereumClient, ConnectionParams as EthereumConnectionParams,
};
use relay_rialto_client::{Rialto, SigningParams as RialtoSigningParams};
use relay_substrate_client::{
Chain as SubstrateChain, Client as SubstrateClient, ConnectionParams as SubstrateConnectionParams,
};
use relay_utils::{metrics::MetricsParams, relay_loop::Client as RelayClient, HeaderId};
use rialto_runtime::exchange::EthereumTransactionInclusionProof;
use std::{sync::Arc, time::Duration};
/// Interval at which we ask Ethereum node for updates.
const ETHEREUM_TICK_INTERVAL: Duration = Duration::from_secs(10);
/// Exchange relay mode.
#[derive(Debug)]
pub enum ExchangeRelayMode {
/// Relay single transaction and quit.
Single(EthereumTransactionHash),
/// Auto-relay transactions starting with given block.
Auto(Option<u64>),
}
/// PoA exchange transaction relay params.
#[derive(Debug)]
pub struct EthereumExchangeParams {
/// Ethereum connection params.
pub eth_params: EthereumConnectionParams,
/// Substrate connection params.
pub sub_params: SubstrateConnectionParams,
/// Substrate signing params.
pub sub_sign: RialtoSigningParams,
/// Relay working mode.
pub mode: ExchangeRelayMode,
/// Metrics parameters.
pub metrics_params: Option<MetricsParams>,
/// Instance of the bridge pallet being synchronized.
pub instance: Arc<dyn BridgeInstance>,
}
/// Ethereum to Substrate exchange pipeline.
struct EthereumToSubstrateExchange;
impl TransactionProofPipeline for EthereumToSubstrateExchange {
const SOURCE_NAME: &'static str = "Ethereum";
const TARGET_NAME: &'static str = "Substrate";
type Block = EthereumSourceBlock;
type TransactionProof = EthereumTransactionInclusionProof;
}
/// Ethereum source block.
struct EthereumSourceBlock(EthereumHeaderWithTransactions);
impl SourceBlock for EthereumSourceBlock {
type Hash = H256;
type Number = u64;
type Transaction = EthereumSourceTransaction;
fn id(&self) -> EthereumHeaderId {
HeaderId(
self.0.number.expect(HEADER_ID_PROOF).as_u64(),
self.0.hash.expect(HEADER_ID_PROOF),
)
}
fn transactions(&self) -> Vec<Self::Transaction> {
self.0
.transactions
.iter()
.cloned()
.map(EthereumSourceTransaction)
.collect()
}
}
/// Ethereum source transaction.
struct EthereumSourceTransaction(EthereumTransaction);
impl SourceTransaction for EthereumSourceTransaction {
type Hash = EthereumTransactionHash;
fn hash(&self) -> Self::Hash {
self.0.hash
}
}
/// Ethereum node as transactions proof source.
#[derive(Clone)]
struct EthereumTransactionsSource {
client: EthereumClient,
}
#[async_trait]
impl RelayClient for EthereumTransactionsSource {
type Error = RpcError;
async fn reconnect(&mut self) -> Result<(), RpcError> {
self.client.reconnect();
Ok(())
}
}
#[async_trait]
impl SourceClient<EthereumToSubstrateExchange> for EthereumTransactionsSource {
async fn tick(&self) {
async_std::task::sleep(ETHEREUM_TICK_INTERVAL).await;
}
async fn block_by_hash(&self, hash: H256) -> Result<EthereumSourceBlock, RpcError> {
self.client
.header_by_hash_with_transactions(hash)
.await
.map(EthereumSourceBlock)
.map_err(Into::into)
}
async fn block_by_number(&self, number: u64) -> Result<EthereumSourceBlock, RpcError> {
self.client
.header_by_number_with_transactions(number)
.await
.map(EthereumSourceBlock)
.map_err(Into::into)
}
async fn transaction_block(
&self,
hash: &EthereumTransactionHash,
) -> Result<Option<(EthereumHeaderId, usize)>, RpcError> {
let eth_tx = match self.client.transaction_by_hash(*hash).await? {
Some(eth_tx) => eth_tx,
None => return Ok(None),
};
// we need transaction to be mined => check if it is included in the block
let (eth_header_id, eth_tx_index) = match (eth_tx.block_number, eth_tx.block_hash, eth_tx.transaction_index) {
(Some(block_number), Some(block_hash), Some(transaction_index)) => (
HeaderId(block_number.as_u64(), block_hash),
transaction_index.as_u64() as _,
),
_ => return Ok(None),
};
Ok(Some((eth_header_id, eth_tx_index)))
}
async fn transaction_proof(
&self,
block: &EthereumSourceBlock,
tx_index: usize,
) -> Result<EthereumTransactionInclusionProof, RpcError> {
const TRANSACTION_HAS_RAW_FIELD_PROOF: &str = "RPC level checks that transactions from Ethereum\
node are having `raw` field; qed";
const BLOCK_HAS_HASH_FIELD_PROOF: &str = "RPC level checks that block has `hash` field; qed";
let mut transaction_proof = Vec::with_capacity(block.0.transactions.len());
for tx in &block.0.transactions {
let raw_tx_receipt = self
.client
.transaction_receipt(tx.hash)
.await
.map(|receipt| into_substrate_ethereum_receipt(&receipt))
.map(|receipt| receipt.rlp())?;
let raw_tx = tx.raw.clone().expect(TRANSACTION_HAS_RAW_FIELD_PROOF).0;
transaction_proof.push((raw_tx, raw_tx_receipt));
}
Ok(EthereumTransactionInclusionProof {
block: block.0.hash.expect(BLOCK_HAS_HASH_FIELD_PROOF),
index: tx_index as _,
proof: transaction_proof,
})
}
}
/// Substrate node as transactions proof target.
#[derive(Clone)]
struct SubstrateTransactionsTarget {
client: SubstrateClient<Rialto>,
sign_params: RialtoSigningParams,
bridge_instance: Arc<dyn BridgeInstance>,
}
#[async_trait]
impl RelayClient for SubstrateTransactionsTarget {
type Error = RpcError;
async fn reconnect(&mut self) -> Result<(), RpcError> {
Ok(self.client.reconnect().await?)
}
}
#[async_trait]
impl TargetClient<EthereumToSubstrateExchange> for SubstrateTransactionsTarget {
async fn tick(&self) {
async_std::task::sleep(Rialto::AVERAGE_BLOCK_INTERVAL).await;
}
async fn is_header_known(&self, id: &EthereumHeaderId) -> Result<bool, RpcError> {
self.client.ethereum_header_known(*id).await
}
async fn is_header_finalized(&self, id: &EthereumHeaderId) -> Result<bool, RpcError> {
// we check if header is finalized by simple comparison of the header number and
// number of best finalized PoA header known to Substrate node.
//
// this may lead to failure in tx proof import if PoA reorganization has happened
// after we have checked that our tx has been included into given block
//
// the fix is easy, but since this code is mostly developed for demonstration purposes,
// I'm leaving this KISS-based design here
let best_finalized_ethereum_block = self.client.best_ethereum_finalized_block().await?;
Ok(id.0 <= best_finalized_ethereum_block.0)
}
async fn best_finalized_header_id(&self) -> Result<EthereumHeaderId, RpcError> {
// we can't continue to relay exchange proofs if Substrate node is out of sync, because
// it may have already received (some of) proofs that we're going to relay
self.client.ensure_synced().await?;
self.client.best_ethereum_finalized_block().await
}
async fn filter_transaction_proof(&self, proof: &EthereumTransactionInclusionProof) -> Result<bool, RpcError> {
// let's try to parse transaction locally
let (raw_tx, raw_tx_receipt) = &proof.proof[proof.index as usize];
let parse_result = rialto_runtime::exchange::EthTransaction::parse(raw_tx);
if parse_result.is_err() {
return Ok(false);
}
// now let's check if transaction is successful
match bp_eth_poa::Receipt::is_successful_raw_receipt(raw_tx_receipt) {
Ok(true) => (),
_ => return Ok(false),
}
// seems that transaction is relayable - let's check if runtime is able to import it
// (we can't if e.g. header is pruned or there's some issue with tx data)
self.client.verify_exchange_transaction_proof(proof.clone()).await
}
async fn submit_transaction_proof(&self, proof: EthereumTransactionInclusionProof) -> Result<(), RpcError> {
let (sign_params, bridge_instance) = (self.sign_params.clone(), self.bridge_instance.clone());
self.client
.submit_exchange_transaction_proof(sign_params, bridge_instance, proof)
.await
}
}
/// Relay exchange transaction proof(s) to Substrate node.
pub fn run(params: EthereumExchangeParams) {
match params.mode {
ExchangeRelayMode::Single(eth_tx_hash) => run_single_transaction_relay(params, eth_tx_hash),
ExchangeRelayMode::Auto(eth_start_with_block_number) => {
run_auto_transactions_relay_loop(params, eth_start_with_block_number)
}
};
}
/// Run single transaction proof relay and stop.
fn run_single_transaction_relay(params: EthereumExchangeParams, eth_tx_hash: H256) {
let mut local_pool = futures::executor::LocalPool::new();
let EthereumExchangeParams {
eth_params,
sub_params,
sub_sign,
instance,
..
} = params;
let result = local_pool.run_until(async move {
let eth_client = EthereumClient::new(eth_params);
let sub_client = SubstrateClient::<Rialto>::new(sub_params)
.await
.map_err(RpcError::Substrate)?;
let source = EthereumTransactionsSource { client: eth_client };
let target = SubstrateTransactionsTarget {
client: sub_client,
sign_params: sub_sign,
bridge_instance: instance,
};
relay_single_transaction_proof(&source, &target, eth_tx_hash).await
});
match result {
Ok(_) => {
log::info!(
target: "bridge",
"Ethereum transaction {} proof has been successfully submitted to Substrate node",
eth_tx_hash,
);
}
Err(err) => {
log::error!(
target: "bridge",
"Error submitting Ethereum transaction {} proof to Substrate node: {}",
eth_tx_hash,
err,
);
}
}
}
/// Run auto-relay loop.
fn run_auto_transactions_relay_loop(params: EthereumExchangeParams, eth_start_with_block_number: Option<u64>) {
let EthereumExchangeParams {
eth_params,
sub_params,
sub_sign,
metrics_params,
instance,
..
} = params;
let do_run_loop = move || -> Result<(), String> {
let eth_client = EthereumClient::new(eth_params);
let sub_client = async_std::task::block_on(SubstrateClient::<Rialto>::new(sub_params))
.map_err(|err| format!("Error starting Substrate client: {:?}", err))?;
let eth_start_with_block_number = match eth_start_with_block_number {
Some(eth_start_with_block_number) => eth_start_with_block_number,
None => {
async_std::task::block_on(sub_client.best_ethereum_finalized_block())
.map_err(|err| {
format!(
"Error retrieving best finalized Ethereum block from Substrate node: {:?}",
err
)
})?
.0
}
};
run_loop(
InMemoryStorage::new(eth_start_with_block_number),
EthereumTransactionsSource { client: eth_client },
SubstrateTransactionsTarget {
client: sub_client,
sign_params: sub_sign,
bridge_instance: instance,
},
metrics_params,
futures::future::pending(),
);
Ok(())
};
if let Err(err) = do_run_loop() {
log::error!(
target: "bridge",
"Error auto-relaying Ethereum transactions proofs to Substrate node: {}",
err,
);
}
}
@@ -0,0 +1,113 @@
// 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 <http://www.gnu.org/licenses/>.
//! Submitting Ethereum -> Substrate exchange transactions.
use bp_eth_poa::{
signatures::{secret_to_address, SignTransaction},
UnsignedTransaction,
};
use relay_ethereum_client::{
types::{CallRequest, U256},
Client as EthereumClient, ConnectionParams as EthereumConnectionParams, SigningParams as EthereumSigningParams,
};
use rialto_runtime::exchange::LOCK_FUNDS_ADDRESS;
/// Ethereum exchange transaction params.
#[derive(Debug)]
pub struct EthereumExchangeSubmitParams {
/// Ethereum connection params.
pub eth_params: EthereumConnectionParams,
/// Ethereum signing params.
pub eth_sign: EthereumSigningParams,
/// Ethereum signer nonce.
pub eth_nonce: Option<U256>,
/// Amount of Ethereum tokens to lock.
pub eth_amount: U256,
/// Funds recipient on Substrate side.
pub sub_recipient: [u8; 32],
}
/// Submit single Ethereum -> Substrate exchange transaction.
pub fn run(params: EthereumExchangeSubmitParams) {
let mut local_pool = futures::executor::LocalPool::new();
let EthereumExchangeSubmitParams {
eth_params,
eth_sign,
eth_nonce,
eth_amount,
sub_recipient,
} = params;
let result: Result<_, String> = local_pool.run_until(async move {
let eth_client = EthereumClient::new(eth_params);
let eth_signer_address = secret_to_address(&eth_sign.signer);
let sub_recipient_encoded = sub_recipient;
let nonce = match eth_nonce {
Some(eth_nonce) => eth_nonce,
None => eth_client
.account_nonce(eth_signer_address)
.await
.map_err(|err| format!("error fetching acount nonce: {:?}", err))?,
};
let gas = eth_client
.estimate_gas(CallRequest {
from: Some(eth_signer_address),
to: Some(LOCK_FUNDS_ADDRESS.into()),
value: Some(eth_amount),
data: Some(sub_recipient_encoded.to_vec().into()),
..Default::default()
})
.await
.map_err(|err| format!("error estimating gas requirements: {:?}", err))?;
let eth_tx_unsigned = UnsignedTransaction {
nonce,
gas_price: eth_sign.gas_price,
gas,
to: Some(LOCK_FUNDS_ADDRESS.into()),
value: eth_amount,
payload: sub_recipient_encoded.to_vec(),
};
let eth_tx_signed = eth_tx_unsigned
.clone()
.sign_by(&eth_sign.signer, Some(eth_sign.chain_id));
eth_client
.submit_transaction(eth_tx_signed)
.await
.map_err(|err| format!("error submitting transaction: {:?}", err))?;
Ok(eth_tx_unsigned)
});
match result {
Ok(eth_tx_unsigned) => {
log::info!(
target: "bridge",
"Exchange transaction has been submitted to Ethereum node: {:?}",
eth_tx_unsigned,
);
}
Err(err) => {
log::error!(
target: "bridge",
"Error submitting exchange transaction to Ethereum node: {}",
err,
);
}
}
}
@@ -0,0 +1,285 @@
// 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 <http://www.gnu.org/licenses/>.
//! Ethereum PoA -> Rialto-Substrate synchronization.
use crate::ethereum_client::EthereumHighLevelRpc;
use crate::instances::BridgeInstance;
use crate::rialto_client::{SubmitEthereumHeaders, SubstrateHighLevelRpc};
use crate::rpc_errors::RpcError;
use crate::substrate_types::{into_substrate_ethereum_header, into_substrate_ethereum_receipts};
use async_trait::async_trait;
use codec::Encode;
use headers_relay::{
sync::{HeadersSyncParams, TargetTransactionMode},
sync_loop::{SourceClient, TargetClient},
sync_types::{HeadersSyncPipeline, QueuedHeader, SourceHeader, SubmittedHeaders},
};
use relay_ethereum_client::{
types::{HeaderHash, HeaderId as EthereumHeaderId, Receipt, SyncHeader as Header},
Client as EthereumClient, ConnectionParams as EthereumConnectionParams,
};
use relay_rialto_client::{Rialto, SigningParams as RialtoSigningParams};
use relay_substrate_client::{
Chain as SubstrateChain, Client as SubstrateClient, ConnectionParams as SubstrateConnectionParams,
};
use relay_utils::{metrics::MetricsParams, relay_loop::Client as RelayClient};
use std::fmt::Debug;
use std::{collections::HashSet, sync::Arc, time::Duration};
pub mod consts {
use super::*;
/// Interval at which we check new Ethereum headers when we are synced/almost synced.
pub const ETHEREUM_TICK_INTERVAL: Duration = Duration::from_secs(10);
/// Max number of headers in single submit transaction.
pub const MAX_HEADERS_IN_SINGLE_SUBMIT: usize = 32;
/// Max total size of headers in single submit transaction. This only affects signed
/// submissions, when several headers are submitted at once. 4096 is the maximal **expected**
/// size of the Ethereum header + transactions receipts (if they're required).
pub const MAX_HEADERS_SIZE_IN_SINGLE_SUBMIT: usize = MAX_HEADERS_IN_SINGLE_SUBMIT * 4096;
/// Max Ethereum headers we want to have in all 'before-submitted' states.
pub const MAX_FUTURE_HEADERS_TO_DOWNLOAD: usize = 128;
/// Max Ethereum headers count we want to have in 'submitted' state.
pub const MAX_SUBMITTED_HEADERS: usize = 128;
/// Max depth of in-memory headers in all states. Past this depth they will be forgotten (pruned).
pub const PRUNE_DEPTH: u32 = 4096;
}
/// Ethereum synchronization parameters.
#[derive(Debug)]
pub struct EthereumSyncParams {
/// Ethereum connection params.
pub eth_params: EthereumConnectionParams,
/// Substrate connection params.
pub sub_params: SubstrateConnectionParams,
/// Substrate signing params.
pub sub_sign: RialtoSigningParams,
/// Synchronization parameters.
pub sync_params: HeadersSyncParams,
/// Metrics parameters.
pub metrics_params: Option<MetricsParams>,
/// Instance of the bridge pallet being synchronized.
pub instance: Arc<dyn BridgeInstance>,
}
/// Ethereum synchronization pipeline.
#[derive(Clone, Copy, Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct EthereumHeadersSyncPipeline;
impl HeadersSyncPipeline for EthereumHeadersSyncPipeline {
const SOURCE_NAME: &'static str = "Ethereum";
const TARGET_NAME: &'static str = "Substrate";
type Hash = HeaderHash;
type Number = u64;
type Header = Header;
type Extra = Vec<Receipt>;
type Completion = ();
fn estimate_size(source: &QueuedHeader<Self>) -> usize {
into_substrate_ethereum_header(source.header()).encode().len()
+ into_substrate_ethereum_receipts(source.extra())
.map(|extra| extra.encode().len())
.unwrap_or(0)
}
}
/// Queued ethereum header ID.
pub type QueuedEthereumHeader = QueuedHeader<EthereumHeadersSyncPipeline>;
/// Ethereum client as headers source.
#[derive(Clone)]
struct EthereumHeadersSource {
/// Ethereum node client.
client: EthereumClient,
}
impl EthereumHeadersSource {
fn new(client: EthereumClient) -> Self {
Self { client }
}
}
#[async_trait]
impl RelayClient for EthereumHeadersSource {
type Error = RpcError;
async fn reconnect(&mut self) -> Result<(), RpcError> {
self.client.reconnect();
Ok(())
}
}
#[async_trait]
impl SourceClient<EthereumHeadersSyncPipeline> for EthereumHeadersSource {
async fn best_block_number(&self) -> Result<u64, RpcError> {
// we **CAN** continue to relay headers if Ethereum node is out of sync, because
// Substrate node may be missing headers that are already available at the Ethereum
self.client.best_block_number().await.map_err(Into::into)
}
async fn header_by_hash(&self, hash: HeaderHash) -> Result<Header, RpcError> {
self.client
.header_by_hash(hash)
.await
.map(Into::into)
.map_err(Into::into)
}
async fn header_by_number(&self, number: u64) -> Result<Header, RpcError> {
self.client
.header_by_number(number)
.await
.map(Into::into)
.map_err(Into::into)
}
async fn header_completion(&self, id: EthereumHeaderId) -> Result<(EthereumHeaderId, Option<()>), RpcError> {
Ok((id, None))
}
async fn header_extra(
&self,
id: EthereumHeaderId,
header: QueuedEthereumHeader,
) -> Result<(EthereumHeaderId, Vec<Receipt>), RpcError> {
self.client
.transaction_receipts(id, header.header().transactions.clone())
.await
}
}
#[derive(Clone)]
struct SubstrateHeadersTarget {
/// Substrate node client.
client: SubstrateClient<Rialto>,
/// Whether we want to submit signed (true), or unsigned (false) transactions.
sign_transactions: bool,
/// Substrate signing params.
sign_params: RialtoSigningParams,
/// Bridge instance used in Ethereum to Substrate sync.
bridge_instance: Arc<dyn BridgeInstance>,
}
impl SubstrateHeadersTarget {
fn new(
client: SubstrateClient<Rialto>,
sign_transactions: bool,
sign_params: RialtoSigningParams,
bridge_instance: Arc<dyn BridgeInstance>,
) -> Self {
Self {
client,
sign_transactions,
sign_params,
bridge_instance,
}
}
}
#[async_trait]
impl RelayClient for SubstrateHeadersTarget {
type Error = RpcError;
async fn reconnect(&mut self) -> Result<(), RpcError> {
Ok(self.client.reconnect().await?)
}
}
#[async_trait]
impl TargetClient<EthereumHeadersSyncPipeline> for SubstrateHeadersTarget {
async fn best_header_id(&self) -> Result<EthereumHeaderId, RpcError> {
// we can't continue to relay headers if Substrate node is out of sync, because
// it may have already received (some of) headers that we're going to relay
self.client.ensure_synced().await?;
self.client.best_ethereum_block().await
}
async fn is_known_header(&self, id: EthereumHeaderId) -> Result<(EthereumHeaderId, bool), RpcError> {
Ok((id, self.client.ethereum_header_known(id).await?))
}
async fn submit_headers(&self, headers: Vec<QueuedEthereumHeader>) -> SubmittedHeaders<EthereumHeaderId, RpcError> {
let (sign_params, bridge_instance, sign_transactions) = (
self.sign_params.clone(),
self.bridge_instance.clone(),
self.sign_transactions,
);
self.client
.submit_ethereum_headers(sign_params, bridge_instance, headers, sign_transactions)
.await
}
async fn incomplete_headers_ids(&self) -> Result<HashSet<EthereumHeaderId>, RpcError> {
Ok(HashSet::new())
}
#[allow(clippy::unit_arg)]
async fn complete_header(&self, id: EthereumHeaderId, _completion: ()) -> Result<EthereumHeaderId, RpcError> {
Ok(id)
}
async fn requires_extra(&self, header: QueuedEthereumHeader) -> Result<(EthereumHeaderId, bool), RpcError> {
// we can minimize number of receipts_check calls by checking header
// logs bloom here, but it may give us false positives (when authorities
// source is contract, we never need any logs)
let id = header.header().id();
let sub_eth_header = into_substrate_ethereum_header(header.header());
Ok((id, self.client.ethereum_receipts_required(sub_eth_header).await?))
}
}
/// Run Ethereum headers synchronization.
pub fn run(params: EthereumSyncParams) -> Result<(), RpcError> {
let EthereumSyncParams {
eth_params,
sub_params,
sub_sign,
sync_params,
metrics_params,
instance,
} = params;
let eth_client = EthereumClient::new(eth_params);
let sub_client = async_std::task::block_on(async { SubstrateClient::<Rialto>::new(sub_params).await })?;
let sign_sub_transactions = match sync_params.target_tx_mode {
TargetTransactionMode::Signed | TargetTransactionMode::Backup => true,
TargetTransactionMode::Unsigned => false,
};
let source = EthereumHeadersSource::new(eth_client);
let target = SubstrateHeadersTarget::new(sub_client, sign_sub_transactions, sub_sign, instance);
headers_relay::sync_loop::run(
source,
consts::ETHEREUM_TICK_INTERVAL,
target,
Rialto::AVERAGE_BLOCK_INTERVAL,
(),
sync_params,
metrics_params,
futures::future::pending(),
);
Ok(())
}
@@ -0,0 +1,115 @@
// Copyright 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 <http://www.gnu.org/licenses/>.
//! The PoA Bridge Pallet provides a way to include multiple instances of itself in a runtime. When
//! synchronizing a Substrate chain which can include multiple instances of the bridge pallet we
//! must somehow decide which of the instances to sync.
//!
//! Note that each instance of the bridge pallet is coupled with an instance of the currency exchange
//! pallet. We must also have a way to create `Call`s for the correct currency exchange instance.
//!
//! This module helps by preparing the correct `Call`s for each of the different pallet instances.
use crate::ethereum_sync_loop::QueuedEthereumHeader;
use crate::substrate_types::{into_substrate_ethereum_header, into_substrate_ethereum_receipts};
use rialto_runtime::exchange::EthereumTransactionInclusionProof as Proof;
use rialto_runtime::Call;
/// Interface for `Calls` which are needed to correctly sync the bridge.
///
/// Each instance of the bridge and currency exchange pallets in the bridge runtime requires similar
/// but slightly different `Call` in order to be synchronized.
pub trait BridgeInstance: Send + Sync + std::fmt::Debug {
/// Used to build a `Call` for importing signed headers to a Substrate runtime.
fn build_signed_header_call(&self, headers: Vec<QueuedEthereumHeader>) -> Call;
/// Used to build a `Call` for importing an unsigned header to a Substrate runtime.
fn build_unsigned_header_call(&self, header: QueuedEthereumHeader) -> Call;
/// Used to build a `Call` for importing peer transactions to a Substrate runtime.
fn build_currency_exchange_call(&self, proof: Proof) -> Call;
}
/// Corresponds to the Rialto instance used in the bridge runtime.
#[derive(Default, Clone, Debug)]
pub struct RialtoPoA;
impl BridgeInstance for RialtoPoA {
fn build_signed_header_call(&self, headers: Vec<QueuedEthereumHeader>) -> Call {
let pallet_call = rialto_runtime::BridgeEthPoACall::import_signed_headers(
headers
.into_iter()
.map(|header| {
(
into_substrate_ethereum_header(&header.header()),
into_substrate_ethereum_receipts(header.extra()),
)
})
.collect(),
);
rialto_runtime::Call::BridgeRialtoPoA(pallet_call)
}
fn build_unsigned_header_call(&self, header: QueuedEthereumHeader) -> Call {
let pallet_call = rialto_runtime::BridgeEthPoACall::import_unsigned_header(
into_substrate_ethereum_header(&header.header()),
into_substrate_ethereum_receipts(header.extra()),
);
rialto_runtime::Call::BridgeRialtoPoA(pallet_call)
}
fn build_currency_exchange_call(&self, proof: Proof) -> Call {
let pallet_call = rialto_runtime::BridgeCurrencyExchangeCall::import_peer_transaction(proof);
rialto_runtime::Call::BridgeRialtoCurrencyExchange(pallet_call)
}
}
/// Corresponds to the Kovan instance used in the bridge runtime.
#[derive(Default, Clone, Debug)]
pub struct Kovan;
impl BridgeInstance for Kovan {
fn build_signed_header_call(&self, headers: Vec<QueuedEthereumHeader>) -> Call {
let pallet_call = rialto_runtime::BridgeEthPoACall::import_signed_headers(
headers
.into_iter()
.map(|header| {
(
into_substrate_ethereum_header(header.header()),
into_substrate_ethereum_receipts(header.extra()),
)
})
.collect(),
);
rialto_runtime::Call::BridgeKovan(pallet_call)
}
fn build_unsigned_header_call(&self, header: QueuedEthereumHeader) -> Call {
let pallet_call = rialto_runtime::BridgeEthPoACall::import_unsigned_header(
into_substrate_ethereum_header(header.header()),
into_substrate_ethereum_receipts(header.extra()),
);
rialto_runtime::Call::BridgeKovan(pallet_call)
}
fn build_currency_exchange_call(&self, proof: Proof) -> Call {
let pallet_call = rialto_runtime::BridgeCurrencyExchangeCall::import_peer_transaction(proof);
rialto_runtime::Call::BridgeKovanCurrencyExchange(pallet_call)
}
}
@@ -0,0 +1,409 @@
// 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 <http://www.gnu.org/licenses/>.
#![recursion_limit = "1024"]
mod ethereum_client;
mod ethereum_deploy_contract;
mod ethereum_exchange;
mod ethereum_exchange_submit;
mod ethereum_sync_loop;
mod instances;
mod rialto_client;
mod rpc_errors;
mod substrate_sync_loop;
mod substrate_types;
use ethereum_deploy_contract::EthereumDeployContractParams;
use ethereum_exchange::EthereumExchangeParams;
use ethereum_exchange_submit::EthereumExchangeSubmitParams;
use ethereum_sync_loop::EthereumSyncParams;
use headers_relay::sync::TargetTransactionMode;
use hex_literal::hex;
use instances::{BridgeInstance, Kovan, RialtoPoA};
use relay_utils::{initialize::initialize_relay, metrics::MetricsParams};
use secp256k1::SecretKey;
use sp_core::crypto::Pair;
use substrate_sync_loop::SubstrateSyncParams;
use headers_relay::sync::HeadersSyncParams;
use relay_ethereum_client::{ConnectionParams as EthereumConnectionParams, SigningParams as EthereumSigningParams};
use relay_rialto_client::SigningParams as RialtoSigningParams;
use relay_substrate_client::ConnectionParams as SubstrateConnectionParams;
use std::sync::Arc;
fn main() {
initialize_relay();
let yaml = clap::load_yaml!("cli.yml");
let matches = clap::App::from_yaml(yaml).get_matches();
match matches.subcommand() {
("eth-to-sub", Some(eth_to_sub_matches)) => {
log::info!(target: "bridge", "Starting ETH ➡ SUB relay.");
if ethereum_sync_loop::run(match ethereum_sync_params(&eth_to_sub_matches) {
Ok(ethereum_sync_params) => ethereum_sync_params,
Err(err) => {
log::error!(target: "bridge", "Error parsing parameters: {}", err);
return;
}
})
.is_err()
{
log::error!(target: "bridge", "Unable to get Substrate genesis block for Ethereum sync.");
};
}
("sub-to-eth", Some(sub_to_eth_matches)) => {
log::info!(target: "bridge", "Starting SUB ➡ ETH relay.");
if substrate_sync_loop::run(match substrate_sync_params(&sub_to_eth_matches) {
Ok(substrate_sync_params) => substrate_sync_params,
Err(err) => {
log::error!(target: "bridge", "Error parsing parameters: {}", err);
return;
}
})
.is_err()
{
log::error!(target: "bridge", "Unable to get Substrate genesis block for Substrate sync.");
};
}
("eth-deploy-contract", Some(eth_deploy_matches)) => {
log::info!(target: "bridge", "Deploying ETH contracts.");
ethereum_deploy_contract::run(match ethereum_deploy_contract_params(&eth_deploy_matches) {
Ok(ethereum_deploy_params) => ethereum_deploy_params,
Err(err) => {
log::error!(target: "bridge", "Error during contract deployment: {}", err);
return;
}
});
}
("eth-submit-exchange-tx", Some(eth_exchange_submit_matches)) => {
log::info!(target: "bridge", "Submitting ETH ➡ SUB exchange transaction.");
ethereum_exchange_submit::run(match ethereum_exchange_submit_params(&eth_exchange_submit_matches) {
Ok(eth_exchange_submit_params) => eth_exchange_submit_params,
Err(err) => {
log::error!(target: "bridge", "Error submitting Eethereum exchange transaction: {}", err);
return;
}
});
}
("eth-exchange-sub", Some(eth_exchange_matches)) => {
log::info!(target: "bridge", "Starting ETH ➡ SUB exchange transactions relay.");
ethereum_exchange::run(match ethereum_exchange_params(&eth_exchange_matches) {
Ok(eth_exchange_params) => eth_exchange_params,
Err(err) => {
log::error!(target: "bridge", "Error relaying Ethereum transactions proofs: {}", err);
return;
}
});
}
("", _) => {
log::error!(target: "bridge", "No subcommand specified");
}
_ => unreachable!("all possible subcommands are checked above; qed"),
}
}
fn ethereum_connection_params(matches: &clap::ArgMatches) -> Result<EthereumConnectionParams, String> {
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))?;
}
Ok(params)
}
fn ethereum_signing_params(matches: &clap::ArgMatches) -> Result<EthereumSigningParams, String> {
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))?)
.map_err(|e| format!("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))?;
}
Ok(params)
}
fn substrate_connection_params(matches: &clap::ArgMatches) -> Result<SubstrateConnectionParams, String> {
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))?;
}
Ok(params)
}
fn rialto_signing_params(matches: &clap::ArgMatches) -> Result<RialtoSigningParams, String> {
let mut params = RialtoSigningParams::default();
if let Some(sub_signer) = matches.value_of("sub-signer") {
let sub_signer_password = matches.value_of("sub-signer-password");
params.signer = sp_core::sr25519::Pair::from_string(sub_signer, sub_signer_password)
.map_err(|e| format!("Failed to parse sub-signer: {:?}", e))?;
}
Ok(params)
}
fn ethereum_sync_params(matches: &clap::ArgMatches) -> Result<EthereumSyncParams, String> {
use crate::ethereum_sync_loop::consts::*;
let mut sync_params = HeadersSyncParams {
max_future_headers_to_download: MAX_FUTURE_HEADERS_TO_DOWNLOAD,
max_headers_in_submitted_status: MAX_SUBMITTED_HEADERS,
max_headers_in_single_submit: MAX_HEADERS_IN_SINGLE_SUBMIT,
max_headers_size_in_single_submit: MAX_HEADERS_SIZE_IN_SINGLE_SUBMIT,
prune_depth: PRUNE_DEPTH,
target_tx_mode: TargetTransactionMode::Signed,
};
match matches.value_of("sub-tx-mode") {
Some("signed") => sync_params.target_tx_mode = TargetTransactionMode::Signed,
Some("unsigned") => {
sync_params.target_tx_mode = TargetTransactionMode::Unsigned;
// tx pool won't accept too much unsigned transactions
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)),
None => sync_params.target_tx_mode = TargetTransactionMode::Signed,
}
let params = EthereumSyncParams {
eth_params: ethereum_connection_params(matches)?,
sub_params: substrate_connection_params(matches)?,
sub_sign: rialto_signing_params(matches)?,
metrics_params: metrics_params(matches)?,
instance: instance_params(matches)?,
sync_params,
};
log::debug!(target: "bridge", "Ethereum sync params: {:?}", params);
Ok(params)
}
fn substrate_sync_params(matches: &clap::ArgMatches) -> Result<SubstrateSyncParams, String> {
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))?
} else {
"731a10897d267e19b34503ad902d0a29173ba4b1"
.parse()
.expect("address is hardcoded, thus valid; qed")
};
let params = SubstrateSyncParams {
sub_params: substrate_connection_params(matches)?,
eth_params: ethereum_connection_params(matches)?,
eth_sign: ethereum_signing_params(matches)?,
metrics_params: metrics_params(matches)?,
sync_params: HeadersSyncParams {
max_future_headers_to_download: MAX_FUTURE_HEADERS_TO_DOWNLOAD,
max_headers_in_submitted_status: MAX_SUBMITTED_HEADERS,
max_headers_in_single_submit: MAX_SUBMITTED_HEADERS,
max_headers_size_in_single_submit: std::usize::MAX,
prune_depth: PRUNE_DEPTH,
target_tx_mode: TargetTransactionMode::Signed,
},
eth_contract_address,
};
log::debug!(target: "bridge", "Substrate sync params: {:?}", params);
Ok(params)
}
fn ethereum_deploy_contract_params(matches: &clap::ArgMatches) -> Result<EthereumDeployContractParams, String> {
let eth_contract_code = parse_hex_argument(matches, "eth-contract-code")?.unwrap_or_else(|| {
hex::decode(include_str!("../res/substrate-bridge-bytecode.hex")).expect("code is hardcoded, thus valid; qed")
});
let sub_initial_authorities_set_id = match matches.value_of("sub-authorities-set-id") {
Some(sub_initial_authorities_set_id) => Some(
sub_initial_authorities_set_id
.parse()
.map_err(|e| format!("Failed to parse sub-authorities-set-id: {}", e))?,
),
None => None,
};
let sub_initial_authorities_set = parse_hex_argument(matches, "sub-authorities-set")?;
let sub_initial_header = parse_hex_argument(matches, "sub-initial-header")?;
let params = EthereumDeployContractParams {
eth_params: ethereum_connection_params(matches)?,
eth_sign: ethereum_signing_params(matches)?,
sub_params: substrate_connection_params(matches)?,
sub_initial_authorities_set_id,
sub_initial_authorities_set,
sub_initial_header,
eth_contract_code,
};
log::debug!(target: "bridge", "Deploy params: {:?}", params);
Ok(params)
}
fn ethereum_exchange_submit_params(matches: &clap::ArgMatches) -> Result<EthereumExchangeSubmitParams, String> {
let eth_nonce = if let Some(eth_nonce) = matches.value_of("eth-nonce") {
Some(
relay_ethereum_client::types::U256::from_dec_str(&eth_nonce)
.map_err(|e| format!("Failed to parse eth-nonce: {}", e))?,
)
} else {
None
};
let eth_amount = if let Some(eth_amount) = matches.value_of("eth-amount") {
eth_amount
.parse()
.map_err(|e| format!("Failed to parse eth-amount: {}", e))?
} else {
// This is in Wei, represents 1 ETH
1_000_000_000_000_000_000_u64.into()
};
// This is the well-known Substrate account of Ferdie
let default_recepient = hex!("1cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c");
let sub_recipient = if let Some(sub_recipient) = matches.value_of("sub-recipient") {
hex::decode(&sub_recipient)
.map_err(|err| err.to_string())
.and_then(|vsub_recipient| {
let expected_len = default_recepient.len();
if expected_len != vsub_recipient.len() {
Err(format!("invalid length. Expected {} bytes", expected_len))
} else {
let mut sub_recipient = default_recepient;
sub_recipient.copy_from_slice(&vsub_recipient[..expected_len]);
Ok(sub_recipient)
}
})
.map_err(|e| format!("Failed to parse sub-recipient: {}", e))?
} else {
default_recepient
};
let params = EthereumExchangeSubmitParams {
eth_params: ethereum_connection_params(matches)?,
eth_sign: ethereum_signing_params(matches)?,
eth_nonce,
eth_amount,
sub_recipient,
};
log::debug!(target: "bridge", "Submit Ethereum exchange tx params: {:?}", params);
Ok(params)
}
fn ethereum_exchange_params(matches: &clap::ArgMatches) -> Result<EthereumExchangeParams, String> {
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))?,
),
None => ethereum_exchange::ExchangeRelayMode::Auto(match matches.value_of("eth-start-with-block") {
Some(eth_start_with_block) => Some(
eth_start_with_block
.parse()
.map_err(|e| format!("Failed to parse eth-start-with-block: {}", e))?,
),
None => None,
}),
};
let params = EthereumExchangeParams {
eth_params: ethereum_connection_params(matches)?,
sub_params: substrate_connection_params(matches)?,
sub_sign: rialto_signing_params(matches)?,
metrics_params: metrics_params(matches)?,
instance: instance_params(matches)?,
mode,
};
log::debug!(target: "bridge", "Ethereum exchange params: {:?}", params);
Ok(params)
}
fn metrics_params(matches: &clap::ArgMatches) -> Result<Option<MetricsParams>, String> {
if matches.is_present("no-prometheus") {
return Ok(None);
}
let mut metrics_params = MetricsParams::default();
if let Some(prometheus_host) = matches.value_of("prometheus-host") {
metrics_params.host = prometheus_host.into();
}
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))?;
}
Ok(Some(metrics_params))
}
fn instance_params(matches: &clap::ArgMatches) -> Result<Arc<dyn BridgeInstance>, String> {
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()),
}
} else {
unreachable!("CLI config enforces a default instance, can never be None")
};
Ok(instance)
}
fn parse_hex_argument(matches: &clap::ArgMatches, arg: &str) -> Result<Option<Vec<u8>>, String> {
match matches.value_of(arg) {
Some(value) => Ok(Some(
hex::decode(value).map_err(|e| format!("Failed to parse {}: {}", arg, e))?,
)),
None => Ok(None),
}
}
#[cfg(test)]
mod tests {
// Details: https://github.com/paritytech/parity-bridges-common/issues/118
#[test]
fn async_std_sleep_works() {
let mut local_pool = futures::executor::LocalPool::new();
local_pool.run_until(async move {
async_std::task::sleep(std::time::Duration::from_secs(1)).await;
});
}
}
@@ -0,0 +1,269 @@
// 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 <http://www.gnu.org/licenses/>.
use crate::ethereum_sync_loop::QueuedEthereumHeader;
use crate::instances::BridgeInstance;
use crate::rpc_errors::RpcError;
use async_trait::async_trait;
use bp_eth_poa::AuraHeader as SubstrateEthereumHeader;
use codec::{Decode, Encode};
use headers_relay::sync_types::SubmittedHeaders;
use relay_ethereum_client::types::HeaderId as EthereumHeaderId;
use relay_rialto_client::{Rialto, SigningParams as RialtoSigningParams};
use relay_substrate_client::{Client as SubstrateClient, TransactionSignScheme};
use relay_utils::HeaderId;
use sp_core::{crypto::Pair, Bytes};
use std::{collections::VecDeque, sync::Arc};
const ETH_API_IMPORT_REQUIRES_RECEIPTS: &str = "RialtoPoAHeaderApi_is_import_requires_receipts";
const ETH_API_IS_KNOWN_BLOCK: &str = "RialtoPoAHeaderApi_is_known_block";
const ETH_API_BEST_BLOCK: &str = "RialtoPoAHeaderApi_best_block";
const ETH_API_BEST_FINALIZED_BLOCK: &str = "RialtoPoAHeaderApi_finalized_block";
const EXCH_API_FILTER_TRANSACTION_PROOF: &str = "RialtoCurrencyExchangeApi_filter_transaction_proof";
type RpcResult<T> = std::result::Result<T, RpcError>;
/// A trait which contains methods that work by using multiple low-level RPCs, or more complicated
/// interactions involving, for example, an Ethereum bridge module.
#[async_trait]
pub trait SubstrateHighLevelRpc {
/// Returns best Ethereum block that Substrate runtime knows of.
async fn best_ethereum_block(&self) -> RpcResult<EthereumHeaderId>;
/// Returns best finalized Ethereum block that Substrate runtime knows of.
async fn best_ethereum_finalized_block(&self) -> RpcResult<EthereumHeaderId>;
/// Returns whether or not transactions receipts are required for Ethereum header submission.
async fn ethereum_receipts_required(&self, header: SubstrateEthereumHeader) -> RpcResult<bool>;
/// Returns whether or not the given Ethereum header is known to the Substrate runtime.
async fn ethereum_header_known(&self, header_id: EthereumHeaderId) -> RpcResult<bool>;
}
#[async_trait]
impl SubstrateHighLevelRpc for SubstrateClient<Rialto> {
async fn best_ethereum_block(&self) -> RpcResult<EthereumHeaderId> {
let call = ETH_API_BEST_BLOCK.to_string();
let data = Bytes(Vec::new());
let encoded_response = self.state_call(call, data, None).await?;
let decoded_response: (u64, bp_eth_poa::H256) = Decode::decode(&mut &encoded_response.0[..])?;
let best_header_id = HeaderId(decoded_response.0, decoded_response.1);
Ok(best_header_id)
}
async fn best_ethereum_finalized_block(&self) -> RpcResult<EthereumHeaderId> {
let call = ETH_API_BEST_FINALIZED_BLOCK.to_string();
let data = Bytes(Vec::new());
let encoded_response = self.state_call(call, data, None).await?;
let decoded_response: (u64, bp_eth_poa::H256) = Decode::decode(&mut &encoded_response.0[..])?;
let best_header_id = HeaderId(decoded_response.0, decoded_response.1);
Ok(best_header_id)
}
async fn ethereum_receipts_required(&self, header: SubstrateEthereumHeader) -> RpcResult<bool> {
let call = ETH_API_IMPORT_REQUIRES_RECEIPTS.to_string();
let data = Bytes(header.encode());
let encoded_response = self.state_call(call, data, None).await?;
let receipts_required: bool = Decode::decode(&mut &encoded_response.0[..])?;
Ok(receipts_required)
}
// The Substrate module could prune old headers. So this function could return false even
// if header is synced. And we'll mark corresponding Ethereum header as Orphan.
//
// But when we read the best header from Substrate next time, we will know that
// there's a better header. This Orphan will either be marked as synced, or
// eventually pruned.
async fn ethereum_header_known(&self, header_id: EthereumHeaderId) -> RpcResult<bool> {
let call = ETH_API_IS_KNOWN_BLOCK.to_string();
let data = Bytes(header_id.1.encode());
let encoded_response = self.state_call(call, data, None).await?;
let is_known_block: bool = Decode::decode(&mut &encoded_response.0[..])?;
Ok(is_known_block)
}
}
/// A trait for RPC calls which are used to submit Ethereum headers to a Substrate
/// runtime. These are typically calls which use a combination of other low-level RPC
/// calls.
#[async_trait]
pub trait SubmitEthereumHeaders {
/// Submits Ethereum header to Substrate runtime.
async fn submit_ethereum_headers(
&self,
params: RialtoSigningParams,
instance: Arc<dyn BridgeInstance>,
headers: Vec<QueuedEthereumHeader>,
sign_transactions: bool,
) -> SubmittedHeaders<EthereumHeaderId, RpcError>;
/// Submits signed Ethereum header to Substrate runtime.
async fn submit_signed_ethereum_headers(
&self,
params: RialtoSigningParams,
instance: Arc<dyn BridgeInstance>,
headers: Vec<QueuedEthereumHeader>,
) -> SubmittedHeaders<EthereumHeaderId, RpcError>;
/// Submits unsigned Ethereum header to Substrate runtime.
async fn submit_unsigned_ethereum_headers(
&self,
instance: Arc<dyn BridgeInstance>,
headers: Vec<QueuedEthereumHeader>,
) -> SubmittedHeaders<EthereumHeaderId, RpcError>;
}
#[async_trait]
impl SubmitEthereumHeaders for SubstrateClient<Rialto> {
async fn submit_ethereum_headers(
&self,
params: RialtoSigningParams,
instance: Arc<dyn BridgeInstance>,
headers: Vec<QueuedEthereumHeader>,
sign_transactions: bool,
) -> SubmittedHeaders<EthereumHeaderId, RpcError> {
if sign_transactions {
self.submit_signed_ethereum_headers(params, instance, headers).await
} else {
self.submit_unsigned_ethereum_headers(instance, headers).await
}
}
async fn submit_signed_ethereum_headers(
&self,
params: RialtoSigningParams,
instance: Arc<dyn BridgeInstance>,
headers: Vec<QueuedEthereumHeader>,
) -> SubmittedHeaders<EthereumHeaderId, RpcError> {
let ids = headers.iter().map(|header| header.id()).collect();
let submission_result = async {
let account_id = params.signer.public().as_array_ref().clone().into();
let nonce = self.next_account_index(account_id).await?;
let call = instance.build_signed_header_call(headers);
let transaction = Rialto::sign_transaction(self, &params.signer, nonce, call);
let _ = self.submit_extrinsic(Bytes(transaction.encode())).await?;
Ok(())
}
.await;
match submission_result {
Ok(_) => SubmittedHeaders {
submitted: ids,
incomplete: Vec::new(),
rejected: Vec::new(),
fatal_error: None,
},
Err(error) => SubmittedHeaders {
submitted: Vec::new(),
incomplete: Vec::new(),
rejected: ids,
fatal_error: Some(error),
},
}
}
async fn submit_unsigned_ethereum_headers(
&self,
instance: Arc<dyn BridgeInstance>,
headers: Vec<QueuedEthereumHeader>,
) -> SubmittedHeaders<EthereumHeaderId, RpcError> {
let mut ids = headers.iter().map(|header| header.id()).collect::<VecDeque<_>>();
let mut submitted_headers = SubmittedHeaders::default();
for header in headers {
let id = ids.pop_front().expect("both collections have same size; qed");
let call = instance.build_unsigned_header_call(header);
let transaction = create_unsigned_submit_transaction(call);
match self.submit_extrinsic(Bytes(transaction.encode())).await {
Ok(_) => submitted_headers.submitted.push(id),
Err(error) => {
submitted_headers.rejected.push(id);
submitted_headers.rejected.extend(ids);
submitted_headers.fatal_error = Some(error.into());
break;
}
}
}
submitted_headers
}
}
/// A trait for RPC calls which are used to submit proof of Ethereum exchange transaction to a
/// Substrate runtime. These are typically calls which use a combination of other low-level RPC
/// calls.
#[async_trait]
pub trait SubmitEthereumExchangeTransactionProof {
/// Pre-verify Ethereum exchange transaction proof.
async fn verify_exchange_transaction_proof(
&self,
proof: rialto_runtime::exchange::EthereumTransactionInclusionProof,
) -> RpcResult<bool>;
/// Submits Ethereum exchange transaction proof to Substrate runtime.
async fn submit_exchange_transaction_proof(
&self,
params: RialtoSigningParams,
instance: Arc<dyn BridgeInstance>,
proof: rialto_runtime::exchange::EthereumTransactionInclusionProof,
) -> RpcResult<()>;
}
#[async_trait]
impl SubmitEthereumExchangeTransactionProof for SubstrateClient<Rialto> {
async fn verify_exchange_transaction_proof(
&self,
proof: rialto_runtime::exchange::EthereumTransactionInclusionProof,
) -> RpcResult<bool> {
let call = EXCH_API_FILTER_TRANSACTION_PROOF.to_string();
let data = Bytes(proof.encode());
let encoded_response = self.state_call(call, data, None).await?;
let is_allowed: bool = Decode::decode(&mut &encoded_response.0[..])?;
Ok(is_allowed)
}
async fn submit_exchange_transaction_proof(
&self,
params: RialtoSigningParams,
instance: Arc<dyn BridgeInstance>,
proof: rialto_runtime::exchange::EthereumTransactionInclusionProof,
) -> RpcResult<()> {
let account_id = params.signer.public().as_array_ref().clone().into();
let nonce = self.next_account_index(account_id).await?;
let call = instance.build_currency_exchange_call(proof);
let transaction = Rialto::sign_transaction(self, &params.signer, nonce, call);
let _ = self.submit_extrinsic(Bytes(transaction.encode())).await?;
Ok(())
}
}
/// Create unsigned Substrate transaction for submitting Ethereum header.
fn create_unsigned_submit_transaction(call: rialto_runtime::Call) -> rialto_runtime::UncheckedExtrinsic {
rialto_runtime::UncheckedExtrinsic::new_unsigned(call)
}
@@ -0,0 +1,82 @@
// 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 <http://www.gnu.org/licenses/>.
use relay_ethereum_client::Error as EthereumNodeError;
use relay_substrate_client::Error as SubstrateNodeError;
use relay_utils::MaybeConnectionError;
/// Contains common errors that can occur when
/// interacting with a Substrate or Ethereum node
/// through RPC.
#[derive(Debug)]
pub enum RpcError {
/// The arguments to the RPC method failed to serialize.
Serialization(serde_json::Error),
/// An error occured when interacting with an Ethereum node.
Ethereum(EthereumNodeError),
/// An error occured when interacting with a Substrate node.
Substrate(SubstrateNodeError),
}
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(),
}
}
}
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)
}
}
impl From<ethabi::Error> for RpcError {
fn from(err: ethabi::Error) -> Self {
Self::Ethereum(EthereumNodeError::ResponseParseFailed(format!("{}", err)))
}
}
impl MaybeConnectionError for RpcError {
fn is_connection_error(&self) -> bool {
match self {
RpcError::Ethereum(ref error) => error.is_connection_error(),
RpcError::Substrate(ref error) => error.is_connection_error(),
_ => false,
}
}
}
impl From<codec::Error> for RpcError {
fn from(err: codec::Error) -> Self {
Self::Substrate(SubstrateNodeError::ResponseParseFailed(err))
}
}
@@ -0,0 +1,195 @@
// 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 <http://www.gnu.org/licenses/>.
//! Rialto-Substrate -> Ethereum PoA synchronization.
use crate::ethereum_client::EthereumHighLevelRpc;
use crate::rpc_errors::RpcError;
use async_trait::async_trait;
use codec::Encode;
use headers_relay::{
sync::HeadersSyncParams,
sync_loop::TargetClient,
sync_types::{HeadersSyncPipeline, QueuedHeader, SourceHeader, SubmittedHeaders},
};
use relay_ethereum_client::{
types::Address, Client as EthereumClient, ConnectionParams as EthereumConnectionParams,
SigningParams as EthereumSigningParams,
};
use relay_rialto_client::{HeaderId as RialtoHeaderId, Rialto, SyncHeader as RialtoSyncHeader};
use relay_substrate_client::{
headers_source::HeadersSource, Chain as SubstrateChain, Client as SubstrateClient,
ConnectionParams as SubstrateConnectionParams,
};
use relay_utils::{metrics::MetricsParams, relay_loop::Client as RelayClient};
use sp_runtime::Justification;
use std::fmt::Debug;
use std::{collections::HashSet, time::Duration};
pub mod consts {
use super::*;
/// Interval at which we check new Ethereum blocks.
pub const ETHEREUM_TICK_INTERVAL: Duration = Duration::from_secs(5);
/// Max Ethereum headers we want to have in all 'before-submitted' states.
pub const MAX_FUTURE_HEADERS_TO_DOWNLOAD: usize = 8;
/// Max Ethereum headers count we want to have in 'submitted' state.
pub const MAX_SUBMITTED_HEADERS: usize = 4;
/// Max depth of in-memory headers in all states. Past this depth they will be forgotten (pruned).
pub const PRUNE_DEPTH: u32 = 256;
}
/// Substrate synchronization parameters.
#[derive(Debug)]
pub struct SubstrateSyncParams {
/// Substrate connection params.
pub sub_params: SubstrateConnectionParams,
/// Ethereum connection params.
pub eth_params: EthereumConnectionParams,
/// Ethereum signing params.
pub eth_sign: EthereumSigningParams,
/// Ethereum bridge contract address.
pub eth_contract_address: Address,
/// Synchronization parameters.
pub sync_params: HeadersSyncParams,
/// Metrics parameters.
pub metrics_params: Option<MetricsParams>,
}
/// Substrate synchronization pipeline.
#[derive(Clone, Copy, Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct SubstrateHeadersSyncPipeline;
impl HeadersSyncPipeline for SubstrateHeadersSyncPipeline {
const SOURCE_NAME: &'static str = "Substrate";
const TARGET_NAME: &'static str = "Ethereum";
type Hash = rialto_runtime::Hash;
type Number = rialto_runtime::BlockNumber;
type Header = RialtoSyncHeader;
type Extra = ();
type Completion = Justification;
fn estimate_size(source: &QueuedHeader<Self>) -> usize {
source.header().encode().len()
}
}
/// Queued substrate header ID.
pub type QueuedRialtoHeader = QueuedHeader<SubstrateHeadersSyncPipeline>;
/// Rialto node as headers source.
type SubstrateHeadersSource = HeadersSource<Rialto, SubstrateHeadersSyncPipeline>;
/// Ethereum client as Substrate headers target.
#[derive(Clone)]
struct EthereumHeadersTarget {
/// Ethereum node client.
client: EthereumClient,
/// Bridge contract address.
contract: Address,
/// Ethereum signing params.
sign_params: EthereumSigningParams,
}
impl EthereumHeadersTarget {
fn new(client: EthereumClient, contract: Address, sign_params: EthereumSigningParams) -> Self {
Self {
client,
contract,
sign_params,
}
}
}
#[async_trait]
impl RelayClient for EthereumHeadersTarget {
type Error = RpcError;
async fn reconnect(&mut self) -> Result<(), RpcError> {
self.client.reconnect();
Ok(())
}
}
#[async_trait]
impl TargetClient<SubstrateHeadersSyncPipeline> for EthereumHeadersTarget {
async fn best_header_id(&self) -> Result<RialtoHeaderId, RpcError> {
// we can't continue to relay headers if Ethereum node is out of sync, because
// it may have already received (some of) headers that we're going to relay
self.client.ensure_synced().await?;
self.client.best_substrate_block(self.contract).await
}
async fn is_known_header(&self, id: RialtoHeaderId) -> Result<(RialtoHeaderId, bool), RpcError> {
self.client.substrate_header_known(self.contract, id).await
}
async fn submit_headers(&self, headers: Vec<QueuedRialtoHeader>) -> SubmittedHeaders<RialtoHeaderId, RpcError> {
self.client
.submit_substrate_headers(self.sign_params.clone(), self.contract, headers)
.await
}
async fn incomplete_headers_ids(&self) -> Result<HashSet<RialtoHeaderId>, RpcError> {
self.client.incomplete_substrate_headers(self.contract).await
}
async fn complete_header(&self, id: RialtoHeaderId, completion: Justification) -> Result<RialtoHeaderId, RpcError> {
self.client
.complete_substrate_header(self.sign_params.clone(), self.contract, id, completion)
.await
}
async fn requires_extra(&self, header: QueuedRialtoHeader) -> Result<(RialtoHeaderId, bool), RpcError> {
Ok((header.header().id(), false))
}
}
/// Run Substrate headers synchronization.
pub fn run(params: SubstrateSyncParams) -> Result<(), RpcError> {
let SubstrateSyncParams {
sub_params,
eth_params,
eth_sign,
eth_contract_address,
sync_params,
metrics_params,
} = params;
let eth_client = EthereumClient::new(eth_params);
let sub_client = async_std::task::block_on(async { SubstrateClient::<Rialto>::new(sub_params).await })?;
let target = EthereumHeadersTarget::new(eth_client, eth_contract_address, eth_sign);
let source = SubstrateHeadersSource::new(sub_client);
headers_relay::sync_loop::run(
source,
Rialto::AVERAGE_BLOCK_INTERVAL,
target,
consts::ETHEREUM_TICK_INTERVAL,
(),
sync_params,
metrics_params,
futures::future::pending(),
);
Ok(())
}
@@ -0,0 +1,76 @@
// Copyright 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 <http://www.gnu.org/licenses/>.
//! Converting between Ethereum headers and bridge module types.
use bp_eth_poa::{
AuraHeader as SubstrateEthereumHeader, LogEntry as SubstrateEthereumLogEntry, Receipt as SubstrateEthereumReceipt,
TransactionOutcome as SubstrateEthereumTransactionOutcome,
};
use relay_ethereum_client::types::{
Header as EthereumHeader, Receipt as EthereumReceipt, HEADER_ID_PROOF as ETHEREUM_HEADER_ID_PROOF,
};
/// Convert Ethereum header into Ethereum header for Substrate.
pub fn into_substrate_ethereum_header(header: &EthereumHeader) -> SubstrateEthereumHeader {
SubstrateEthereumHeader {
parent_hash: header.parent_hash,
timestamp: header.timestamp.as_u64(),
number: header.number.expect(ETHEREUM_HEADER_ID_PROOF).as_u64(),
author: header.author,
transactions_root: header.transactions_root,
uncles_hash: header.uncles_hash,
extra_data: header.extra_data.0.clone(),
state_root: header.state_root,
receipts_root: header.receipts_root,
log_bloom: header.logs_bloom.unwrap_or_default().data().into(),
gas_used: header.gas_used,
gas_limit: header.gas_limit,
difficulty: header.difficulty,
seal: header.seal_fields.iter().map(|s| s.0.clone()).collect(),
}
}
/// Convert Ethereum transactions receipts into Ethereum transactions receipts for Substrate.
pub fn into_substrate_ethereum_receipts(
receipts: &Option<Vec<EthereumReceipt>>,
) -> Option<Vec<SubstrateEthereumReceipt>> {
receipts
.as_ref()
.map(|receipts| receipts.iter().map(into_substrate_ethereum_receipt).collect())
}
/// Convert Ethereum transactions receipt into Ethereum transactions receipt for Substrate.
pub fn into_substrate_ethereum_receipt(receipt: &EthereumReceipt) -> SubstrateEthereumReceipt {
SubstrateEthereumReceipt {
gas_used: receipt.cumulative_gas_used,
log_bloom: receipt.logs_bloom.data().into(),
logs: receipt
.logs
.iter()
.map(|log_entry| SubstrateEthereumLogEntry {
address: log_entry.address,
topics: log_entry.topics.clone(),
data: log_entry.data.0.clone(),
})
.collect(),
outcome: match (receipt.status, receipt.root) {
(Some(status), None) => SubstrateEthereumTransactionOutcome::StatusCode(status.as_u64() as u8),
(None, Some(root)) => SubstrateEthereumTransactionOutcome::StateRoot(root),
_ => SubstrateEthereumTransactionOutcome::Unknown,
},
}
}
@@ -0,0 +1,16 @@
[package]
name = "exchange-relay"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
async-std = "1.6.5"
async-trait = "0.1.40"
backoff = "0.2"
futures = "0.3.5"
log = "0.4.11"
num-traits = "0.2"
parking_lot = "0.11.0"
relay-utils = { path = "../utils" }
@@ -0,0 +1,916 @@
// 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 <http://www.gnu.org/licenses/>.
//! Relaying proofs of exchange transaction.
use async_trait::async_trait;
use relay_utils::{
relay_loop::Client as RelayClient, FailedClient, MaybeConnectionError, StringifiedMaybeConnectionError,
};
use std::{
fmt::{Debug, Display},
string::ToString,
};
/// Transaction proof pipeline.
pub trait TransactionProofPipeline {
/// Name of the transaction proof source.
const SOURCE_NAME: &'static str;
/// Name of the transaction proof target.
const TARGET_NAME: &'static str;
/// Block type.
type Block: SourceBlock;
/// Transaction inclusion proof type.
type TransactionProof;
}
/// Block that is participating in exchange.
pub trait SourceBlock {
/// Block hash type.
type Hash: Clone + Debug + Display;
/// Block number type.
type Number: Debug
+ Display
+ Clone
+ Copy
+ Into<u64>
+ std::cmp::Ord
+ std::ops::Add<Output = Self::Number>
+ num_traits::One;
/// Block transaction.
type Transaction: SourceTransaction;
/// Return hash of the block.
fn id(&self) -> relay_utils::HeaderId<Self::Hash, Self::Number>;
/// Return block transactions iterator.
fn transactions(&self) -> Vec<Self::Transaction>;
}
/// Transaction that is participating in exchange.
pub trait SourceTransaction {
/// Transaction hash type.
type Hash: Debug + Display;
/// Return transaction hash.
fn hash(&self) -> Self::Hash;
}
/// Block hash for given pipeline.
pub type BlockHashOf<P> = <<P as TransactionProofPipeline>::Block as SourceBlock>::Hash;
/// Block number for given pipeline.
pub type BlockNumberOf<P> = <<P as TransactionProofPipeline>::Block as SourceBlock>::Number;
/// Transaction hash for given pipeline.
pub type TransactionOf<P> = <<P as TransactionProofPipeline>::Block as SourceBlock>::Transaction;
/// Transaction hash for given pipeline.
pub type TransactionHashOf<P> = <TransactionOf<P> as SourceTransaction>::Hash;
/// Header id.
pub type HeaderId<P> = relay_utils::HeaderId<BlockHashOf<P>, BlockNumberOf<P>>;
/// Source client API.
#[async_trait]
pub trait SourceClient<P: TransactionProofPipeline>: RelayClient {
/// Sleep until exchange-related data is (probably) updated.
async fn tick(&self);
/// Get block by hash.
async fn block_by_hash(&self, hash: BlockHashOf<P>) -> Result<P::Block, Self::Error>;
/// Get canonical block by number.
async fn block_by_number(&self, number: BlockNumberOf<P>) -> Result<P::Block, Self::Error>;
/// Return block + index where transaction has been **mined**. May return `Ok(None)` if transaction
/// is unknown to the source node.
async fn transaction_block(&self, hash: &TransactionHashOf<P>)
-> Result<Option<(HeaderId<P>, usize)>, Self::Error>;
/// Prepare transaction proof.
async fn transaction_proof(&self, block: &P::Block, tx_index: usize) -> Result<P::TransactionProof, Self::Error>;
}
/// Target client API.
#[async_trait]
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>;
/// 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.
async fn best_finalized_header_id(&self) -> Result<HeaderId<P>, Self::Error>;
/// Returns `Ok(true)` if transaction proof is need to be relayed.
async fn filter_transaction_proof(&self, proof: &P::TransactionProof) -> Result<bool, Self::Error>;
/// Submits transaction proof to the target node.
async fn submit_transaction_proof(&self, proof: P::TransactionProof) -> Result<(), Self::Error>;
}
/// Block transaction statistics.
#[derive(Debug, Default)]
#[cfg_attr(test, derive(PartialEq))]
pub struct RelayedBlockTransactions {
/// Total number of transactions processed (either relayed or ignored) so far.
pub processed: usize,
/// Total number of transactions successfully relayed so far.
pub relayed: usize,
/// Total number of transactions that we have failed to relay so far.
pub failed: usize,
}
/// Relay all suitable transactions from single block.
///
/// If connection error occurs, returns Err with number of successfully processed transactions.
/// If some other error occurs, it is ignored and other transactions are processed.
///
/// All transaction-level traces are written by this function. This function is not tracing
/// any information about block.
pub async fn relay_block_transactions<P: TransactionProofPipeline>(
source_client: &impl SourceClient<P>,
target_client: &impl TargetClient<P>,
source_block: &P::Block,
mut relayed_transactions: RelayedBlockTransactions,
) -> Result<RelayedBlockTransactions, (FailedClient, RelayedBlockTransactions)> {
let transactions_to_process = source_block
.transactions()
.into_iter()
.enumerate()
.skip(relayed_transactions.processed);
for (source_tx_index, source_tx) in transactions_to_process {
let result = async {
let source_tx_id = format!("{}/{}", source_block.id().1, source_tx_index);
let source_tx_proof =
prepare_transaction_proof(source_client, &source_tx_id, source_block, source_tx_index)
.await
.map_err(|e| (FailedClient::Source, e))?;
let needs_to_be_relayed =
target_client
.filter_transaction_proof(&source_tx_proof)
.await
.map_err(|err| {
(
FailedClient::Target,
StringifiedMaybeConnectionError::new(
err.is_connection_error(),
format!("Transaction filtering has failed with {:?}", err),
),
)
})?;
if !needs_to_be_relayed {
return Ok(false);
}
relay_ready_transaction_proof(target_client, &source_tx_id, source_tx_proof)
.await
.map(|_| true)
.map_err(|e| (FailedClient::Target, e))
}
.await;
// We have two options here:
// 1) retry with the same transaction later;
// 2) report error and proceed with next transaction.
//
// Option#1 may seems better, but:
// 1) we do not track if transaction is mined (without an error) by the target node;
// 2) error could be irrecoverable (e.g. when block is already pruned by bridge module or tx
// has invalid format) && we'll end up in infinite loop of retrying the same transaction proof.
//
// So we're going with option#2 here (the only exception are connection errors).
match result {
Ok(false) => {
relayed_transactions.processed += 1;
}
Ok(true) => {
log::info!(
target: "bridge",
"{} transaction {} proof has been successfully submitted to {} node",
P::SOURCE_NAME,
source_tx.hash(),
P::TARGET_NAME,
);
relayed_transactions.processed += 1;
relayed_transactions.relayed += 1;
}
Err((failed_client, err)) => {
log::error!(
target: "bridge",
"Error relaying {} transaction {} proof to {} node: {}. {}",
P::SOURCE_NAME,
source_tx.hash(),
P::TARGET_NAME,
err.to_string(),
if err.is_connection_error() {
"Going to retry after delay..."
} else {
"You may need to submit proof of this transaction manually"
},
);
if err.is_connection_error() {
return Err((failed_client, relayed_transactions));
}
relayed_transactions.processed += 1;
relayed_transactions.failed += 1;
}
}
}
Ok(relayed_transactions)
}
/// Relay single transaction proof.
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> {
// 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,
)
})?;
// 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?;
// and finally - prepare and submit transaction proof to target node
let source_tx_id = format!("{}", source_tx_hash);
relay_ready_transaction_proof(
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
.map_err(|err| err.to_string())
}
/// Prepare transaction proof.
async fn prepare_transaction_proof<P: TransactionProofPipeline>(
source_client: &impl SourceClient<P>,
source_tx_id: &str,
source_block: &P::Block,
source_tx_index: usize,
) -> Result<P::TransactionProof, StringifiedMaybeConnectionError> {
source_client
.transaction_proof(source_block, source_tx_index)
.await
.map_err(|err| {
StringifiedMaybeConnectionError::new(
err.is_connection_error(),
format!(
"Error building transaction {} proof on {} node: {:?}",
source_tx_id,
P::SOURCE_NAME,
err,
),
)
})
}
/// Relay prepared proof of transaction.
async fn relay_ready_transaction_proof<P: TransactionProofPipeline>(
target_client: &impl TargetClient<P>,
source_tx_id: &str,
source_tx_proof: P::TransactionProof,
) -> Result<(), StringifiedMaybeConnectionError> {
target_client
.submit_transaction_proof(source_tx_proof)
.await
.map_err(|err| {
StringifiedMaybeConnectionError::new(
err.is_connection_error(),
format!(
"Error submitting transaction {} proof to {} node: {:?}",
source_tx_id,
P::TARGET_NAME,
err,
),
)
})
}
/// Wait until transaction is mined by source node.
async fn wait_transaction_mined<P: TransactionProofPipeline>(
source_client: &impl SourceClient<P>,
source_tx_hash: &TransactionHashOf<P>,
) -> Result<(HeaderId<P>, usize), String> {
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,
P::SOURCE_NAME,
err,
)
})?;
match source_header_and_tx {
Some((source_header_id, source_tx)) => {
log::info!(
target: "bridge",
"Transaction {} is retrieved from {} node. Continuing...",
source_tx_hash,
P::SOURCE_NAME,
);
return Ok((source_header_id, source_tx));
}
None => {
log::info!(
target: "bridge",
"Waiting for transaction {} to be mined by {} node...",
source_tx_hash,
P::SOURCE_NAME,
);
source_client.tick().await;
}
}
}
}
/// Wait until target node imports required header.
async fn wait_header_imported<P: TransactionProofPipeline>(
target_client: &impl TargetClient<P>,
source_header_id: &HeaderId<P>,
) -> Result<(), String> {
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: {:?}",
source_header_id.0,
source_header_id.1,
P::TARGET_NAME,
err,
)
})?;
match is_header_known {
true => {
log::info!(
target: "bridge",
"Header {}/{} is known to {} node. Continuing.",
source_header_id.0,
source_header_id.1,
P::TARGET_NAME,
);
return Ok(());
}
false => {
log::info!(
target: "bridge",
"Waiting for header {}/{} to be imported by {} node...",
source_header_id.0,
source_header_id.1,
P::TARGET_NAME,
);
target_client.tick().await;
}
}
}
}
/// Wait until target node finalizes required header.
async fn wait_header_finalized<P: TransactionProofPipeline>(
target_client: &impl TargetClient<P>,
source_header_id: &HeaderId<P>,
) -> Result<(), String> {
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: {:?}",
source_header_id.0,
source_header_id.1,
P::TARGET_NAME,
err,
)
})?;
match is_header_finalized {
true => {
log::info!(
target: "bridge",
"Header {}/{} is finalizd by {} node. Continuing.",
source_header_id.0,
source_header_id.1,
P::TARGET_NAME,
);
return Ok(());
}
false => {
log::info!(
target: "bridge",
"Waiting for header {}/{} to be finalized by {} node...",
source_header_id.0,
source_header_id.1,
P::TARGET_NAME,
);
target_client.tick().await;
}
}
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use parking_lot::Mutex;
use relay_utils::HeaderId;
use std::{
collections::{HashMap, HashSet},
sync::Arc,
};
pub fn test_block_id() -> TestHeaderId {
HeaderId(1, 1)
}
pub fn test_next_block_id() -> TestHeaderId {
HeaderId(2, 2)
}
pub fn test_transaction_hash(tx_index: u64) -> TestTransactionHash {
200 + tx_index
}
pub fn test_transaction(tx_index: u64) -> TestTransaction {
TestTransaction(test_transaction_hash(tx_index))
}
pub fn test_block() -> TestBlock {
TestBlock(test_block_id(), vec![test_transaction(0)])
}
pub fn test_next_block() -> TestBlock {
TestBlock(test_next_block_id(), vec![test_transaction(1)])
}
pub type TestBlockNumber = u64;
pub type TestBlockHash = u64;
pub type TestTransactionHash = u64;
pub type TestHeaderId = HeaderId<TestBlockHash, TestBlockNumber>;
#[derive(Debug, Clone, PartialEq)]
pub struct TestError(pub bool);
impl MaybeConnectionError for TestError {
fn is_connection_error(&self) -> bool {
self.0
}
}
pub struct TestTransactionProofPipeline;
impl TransactionProofPipeline for TestTransactionProofPipeline {
const SOURCE_NAME: &'static str = "TestSource";
const TARGET_NAME: &'static str = "TestTarget";
type Block = TestBlock;
type TransactionProof = TestTransactionProof;
}
#[derive(Debug, Clone)]
pub struct TestBlock(pub TestHeaderId, pub Vec<TestTransaction>);
impl SourceBlock for TestBlock {
type Hash = TestBlockHash;
type Number = TestBlockNumber;
type Transaction = TestTransaction;
fn id(&self) -> TestHeaderId {
self.0
}
fn transactions(&self) -> Vec<TestTransaction> {
self.1.clone()
}
}
#[derive(Debug, Clone)]
pub struct TestTransaction(pub TestTransactionHash);
impl SourceTransaction for TestTransaction {
type Hash = TestTransactionHash;
fn hash(&self) -> Self::Hash {
self.0
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct TestTransactionProof(pub TestTransactionHash);
#[derive(Clone)]
pub struct TestTransactionsSource {
pub on_tick: Arc<dyn Fn(&mut TestTransactionsSourceData) + Send + Sync>,
pub data: Arc<Mutex<TestTransactionsSourceData>>,
}
pub struct TestTransactionsSourceData {
pub block: Result<TestBlock, TestError>,
pub transaction_block: Result<Option<(TestHeaderId, usize)>, TestError>,
pub proofs_to_fail: HashMap<TestTransactionHash, TestError>,
}
impl TestTransactionsSource {
pub fn new(on_tick: Box<dyn Fn(&mut TestTransactionsSourceData) + Send + Sync>) -> Self {
Self {
on_tick: Arc::new(on_tick),
data: Arc::new(Mutex::new(TestTransactionsSourceData {
block: Ok(test_block()),
transaction_block: Ok(Some((test_block_id(), 0))),
proofs_to_fail: HashMap::new(),
})),
}
}
}
#[async_trait]
impl RelayClient for TestTransactionsSource {
type Error = TestError;
async fn reconnect(&mut self) -> Result<(), TestError> {
Ok(())
}
}
#[async_trait]
impl SourceClient<TestTransactionProofPipeline> for TestTransactionsSource {
async fn tick(&self) {
(self.on_tick)(&mut *self.data.lock())
}
async fn block_by_hash(&self, _: TestBlockHash) -> Result<TestBlock, TestError> {
self.data.lock().block.clone()
}
async fn block_by_number(&self, _: TestBlockNumber) -> Result<TestBlock, TestError> {
self.data.lock().block.clone()
}
async fn transaction_block(&self, _: &TestTransactionHash) -> Result<Option<(TestHeaderId, usize)>, TestError> {
self.data.lock().transaction_block.clone()
}
async fn transaction_proof(&self, block: &TestBlock, index: usize) -> Result<TestTransactionProof, TestError> {
let tx_hash = block.1[index].hash();
let proof_error = self.data.lock().proofs_to_fail.get(&tx_hash).cloned();
if let Some(err) = proof_error {
return Err(err);
}
Ok(TestTransactionProof(tx_hash))
}
}
#[derive(Clone)]
pub struct TestTransactionsTarget {
pub on_tick: Arc<dyn Fn(&mut TestTransactionsTargetData) + Send + Sync>,
pub data: Arc<Mutex<TestTransactionsTargetData>>,
}
pub struct TestTransactionsTargetData {
pub is_header_known: Result<bool, TestError>,
pub is_header_finalized: Result<bool, TestError>,
pub best_finalized_header_id: Result<TestHeaderId, TestError>,
pub transactions_to_accept: HashSet<TestTransactionHash>,
pub submitted_proofs: Vec<TestTransactionProof>,
}
impl TestTransactionsTarget {
pub fn new(on_tick: Box<dyn Fn(&mut TestTransactionsTargetData) + Send + Sync>) -> Self {
Self {
on_tick: Arc::new(on_tick),
data: Arc::new(Mutex::new(TestTransactionsTargetData {
is_header_known: Ok(true),
is_header_finalized: Ok(true),
best_finalized_header_id: Ok(test_block_id()),
transactions_to_accept: vec![test_transaction_hash(0)].into_iter().collect(),
submitted_proofs: Vec::new(),
})),
}
}
}
#[async_trait]
impl RelayClient for TestTransactionsTarget {
type Error = TestError;
async fn reconnect(&mut self) -> Result<(), TestError> {
Ok(())
}
}
#[async_trait]
impl TargetClient<TestTransactionProofPipeline> for TestTransactionsTarget {
async fn tick(&self) {
(self.on_tick)(&mut *self.data.lock())
}
async fn is_header_known(&self, _: &TestHeaderId) -> Result<bool, TestError> {
self.data.lock().is_header_known.clone()
}
async fn is_header_finalized(&self, _: &TestHeaderId) -> Result<bool, TestError> {
self.data.lock().is_header_finalized.clone()
}
async fn best_finalized_header_id(&self) -> Result<TestHeaderId, TestError> {
self.data.lock().best_finalized_header_id.clone()
}
async fn filter_transaction_proof(&self, proof: &TestTransactionProof) -> Result<bool, TestError> {
Ok(self.data.lock().transactions_to_accept.contains(&proof.0))
}
async fn submit_transaction_proof(&self, proof: TestTransactionProof) -> Result<(), TestError> {
self.data.lock().submitted_proofs.push(proof);
Ok(())
}
}
fn ensure_relay_single_success(source: &TestTransactionsSource, target: &TestTransactionsTarget) {
assert_eq!(
async_std::task::block_on(relay_single_transaction_proof(source, target, test_transaction_hash(0),)),
Ok(()),
);
assert_eq!(
target.data.lock().submitted_proofs,
vec![TestTransactionProof(test_transaction_hash(0))],
);
}
fn ensure_relay_single_failure(source: TestTransactionsSource, target: TestTransactionsTarget) {
assert!(async_std::task::block_on(relay_single_transaction_proof(
&source,
&target,
test_transaction_hash(0),
))
.is_err(),);
assert!(target.data.lock().submitted_proofs.is_empty());
}
#[test]
fn ready_transaction_proof_relayed_immediately() {
let source = TestTransactionsSource::new(Box::new(|_| unreachable!("no ticks allowed")));
let target = TestTransactionsTarget::new(Box::new(|_| unreachable!("no ticks allowed")));
ensure_relay_single_success(&source, &target)
}
#[test]
fn relay_transaction_proof_waits_for_transaction_to_be_mined() {
let source = TestTransactionsSource::new(Box::new(|source_data| {
assert_eq!(source_data.transaction_block, Ok(None));
source_data.transaction_block = Ok(Some((test_block_id(), 0)));
}));
let target = TestTransactionsTarget::new(Box::new(|_| unreachable!("no ticks allowed")));
// transaction is not yet mined, but will be available after first wait (tick)
source.data.lock().transaction_block = Ok(None);
ensure_relay_single_success(&source, &target)
}
#[test]
fn relay_transaction_fails_when_transaction_retrieval_fails() {
let source = TestTransactionsSource::new(Box::new(|_| unreachable!("no ticks allowed")));
let target = TestTransactionsTarget::new(Box::new(|_| unreachable!("no ticks allowed")));
source.data.lock().transaction_block = Err(TestError(false));
ensure_relay_single_failure(source, target)
}
#[test]
fn relay_transaction_fails_when_proof_retrieval_fails() {
let source = TestTransactionsSource::new(Box::new(|_| unreachable!("no ticks allowed")));
let target = TestTransactionsTarget::new(Box::new(|_| unreachable!("no ticks allowed")));
source
.data
.lock()
.proofs_to_fail
.insert(test_transaction_hash(0), TestError(false));
ensure_relay_single_failure(source, target)
}
#[test]
fn relay_transaction_proof_waits_for_header_to_be_imported() {
let source = TestTransactionsSource::new(Box::new(|_| unreachable!("no ticks allowed")));
let target = TestTransactionsTarget::new(Box::new(|target_data| {
assert_eq!(target_data.is_header_known, Ok(false));
target_data.is_header_known = Ok(true);
}));
// header is not yet imported, but will be available after first wait (tick)
target.data.lock().is_header_known = Ok(false);
ensure_relay_single_success(&source, &target)
}
#[test]
fn relay_transaction_proof_fails_when_is_header_known_fails() {
let source = TestTransactionsSource::new(Box::new(|_| unreachable!("no ticks allowed")));
let target = TestTransactionsTarget::new(Box::new(|_| unreachable!("no ticks allowed")));
target.data.lock().is_header_known = Err(TestError(false));
ensure_relay_single_failure(source, target)
}
#[test]
fn relay_transaction_proof_waits_for_header_to_be_finalized() {
let source = TestTransactionsSource::new(Box::new(|_| unreachable!("no ticks allowed")));
let target = TestTransactionsTarget::new(Box::new(|target_data| {
assert_eq!(target_data.is_header_finalized, Ok(false));
target_data.is_header_finalized = Ok(true);
}));
// header is not yet finalized, but will be available after first wait (tick)
target.data.lock().is_header_finalized = Ok(false);
ensure_relay_single_success(&source, &target)
}
#[test]
fn relay_transaction_proof_fails_when_is_header_finalized_fails() {
let source = TestTransactionsSource::new(Box::new(|_| unreachable!("no ticks allowed")));
let target = TestTransactionsTarget::new(Box::new(|_| unreachable!("no ticks allowed")));
target.data.lock().is_header_finalized = Err(TestError(false));
ensure_relay_single_failure(source, target)
}
#[test]
fn relay_transaction_proof_fails_when_target_node_rejects_proof() {
let source = TestTransactionsSource::new(Box::new(|_| unreachable!("no ticks allowed")));
let target = TestTransactionsTarget::new(Box::new(|_| unreachable!("no ticks allowed")));
target
.data
.lock()
.transactions_to_accept
.remove(&test_transaction_hash(0));
ensure_relay_single_success(&source, &target)
}
fn test_relay_block_transactions(
source: &TestTransactionsSource,
target: &TestTransactionsTarget,
pre_relayed: RelayedBlockTransactions,
) -> Result<RelayedBlockTransactions, RelayedBlockTransactions> {
async_std::task::block_on(relay_block_transactions(
source,
target,
&TestBlock(
test_block_id(),
vec![test_transaction(0), test_transaction(1), test_transaction(2)],
),
pre_relayed,
))
.map_err(|(_, transactions)| transactions)
}
#[test]
fn relay_block_transactions_process_all_transactions() {
let source = TestTransactionsSource::new(Box::new(|_| unreachable!("no ticks allowed")));
let target = TestTransactionsTarget::new(Box::new(|_| unreachable!("no ticks allowed")));
// let's only accept tx#1
target
.data
.lock()
.transactions_to_accept
.remove(&test_transaction_hash(0));
target
.data
.lock()
.transactions_to_accept
.insert(test_transaction_hash(1));
let relayed_transactions = test_relay_block_transactions(&source, &target, Default::default());
assert_eq!(
relayed_transactions,
Ok(RelayedBlockTransactions {
processed: 3,
relayed: 1,
failed: 0,
}),
);
assert_eq!(
target.data.lock().submitted_proofs,
vec![TestTransactionProof(test_transaction_hash(1))],
);
}
#[test]
fn relay_block_transactions_ignores_transaction_failure() {
let source = TestTransactionsSource::new(Box::new(|_| unreachable!("no ticks allowed")));
let target = TestTransactionsTarget::new(Box::new(|_| unreachable!("no ticks allowed")));
// let's reject proof for tx#0
source
.data
.lock()
.proofs_to_fail
.insert(test_transaction_hash(0), TestError(false));
let relayed_transactions = test_relay_block_transactions(&source, &target, Default::default());
assert_eq!(
relayed_transactions,
Ok(RelayedBlockTransactions {
processed: 3,
relayed: 0,
failed: 1,
}),
);
assert_eq!(target.data.lock().submitted_proofs, vec![],);
}
#[test]
fn relay_block_transactions_fails_on_connection_error() {
let source = TestTransactionsSource::new(Box::new(|_| unreachable!("no ticks allowed")));
let target = TestTransactionsTarget::new(Box::new(|_| unreachable!("no ticks allowed")));
// fail with connection error when preparing proof for tx#1
source
.data
.lock()
.proofs_to_fail
.insert(test_transaction_hash(1), TestError(true));
let relayed_transactions = test_relay_block_transactions(&source, &target, Default::default());
assert_eq!(
relayed_transactions,
Err(RelayedBlockTransactions {
processed: 1,
relayed: 1,
failed: 0,
}),
);
assert_eq!(
target.data.lock().submitted_proofs,
vec![TestTransactionProof(test_transaction_hash(0))],
);
// now do not fail on tx#2
source.data.lock().proofs_to_fail.clear();
// and also relay tx#3
target
.data
.lock()
.transactions_to_accept
.insert(test_transaction_hash(2));
let relayed_transactions = test_relay_block_transactions(&source, &target, relayed_transactions.unwrap_err());
assert_eq!(
relayed_transactions,
Ok(RelayedBlockTransactions {
processed: 3,
relayed: 2,
failed: 0,
}),
);
assert_eq!(
target.data.lock().submitted_proofs,
vec![
TestTransactionProof(test_transaction_hash(0)),
TestTransactionProof(test_transaction_hash(2))
],
);
}
}
@@ -0,0 +1,331 @@
// 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 <http://www.gnu.org/licenses/>.
//! Relaying proofs of exchange transactions.
use crate::exchange::{
relay_block_transactions, BlockNumberOf, RelayedBlockTransactions, SourceClient, TargetClient,
TransactionProofPipeline,
};
use crate::exchange_loop_metrics::ExchangeLoopMetrics;
use backoff::backoff::Backoff;
use futures::{future::FutureExt, select};
use num_traits::One;
use relay_utils::{
metrics::{start as metrics_start, GlobalMetrics, MetricsParams},
retry_backoff, FailedClient, MaybeConnectionError,
};
use std::future::Future;
/// Transactions proofs relay state.
#[derive(Debug)]
pub struct TransactionProofsRelayState<BlockNumber> {
/// Number of last header we have processed so far.
pub best_processed_header_number: BlockNumber,
}
/// Transactions proofs relay storage.
pub trait TransactionProofsRelayStorage: Clone {
/// Associated block number.
type BlockNumber;
/// Get relay state.
fn state(&self) -> TransactionProofsRelayState<Self::BlockNumber>;
/// Update relay state.
fn set_state(&mut self, state: &TransactionProofsRelayState<Self::BlockNumber>);
}
/// In-memory storage for auto-relay loop.
#[derive(Debug, Clone)]
pub struct InMemoryStorage<BlockNumber> {
best_processed_header_number: BlockNumber,
}
impl<BlockNumber> InMemoryStorage<BlockNumber> {
/// Created new in-memory storage with given best processed block number.
pub fn new(best_processed_header_number: BlockNumber) -> Self {
InMemoryStorage {
best_processed_header_number,
}
}
}
impl<BlockNumber: Clone + Copy> TransactionProofsRelayStorage for InMemoryStorage<BlockNumber> {
type BlockNumber = BlockNumber;
fn state(&self) -> TransactionProofsRelayState<BlockNumber> {
TransactionProofsRelayState {
best_processed_header_number: self.best_processed_header_number,
}
}
fn set_state(&mut self, state: &TransactionProofsRelayState<BlockNumber>) {
self.best_processed_header_number = state.best_processed_header_number;
}
}
/// Run proofs synchronization.
pub fn run<P: TransactionProofPipeline>(
storage: impl TransactionProofsRelayStorage<BlockNumber = BlockNumberOf<P>>,
source_client: impl SourceClient<P>,
target_client: impl TargetClient<P>,
metrics_params: Option<MetricsParams>,
exit_signal: impl Future<Output = ()>,
) {
let exit_signal = exit_signal.shared();
let metrics_global = GlobalMetrics::default();
let metrics_exch = ExchangeLoopMetrics::default();
let metrics_enabled = metrics_params.is_some();
metrics_start(
format!("{}_to_{}_Exchange", P::SOURCE_NAME, P::TARGET_NAME),
metrics_params,
&metrics_global,
&metrics_exch,
);
relay_utils::relay_loop::run(
relay_utils::relay_loop::RECONNECT_DELAY,
source_client,
target_client,
|source_client, target_client| {
run_until_connection_lost(
storage.clone(),
source_client,
target_client,
if metrics_enabled {
Some(metrics_global.clone())
} else {
None
},
if metrics_enabled {
Some(metrics_exch.clone())
} else {
None
},
exit_signal.clone(),
)
},
);
}
/// Run proofs synchronization.
async fn run_until_connection_lost<P: TransactionProofPipeline>(
mut storage: impl TransactionProofsRelayStorage<BlockNumber = BlockNumberOf<P>>,
source_client: impl SourceClient<P>,
target_client: impl TargetClient<P>,
metrics_global: Option<GlobalMetrics>,
metrics_exch: Option<ExchangeLoopMetrics>,
exit_signal: impl Future<Output = ()>,
) -> Result<(), FailedClient> {
let mut retry_backoff = retry_backoff();
let mut state = storage.state();
let mut current_finalized_block = None;
let exit_signal = exit_signal.fuse();
futures::pin_mut!(exit_signal);
loop {
let iteration_result = run_loop_iteration(
&mut storage,
&source_client,
&target_client,
&mut state,
&mut current_finalized_block,
metrics_exch.as_ref(),
)
.await;
if let Some(ref metrics_global) = metrics_global {
metrics_global.update().await;
}
if let Err((is_connection_error, failed_client)) = iteration_result {
if is_connection_error {
return Err(failed_client);
}
let retry_timeout = retry_backoff
.next_backoff()
.unwrap_or(relay_utils::relay_loop::RECONNECT_DELAY);
select! {
_ = async_std::task::sleep(retry_timeout).fuse() => {},
_ = exit_signal => return Ok(()),
}
} else {
retry_backoff.reset();
select! {
_ = source_client.tick().fuse() => {},
_ = exit_signal => return Ok(()),
}
}
}
}
/// Run exchange loop until we need to break.
async fn run_loop_iteration<P: TransactionProofPipeline>(
storage: &mut impl TransactionProofsRelayStorage<BlockNumber = BlockNumberOf<P>>,
source_client: &impl SourceClient<P>,
target_client: &impl TargetClient<P>,
state: &mut TransactionProofsRelayState<BlockNumberOf<P>>,
current_finalized_block: &mut Option<(P::Block, RelayedBlockTransactions)>,
exchange_loop_metrics: Option<&ExchangeLoopMetrics>,
) -> Result<(), (bool, FailedClient)> {
let best_finalized_header_id = match target_client.best_finalized_header_id().await {
Ok(best_finalized_header_id) => {
log::debug!(
target: "bridge",
"Got best finalized {} block from {} node: {:?}",
P::SOURCE_NAME,
P::TARGET_NAME,
best_finalized_header_id,
);
best_finalized_header_id
}
Err(err) => {
log::error!(
target: "bridge",
"Failed to retrieve best {} header id from {} node: {:?}. Going to retry...",
P::SOURCE_NAME,
P::TARGET_NAME,
err,
);
return Err((err.is_connection_error(), FailedClient::Target));
}
};
loop {
// if we already have some finalized block body, try to relay its transactions
if let Some((block, relayed_transactions)) = current_finalized_block.take() {
let result = relay_block_transactions(source_client, target_client, &block, relayed_transactions).await;
match result {
Ok(relayed_transactions) => {
log::info!(
target: "bridge",
"Relay has processed {} block #{}. Total/Relayed/Failed transactions: {}/{}/{}",
P::SOURCE_NAME,
state.best_processed_header_number,
relayed_transactions.processed,
relayed_transactions.relayed,
relayed_transactions.failed,
);
state.best_processed_header_number = state.best_processed_header_number + One::one();
storage.set_state(state);
if let Some(ref exchange_loop_metrics) = exchange_loop_metrics {
exchange_loop_metrics.update::<P>(
state.best_processed_header_number,
best_finalized_header_id.0,
relayed_transactions,
);
}
// we have just updated state => proceed to next block retrieval
}
Err((failed_client, relayed_transactions)) => {
*current_finalized_block = Some((block, relayed_transactions));
return Err((true, failed_client));
}
}
}
// we may need to retrieve finalized block body from source node
if best_finalized_header_id.0 > state.best_processed_header_number {
let next_block_number = state.best_processed_header_number + One::one();
let result = source_client.block_by_number(next_block_number).await;
match result {
Ok(block) => {
*current_finalized_block = Some((block, RelayedBlockTransactions::default()));
// we have received new finalized block => go back to relay its transactions
continue;
}
Err(err) => {
log::error!(
target: "bridge",
"Failed to retrieve canonical block #{} from {} node: {:?}. Going to retry...",
next_block_number,
P::SOURCE_NAME,
err,
);
return Err((err.is_connection_error(), FailedClient::Source));
}
}
}
// there are no any transactions we need to relay => wait for new data
return Ok(());
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::exchange::tests::{
test_next_block, test_next_block_id, test_transaction_hash, TestTransactionProof, TestTransactionsSource,
TestTransactionsTarget,
};
use futures::{future::FutureExt, stream::StreamExt};
#[test]
fn exchange_loop_is_able_to_relay_proofs() {
let storage = InMemoryStorage {
best_processed_header_number: 0,
};
let target = TestTransactionsTarget::new(Box::new(|_| unreachable!("no target ticks allowed")));
let target_data = target.data.clone();
let (exit_sender, exit_receiver) = futures::channel::mpsc::unbounded();
let source = TestTransactionsSource::new(Box::new(move |data| {
let transaction1_relayed = target_data
.lock()
.submitted_proofs
.contains(&TestTransactionProof(test_transaction_hash(0)));
let transaction2_relayed = target_data
.lock()
.submitted_proofs
.contains(&TestTransactionProof(test_transaction_hash(1)));
match (transaction1_relayed, transaction2_relayed) {
(true, true) => exit_sender.unbounded_send(()).unwrap(),
(true, false) => {
data.block = Ok(test_next_block());
target_data.lock().best_finalized_header_id = Ok(test_next_block_id());
target_data
.lock()
.transactions_to_accept
.insert(test_transaction_hash(1));
}
_ => (),
}
}));
run(
storage,
source,
target,
None,
exit_receiver.into_future().map(|(_, _)| ()),
);
}
}
@@ -0,0 +1,88 @@
// 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 <http://www.gnu.org/licenses/>.
//! Metrics for currency-exchange relay loop.
use crate::exchange::{BlockNumberOf, RelayedBlockTransactions, TransactionProofPipeline};
use relay_utils::metrics::{register, Counter, CounterVec, GaugeVec, Metrics, Opts, Registry, U64};
/// Exchange transactions relay metrics.
#[derive(Clone)]
pub struct ExchangeLoopMetrics {
/// Best finalized block numbers - "processed" and "known".
best_block_numbers: GaugeVec<U64>,
/// Number of processed blocks ("total").
processed_blocks: Counter<U64>,
/// Number of processed transactions ("total", "relayed" and "failed").
processed_transactions: CounterVec<U64>,
}
impl Metrics for ExchangeLoopMetrics {
fn register(&self, registry: &Registry) -> Result<(), String> {
register(self.best_block_numbers.clone(), registry).map_err(|e| e.to_string())?;
register(self.processed_blocks.clone(), registry).map_err(|e| e.to_string())?;
register(self.processed_transactions.clone(), registry).map_err(|e| e.to_string())?;
Ok(())
}
}
impl Default for ExchangeLoopMetrics {
fn default() -> Self {
ExchangeLoopMetrics {
best_block_numbers: GaugeVec::new(
Opts::new("best_block_numbers", "Best finalized block numbers"),
&["type"],
)
.expect("metric is static and thus valid; qed"),
processed_blocks: Counter::new("processed_blocks", "Total number of processed blocks")
.expect("metric is static and thus valid; qed"),
processed_transactions: CounterVec::new(
Opts::new("processed_transactions", "Total number of processed transactions"),
&["type"],
)
.expect("metric is static and thus valid; qed"),
}
}
}
impl ExchangeLoopMetrics {
/// Update metrics when single block is relayed.
pub fn update<P: TransactionProofPipeline>(
&self,
best_processed_block_number: BlockNumberOf<P>,
best_known_block_number: BlockNumberOf<P>,
relayed_transactions: RelayedBlockTransactions,
) {
self.best_block_numbers
.with_label_values(&["processed"])
.set(best_processed_block_number.into());
self.best_block_numbers
.with_label_values(&["known"])
.set(best_known_block_number.into());
self.processed_blocks.inc();
self.processed_transactions
.with_label_values(&["total"])
.inc_by(relayed_transactions.processed as _);
self.processed_transactions
.with_label_values(&["relayed"])
.inc_by(relayed_transactions.relayed as _);
self.processed_transactions
.with_label_values(&["failed"])
.inc_by(relayed_transactions.failed as _);
}
}
@@ -0,0 +1,26 @@
// 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 <http://www.gnu.org/licenses/>.
//! Relaying [`currency-exchange`](../pallet_bridge_currency_exchange/index.html) application
//! specific data. Currency exchange application allows exchanging tokens between bridged chains.
//! This module provides entrypoints for crafting and submitting (single and multiple)
//! proof-of-exchange-at-source-chain transaction(s) to target chain.
#![warn(missing_docs)]
pub mod exchange;
pub mod exchange_loop;
pub mod exchange_loop_metrics;
@@ -0,0 +1,17 @@
[package]
name = "headers-relay"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
async-std = "1.6.5"
async-trait = "0.1.40"
backoff = "0.2"
futures = "0.3.5"
linked-hash-map = "0.5.3"
log = "0.4.11"
num-traits = "0.2"
parking_lot = "0.11.0"
relay-utils = { path = "../utils" }
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,33 @@
// 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 <http://www.gnu.org/licenses/>.
//! Relaying source chain headers to target chain. This module provides entrypoint
//! that starts reading new headers from source chain and submit these headers as
//! module/contract transactions to the target chain. Module/contract on the target
//! chain is a light-client of the source chain. All other trustless bridge
//! applications are built using this light-client, so running headers-relay is
//! essential for running all other bridge applications.
// required for futures::select!
#![recursion_limit = "1024"]
#![warn(missing_docs)]
pub mod headers;
pub mod sync;
pub mod sync_loop;
pub mod sync_loop_metrics;
pub mod sync_loop_tests;
pub mod sync_types;
@@ -0,0 +1,523 @@
// 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 <http://www.gnu.org/licenses/>.
//! Headers synchronization context. This structure wraps headers queue and is
//! able to choose: which headers to read from the source chain? Which headers
//! to submit to the target chain? The context makes decisions basing on parameters
//! passed using `HeadersSyncParams` structure.
use crate::headers::QueuedHeaders;
use crate::sync_types::{HeaderIdOf, HeaderStatus, HeadersSyncPipeline, QueuedHeader};
use num_traits::{One, Saturating, Zero};
/// Common sync params.
#[derive(Debug, Clone)]
pub struct HeadersSyncParams {
/// Maximal number of ethereum headers to pre-download.
pub max_future_headers_to_download: usize,
/// Maximal number of active (we believe) submit header transactions.
pub max_headers_in_submitted_status: usize,
/// Maximal number of headers in single submit request.
pub max_headers_in_single_submit: usize,
/// Maximal total headers size in single submit request.
pub max_headers_size_in_single_submit: usize,
/// We only may store and accept (from Ethereum node) headers that have
/// number >= than best_substrate_header.number - prune_depth.
pub prune_depth: u32,
/// Target transactions mode.
pub target_tx_mode: TargetTransactionMode,
}
/// Target transaction mode.
#[derive(Debug, PartialEq, Clone)]
pub enum TargetTransactionMode {
/// Submit new headers using signed transactions.
Signed,
/// Submit new headers using unsigned transactions.
Unsigned,
/// Submit new headers using signed transactions, but only when we
/// believe that sync has stalled.
Backup,
}
/// Headers synchronization context.
#[derive(Debug)]
pub struct HeadersSync<P: HeadersSyncPipeline> {
/// Synchronization parameters.
params: HeadersSyncParams,
/// Best header number known to source node.
source_best_number: Option<P::Number>,
/// Best header known to target node.
target_best_header: Option<HeaderIdOf<P>>,
/// Headers queue.
headers: QueuedHeaders<P>,
/// Pause headers submission.
pause_submit: bool,
}
impl<P: HeadersSyncPipeline> HeadersSync<P> {
/// Creates new headers synchronizer.
pub fn new(params: HeadersSyncParams) -> Self {
HeadersSync {
headers: QueuedHeaders::default(),
params,
source_best_number: None,
target_best_header: None,
pause_submit: false,
}
}
/// Return best header number known to source node.
pub fn source_best_number(&self) -> Option<P::Number> {
self.source_best_number
}
/// Best header known to target node.
pub fn target_best_header(&self) -> Option<HeaderIdOf<P>> {
self.target_best_header
}
/// Returns true if we have synced almost all known headers.
pub fn is_almost_synced(&self) -> bool {
match self.source_best_number {
Some(source_best_number) => self
.target_best_header
.map(|best| source_best_number.saturating_sub(best.0) < 4.into())
.unwrap_or(false),
None => true,
}
}
/// Returns synchronization status.
pub fn status(&self) -> (&Option<HeaderIdOf<P>>, &Option<P::Number>) {
(&self.target_best_header, &self.source_best_number)
}
/// Returns reference to the headers queue.
pub fn headers(&self) -> &QueuedHeaders<P> {
&self.headers
}
/// Returns mutable reference to the headers queue.
pub fn headers_mut(&mut self) -> &mut QueuedHeaders<P> {
&mut self.headers
}
/// Select header that needs to be downloaded from the source node.
pub fn select_new_header_to_download(&self) -> Option<P::Number> {
// if we haven't received best header from source node yet, there's nothing we can download
let source_best_number = self.source_best_number?;
// if we haven't received known best header from target node yet, there's nothing we can download
let target_best_header = self.target_best_header.as_ref()?;
// if there's too many headers in the queue, stop downloading
let in_memory_headers = self.headers.total_headers();
if in_memory_headers >= self.params.max_future_headers_to_download {
return None;
}
// if queue is empty and best header on target is > than best header on source,
// then we shoud reorg
let best_queued_number = self.headers.best_queued_number();
if best_queued_number.is_zero() && source_best_number < target_best_header.0 {
return Some(source_best_number);
}
// we assume that there were no reorgs if we have already downloaded best header
let best_downloaded_number = std::cmp::max(
std::cmp::max(best_queued_number, self.headers.best_synced_number()),
target_best_header.0,
);
if best_downloaded_number >= source_best_number {
return None;
}
// download new header
Some(best_downloaded_number + One::one())
}
/// Selech orphan header to downoload.
pub fn select_orphan_header_to_download(&self) -> Option<&QueuedHeader<P>> {
let orphan_header = self.headers.header(HeaderStatus::Orphan)?;
// we consider header orphan until we'll find it ancestor that is known to the target node
// => we may get orphan header while we ask target node whether it knows its parent
// => let's avoid fetching duplicate headers
let parent_id = orphan_header.parent_id();
if self.headers.status(&parent_id) != HeaderStatus::Unknown {
return None;
}
Some(orphan_header)
}
/// Select headers that need to be submitted to the target node.
pub fn select_headers_to_submit(&self, stalled: bool) -> Option<Vec<&QueuedHeader<P>>> {
// maybe we have paused new headers submit?
if self.pause_submit {
return None;
}
// if we operate in backup mode, we only submit headers when sync has stalled
if self.params.target_tx_mode == TargetTransactionMode::Backup && !stalled {
return None;
}
let headers_in_submit_status = self.headers.headers_in_status(HeaderStatus::Submitted);
let headers_to_submit_count = self
.params
.max_headers_in_submitted_status
.checked_sub(headers_in_submit_status)?;
let mut total_size = 0;
let mut total_headers = 0;
self.headers.headers(HeaderStatus::Ready, |header| {
if total_headers == headers_to_submit_count {
return false;
}
if total_headers == self.params.max_headers_in_single_submit {
return false;
}
let encoded_size = P::estimate_size(header);
if total_headers != 0 && total_size + encoded_size > self.params.max_headers_size_in_single_submit {
return false;
}
total_size += encoded_size;
total_headers += 1;
true
})
}
/// Receive new target header number from the source node.
pub fn source_best_header_number_response(&mut self, best_header_number: P::Number) {
log::debug!(
target: "bridge",
"Received best header number from {} node: {}",
P::SOURCE_NAME,
best_header_number,
);
self.source_best_number = Some(best_header_number);
}
/// Receive new best header from the target node.
/// Returns true if it is different from the previous block known to us.
pub fn target_best_header_response(&mut self, best_header: HeaderIdOf<P>) -> bool {
log::debug!(
target: "bridge",
"Received best known header from {}: {:?}",
P::TARGET_NAME,
best_header,
);
// early return if it is still the same
if self.target_best_header == Some(best_header) {
return false;
}
// remember that this header is now known to the Substrate runtime
self.headers.target_best_header_response(&best_header);
// prune ancient headers
self.headers
.prune(best_header.0.saturating_sub(self.params.prune_depth.into()));
// finally remember the best header itself
self.target_best_header = Some(best_header);
// we are ready to submit headers again
if self.pause_submit {
log::debug!(
target: "bridge",
"Ready to submit {} headers to {} node again!",
P::SOURCE_NAME,
P::TARGET_NAME,
);
self.pause_submit = false;
}
true
}
/// Pause headers submit until best header will be updated on target node.
pub fn pause_submit(&mut self) {
log::debug!(
target: "bridge",
"Stopping submitting {} headers to {} node. Waiting for {} submitted headers to be accepted",
P::SOURCE_NAME,
P::TARGET_NAME,
self.headers.headers_in_status(HeaderStatus::Submitted),
);
self.pause_submit = true;
}
/// Restart synchronization.
pub fn restart(&mut self) {
self.source_best_number = None;
self.target_best_header = None;
self.headers.clear();
self.pause_submit = false;
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::headers::tests::{header, id};
use crate::sync_loop_tests::{TestHash, TestHeadersSyncPipeline, TestNumber};
use crate::sync_types::HeaderStatus;
use relay_utils::HeaderId;
fn side_hash(number: TestNumber) -> TestHash {
1000 + number
}
pub fn default_sync_params() -> HeadersSyncParams {
HeadersSyncParams {
max_future_headers_to_download: 128,
max_headers_in_submitted_status: 128,
max_headers_in_single_submit: 32,
max_headers_size_in_single_submit: 131_072,
prune_depth: 4096,
target_tx_mode: TargetTransactionMode::Signed,
}
}
#[test]
fn select_new_header_to_download_works() {
let mut eth_sync = HeadersSync::<TestHeadersSyncPipeline>::new(default_sync_params());
// both best && target headers are unknown
assert_eq!(eth_sync.select_new_header_to_download(), None);
// best header is known, target header is unknown
eth_sync.target_best_header = Some(HeaderId(0, Default::default()));
assert_eq!(eth_sync.select_new_header_to_download(), None);
// target header is known, best header is unknown
eth_sync.target_best_header = None;
eth_sync.source_best_number = Some(100);
assert_eq!(eth_sync.select_new_header_to_download(), None);
// when our best block has the same number as the target
eth_sync.target_best_header = Some(HeaderId(100, Default::default()));
assert_eq!(eth_sync.select_new_header_to_download(), None);
// when we actually need a new header
eth_sync.source_best_number = Some(101);
assert_eq!(eth_sync.select_new_header_to_download(), Some(101));
// when we have to reorganize to longer fork
eth_sync.source_best_number = Some(100);
eth_sync.target_best_header = Some(HeaderId(200, Default::default()));
assert_eq!(eth_sync.select_new_header_to_download(), Some(100));
// when there are too many headers scheduled for submitting
for i in 1..1000 {
eth_sync.headers.header_response(header(i).header().clone());
}
assert_eq!(eth_sync.select_new_header_to_download(), None);
}
#[test]
fn select_new_header_to_download_works_with_empty_queue() {
let mut eth_sync = HeadersSync::<TestHeadersSyncPipeline>::new(default_sync_params());
eth_sync.source_best_header_number_response(100);
// when queue is not empty => everything goes as usually
eth_sync.target_best_header_response(header(10).id());
eth_sync.headers_mut().header_response(header(11).header().clone());
eth_sync.headers_mut().maybe_extra_response(&header(11).id(), false);
assert_eq!(eth_sync.select_new_header_to_download(), Some(12));
// but then queue is drained
eth_sync.headers_mut().target_best_header_response(&header(11).id());
// even though it's empty, we know that header#11 is synced
assert_eq!(eth_sync.headers().best_queued_number(), 0);
assert_eq!(eth_sync.headers().best_synced_number(), 11);
assert_eq!(eth_sync.select_new_header_to_download(), Some(12));
}
#[test]
fn sync_without_reorgs_works() {
let mut eth_sync = HeadersSync::new(default_sync_params());
eth_sync.params.max_headers_in_submitted_status = 1;
// ethereum reports best header #102
eth_sync.source_best_header_number_response(102);
// substrate reports that it is at block #100
eth_sync.target_best_header_response(id(100));
// block #101 is downloaded first
assert_eq!(eth_sync.select_new_header_to_download(), Some(101));
eth_sync.headers.header_response(header(101).header().clone());
// now header #101 is ready to be submitted
assert_eq!(eth_sync.headers.header(HeaderStatus::MaybeExtra), Some(&header(101)));
eth_sync.headers.maybe_extra_response(&id(101), false);
assert_eq!(eth_sync.headers.header(HeaderStatus::Ready), Some(&header(101)));
assert_eq!(eth_sync.select_headers_to_submit(false), Some(vec![&header(101)]));
// and header #102 is ready to be downloaded
assert_eq!(eth_sync.select_new_header_to_download(), Some(102));
eth_sync.headers.header_response(header(102).header().clone());
// receive submission confirmation
eth_sync.headers.headers_submitted(vec![id(101)]);
// we have nothing to submit because previous header hasn't been confirmed yet
// (and we allow max 1 submit transaction in the wild)
assert_eq!(eth_sync.headers.header(HeaderStatus::MaybeExtra), Some(&header(102)));
eth_sync.headers.maybe_extra_response(&id(102), false);
assert_eq!(eth_sync.headers.header(HeaderStatus::Ready), Some(&header(102)));
assert_eq!(eth_sync.select_headers_to_submit(false), None);
// substrate reports that it has imported block #101
eth_sync.target_best_header_response(id(101));
// and we are ready to submit #102
assert_eq!(eth_sync.select_headers_to_submit(false), Some(vec![&header(102)]));
eth_sync.headers.headers_submitted(vec![id(102)]);
// substrate reports that it has imported block #102
eth_sync.target_best_header_response(id(102));
// and we have nothing to download
assert_eq!(eth_sync.select_new_header_to_download(), None);
}
#[test]
fn sync_with_orphan_headers_work() {
let mut eth_sync = HeadersSync::new(default_sync_params());
// ethereum reports best header #102
eth_sync.source_best_header_number_response(102);
// substrate reports that it is at block #100, but it isn't part of best chain
eth_sync.target_best_header_response(HeaderId(100, side_hash(100)));
// block #101 is downloaded first
assert_eq!(eth_sync.select_new_header_to_download(), Some(101));
eth_sync.headers.header_response(header(101).header().clone());
// we can't submit header #101, because its parent status is unknown
assert_eq!(eth_sync.select_headers_to_submit(false), None);
// instead we are trying to determine status of its parent (#100)
assert_eq!(eth_sync.headers.header(HeaderStatus::MaybeOrphan), Some(&header(101)));
// and the status is still unknown
eth_sync.headers.maybe_orphan_response(&id(100), false);
// so we consider #101 orphaned now && will download its parent - #100
assert_eq!(eth_sync.headers.header(HeaderStatus::Orphan), Some(&header(101)));
eth_sync.headers.header_response(header(100).header().clone());
// #101 is now Orphan and #100 is MaybeOrphan => we do not want to retrieve
// header #100 again
assert_eq!(eth_sync.headers.header(HeaderStatus::Orphan), Some(&header(101)));
assert_eq!(eth_sync.select_orphan_header_to_download(), None);
// we can't submit header #100, because its parent status is unknown
assert_eq!(eth_sync.select_headers_to_submit(false), None);
// instead we are trying to determine status of its parent (#99)
assert_eq!(eth_sync.headers.header(HeaderStatus::MaybeOrphan), Some(&header(100)));
// and the status is known, so we move previously orphaned #100 and #101 to ready queue
eth_sync.headers.maybe_orphan_response(&id(99), true);
// and we are ready to submit #100
assert_eq!(eth_sync.headers.header(HeaderStatus::MaybeExtra), Some(&header(100)));
eth_sync.headers.maybe_extra_response(&id(100), false);
assert_eq!(eth_sync.select_headers_to_submit(false), Some(vec![&header(100)]));
eth_sync.headers.headers_submitted(vec![id(100)]);
// and we are ready to submit #101
assert_eq!(eth_sync.headers.header(HeaderStatus::MaybeExtra), Some(&header(101)));
eth_sync.headers.maybe_extra_response(&id(101), false);
assert_eq!(eth_sync.select_headers_to_submit(false), Some(vec![&header(101)]));
eth_sync.headers.headers_submitted(vec![id(101)]);
}
#[test]
fn pruning_happens_on_target_best_header_response() {
let mut eth_sync = HeadersSync::<TestHeadersSyncPipeline>::new(default_sync_params());
eth_sync.params.prune_depth = 50;
eth_sync.target_best_header_response(id(100));
assert_eq!(eth_sync.headers.prune_border(), 50);
}
#[test]
fn only_submitting_headers_in_backup_mode_when_stalled() {
let mut eth_sync = HeadersSync::new(default_sync_params());
eth_sync.params.target_tx_mode = TargetTransactionMode::Backup;
// ethereum reports best header #102
eth_sync.source_best_header_number_response(102);
// substrate reports that it is at block #100
eth_sync.target_best_header_response(id(100));
// block #101 is downloaded first
eth_sync.headers.header_response(header(101).header().clone());
eth_sync.headers.maybe_extra_response(&id(101), false);
// ensure that headers are not submitted when sync is not stalled
assert_eq!(eth_sync.select_headers_to_submit(false), None);
// ensure that headers are not submitted when sync is stalled
assert_eq!(eth_sync.select_headers_to_submit(true), Some(vec![&header(101)]));
}
#[test]
fn does_not_select_new_headers_to_submit_when_submit_is_paused() {
let mut eth_sync = HeadersSync::new(default_sync_params());
eth_sync.params.max_headers_in_submitted_status = 1;
// ethereum reports best header #102 and substrate is at #100
eth_sync.source_best_header_number_response(102);
eth_sync.target_best_header_response(id(100));
// let's prepare #101 and #102 for submitting
eth_sync.headers.header_response(header(101).header().clone());
eth_sync.headers.maybe_extra_response(&id(101), false);
eth_sync.headers.header_response(header(102).header().clone());
eth_sync.headers.maybe_extra_response(&id(102), false);
// when submit is not paused, we're ready to submit #101
assert_eq!(eth_sync.select_headers_to_submit(false), Some(vec![&header(101)]));
// when submit is paused, we're not ready to submit anything
eth_sync.pause_submit();
assert_eq!(eth_sync.select_headers_to_submit(false), None);
// if best header on substrate node isn't updated, we still not submitting anything
eth_sync.target_best_header_response(id(100));
assert_eq!(eth_sync.select_headers_to_submit(false), None);
// but after it is actually updated, we are ready to submit
eth_sync.target_best_header_response(id(101));
assert_eq!(eth_sync.select_headers_to_submit(false), Some(vec![&header(102)]));
}
}
@@ -0,0 +1,654 @@
// 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 <http://www.gnu.org/licenses/>.
//! Entrypoint for running headers synchronization loop.
use crate::sync::{HeadersSync, HeadersSyncParams};
use crate::sync_loop_metrics::SyncLoopMetrics;
use crate::sync_types::{HeaderIdOf, HeaderStatus, HeadersSyncPipeline, QueuedHeader, SubmittedHeaders};
use async_trait::async_trait;
use futures::{future::FutureExt, stream::StreamExt};
use num_traits::{Saturating, Zero};
use relay_utils::{
format_ids, interval,
metrics::{start as metrics_start, GlobalMetrics, MetricsParams},
process_future_result,
relay_loop::Client as RelayClient,
retry_backoff, FailedClient, MaybeConnectionError, StringifiedMaybeConnectionError,
};
use std::{
collections::HashSet,
future::Future,
time::{Duration, Instant},
};
/// When we submit headers to target node, but see no updates of best
/// source block known to target node during STALL_SYNC_TIMEOUT seconds,
/// we consider that our headers are rejected because there has been reorg in target chain.
/// This reorg could invalidate our knowledge about sync process (i.e. we have asked if
/// HeaderA is known to target, but then reorg happened and the answer is different
/// now) => we need to reset sync.
/// The other option is to receive **EVERY** best target header and check if it is
/// direct child of previous best header. But: (1) subscription doesn't guarantee that
/// the subscriber will receive every best header (2) reorg won't always lead to sync
/// stall and restart is a heavy operation (we forget all in-memory headers).
const STALL_SYNC_TIMEOUT: Duration = Duration::from_secs(5 * 60);
/// Delay after we have seen update of best source header at target node,
/// for us to treat sync stalled. ONLY when relay operates in backup mode.
const BACKUP_STALL_SYNC_TIMEOUT: Duration = Duration::from_secs(10 * 60);
/// Interval between calling sync maintain procedure.
const MAINTAIN_INTERVAL: Duration = Duration::from_secs(30);
/// Source client trait.
#[async_trait]
pub trait SourceClient<P: HeadersSyncPipeline>: RelayClient {
/// Get best block number.
async fn best_block_number(&self) -> Result<P::Number, Self::Error>;
/// Get header by hash.
async fn header_by_hash(&self, hash: P::Hash) -> Result<P::Header, Self::Error>;
/// Get canonical header by number.
async fn header_by_number(&self, number: P::Number) -> Result<P::Header, Self::Error>;
/// Get completion data by header hash.
async fn header_completion(&self, id: HeaderIdOf<P>)
-> Result<(HeaderIdOf<P>, Option<P::Completion>), Self::Error>;
/// Get extra data by header hash.
async fn header_extra(
&self,
id: HeaderIdOf<P>,
header: QueuedHeader<P>,
) -> Result<(HeaderIdOf<P>, P::Extra), Self::Error>;
}
/// Target client trait.
#[async_trait]
pub trait TargetClient<P: HeadersSyncPipeline>: RelayClient {
/// Returns ID of best header known to the target node.
async fn best_header_id(&self) -> Result<HeaderIdOf<P>, Self::Error>;
/// Returns true if header is known to the target node.
async fn is_known_header(&self, id: HeaderIdOf<P>) -> Result<(HeaderIdOf<P>, bool), Self::Error>;
/// Submit headers.
async fn submit_headers(&self, headers: Vec<QueuedHeader<P>>) -> SubmittedHeaders<HeaderIdOf<P>, Self::Error>;
/// Returns ID of headers that require to be 'completed' before children can be submitted.
async fn incomplete_headers_ids(&self) -> Result<HashSet<HeaderIdOf<P>>, Self::Error>;
/// Submit completion data for header.
async fn complete_header(&self, id: HeaderIdOf<P>, completion: P::Completion)
-> Result<HeaderIdOf<P>, Self::Error>;
/// Returns true if header requires extra data to be submitted.
async fn requires_extra(&self, header: QueuedHeader<P>) -> Result<(HeaderIdOf<P>, bool), Self::Error>;
}
/// Synchronization maintain procedure.
#[async_trait]
pub trait SyncMaintain<P: HeadersSyncPipeline>: Clone + Send + Sync {
/// Run custom maintain procedures. This is guaranteed to be called when both source and target
/// clients are unoccupied.
async fn maintain(&self, _sync: &mut HeadersSync<P>) {}
}
impl<P: HeadersSyncPipeline> SyncMaintain<P> for () {}
/// Run headers synchronization.
#[allow(clippy::too_many_arguments)]
pub fn run<P: HeadersSyncPipeline, TC: TargetClient<P>>(
source_client: impl SourceClient<P>,
source_tick: Duration,
target_client: TC,
target_tick: Duration,
sync_maintain: impl SyncMaintain<P>,
sync_params: HeadersSyncParams,
metrics_params: Option<MetricsParams>,
exit_signal: impl Future<Output = ()>,
) {
let exit_signal = exit_signal.shared();
let metrics_global = GlobalMetrics::default();
let metrics_sync = SyncLoopMetrics::default();
let metrics_enabled = metrics_params.is_some();
metrics_start(
format!("{}_to_{}_Sync", P::SOURCE_NAME, P::TARGET_NAME),
metrics_params,
&metrics_global,
&metrics_sync,
);
relay_utils::relay_loop::run(
relay_utils::relay_loop::RECONNECT_DELAY,
source_client,
target_client,
|source_client, target_client| {
run_until_connection_lost(
source_client,
source_tick,
target_client,
target_tick,
sync_maintain.clone(),
sync_params.clone(),
if metrics_enabled {
Some(metrics_global.clone())
} else {
None
},
if metrics_enabled {
Some(metrics_sync.clone())
} else {
None
},
exit_signal.clone(),
)
},
);
}
/// Run headers synchronization.
#[allow(clippy::too_many_arguments)]
async fn run_until_connection_lost<P: HeadersSyncPipeline, TC: TargetClient<P>>(
source_client: impl SourceClient<P>,
source_tick: Duration,
target_client: TC,
target_tick: Duration,
sync_maintain: impl SyncMaintain<P>,
sync_params: HeadersSyncParams,
metrics_global: Option<GlobalMetrics>,
metrics_sync: Option<SyncLoopMetrics>,
exit_signal: impl Future<Output = ()>,
) -> Result<(), FailedClient> {
let mut progress_context = (Instant::now(), None, None);
let mut sync = HeadersSync::<P>::new(sync_params);
let mut stall_countdown = None;
let mut last_update_time = Instant::now();
let mut source_retry_backoff = retry_backoff();
let mut source_client_is_online = false;
let mut source_best_block_number_required = false;
let source_best_block_number_future = source_client.best_block_number().fuse();
let source_new_header_future = futures::future::Fuse::terminated();
let source_orphan_header_future = futures::future::Fuse::terminated();
let source_extra_future = futures::future::Fuse::terminated();
let source_completion_future = futures::future::Fuse::terminated();
let source_go_offline_future = futures::future::Fuse::terminated();
let source_tick_stream = interval(source_tick).fuse();
let mut target_retry_backoff = retry_backoff();
let mut target_client_is_online = false;
let mut target_best_block_required = false;
let mut target_incomplete_headers_required = true;
let target_best_block_future = target_client.best_header_id().fuse();
let target_incomplete_headers_future = futures::future::Fuse::terminated();
let target_extra_check_future = futures::future::Fuse::terminated();
let target_existence_status_future = futures::future::Fuse::terminated();
let target_submit_header_future = futures::future::Fuse::terminated();
let target_complete_header_future = futures::future::Fuse::terminated();
let target_go_offline_future = futures::future::Fuse::terminated();
let target_tick_stream = interval(target_tick).fuse();
let mut maintain_required = false;
let maintain_stream = interval(MAINTAIN_INTERVAL).fuse();
let exit_signal = exit_signal.fuse();
futures::pin_mut!(
source_best_block_number_future,
source_new_header_future,
source_orphan_header_future,
source_extra_future,
source_completion_future,
source_go_offline_future,
source_tick_stream,
target_best_block_future,
target_incomplete_headers_future,
target_extra_check_future,
target_existence_status_future,
target_submit_header_future,
target_complete_header_future,
target_go_offline_future,
target_tick_stream,
maintain_stream,
exit_signal
);
loop {
futures::select! {
source_best_block_number = source_best_block_number_future => {
source_best_block_number_required = false;
source_client_is_online = process_future_result(
source_best_block_number,
&mut source_retry_backoff,
|source_best_block_number| sync.source_best_header_number_response(source_best_block_number),
&mut source_go_offline_future,
async_std::task::sleep,
|| format!("Error retrieving best header number from {}", P::SOURCE_NAME),
).fail_if_connection_error(FailedClient::Source)?;
},
source_new_header = source_new_header_future => {
source_client_is_online = process_future_result(
source_new_header,
&mut source_retry_backoff,
|source_new_header| sync.headers_mut().header_response(source_new_header),
&mut source_go_offline_future,
async_std::task::sleep,
|| format!("Error retrieving header from {} node", P::SOURCE_NAME),
).fail_if_connection_error(FailedClient::Source)?;
},
source_orphan_header = source_orphan_header_future => {
source_client_is_online = process_future_result(
source_orphan_header,
&mut source_retry_backoff,
|source_orphan_header| sync.headers_mut().header_response(source_orphan_header),
&mut source_go_offline_future,
async_std::task::sleep,
|| format!("Error retrieving orphan header from {} node", P::SOURCE_NAME),
).fail_if_connection_error(FailedClient::Source)?;
},
source_extra = source_extra_future => {
source_client_is_online = process_future_result(
source_extra,
&mut source_retry_backoff,
|(header, extra)| sync.headers_mut().extra_response(&header, extra),
&mut source_go_offline_future,
async_std::task::sleep,
|| format!("Error retrieving extra data from {} node", P::SOURCE_NAME),
).fail_if_connection_error(FailedClient::Source)?;
},
source_completion = source_completion_future => {
source_client_is_online = process_future_result(
source_completion,
&mut source_retry_backoff,
|(header, completion)| sync.headers_mut().completion_response(&header, completion),
&mut source_go_offline_future,
async_std::task::sleep,
|| format!("Error retrieving completion data from {} node", P::SOURCE_NAME),
).fail_if_connection_error(FailedClient::Source)?;
},
_ = source_go_offline_future => {
source_client_is_online = true;
},
_ = source_tick_stream.next() => {
if sync.is_almost_synced() {
source_best_block_number_required = true;
}
},
target_best_block = target_best_block_future => {
target_best_block_required = false;
target_client_is_online = process_future_result(
target_best_block,
&mut target_retry_backoff,
|target_best_block| {
let head_updated = sync.target_best_header_response(target_best_block);
if head_updated {
last_update_time = Instant::now();
}
match head_updated {
// IF head is updated AND there are still our transactions:
// => restart stall countdown timer
true if sync.headers().headers_in_status(HeaderStatus::Submitted) != 0 =>
stall_countdown = Some(Instant::now()),
// IF head is updated AND there are no our transactions:
// => stop stall countdown timer
true => stall_countdown = None,
// IF head is not updated AND stall countdown is not yet completed
// => do nothing
false if stall_countdown
.map(|stall_countdown| stall_countdown.elapsed() < STALL_SYNC_TIMEOUT)
.unwrap_or(true)
=> (),
// IF head is not updated AND stall countdown has completed
// => restart sync
false => {
log::info!(
target: "bridge",
"Sync has stalled. Restarting {} headers synchronization.",
P::SOURCE_NAME,
);
stall_countdown = None;
sync.restart();
},
}
},
&mut target_go_offline_future,
async_std::task::sleep,
|| format!("Error retrieving best known {} header from {} node", P::SOURCE_NAME, P::TARGET_NAME),
).fail_if_connection_error(FailedClient::Target)?;
},
incomplete_headers_ids = target_incomplete_headers_future => {
target_incomplete_headers_required = false;
target_client_is_online = process_future_result(
incomplete_headers_ids,
&mut target_retry_backoff,
|incomplete_headers_ids| sync.headers_mut().incomplete_headers_response(incomplete_headers_ids),
&mut target_go_offline_future,
async_std::task::sleep,
|| format!("Error retrieving incomplete headers from {} node", P::TARGET_NAME),
).fail_if_connection_error(FailedClient::Target)?;
},
target_existence_status = target_existence_status_future => {
target_client_is_online = process_future_result(
target_existence_status,
&mut target_retry_backoff,
|(target_header, target_existence_status)| sync
.headers_mut()
.maybe_orphan_response(&target_header, target_existence_status),
&mut target_go_offline_future,
async_std::task::sleep,
|| format!("Error retrieving existence status from {} node", P::TARGET_NAME),
).fail_if_connection_error(FailedClient::Target)?;
},
submitted_headers = target_submit_header_future => {
// following line helps Rust understand the type of `submitted_headers` :/
let submitted_headers: SubmittedHeaders<HeaderIdOf<P>, TC::Error> = submitted_headers;
let submitted_headers_str = format!("{}", submitted_headers);
let all_headers_rejected = submitted_headers.submitted.is_empty()
&& submitted_headers.incomplete.is_empty();
let has_submitted_headers = sync.headers().headers_in_status(HeaderStatus::Submitted) != 0;
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 all_headers_rejected && !has_submitted_headers =>
Err(StringifiedMaybeConnectionError::new(false, "All headers were rejected".into())),
None => Ok(()),
};
let no_fatal_error = maybe_fatal_error.is_ok();
target_client_is_online = process_future_result(
maybe_fatal_error,
&mut target_retry_backoff,
|_| {},
&mut target_go_offline_future,
async_std::task::sleep,
|| format!("Error submitting headers to {} node", P::TARGET_NAME),
).fail_if_connection_error(FailedClient::Target)?;
log::debug!(target: "bridge", "Header submit result: {}", submitted_headers_str);
sync.headers_mut().headers_submitted(submitted_headers.submitted);
sync.headers_mut().add_incomplete_headers(false, submitted_headers.incomplete);
// when there's no fatal error, but node has rejected all our headers we may
// want to pause until our submitted headers will be accepted
if no_fatal_error && all_headers_rejected && has_submitted_headers {
sync.pause_submit();
}
},
target_complete_header_result = target_complete_header_future => {
target_client_is_online = process_future_result(
target_complete_header_result,
&mut target_retry_backoff,
|completed_header| sync.headers_mut().header_completed(&completed_header),
&mut target_go_offline_future,
async_std::task::sleep,
|| format!("Error completing headers at {}", P::TARGET_NAME),
).fail_if_connection_error(FailedClient::Target)?;
},
target_extra_check_result = target_extra_check_future => {
target_client_is_online = process_future_result(
target_extra_check_result,
&mut target_retry_backoff,
|(header, extra_check_result)| sync
.headers_mut()
.maybe_extra_response(&header, extra_check_result),
&mut target_go_offline_future,
async_std::task::sleep,
|| format!("Error retrieving receipts requirement from {} node", P::TARGET_NAME),
).fail_if_connection_error(FailedClient::Target)?;
},
_ = target_go_offline_future => {
target_client_is_online = true;
},
_ = target_tick_stream.next() => {
target_best_block_required = true;
target_incomplete_headers_required = true;
},
_ = maintain_stream.next() => {
maintain_required = true;
},
_ = exit_signal => {
return Ok(());
}
}
// update metrics
if let Some(ref metrics_global) = metrics_global {
metrics_global.update().await;
}
if let Some(ref metrics_sync) = metrics_sync {
metrics_sync.update(&sync);
}
// print progress
progress_context = print_sync_progress(progress_context, &sync);
// run maintain procedures
if maintain_required && source_client_is_online && target_client_is_online {
log::debug!(target: "bridge", "Maintaining headers sync loop");
maintain_required = false;
sync_maintain.maintain(&mut sync).await;
}
// If the target client is accepting requests we update the requests that
// we want it to run
if !maintain_required && target_client_is_online {
// NOTE: Is is important to reset this so that we only have one
// request being processed by the client at a time. This prevents
// race conditions like receiving two transactions with the same
// nonce from the client.
target_client_is_online = false;
// The following is how we prioritize requests:
//
// 1. Get best block
// - Stops us from downloading or submitting new blocks
// - Only called rarely
//
// 2. Get incomplete headers
// - Stops us from submitting new blocks
// - Only called rarely
//
// 3. Get complete headers
// - Stops us from submitting new blocks
//
// 4. Check if we need extra data from source
// - Stops us from downloading or submitting new blocks
//
// 5. Check existence of header
// - Stops us from submitting new blocks
//
// 6. Submit header
if target_best_block_required {
log::debug!(target: "bridge", "Asking {} about best block", P::TARGET_NAME);
target_best_block_future.set(target_client.best_header_id().fuse());
} else if target_incomplete_headers_required {
log::debug!(target: "bridge", "Asking {} about incomplete headers", P::TARGET_NAME);
target_incomplete_headers_future.set(target_client.incomplete_headers_ids().fuse());
} else if let Some((id, completion)) = sync.headers_mut().header_to_complete() {
log::debug!(
target: "bridge",
"Going to complete header: {:?}",
id,
);
target_complete_header_future.set(target_client.complete_header(id, completion.clone()).fuse());
} else if let Some(header) = sync.headers().header(HeaderStatus::MaybeExtra) {
log::debug!(
target: "bridge",
"Checking if header submission requires extra: {:?}",
header.id(),
);
target_extra_check_future.set(target_client.requires_extra(header.clone()).fuse());
} else if let Some(header) = sync.headers().header(HeaderStatus::MaybeOrphan) {
// for MaybeOrphan we actually ask for parent' header existence
let parent_id = header.parent_id();
log::debug!(
target: "bridge",
"Asking {} node for existence of: {:?}",
P::TARGET_NAME,
parent_id,
);
target_existence_status_future.set(target_client.is_known_header(parent_id).fuse());
} else if let Some(headers) =
sync.select_headers_to_submit(last_update_time.elapsed() > BACKUP_STALL_SYNC_TIMEOUT)
{
log::debug!(
target: "bridge",
"Submitting {} header(s) to {} node: {:?}",
headers.len(),
P::TARGET_NAME,
format_ids(headers.iter().map(|header| header.id())),
);
let headers = headers.into_iter().cloned().collect();
target_submit_header_future.set(target_client.submit_headers(headers).fuse());
// remember that we have submitted some headers
if stall_countdown.is_none() {
stall_countdown = Some(Instant::now());
}
} else {
target_client_is_online = true;
}
}
// If the source client is accepting requests we update the requests that
// we want it to run
if !maintain_required && source_client_is_online {
// NOTE: Is is important to reset this so that we only have one
// request being processed by the client at a time. This prevents
// race conditions like receiving two transactions with the same
// nonce from the client.
source_client_is_online = false;
// The following is how we prioritize requests:
//
// 1. Get best block
// - Stops us from downloading or submitting new blocks
// - Only called rarely
//
// 2. Download completion data
// - Stops us from submitting new blocks
//
// 3. Download extra data
// - Stops us from submitting new blocks
//
// 4. Download missing headers
// - Stops us from downloading or submitting new blocks
//
// 5. Downloading new headers
if source_best_block_number_required {
log::debug!(target: "bridge", "Asking {} node about best block number", P::SOURCE_NAME);
source_best_block_number_future.set(source_client.best_block_number().fuse());
} else if let Some(id) = sync.headers_mut().incomplete_header() {
log::debug!(
target: "bridge",
"Retrieving completion data for header: {:?}",
id,
);
source_completion_future.set(source_client.header_completion(id).fuse());
} else if let Some(header) = sync.headers().header(HeaderStatus::Extra) {
let id = header.id();
log::debug!(
target: "bridge",
"Retrieving extra data for header: {:?}",
id,
);
source_extra_future.set(source_client.header_extra(id, header.clone()).fuse());
} else if let Some(header) = sync.select_orphan_header_to_download() {
// for Orphan we actually ask for parent' header
let parent_id = header.parent_id();
// if we have end up with orphan header#0, then we are misconfigured
if parent_id.0.is_zero() {
log::error!(
target: "bridge",
"Misconfiguration. Genesis {} header is considered orphan by {} node",
P::SOURCE_NAME,
P::TARGET_NAME,
);
return Ok(());
}
log::debug!(
target: "bridge",
"Going to download orphan header from {} node: {:?}",
P::SOURCE_NAME,
parent_id,
);
source_orphan_header_future.set(source_client.header_by_hash(parent_id.1).fuse());
} else if let Some(id) = sync.select_new_header_to_download() {
log::debug!(
target: "bridge",
"Going to download new header from {} node: {:?}",
P::SOURCE_NAME,
id,
);
source_new_header_future.set(source_client.header_by_number(id).fuse());
} else {
source_client_is_online = true;
}
}
}
}
/// Print synchronization progress.
fn print_sync_progress<P: HeadersSyncPipeline>(
progress_context: (Instant, Option<P::Number>, Option<P::Number>),
eth_sync: &HeadersSync<P>,
) -> (Instant, Option<P::Number>, Option<P::Number>) {
let (prev_time, prev_best_header, prev_target_header) = progress_context;
let now_time = Instant::now();
let (now_best_header, now_target_header) = eth_sync.status();
let need_update = now_time - prev_time > Duration::from_secs(10)
|| match (prev_best_header, now_best_header) {
(Some(prev_best_header), Some(now_best_header)) => {
now_best_header.0.saturating_sub(prev_best_header) > 10.into()
}
_ => false,
};
if !need_update {
return (prev_time, prev_best_header, prev_target_header);
}
log::info!(
target: "bridge",
"Synced {:?} of {:?} headers",
now_best_header.map(|id| id.0),
now_target_header,
);
(now_time, now_best_header.clone().map(|id| id.0), *now_target_header)
}
@@ -0,0 +1,95 @@
// 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 <http://www.gnu.org/licenses/>.
//! Metrics for headers synchronization relay loop.
use crate::sync::HeadersSync;
use crate::sync_types::{HeaderStatus, HeadersSyncPipeline};
use num_traits::Zero;
use relay_utils::metrics::{register, GaugeVec, Metrics, Opts, Registry, U64};
/// Headers sync metrics.
#[derive(Clone)]
pub struct SyncLoopMetrics {
/// Best syncing headers at "source" and "target" nodes.
best_block_numbers: GaugeVec<U64>,
/// Number of headers in given states (see `HeaderStatus`).
blocks_in_state: GaugeVec<U64>,
}
impl Metrics for SyncLoopMetrics {
fn register(&self, registry: &Registry) -> Result<(), String> {
register(self.best_block_numbers.clone(), registry).map_err(|e| e.to_string())?;
register(self.blocks_in_state.clone(), registry).map_err(|e| e.to_string())?;
Ok(())
}
}
impl Default for SyncLoopMetrics {
fn default() -> Self {
SyncLoopMetrics {
best_block_numbers: GaugeVec::new(
Opts::new("best_block_numbers", "Best block numbers on source and target nodes"),
&["node"],
)
.expect("metric is static and thus valid; qed"),
blocks_in_state: GaugeVec::new(
Opts::new("blocks_in_state", "Number of blocks in given state"),
&["state"],
)
.expect("metric is static and thus valid; qed"),
}
}
}
impl SyncLoopMetrics {
/// Update metrics.
pub fn update<P: HeadersSyncPipeline>(&self, sync: &HeadersSync<P>) {
let headers = sync.headers();
let source_best_number = sync.source_best_number().unwrap_or_else(Zero::zero);
let target_best_number = sync.target_best_header().map(|id| id.0).unwrap_or_else(Zero::zero);
self.best_block_numbers
.with_label_values(&["source"])
.set(source_best_number.into());
self.best_block_numbers
.with_label_values(&["target"])
.set(target_best_number.into());
self.blocks_in_state
.with_label_values(&["maybe_orphan"])
.set(headers.headers_in_status(HeaderStatus::MaybeOrphan) as _);
self.blocks_in_state
.with_label_values(&["orphan"])
.set(headers.headers_in_status(HeaderStatus::Orphan) as _);
self.blocks_in_state
.with_label_values(&["maybe_extra"])
.set(headers.headers_in_status(HeaderStatus::MaybeExtra) as _);
self.blocks_in_state
.with_label_values(&["extra"])
.set(headers.headers_in_status(HeaderStatus::Extra) as _);
self.blocks_in_state
.with_label_values(&["ready"])
.set(headers.headers_in_status(HeaderStatus::Ready) as _);
self.blocks_in_state
.with_label_values(&["incomplete"])
.set(headers.headers_in_status(HeaderStatus::Incomplete) as _);
self.blocks_in_state
.with_label_values(&["submitted"])
.set(headers.headers_in_status(HeaderStatus::Submitted) as _);
}
}
@@ -0,0 +1,593 @@
// 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 <http://www.gnu.org/licenses/>.
#![cfg(test)]
use crate::sync_loop::{run, SourceClient, TargetClient};
use crate::sync_types::{HeadersSyncPipeline, QueuedHeader, SourceHeader, SubmittedHeaders};
use async_trait::async_trait;
use backoff::backoff::Backoff;
use futures::{future::FutureExt, stream::StreamExt};
use parking_lot::Mutex;
use relay_utils::{
process_future_result, relay_loop::Client as RelayClient, retry_backoff, HeaderId, MaybeConnectionError,
};
use std::{
collections::{HashMap, HashSet},
sync::Arc,
time::Duration,
};
pub type TestNumber = u64;
pub type TestHash = u64;
pub type TestHeaderId = HeaderId<TestHash, TestNumber>;
pub type TestExtra = u64;
pub type TestCompletion = u64;
pub type TestQueuedHeader = QueuedHeader<TestHeadersSyncPipeline>;
#[derive(Default, Debug, Clone, PartialEq)]
pub struct TestHeader {
pub hash: TestHash,
pub number: TestNumber,
pub parent_hash: TestHash,
}
impl SourceHeader<TestHash, TestNumber> for TestHeader {
fn id(&self) -> TestHeaderId {
HeaderId(self.number, self.hash)
}
fn parent_id(&self) -> TestHeaderId {
HeaderId(self.number - 1, self.parent_hash)
}
}
#[derive(Debug, Clone)]
struct TestError(bool);
impl MaybeConnectionError for TestError {
fn is_connection_error(&self) -> bool {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TestHeadersSyncPipeline;
impl HeadersSyncPipeline for TestHeadersSyncPipeline {
const SOURCE_NAME: &'static str = "Source";
const TARGET_NAME: &'static str = "Target";
type Hash = TestHash;
type Number = TestNumber;
type Header = TestHeader;
type Extra = TestExtra;
type Completion = TestCompletion;
fn estimate_size(_: &TestQueuedHeader) -> usize {
0
}
}
enum SourceMethod {
BestBlockNumber,
HeaderByHash(TestHash),
HeaderByNumber(TestNumber),
HeaderCompletion(TestHeaderId),
HeaderExtra(TestHeaderId, TestQueuedHeader),
}
#[derive(Clone)]
struct Source {
data: Arc<Mutex<SourceData>>,
on_method_call: Arc<dyn Fn(SourceMethod, &mut SourceData) + Send + Sync>,
}
struct SourceData {
best_block_number: Result<TestNumber, TestError>,
header_by_hash: HashMap<TestHash, TestHeader>,
header_by_number: HashMap<TestNumber, TestHeader>,
provides_completion: bool,
provides_extra: bool,
}
impl Source {
pub fn new(
best_block_id: TestHeaderId,
headers: Vec<(bool, TestHeader)>,
on_method_call: impl Fn(SourceMethod, &mut SourceData) + Send + Sync + 'static,
) -> Self {
Source {
data: Arc::new(Mutex::new(SourceData {
best_block_number: Ok(best_block_id.0),
header_by_hash: headers
.iter()
.map(|(_, header)| (header.hash, header.clone()))
.collect(),
header_by_number: headers
.iter()
.filter_map(|(is_canonical, header)| {
if *is_canonical {
Some((header.hash, header.clone()))
} else {
None
}
})
.collect(),
provides_completion: true,
provides_extra: true,
})),
on_method_call: Arc::new(on_method_call),
}
}
}
#[async_trait]
impl RelayClient for Source {
type Error = TestError;
async fn reconnect(&mut self) -> Result<(), TestError> {
unimplemented!()
}
}
#[async_trait]
impl SourceClient<TestHeadersSyncPipeline> for Source {
async fn best_block_number(&self) -> Result<TestNumber, TestError> {
let mut data = self.data.lock();
(self.on_method_call)(SourceMethod::BestBlockNumber, &mut *data);
data.best_block_number.clone()
}
async fn header_by_hash(&self, hash: TestHash) -> Result<TestHeader, TestError> {
let mut data = self.data.lock();
(self.on_method_call)(SourceMethod::HeaderByHash(hash), &mut *data);
data.header_by_hash.get(&hash).cloned().ok_or(TestError(false))
}
async fn header_by_number(&self, number: TestNumber) -> Result<TestHeader, TestError> {
let mut data = self.data.lock();
(self.on_method_call)(SourceMethod::HeaderByNumber(number), &mut *data);
data.header_by_number.get(&number).cloned().ok_or(TestError(false))
}
async fn header_completion(&self, id: TestHeaderId) -> Result<(TestHeaderId, Option<TestCompletion>), TestError> {
let mut data = self.data.lock();
(self.on_method_call)(SourceMethod::HeaderCompletion(id), &mut *data);
if data.provides_completion {
Ok((id, Some(test_completion(id))))
} else {
Ok((id, None))
}
}
async fn header_extra(
&self,
id: TestHeaderId,
header: TestQueuedHeader,
) -> Result<(TestHeaderId, TestExtra), TestError> {
let mut data = self.data.lock();
(self.on_method_call)(SourceMethod::HeaderExtra(id, header), &mut *data);
if data.provides_extra {
Ok((id, test_extra(id)))
} else {
Err(TestError(false))
}
}
}
enum TargetMethod {
BestHeaderId,
IsKnownHeader(TestHeaderId),
SubmitHeaders(Vec<TestQueuedHeader>),
IncompleteHeadersIds,
CompleteHeader(TestHeaderId, TestCompletion),
RequiresExtra(TestQueuedHeader),
}
#[derive(Clone)]
struct Target {
data: Arc<Mutex<TargetData>>,
on_method_call: Arc<dyn Fn(TargetMethod, &mut TargetData) + Send + Sync>,
}
struct TargetData {
best_header_id: Result<TestHeaderId, TestError>,
is_known_header_by_hash: HashMap<TestHash, bool>,
submitted_headers: HashMap<TestHash, TestQueuedHeader>,
submit_headers_result: Option<SubmittedHeaders<TestHeaderId, TestError>>,
completed_headers: HashMap<TestHash, TestCompletion>,
requires_completion: bool,
requires_extra: bool,
}
impl Target {
pub fn new(
best_header_id: TestHeaderId,
headers: Vec<TestHeaderId>,
on_method_call: impl Fn(TargetMethod, &mut TargetData) + Send + Sync + 'static,
) -> Self {
Target {
data: Arc::new(Mutex::new(TargetData {
best_header_id: Ok(best_header_id),
is_known_header_by_hash: headers.iter().map(|header| (header.1, true)).collect(),
submitted_headers: HashMap::new(),
submit_headers_result: None,
completed_headers: HashMap::new(),
requires_completion: false,
requires_extra: false,
})),
on_method_call: Arc::new(on_method_call),
}
}
}
#[async_trait]
impl RelayClient for Target {
type Error = TestError;
async fn reconnect(&mut self) -> Result<(), TestError> {
unimplemented!()
}
}
#[async_trait]
impl TargetClient<TestHeadersSyncPipeline> for Target {
async fn best_header_id(&self) -> Result<TestHeaderId, TestError> {
let mut data = self.data.lock();
(self.on_method_call)(TargetMethod::BestHeaderId, &mut *data);
data.best_header_id.clone()
}
async fn is_known_header(&self, id: TestHeaderId) -> Result<(TestHeaderId, bool), TestError> {
let mut data = self.data.lock();
(self.on_method_call)(TargetMethod::IsKnownHeader(id), &mut *data);
data.is_known_header_by_hash
.get(&id.1)
.cloned()
.map(|is_known_header| Ok((id, is_known_header)))
.unwrap_or(Ok((id, false)))
}
async fn submit_headers(&self, headers: Vec<TestQueuedHeader>) -> SubmittedHeaders<TestHeaderId, TestError> {
let mut data = self.data.lock();
(self.on_method_call)(TargetMethod::SubmitHeaders(headers.clone()), &mut *data);
data.submitted_headers
.extend(headers.iter().map(|header| (header.id().1, header.clone())));
data.submit_headers_result.take().expect("test must accept headers")
}
async fn incomplete_headers_ids(&self) -> Result<HashSet<TestHeaderId>, TestError> {
let mut data = self.data.lock();
(self.on_method_call)(TargetMethod::IncompleteHeadersIds, &mut *data);
if data.requires_completion {
Ok(data
.submitted_headers
.iter()
.filter(|(hash, _)| !data.completed_headers.contains_key(hash))
.map(|(_, header)| header.id())
.collect())
} else {
Ok(HashSet::new())
}
}
async fn complete_header(&self, id: TestHeaderId, completion: TestCompletion) -> Result<TestHeaderId, TestError> {
let mut data = self.data.lock();
(self.on_method_call)(TargetMethod::CompleteHeader(id, completion), &mut *data);
data.completed_headers.insert(id.1, completion);
Ok(id)
}
async fn requires_extra(&self, header: TestQueuedHeader) -> Result<(TestHeaderId, bool), TestError> {
let mut data = self.data.lock();
(self.on_method_call)(TargetMethod::RequiresExtra(header.clone()), &mut *data);
if data.requires_extra {
Ok((header.id(), true))
} else {
Ok((header.id(), false))
}
}
}
fn test_tick() -> Duration {
// in ideal world that should have been Duration::from_millis(0), because we do not want
// to sleep in tests at all, but that could lead to `select! {}` always waking on tick
// => not doing actual job
Duration::from_millis(10)
}
fn test_id(number: TestNumber) -> TestHeaderId {
HeaderId(number, number)
}
fn test_header(number: TestNumber) -> TestHeader {
let id = test_id(number);
TestHeader {
hash: id.1,
number: id.0,
parent_hash: if number == 0 {
TestHash::default()
} else {
test_id(number - 1).1
},
}
}
fn test_forked_id(number: TestNumber, forked_from: TestNumber) -> TestHeaderId {
const FORK_OFFSET: TestNumber = 1000;
if number == forked_from {
HeaderId(number, number)
} else {
HeaderId(number, FORK_OFFSET + number)
}
}
fn test_forked_header(number: TestNumber, forked_from: TestNumber) -> TestHeader {
let id = test_forked_id(number, forked_from);
TestHeader {
hash: id.1,
number: id.0,
parent_hash: if number == 0 {
TestHash::default()
} else {
test_forked_id(number - 1, forked_from).1
},
}
}
fn test_completion(id: TestHeaderId) -> TestCompletion {
id.0
}
fn test_extra(id: TestHeaderId) -> TestExtra {
id.0
}
fn source_reject_completion(method: &SourceMethod) {
if let SourceMethod::HeaderCompletion(_) = method {
unreachable!("HeaderCompletion request is not expected")
}
}
fn source_reject_extra(method: &SourceMethod) {
if let SourceMethod::HeaderExtra(_, _) = method {
unreachable!("HeaderExtra request is not expected")
}
}
fn target_accept_all_headers(method: &TargetMethod, data: &mut TargetData, requires_extra: bool) {
if let TargetMethod::SubmitHeaders(ref submitted) = method {
assert_eq!(submitted.iter().all(|header| header.extra().is_some()), requires_extra,);
data.submit_headers_result = Some(SubmittedHeaders {
submitted: submitted.iter().map(|header| header.id()).collect(),
..Default::default()
});
}
}
fn target_signal_exit_when_header_submitted(
method: &TargetMethod,
header_id: TestHeaderId,
exit_signal: &futures::channel::mpsc::UnboundedSender<()>,
) {
if let TargetMethod::SubmitHeaders(ref submitted) = method {
if submitted.iter().any(|header| header.id() == header_id) {
exit_signal.unbounded_send(()).unwrap();
}
}
}
fn target_signal_exit_when_header_completed(
method: &TargetMethod,
header_id: TestHeaderId,
exit_signal: &futures::channel::mpsc::UnboundedSender<()>,
) {
if let TargetMethod::CompleteHeader(completed_id, _) = method {
if *completed_id == header_id {
exit_signal.unbounded_send(()).unwrap();
}
}
}
fn run_backoff_test(result: Result<(), TestError>) -> (Duration, Duration) {
let mut backoff = retry_backoff();
// no randomness in tests (otherwise intervals may overlap => asserts are failing)
backoff.randomization_factor = 0f64;
// increase backoff's current interval
let interval1 = backoff.next_backoff().unwrap();
let interval2 = backoff.next_backoff().unwrap();
assert!(interval2 > interval1);
// successful future result leads to backoff's reset
let go_offline_future = futures::future::Fuse::terminated();
futures::pin_mut!(go_offline_future);
process_future_result(
result,
&mut backoff,
|_| {},
&mut go_offline_future,
async_std::task::sleep,
|| "Test error".into(),
);
(interval2, backoff.next_backoff().unwrap())
}
#[test]
fn process_future_result_resets_backoff_on_success() {
let (interval2, interval_after_reset) = run_backoff_test(Ok(()));
assert!(interval2 > interval_after_reset);
}
#[test]
fn process_future_result_resets_backoff_on_connection_error() {
let (interval2, interval_after_reset) = run_backoff_test(Err(TestError(true)));
assert!(interval2 > interval_after_reset);
}
#[test]
fn process_future_result_does_not_reset_backoff_on_non_connection_error() {
let (interval2, interval_after_reset) = run_backoff_test(Err(TestError(false)));
assert!(interval2 < interval_after_reset);
}
struct SyncLoopTestParams {
best_source_header: TestHeader,
headers_on_source: Vec<(bool, TestHeader)>,
best_target_header: TestHeader,
headers_on_target: Vec<TestHeader>,
target_requires_extra: bool,
target_requires_completion: bool,
stop_at: TestHeaderId,
}
fn run_sync_loop_test(params: SyncLoopTestParams) {
let (exit_sender, exit_receiver) = futures::channel::mpsc::unbounded();
let target_requires_extra = params.target_requires_extra;
let target_requires_completion = params.target_requires_completion;
let stop_at = params.stop_at;
let source = Source::new(
params.best_source_header.id(),
params.headers_on_source,
move |method, _| {
if !target_requires_extra {
source_reject_extra(&method);
}
if !target_requires_completion {
source_reject_completion(&method);
}
},
);
let target = Target::new(
params.best_target_header.id(),
params.headers_on_target.into_iter().map(|header| header.id()).collect(),
move |method, data| {
target_accept_all_headers(&method, data, target_requires_extra);
if target_requires_completion {
target_signal_exit_when_header_completed(&method, stop_at, &exit_sender);
} else {
target_signal_exit_when_header_submitted(&method, stop_at, &exit_sender);
}
},
);
target.data.lock().requires_extra = target_requires_extra;
target.data.lock().requires_completion = target_requires_completion;
run(
source,
test_tick(),
target,
test_tick(),
(),
crate::sync::tests::default_sync_params(),
None,
exit_receiver.into_future().map(|(_, _)| ()),
);
}
#[test]
fn sync_loop_is_able_to_synchronize_single_header() {
run_sync_loop_test(SyncLoopTestParams {
best_source_header: test_header(1),
headers_on_source: vec![(true, test_header(1))],
best_target_header: test_header(0),
headers_on_target: vec![test_header(0)],
target_requires_extra: false,
target_requires_completion: false,
stop_at: test_id(1),
});
}
#[test]
fn sync_loop_is_able_to_synchronize_single_header_with_extra() {
run_sync_loop_test(SyncLoopTestParams {
best_source_header: test_header(1),
headers_on_source: vec![(true, test_header(1))],
best_target_header: test_header(0),
headers_on_target: vec![test_header(0)],
target_requires_extra: true,
target_requires_completion: false,
stop_at: test_id(1),
});
}
#[test]
fn sync_loop_is_able_to_synchronize_single_header_with_completion() {
run_sync_loop_test(SyncLoopTestParams {
best_source_header: test_header(1),
headers_on_source: vec![(true, test_header(1))],
best_target_header: test_header(0),
headers_on_target: vec![test_header(0)],
target_requires_extra: false,
target_requires_completion: true,
stop_at: test_id(1),
});
}
#[test]
fn sync_loop_is_able_to_reorganize_from_shorter_fork() {
run_sync_loop_test(SyncLoopTestParams {
best_source_header: test_header(3),
headers_on_source: vec![
(true, test_header(1)),
(true, test_header(2)),
(true, test_header(3)),
(false, test_forked_header(1, 0)),
(false, test_forked_header(2, 0)),
],
best_target_header: test_forked_header(2, 0),
headers_on_target: vec![test_header(0), test_forked_header(1, 0), test_forked_header(2, 0)],
target_requires_extra: false,
target_requires_completion: false,
stop_at: test_id(3),
});
}
#[test]
fn sync_loop_is_able_to_reorganize_from_longer_fork() {
run_sync_loop_test(SyncLoopTestParams {
best_source_header: test_header(3),
headers_on_source: vec![
(true, test_header(1)),
(true, test_header(2)),
(true, test_header(3)),
(false, test_forked_header(1, 0)),
(false, test_forked_header(2, 0)),
(false, test_forked_header(3, 0)),
(false, test_forked_header(4, 0)),
(false, test_forked_header(5, 0)),
],
best_target_header: test_forked_header(5, 0),
headers_on_target: vec![
test_header(0),
test_forked_header(1, 0),
test_forked_header(2, 0),
test_forked_header(3, 0),
test_forked_header(4, 0),
test_forked_header(5, 0),
],
target_requires_extra: false,
target_requires_completion: false,
stop_at: test_id(3),
});
}
@@ -0,0 +1,189 @@
// 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 <http://www.gnu.org/licenses/>.
//! Types that are used by headers synchronization components.
use relay_utils::{format_ids, HeaderId};
use std::{ops::Deref, sync::Arc};
/// Ethereum header synchronization status.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum HeaderStatus {
/// Header is unknown.
Unknown,
/// Header is in MaybeOrphan queue.
MaybeOrphan,
/// Header is in Orphan queue.
Orphan,
/// Header is in MaybeExtra queue.
MaybeExtra,
/// Header is in Extra queue.
Extra,
/// Header is in Ready queue.
Ready,
/// Header is in Incomplete queue.
Incomplete,
/// Header has been recently submitted to the target node.
Submitted,
/// Header is known to the target node.
Synced,
}
/// Headers synchronization pipeline.
pub trait HeadersSyncPipeline: Clone + Send + Sync {
/// Name of the headers source.
const SOURCE_NAME: &'static str;
/// Name of the headers target.
const TARGET_NAME: &'static str;
/// Headers we're syncing are identified by this hash.
type Hash: Eq + Clone + Copy + Send + Sync + std::fmt::Debug + std::fmt::Display + std::hash::Hash;
/// Headers we're syncing are identified by this number.
type Number: relay_utils::BlockNumberBase;
/// Type of header that we're syncing.
type Header: SourceHeader<Self::Hash, Self::Number>;
/// Type of extra data for the header that we're receiving from the source node:
/// 1) extra data is required for some headers;
/// 2) target node may answer if it'll require extra data before header is submitted;
/// 3) extra data available since the header creation time;
/// 4) header and extra data are submitted in single transaction.
///
/// Example: Ethereum transactions receipts.
type Extra: Clone + Send + Sync + PartialEq + std::fmt::Debug;
/// Type of data required to 'complete' header that we're receiving from the source node:
/// 1) completion data is required for some headers;
/// 2) target node can't answer if it'll require completion data before header is accepted;
/// 3) completion data may be generated after header generation;
/// 4) header and completion data are submitted in separate transactions.
///
/// Example: Substrate GRANDPA justifications.
type Completion: Clone + Send + Sync + std::fmt::Debug;
/// Function used to estimate size of target-encoded header.
fn estimate_size(source: &QueuedHeader<Self>) -> usize;
}
/// A HeaderId for `HeaderSyncPipeline`.
pub type HeaderIdOf<P> = HeaderId<<P as HeadersSyncPipeline>::Hash, <P as HeadersSyncPipeline>::Number>;
/// Header that we're receiving from source node.
pub trait SourceHeader<Hash, Number>: Clone + std::fmt::Debug + PartialEq + Send + Sync {
/// Returns ID of header.
fn id(&self) -> HeaderId<Hash, Number>;
/// Returns ID of parent header.
///
/// Panics if called for genesis header.
fn parent_id(&self) -> HeaderId<Hash, Number>;
}
/// Header how it's stored in the synchronization queue.
#[derive(Clone, Debug, PartialEq)]
pub struct QueuedHeader<P: HeadersSyncPipeline>(Arc<QueuedHeaderData<P>>);
impl<P: HeadersSyncPipeline> QueuedHeader<P> {
/// Creates new queued header.
pub fn new(header: P::Header) -> Self {
QueuedHeader(Arc::new(QueuedHeaderData { header, extra: None }))
}
/// Set associated extra data.
pub fn set_extra(self, extra: P::Extra) -> Self {
QueuedHeader(Arc::new(QueuedHeaderData {
header: Arc::try_unwrap(self.0)
.map(|data| data.header)
.unwrap_or_else(|data| data.header.clone()),
extra: Some(extra),
}))
}
}
impl<P: HeadersSyncPipeline> Deref for QueuedHeader<P> {
type Target = QueuedHeaderData<P>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
/// Header how it's stored in the synchronization queue.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct QueuedHeaderData<P: HeadersSyncPipeline> {
header: P::Header,
extra: Option<P::Extra>,
}
impl<P: HeadersSyncPipeline> QueuedHeader<P> {
/// Returns ID of header.
pub fn id(&self) -> HeaderId<P::Hash, P::Number> {
self.header.id()
}
/// Returns ID of parent header.
pub fn parent_id(&self) -> HeaderId<P::Hash, P::Number> {
self.header.parent_id()
}
/// Returns reference to header.
pub fn header(&self) -> &P::Header {
&self.header
}
/// Returns reference to associated extra data.
pub fn extra(&self) -> &Option<P::Extra> {
&self.extra
}
}
/// Headers submission result.
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct SubmittedHeaders<Id, Error> {
/// IDs of headers that have been submitted to target node.
pub submitted: Vec<Id>,
/// IDs of incomplete headers. These headers were submitted (so this id is also in `submitted` vec),
/// but all descendants are not.
pub incomplete: Vec<Id>,
/// IDs of ignored headers that we have decided not to submit (they're either rejected by
/// target node immediately, or they're descendants of incomplete headers).
pub rejected: Vec<Id>,
/// Fatal target node error, if it has occured during submission.
pub fatal_error: Option<Error>,
}
impl<Id, Error> Default for SubmittedHeaders<Id, Error> {
fn default() -> Self {
SubmittedHeaders {
submitted: Vec::new(),
incomplete: Vec::new(),
rejected: Vec::new(),
fatal_error: None,
}
}
}
impl<Id: std::fmt::Debug, Error> std::fmt::Display for SubmittedHeaders<Id, Error> {
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
)
}
}
@@ -0,0 +1,25 @@
[package]
name = "relay-kusama-client"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
codec = { package = "parity-scale-codec", version = "2.0.0" }
headers-relay = { path = "../headers-relay" }
relay-substrate-client = { path = "../substrate-client" }
relay-utils = { path = "../utils" }
# Bridge dependencies
bp-kusama = { path = "../../primitives/kusama" }
# Substrate Dependencies
frame-system = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
frame-support = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
pallet-transaction-payment = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
sp-core = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
sp-keyring = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
sp-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
@@ -0,0 +1,47 @@
// 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 <http://www.gnu.org/licenses/>.
//! Types used to connect to the Kusama chain.
use relay_substrate_client::{Chain, ChainBase};
use std::time::Duration;
/// Kusama header id.
pub type HeaderId = relay_utils::HeaderId<bp_kusama::Hash, bp_kusama::BlockNumber>;
/// Kusama chain definition
#[derive(Debug, Clone, Copy)]
pub struct Kusama;
impl ChainBase for Kusama {
type BlockNumber = bp_kusama::BlockNumber;
type Hash = bp_kusama::Hash;
type Hasher = bp_kusama::Hasher;
type Header = bp_kusama::Header;
}
impl Chain for Kusama {
const NAME: &'static str = "Kusama";
const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_secs(6);
type AccountId = bp_kusama::AccountId;
type Index = bp_kusama::Nonce;
type SignedBlock = bp_kusama::SignedBlock;
type Call = ();
}
/// Kusama header type used in headers sync.
pub type SyncHeader = relay_substrate_client::SyncHeader<bp_kusama::Header>;
@@ -0,0 +1,19 @@
[package]
name = "messages-relay"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
async-std = "1.6.5"
async-trait = "0.1.40"
futures = "0.3.5"
hex = "0.4"
log = "0.4.11"
parking_lot = "0.11.0"
# Bridge Dependencies
bp-message-lane = { path = "../../primitives/message-lane" }
relay-utils = { path = "../utils" }
@@ -0,0 +1,36 @@
// 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 <http://www.gnu.org/licenses/>.
//! Relaying [`message-lane`](../pallet_message_lane/index.html) application specific
//! data. Message lane allows sending arbitrary messages between bridged chains. This
//! module provides entrypoint that starts reading messages from given message lane
//! of source chain and submits proof-of-message-at-source-chain transactions to the
//! target chain. Additionaly, proofs-of-messages-delivery are sent back from the
//! target chain to the source chain.
// required for futures::select!
#![recursion_limit = "1024"]
#![warn(missing_docs)]
mod metrics;
pub mod message_lane;
pub mod message_lane_loop;
mod message_race_delivery;
mod message_race_loop;
mod message_race_receiving;
mod message_race_strategy;
@@ -0,0 +1,52 @@
// 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 <http://www.gnu.org/licenses/>.
//! One-way message lane types. Within single one-way lane we have three 'races' where we try to:
//!
//! 1) relay new messages from source to target node;
//! 2) relay proof-of-delivery from target to source node.
use relay_utils::{BlockNumberBase, HeaderId};
use std::fmt::Debug;
/// One-way message lane.
pub trait MessageLane: Clone + Send + Sync {
/// Name of the messages source.
const SOURCE_NAME: &'static str;
/// Name of the messages target.
const TARGET_NAME: &'static str;
/// Messages proof.
type MessagesProof: Clone + Debug + Send + Sync;
/// Messages receiving proof.
type MessagesReceivingProof: Clone + Debug + Send + Sync;
/// Number of the source header.
type SourceHeaderNumber: BlockNumberBase;
/// Hash of the source header.
type SourceHeaderHash: Clone + Debug + Default + PartialEq + Send + Sync;
/// Number of the target header.
type TargetHeaderNumber: BlockNumberBase;
/// Hash of the target header.
type TargetHeaderHash: Clone + Debug + Default + PartialEq + Send + Sync;
}
/// Source header id within given one-way message lane.
pub type SourceHeaderIdOf<P> = HeaderId<<P as MessageLane>::SourceHeaderHash, <P as MessageLane>::SourceHeaderNumber>;
/// Target header id within given one-way message lane.
pub type TargetHeaderIdOf<P> = HeaderId<<P as MessageLane>::TargetHeaderHash, <P as MessageLane>::TargetHeaderNumber>;
@@ -0,0 +1,841 @@
// 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 <http://www.gnu.org/licenses/>.
//! Message delivery loop. Designed to work with message-lane pallet.
//!
//! Single relay instance delivers messages of single lane in single direction.
//! To serve two-way lane, you would need two instances of relay.
//! To serve N two-way lanes, you would need N*2 instances of relay.
//!
//! Please keep in mind that the best header in this file is actually best
//! finalized header. I.e. when talking about headers in lane context, we
//! only care about finalized headers.
use crate::message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf};
use crate::message_race_delivery::run as run_message_delivery_race;
use crate::message_race_receiving::run as run_message_receiving_race;
use crate::metrics::MessageLaneLoopMetrics;
use async_trait::async_trait;
use bp_message_lane::{LaneId, MessageNonce, UnrewardedRelayersState, Weight};
use futures::{channel::mpsc::unbounded, future::FutureExt, stream::StreamExt};
use relay_utils::{
interval,
metrics::{start as metrics_start, GlobalMetrics, MetricsParams},
process_future_result,
relay_loop::Client as RelayClient,
retry_backoff, FailedClient,
};
use std::{collections::BTreeMap, fmt::Debug, future::Future, ops::RangeInclusive, time::Duration};
/// Message lane loop configuration params.
#[derive(Debug, Clone)]
pub struct Params {
/// Id of lane this loop is servicing.
pub lane: LaneId,
/// Interval at which we ask target node about its updates.
pub source_tick: Duration,
/// Interval at which we ask target node about its updates.
pub target_tick: Duration,
/// Delay between moments when connection error happens and our reconnect attempt.
pub reconnect_delay: Duration,
/// The loop will auto-restart if there has been no updates during this period.
pub stall_timeout: Duration,
/// Message delivery race parameters.
pub delivery_params: MessageDeliveryParams,
}
/// Message delivery race parameters.
#[derive(Debug, Clone)]
pub struct MessageDeliveryParams {
/// Maximal number of unconfirmed relayer entries at the inbound lane. If there's that number of entries
/// in the `InboundLaneData::relayers` set, all new messages will be rejected until reward payment will
/// be proved (by including outbound lane state to the message delivery transaction).
pub max_unrewarded_relayer_entries_at_target: MessageNonce,
/// Message delivery race will stop delivering messages if there are `max_unconfirmed_nonces_at_target`
/// unconfirmed nonces on the target node. The race would continue once they're confirmed by the
/// receiving race.
pub max_unconfirmed_nonces_at_target: MessageNonce,
/// Maximal number of relayed messages in single delivery transaction.
pub max_messages_in_single_batch: MessageNonce,
/// Maximal cumulative dispatch weight of relayed messages in single delivery transaction.
pub max_messages_weight_in_single_batch: Weight,
/// Maximal cumulative size of relayed messages in single delivery transaction.
pub max_messages_size_in_single_batch: usize,
}
/// Message weights.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct MessageWeights {
/// Message dispatch weight.
pub weight: Weight,
/// Message size (number of bytes in encoded payload).
pub size: usize,
}
/// Messages weights map.
pub type MessageWeightsMap = BTreeMap<MessageNonce, MessageWeights>;
/// Message delivery race proof parameters.
#[derive(Debug, PartialEq)]
pub struct MessageProofParameters {
/// Include outbound lane state proof?
pub outbound_state_proof_required: bool,
/// Cumulative dispatch weight of messages that we're building proof for.
pub dispatch_weight: Weight,
}
/// Source client trait.
#[async_trait]
pub trait SourceClient<P: MessageLane>: RelayClient {
/// Returns state of the client.
async fn state(&self) -> Result<SourceClientState<P>, Self::Error>;
/// Get nonce of instance of latest generated message.
async fn latest_generated_nonce(
&self,
id: SourceHeaderIdOf<P>,
) -> Result<(SourceHeaderIdOf<P>, MessageNonce), Self::Error>;
/// Get nonce of the latest message, which receiving has been confirmed by the target chain.
async fn latest_confirmed_received_nonce(
&self,
id: SourceHeaderIdOf<P>,
) -> Result<(SourceHeaderIdOf<P>, MessageNonce), Self::Error>;
/// Returns mapping of message nonces, generated on this client, to their weights.
///
/// Some weights may be missing from returned map, if corresponding messages were pruned at
/// the source chain.
async fn generated_messages_weights(
&self,
id: SourceHeaderIdOf<P>,
nonces: RangeInclusive<MessageNonce>,
) -> Result<MessageWeightsMap, Self::Error>;
/// Prove messages in inclusive range [begin; end].
async fn prove_messages(
&self,
id: SourceHeaderIdOf<P>,
nonces: RangeInclusive<MessageNonce>,
proof_parameters: MessageProofParameters,
) -> Result<(SourceHeaderIdOf<P>, RangeInclusive<MessageNonce>, P::MessagesProof), Self::Error>;
/// Submit messages receiving proof.
async fn submit_messages_receiving_proof(
&self,
generated_at_block: TargetHeaderIdOf<P>,
proof: P::MessagesReceivingProof,
) -> Result<(), Self::Error>;
}
/// Target client trait.
#[async_trait]
pub trait TargetClient<P: MessageLane>: RelayClient {
/// Returns state of the client.
async fn state(&self) -> Result<TargetClientState<P>, Self::Error>;
/// Get nonce of latest received message.
async fn latest_received_nonce(
&self,
id: TargetHeaderIdOf<P>,
) -> Result<(TargetHeaderIdOf<P>, MessageNonce), Self::Error>;
/// Get nonce of latest confirmed message.
async fn latest_confirmed_received_nonce(
&self,
id: TargetHeaderIdOf<P>,
) -> Result<(TargetHeaderIdOf<P>, MessageNonce), Self::Error>;
/// Get state of unrewarded relayers set at the inbound lane.
async fn unrewarded_relayers_state(
&self,
id: TargetHeaderIdOf<P>,
) -> Result<(TargetHeaderIdOf<P>, UnrewardedRelayersState), Self::Error>;
/// Prove messages receiving at given block.
async fn prove_messages_receiving(
&self,
id: TargetHeaderIdOf<P>,
) -> Result<(TargetHeaderIdOf<P>, P::MessagesReceivingProof), Self::Error>;
/// Submit messages proof.
async fn submit_messages_proof(
&self,
generated_at_header: SourceHeaderIdOf<P>,
nonces: RangeInclusive<MessageNonce>,
proof: P::MessagesProof,
) -> Result<RangeInclusive<MessageNonce>, Self::Error>;
}
/// State of the client.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct ClientState<SelfHeaderId, PeerHeaderId> {
/// Best header id of this chain.
pub best_self: SelfHeaderId,
/// Best finalized header id of this chain.
pub best_finalized_self: SelfHeaderId,
/// Best finalized header id of the peer chain read at the best block of this chain (at `best_finalized_self`).
pub best_finalized_peer_at_best_self: PeerHeaderId,
}
/// State of source client in one-way message lane.
pub type SourceClientState<P> = ClientState<SourceHeaderIdOf<P>, TargetHeaderIdOf<P>>;
/// State of target client in one-way message lane.
pub type TargetClientState<P> = ClientState<TargetHeaderIdOf<P>, SourceHeaderIdOf<P>>;
/// Both clients state.
#[derive(Debug, Default)]
pub struct ClientsState<P: MessageLane> {
/// Source client state.
pub source: Option<SourceClientState<P>>,
/// Target client state.
pub target: Option<TargetClientState<P>>,
}
/// Run message lane service loop.
pub fn run<P: MessageLane>(
params: Params,
source_client: impl SourceClient<P>,
target_client: impl TargetClient<P>,
metrics_params: Option<MetricsParams>,
exit_signal: impl Future<Output = ()>,
) {
let exit_signal = exit_signal.shared();
let metrics_global = GlobalMetrics::default();
let metrics_msg = MessageLaneLoopMetrics::default();
let metrics_enabled = metrics_params.is_some();
metrics_start(
format!(
"{}_to_{}_MessageLane_{}",
P::SOURCE_NAME,
P::TARGET_NAME,
hex::encode(params.lane)
),
metrics_params,
&metrics_global,
&metrics_msg,
);
relay_utils::relay_loop::run(
params.reconnect_delay,
source_client,
target_client,
|source_client, target_client| {
run_until_connection_lost(
params.clone(),
source_client,
target_client,
if metrics_enabled {
Some(metrics_global.clone())
} else {
None
},
if metrics_enabled {
Some(metrics_msg.clone())
} else {
None
},
exit_signal.clone(),
)
},
);
}
/// Run one-way message delivery loop until connection with target or source node is lost, or exit signal is received.
async fn run_until_connection_lost<P: MessageLane, SC: SourceClient<P>, TC: TargetClient<P>>(
params: Params,
source_client: SC,
target_client: TC,
metrics_global: Option<GlobalMetrics>,
metrics_msg: Option<MessageLaneLoopMetrics>,
exit_signal: impl Future<Output = ()>,
) -> Result<(), FailedClient> {
let mut source_retry_backoff = retry_backoff();
let mut source_client_is_online = false;
let mut source_state_required = true;
let source_state = source_client.state().fuse();
let source_go_offline_future = futures::future::Fuse::terminated();
let source_tick_stream = interval(params.source_tick).fuse();
let mut target_retry_backoff = retry_backoff();
let mut target_client_is_online = false;
let mut target_state_required = true;
let target_state = target_client.state().fuse();
let target_go_offline_future = futures::future::Fuse::terminated();
let target_tick_stream = interval(params.target_tick).fuse();
let (
(delivery_source_state_sender, delivery_source_state_receiver),
(delivery_target_state_sender, delivery_target_state_receiver),
) = (unbounded(), unbounded());
let delivery_race_loop = run_message_delivery_race(
source_client.clone(),
delivery_source_state_receiver,
target_client.clone(),
delivery_target_state_receiver,
params.stall_timeout,
metrics_msg.clone(),
params.delivery_params,
)
.fuse();
let (
(receiving_source_state_sender, receiving_source_state_receiver),
(receiving_target_state_sender, receiving_target_state_receiver),
) = (unbounded(), unbounded());
let receiving_race_loop = run_message_receiving_race(
source_client.clone(),
receiving_source_state_receiver,
target_client.clone(),
receiving_target_state_receiver,
params.stall_timeout,
metrics_msg.clone(),
)
.fuse();
let exit_signal = exit_signal.fuse();
futures::pin_mut!(
source_state,
source_go_offline_future,
source_tick_stream,
target_state,
target_go_offline_future,
target_tick_stream,
delivery_race_loop,
receiving_race_loop,
exit_signal
);
loop {
futures::select! {
new_source_state = source_state => {
source_state_required = false;
source_client_is_online = process_future_result(
new_source_state,
&mut source_retry_backoff,
|new_source_state| {
log::debug!(
target: "bridge",
"Received state from {} node: {:?}",
P::SOURCE_NAME,
new_source_state,
);
let _ = delivery_source_state_sender.unbounded_send(new_source_state.clone());
let _ = receiving_source_state_sender.unbounded_send(new_source_state.clone());
if let Some(metrics_msg) = metrics_msg.as_ref() {
metrics_msg.update_source_state::<P>(new_source_state);
}
},
&mut source_go_offline_future,
async_std::task::sleep,
|| format!("Error retrieving state from {} node", P::SOURCE_NAME),
).fail_if_connection_error(FailedClient::Source)?;
},
_ = source_go_offline_future => {
source_client_is_online = true;
},
_ = source_tick_stream.next() => {
source_state_required = true;
},
new_target_state = target_state => {
target_state_required = false;
target_client_is_online = process_future_result(
new_target_state,
&mut target_retry_backoff,
|new_target_state| {
log::debug!(
target: "bridge",
"Received state from {} node: {:?}",
P::TARGET_NAME,
new_target_state,
);
let _ = delivery_target_state_sender.unbounded_send(new_target_state.clone());
let _ = receiving_target_state_sender.unbounded_send(new_target_state.clone());
if let Some(metrics_msg) = metrics_msg.as_ref() {
metrics_msg.update_target_state::<P>(new_target_state);
}
},
&mut target_go_offline_future,
async_std::task::sleep,
|| format!("Error retrieving state from {} node", P::TARGET_NAME),
).fail_if_connection_error(FailedClient::Target)?;
},
_ = target_go_offline_future => {
target_client_is_online = true;
},
_ = target_tick_stream.next() => {
target_state_required = true;
},
delivery_error = delivery_race_loop => {
match delivery_error {
Ok(_) => unreachable!("only ends with error; qed"),
Err(err) => return Err(err),
}
},
receiving_error = receiving_race_loop => {
match receiving_error {
Ok(_) => unreachable!("only ends with error; qed"),
Err(err) => return Err(err),
}
},
() = exit_signal => {
return Ok(());
}
}
if let Some(ref metrics_global) = metrics_global {
metrics_global.update().await;
}
if source_client_is_online && source_state_required {
log::debug!(target: "bridge", "Asking {} node about its state", P::SOURCE_NAME);
source_state.set(source_client.state().fuse());
source_client_is_online = false;
}
if target_client_is_online && target_state_required {
log::debug!(target: "bridge", "Asking {} node about its state", P::TARGET_NAME);
target_state.set(target_client.state().fuse());
target_client_is_online = false;
}
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use futures::stream::StreamExt;
use parking_lot::Mutex;
use relay_utils::{HeaderId, MaybeConnectionError};
use std::sync::Arc;
pub fn header_id(number: TestSourceHeaderNumber) -> TestSourceHeaderId {
HeaderId(number, number)
}
pub type TestSourceHeaderId = HeaderId<TestSourceHeaderNumber, TestSourceHeaderHash>;
pub type TestTargetHeaderId = HeaderId<TestTargetHeaderNumber, TestTargetHeaderHash>;
pub type TestMessagesProof = (RangeInclusive<MessageNonce>, Option<MessageNonce>);
pub type TestMessagesReceivingProof = MessageNonce;
pub type TestSourceHeaderNumber = u64;
pub type TestSourceHeaderHash = u64;
pub type TestTargetHeaderNumber = u64;
pub type TestTargetHeaderHash = u64;
#[derive(Debug)]
pub struct TestError;
impl MaybeConnectionError for TestError {
fn is_connection_error(&self) -> bool {
true
}
}
#[derive(Clone)]
pub struct TestMessageLane;
impl MessageLane for TestMessageLane {
const SOURCE_NAME: &'static str = "TestSource";
const TARGET_NAME: &'static str = "TestTarget";
type MessagesProof = TestMessagesProof;
type MessagesReceivingProof = TestMessagesReceivingProof;
type SourceHeaderNumber = TestSourceHeaderNumber;
type SourceHeaderHash = TestSourceHeaderHash;
type TargetHeaderNumber = TestTargetHeaderNumber;
type TargetHeaderHash = TestTargetHeaderHash;
}
#[derive(Debug, Default, Clone)]
pub struct TestClientData {
is_source_fails: bool,
is_source_reconnected: bool,
source_state: SourceClientState<TestMessageLane>,
source_latest_generated_nonce: MessageNonce,
source_latest_confirmed_received_nonce: MessageNonce,
submitted_messages_receiving_proofs: Vec<TestMessagesReceivingProof>,
is_target_fails: bool,
is_target_reconnected: bool,
target_state: SourceClientState<TestMessageLane>,
target_latest_received_nonce: MessageNonce,
target_latest_confirmed_received_nonce: MessageNonce,
submitted_messages_proofs: Vec<TestMessagesProof>,
}
#[derive(Clone)]
pub struct TestSourceClient {
data: Arc<Mutex<TestClientData>>,
tick: Arc<dyn Fn(&mut TestClientData) + Send + Sync>,
}
#[async_trait]
impl RelayClient for TestSourceClient {
type Error = TestError;
async fn reconnect(&mut self) -> Result<(), TestError> {
{
let mut data = self.data.lock();
(self.tick)(&mut *data);
data.is_source_reconnected = true;
}
Ok(())
}
}
#[async_trait]
impl SourceClient<TestMessageLane> for TestSourceClient {
async fn state(&self) -> Result<SourceClientState<TestMessageLane>, TestError> {
let mut data = self.data.lock();
(self.tick)(&mut *data);
if data.is_source_fails {
return Err(TestError);
}
Ok(data.source_state.clone())
}
async fn latest_generated_nonce(
&self,
id: SourceHeaderIdOf<TestMessageLane>,
) -> Result<(SourceHeaderIdOf<TestMessageLane>, MessageNonce), TestError> {
let mut data = self.data.lock();
(self.tick)(&mut *data);
if data.is_source_fails {
return Err(TestError);
}
Ok((id, data.source_latest_generated_nonce))
}
async fn latest_confirmed_received_nonce(
&self,
id: SourceHeaderIdOf<TestMessageLane>,
) -> Result<(SourceHeaderIdOf<TestMessageLane>, MessageNonce), TestError> {
let mut data = self.data.lock();
(self.tick)(&mut *data);
Ok((id, data.source_latest_confirmed_received_nonce))
}
async fn generated_messages_weights(
&self,
_id: SourceHeaderIdOf<TestMessageLane>,
nonces: RangeInclusive<MessageNonce>,
) -> Result<MessageWeightsMap, TestError> {
Ok(nonces
.map(|nonce| (nonce, MessageWeights { weight: 1, size: 1 }))
.collect())
}
async fn prove_messages(
&self,
id: SourceHeaderIdOf<TestMessageLane>,
nonces: RangeInclusive<MessageNonce>,
proof_parameters: MessageProofParameters,
) -> Result<
(
SourceHeaderIdOf<TestMessageLane>,
RangeInclusive<MessageNonce>,
TestMessagesProof,
),
TestError,
> {
let mut data = self.data.lock();
(self.tick)(&mut *data);
Ok((
id,
nonces.clone(),
(
nonces,
if proof_parameters.outbound_state_proof_required {
Some(data.source_latest_confirmed_received_nonce)
} else {
None
},
),
))
}
async fn submit_messages_receiving_proof(
&self,
_generated_at_block: TargetHeaderIdOf<TestMessageLane>,
proof: TestMessagesReceivingProof,
) -> Result<(), TestError> {
let mut data = self.data.lock();
(self.tick)(&mut *data);
data.submitted_messages_receiving_proofs.push(proof);
data.source_latest_confirmed_received_nonce = proof;
Ok(())
}
}
#[derive(Clone)]
pub struct TestTargetClient {
data: Arc<Mutex<TestClientData>>,
tick: Arc<dyn Fn(&mut TestClientData) + Send + Sync>,
}
#[async_trait]
impl RelayClient for TestTargetClient {
type Error = TestError;
async fn reconnect(&mut self) -> Result<(), TestError> {
{
let mut data = self.data.lock();
(self.tick)(&mut *data);
data.is_target_reconnected = true;
}
Ok(())
}
}
#[async_trait]
impl TargetClient<TestMessageLane> for TestTargetClient {
async fn state(&self) -> Result<TargetClientState<TestMessageLane>, TestError> {
let mut data = self.data.lock();
(self.tick)(&mut *data);
if data.is_target_fails {
return Err(TestError);
}
Ok(data.target_state.clone())
}
async fn latest_received_nonce(
&self,
id: TargetHeaderIdOf<TestMessageLane>,
) -> Result<(TargetHeaderIdOf<TestMessageLane>, MessageNonce), TestError> {
let mut data = self.data.lock();
(self.tick)(&mut *data);
if data.is_target_fails {
return Err(TestError);
}
Ok((id, data.target_latest_received_nonce))
}
async fn unrewarded_relayers_state(
&self,
id: TargetHeaderIdOf<TestMessageLane>,
) -> Result<(TargetHeaderIdOf<TestMessageLane>, UnrewardedRelayersState), TestError> {
Ok((
id,
UnrewardedRelayersState {
unrewarded_relayer_entries: 0,
messages_in_oldest_entry: 0,
total_messages: 0,
},
))
}
async fn latest_confirmed_received_nonce(
&self,
id: TargetHeaderIdOf<TestMessageLane>,
) -> Result<(TargetHeaderIdOf<TestMessageLane>, MessageNonce), TestError> {
let mut data = self.data.lock();
(self.tick)(&mut *data);
if data.is_target_fails {
return Err(TestError);
}
Ok((id, data.target_latest_confirmed_received_nonce))
}
async fn prove_messages_receiving(
&self,
id: TargetHeaderIdOf<TestMessageLane>,
) -> Result<(TargetHeaderIdOf<TestMessageLane>, TestMessagesReceivingProof), TestError> {
Ok((id, self.data.lock().target_latest_received_nonce))
}
async fn submit_messages_proof(
&self,
_generated_at_header: SourceHeaderIdOf<TestMessageLane>,
nonces: RangeInclusive<MessageNonce>,
proof: TestMessagesProof,
) -> Result<RangeInclusive<MessageNonce>, TestError> {
let mut data = self.data.lock();
(self.tick)(&mut *data);
if data.is_target_fails {
return Err(TestError);
}
data.target_state.best_self =
HeaderId(data.target_state.best_self.0 + 1, data.target_state.best_self.1 + 1);
data.target_latest_received_nonce = *proof.0.end();
if let Some(target_latest_confirmed_received_nonce) = proof.1 {
data.target_latest_confirmed_received_nonce = target_latest_confirmed_received_nonce;
}
data.submitted_messages_proofs.push(proof);
Ok(nonces)
}
}
fn run_loop_test(
data: TestClientData,
source_tick: Arc<dyn Fn(&mut TestClientData) + Send + Sync>,
target_tick: Arc<dyn Fn(&mut TestClientData) + Send + Sync>,
exit_signal: impl Future<Output = ()>,
) -> TestClientData {
async_std::task::block_on(async {
let data = Arc::new(Mutex::new(data));
let source_client = TestSourceClient {
data: data.clone(),
tick: source_tick,
};
let target_client = TestTargetClient {
data: data.clone(),
tick: target_tick,
};
run(
Params {
lane: [0, 0, 0, 0],
source_tick: Duration::from_millis(100),
target_tick: Duration::from_millis(100),
reconnect_delay: Duration::from_millis(0),
stall_timeout: Duration::from_millis(60 * 1000),
delivery_params: MessageDeliveryParams {
max_unrewarded_relayer_entries_at_target: 4,
max_unconfirmed_nonces_at_target: 4,
max_messages_in_single_batch: 4,
max_messages_weight_in_single_batch: 4,
max_messages_size_in_single_batch: 4,
},
},
source_client,
target_client,
None,
exit_signal,
);
let result = data.lock().clone();
result
})
}
#[test]
fn message_lane_loop_is_able_to_recover_from_connection_errors() {
// with this configuration, source client will return Err, making source client
// reconnect. Then the target client will fail with Err + reconnect. Then we finally
// able to deliver messages.
let (exit_sender, exit_receiver) = unbounded();
let result = run_loop_test(
TestClientData {
is_source_fails: true,
source_state: ClientState {
best_self: HeaderId(0, 0),
best_finalized_self: HeaderId(0, 0),
best_finalized_peer_at_best_self: HeaderId(0, 0),
},
source_latest_generated_nonce: 1,
target_state: ClientState {
best_self: HeaderId(0, 0),
best_finalized_self: HeaderId(0, 0),
best_finalized_peer_at_best_self: HeaderId(0, 0),
},
target_latest_received_nonce: 0,
..Default::default()
},
Arc::new(|data: &mut TestClientData| {
if data.is_source_reconnected {
data.is_source_fails = false;
data.is_target_fails = true;
}
}),
Arc::new(move |data: &mut TestClientData| {
if data.is_target_reconnected {
data.is_target_fails = false;
}
if data.target_state.best_finalized_peer_at_best_self.0 < 10 {
data.target_state.best_finalized_peer_at_best_self = HeaderId(
data.target_state.best_finalized_peer_at_best_self.0 + 1,
data.target_state.best_finalized_peer_at_best_self.0 + 1,
);
}
if !data.submitted_messages_proofs.is_empty() {
exit_sender.unbounded_send(()).unwrap();
}
}),
exit_receiver.into_future().map(|(_, _)| ()),
);
assert_eq!(result.submitted_messages_proofs, vec![(1..=1, None)],);
}
#[test]
fn message_lane_loop_works() {
let (exit_sender, exit_receiver) = unbounded();
let result = run_loop_test(
TestClientData {
source_state: ClientState {
best_self: HeaderId(10, 10),
best_finalized_self: HeaderId(10, 10),
best_finalized_peer_at_best_self: HeaderId(0, 0),
},
source_latest_generated_nonce: 10,
target_state: ClientState {
best_self: HeaderId(0, 0),
best_finalized_self: HeaderId(0, 0),
best_finalized_peer_at_best_self: HeaderId(0, 0),
},
target_latest_received_nonce: 0,
..Default::default()
},
Arc::new(|_: &mut TestClientData| {}),
Arc::new(move |data: &mut TestClientData| {
// syncing source headers -> target chain (all at once)
if data.target_state.best_finalized_peer_at_best_self.0 < data.source_state.best_finalized_self.0 {
data.target_state.best_finalized_peer_at_best_self = data.source_state.best_finalized_self;
}
// syncing source headers -> target chain (all at once)
if data.source_state.best_finalized_peer_at_best_self.0 < data.target_state.best_finalized_self.0 {
data.source_state.best_finalized_peer_at_best_self = data.target_state.best_finalized_self;
}
// if target has received messages batch => increase blocks so that confirmations may be sent
if data.target_latest_received_nonce == 4
|| data.target_latest_received_nonce == 8
|| data.target_latest_received_nonce == 10
{
data.target_state.best_self =
HeaderId(data.target_state.best_self.0 + 1, data.target_state.best_self.0 + 1);
data.target_state.best_finalized_self = data.target_state.best_self;
data.source_state.best_self =
HeaderId(data.source_state.best_self.0 + 1, data.source_state.best_self.0 + 1);
data.source_state.best_finalized_self = data.source_state.best_self;
}
// if source has received all messages receiving confirmations => increase source block so that confirmations may be sent
if data.source_latest_confirmed_received_nonce == 10 {
exit_sender.unbounded_send(()).unwrap();
}
}),
exit_receiver.into_future().map(|(_, _)| ()),
);
// there are no strict restrictions on when reward confirmation should come
// (because `max_unconfirmed_nonces_at_target` is `100` in tests and this confirmation
// depends on the state of both clients)
// => we do not check it here
assert_eq!(result.submitted_messages_proofs[0].0, 1..=4);
assert_eq!(result.submitted_messages_proofs[1].0, 5..=8);
assert_eq!(result.submitted_messages_proofs[2].0, 9..=10);
assert!(!result.submitted_messages_receiving_proofs.is_empty());
}
}
@@ -0,0 +1,871 @@
// 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.
//! Message delivery race delivers proof-of-messages from lane.source to lane.target.
use crate::message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf};
use crate::message_lane_loop::{
MessageDeliveryParams, MessageProofParameters, MessageWeightsMap, SourceClient as MessageLaneSourceClient,
SourceClientState, TargetClient as MessageLaneTargetClient, TargetClientState,
};
use crate::message_race_loop::{
MessageRace, NoncesRange, RaceState, RaceStrategy, SourceClient, SourceClientNonces, TargetClient,
TargetClientNonces,
};
use crate::message_race_strategy::BasicStrategy;
use crate::metrics::MessageLaneLoopMetrics;
use async_trait::async_trait;
use bp_message_lane::{MessageNonce, UnrewardedRelayersState, Weight};
use futures::stream::FusedStream;
use relay_utils::FailedClient;
use std::{
collections::{BTreeMap, VecDeque},
marker::PhantomData,
ops::RangeInclusive,
time::Duration,
};
/// Run message delivery race.
pub async fn run<P: MessageLane>(
source_client: impl MessageLaneSourceClient<P>,
source_state_updates: impl FusedStream<Item = SourceClientState<P>>,
target_client: impl MessageLaneTargetClient<P>,
target_state_updates: impl FusedStream<Item = TargetClientState<P>>,
stall_timeout: Duration,
metrics_msg: Option<MessageLaneLoopMetrics>,
params: MessageDeliveryParams,
) -> Result<(), FailedClient> {
crate::message_race_loop::run(
MessageDeliveryRaceSource {
client: source_client,
metrics_msg: metrics_msg.clone(),
_phantom: Default::default(),
},
source_state_updates,
MessageDeliveryRaceTarget {
client: target_client,
metrics_msg,
_phantom: Default::default(),
},
target_state_updates,
stall_timeout,
MessageDeliveryStrategy::<P> {
max_unrewarded_relayer_entries_at_target: params.max_unrewarded_relayer_entries_at_target,
max_unconfirmed_nonces_at_target: params.max_unconfirmed_nonces_at_target,
max_messages_in_single_batch: params.max_messages_in_single_batch,
max_messages_weight_in_single_batch: params.max_messages_weight_in_single_batch,
max_messages_size_in_single_batch: params.max_messages_size_in_single_batch,
latest_confirmed_nonces_at_source: VecDeque::new(),
target_nonces: None,
strategy: BasicStrategy::new(),
},
)
.await
}
/// Message delivery race.
struct MessageDeliveryRace<P>(std::marker::PhantomData<P>);
impl<P: MessageLane> MessageRace for MessageDeliveryRace<P> {
type SourceHeaderId = SourceHeaderIdOf<P>;
type TargetHeaderId = TargetHeaderIdOf<P>;
type MessageNonce = MessageNonce;
type Proof = P::MessagesProof;
fn source_name() -> String {
format!("{}::MessagesDelivery", P::SOURCE_NAME)
}
fn target_name() -> String {
format!("{}::MessagesDelivery", P::TARGET_NAME)
}
}
/// Message delivery race source, which is a source of the lane.
struct MessageDeliveryRaceSource<P: MessageLane, C> {
client: C,
metrics_msg: Option<MessageLaneLoopMetrics>,
_phantom: PhantomData<P>,
}
#[async_trait]
impl<P, C> SourceClient<MessageDeliveryRace<P>> for MessageDeliveryRaceSource<P, C>
where
P: MessageLane,
C: MessageLaneSourceClient<P>,
{
type Error = C::Error;
type NoncesRange = MessageWeightsMap;
type ProofParameters = MessageProofParameters;
async fn nonces(
&self,
at_block: SourceHeaderIdOf<P>,
prev_latest_nonce: MessageNonce,
) -> Result<(SourceHeaderIdOf<P>, SourceClientNonces<Self::NoncesRange>), Self::Error> {
let (at_block, latest_generated_nonce) = self.client.latest_generated_nonce(at_block).await?;
let (at_block, latest_confirmed_nonce) = self.client.latest_confirmed_received_nonce(at_block).await?;
if let Some(metrics_msg) = self.metrics_msg.as_ref() {
metrics_msg.update_source_latest_generated_nonce::<P>(latest_generated_nonce);
metrics_msg.update_source_latest_confirmed_nonce::<P>(latest_confirmed_nonce);
}
let new_nonces = if latest_generated_nonce > prev_latest_nonce {
self.client
.generated_messages_weights(at_block.clone(), prev_latest_nonce + 1..=latest_generated_nonce)
.await?
} else {
MessageWeightsMap::new()
};
Ok((
at_block,
SourceClientNonces {
new_nonces,
confirmed_nonce: Some(latest_confirmed_nonce),
},
))
}
async fn generate_proof(
&self,
at_block: SourceHeaderIdOf<P>,
nonces: RangeInclusive<MessageNonce>,
proof_parameters: Self::ProofParameters,
) -> Result<(SourceHeaderIdOf<P>, RangeInclusive<MessageNonce>, P::MessagesProof), Self::Error> {
self.client.prove_messages(at_block, nonces, proof_parameters).await
}
}
/// Message delivery race target, which is a target of the lane.
struct MessageDeliveryRaceTarget<P: MessageLane, C> {
client: C,
metrics_msg: Option<MessageLaneLoopMetrics>,
_phantom: PhantomData<P>,
}
#[async_trait]
impl<P, C> TargetClient<MessageDeliveryRace<P>> for MessageDeliveryRaceTarget<P, C>
where
P: MessageLane,
C: MessageLaneTargetClient<P>,
{
type Error = C::Error;
type TargetNoncesData = DeliveryRaceTargetNoncesData;
async fn nonces(
&self,
at_block: TargetHeaderIdOf<P>,
update_metrics: bool,
) -> Result<(TargetHeaderIdOf<P>, TargetClientNonces<DeliveryRaceTargetNoncesData>), Self::Error> {
let (at_block, latest_received_nonce) = self.client.latest_received_nonce(at_block).await?;
let (at_block, latest_confirmed_nonce) = self.client.latest_confirmed_received_nonce(at_block).await?;
let (at_block, unrewarded_relayers) = self.client.unrewarded_relayers_state(at_block).await?;
if update_metrics {
if let Some(metrics_msg) = self.metrics_msg.as_ref() {
metrics_msg.update_target_latest_received_nonce::<P>(latest_received_nonce);
metrics_msg.update_target_latest_confirmed_nonce::<P>(latest_confirmed_nonce);
}
}
Ok((
at_block,
TargetClientNonces {
latest_nonce: latest_received_nonce,
nonces_data: DeliveryRaceTargetNoncesData {
confirmed_nonce: latest_confirmed_nonce,
unrewarded_relayers,
},
},
))
}
async fn submit_proof(
&self,
generated_at_block: SourceHeaderIdOf<P>,
nonces: RangeInclusive<MessageNonce>,
proof: P::MessagesProof,
) -> Result<RangeInclusive<MessageNonce>, Self::Error> {
self.client
.submit_messages_proof(generated_at_block, nonces, proof)
.await
}
}
/// Additional nonces data from the target client used by message delivery race.
#[derive(Debug, Clone)]
struct DeliveryRaceTargetNoncesData {
/// Latest nonce that we know: (1) has been delivered to us (2) has been confirmed
/// back to the source node (by confirmations race) and (3) relayer has received
/// reward for (and this has been confirmed by the message delivery race).
confirmed_nonce: MessageNonce,
/// State of the unrewarded relayers set at the target node.
unrewarded_relayers: UnrewardedRelayersState,
}
/// Messages delivery strategy.
struct MessageDeliveryStrategy<P: MessageLane> {
/// Maximal unrewarded relayer entries at target client.
max_unrewarded_relayer_entries_at_target: MessageNonce,
/// Maximal unconfirmed nonces at target client.
max_unconfirmed_nonces_at_target: MessageNonce,
/// Maximal number of messages in the single delivery transaction.
max_messages_in_single_batch: MessageNonce,
/// Maximal cumulative messages weight in the single delivery transaction.
max_messages_weight_in_single_batch: Weight,
/// Maximal messages size in the single delivery transaction.
max_messages_size_in_single_batch: usize,
/// Latest confirmed nonces at the source client + the header id where we have first met this nonce.
latest_confirmed_nonces_at_source: VecDeque<(SourceHeaderIdOf<P>, MessageNonce)>,
/// Target nonces from the source client.
target_nonces: Option<TargetClientNonces<DeliveryRaceTargetNoncesData>>,
/// Basic delivery strategy.
strategy: MessageDeliveryStrategyBase<P>,
}
type MessageDeliveryStrategyBase<P> = BasicStrategy<
<P as MessageLane>::SourceHeaderNumber,
<P as MessageLane>::SourceHeaderHash,
<P as MessageLane>::TargetHeaderNumber,
<P as MessageLane>::TargetHeaderHash,
MessageWeightsMap,
<P as MessageLane>::MessagesProof,
>;
impl<P: MessageLane> std::fmt::Debug for MessageDeliveryStrategy<P> {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
fmt.debug_struct("MessageDeliveryStrategy")
.field(
"max_unrewarded_relayer_entries_at_target",
&self.max_unrewarded_relayer_entries_at_target,
)
.field(
"max_unconfirmed_nonces_at_target",
&self.max_unconfirmed_nonces_at_target,
)
.field("max_messages_in_single_batch", &self.max_messages_in_single_batch)
.field(
"max_messages_weight_in_single_batch",
&self.max_messages_weight_in_single_batch,
)
.field(
"max_messages_size_in_single_batch",
&self.max_messages_size_in_single_batch,
)
.field(
"latest_confirmed_nonces_at_source",
&self.latest_confirmed_nonces_at_source,
)
.field("target_nonces", &self.target_nonces)
.field("strategy", &self.strategy)
.finish()
}
}
impl<P: MessageLane> RaceStrategy<SourceHeaderIdOf<P>, TargetHeaderIdOf<P>, P::MessagesProof>
for MessageDeliveryStrategy<P>
{
type SourceNoncesRange = MessageWeightsMap;
type ProofParameters = MessageProofParameters;
type TargetNoncesData = DeliveryRaceTargetNoncesData;
fn is_empty(&self) -> bool {
self.strategy.is_empty()
}
fn best_at_source(&self) -> Option<MessageNonce> {
self.strategy.best_at_source()
}
fn best_at_target(&self) -> Option<MessageNonce> {
self.strategy.best_at_target()
}
fn source_nonces_updated(
&mut self,
at_block: SourceHeaderIdOf<P>,
nonces: SourceClientNonces<Self::SourceNoncesRange>,
) {
if let Some(confirmed_nonce) = nonces.confirmed_nonce {
let is_confirmed_nonce_updated = self
.latest_confirmed_nonces_at_source
.back()
.map(|(_, prev_nonce)| *prev_nonce != confirmed_nonce)
.unwrap_or(true);
if is_confirmed_nonce_updated {
self.latest_confirmed_nonces_at_source
.push_back((at_block.clone(), confirmed_nonce));
}
}
self.strategy.source_nonces_updated(at_block, nonces)
}
fn best_target_nonces_updated(
&mut self,
nonces: TargetClientNonces<DeliveryRaceTargetNoncesData>,
race_state: &mut RaceState<SourceHeaderIdOf<P>, TargetHeaderIdOf<P>, P::MessagesProof>,
) {
// best target nonces must always be ge than finalized target nonces
let mut target_nonces = self.target_nonces.take().unwrap_or_else(|| nonces.clone());
target_nonces.nonces_data = nonces.nonces_data.clone();
target_nonces.latest_nonce = std::cmp::max(target_nonces.latest_nonce, nonces.latest_nonce);
self.target_nonces = Some(target_nonces);
self.strategy.best_target_nonces_updated(
TargetClientNonces {
latest_nonce: nonces.latest_nonce,
nonces_data: (),
},
race_state,
)
}
fn finalized_target_nonces_updated(
&mut self,
nonces: TargetClientNonces<DeliveryRaceTargetNoncesData>,
race_state: &mut RaceState<SourceHeaderIdOf<P>, TargetHeaderIdOf<P>, P::MessagesProof>,
) {
if let Some(ref best_finalized_source_header_id_at_best_target) =
race_state.best_finalized_source_header_id_at_best_target
{
let oldest_header_number_to_keep = best_finalized_source_header_id_at_best_target.0;
while self
.latest_confirmed_nonces_at_source
.front()
.map(|(id, _)| id.0 < oldest_header_number_to_keep)
.unwrap_or(false)
{
self.latest_confirmed_nonces_at_source.pop_front();
}
}
if let Some(ref mut target_nonces) = self.target_nonces {
target_nonces.latest_nonce = std::cmp::max(target_nonces.latest_nonce, nonces.latest_nonce);
}
self.strategy.finalized_target_nonces_updated(
TargetClientNonces {
latest_nonce: nonces.latest_nonce,
nonces_data: (),
},
race_state,
)
}
fn select_nonces_to_deliver(
&mut self,
race_state: &RaceState<SourceHeaderIdOf<P>, TargetHeaderIdOf<P>, P::MessagesProof>,
) -> Option<(RangeInclusive<MessageNonce>, Self::ProofParameters)> {
let best_finalized_source_header_id_at_best_target =
race_state.best_finalized_source_header_id_at_best_target.clone()?;
let latest_confirmed_nonce_at_source = self
.latest_confirmed_nonces_at_source
.iter()
.take_while(|(id, _)| id.0 <= best_finalized_source_header_id_at_best_target.0)
.last()
.map(|(_, nonce)| *nonce)?;
let target_nonces = self.target_nonces.as_ref()?;
// There's additional condition in the message delivery race: target would reject messages
// if there are too much unconfirmed messages at the inbound lane.
// The receiving race is responsible to deliver confirmations back to the source chain. So if
// there's a lot of unconfirmed messages, let's wait until it'll be able to do its job.
let latest_received_nonce_at_target = target_nonces.latest_nonce;
let confirmations_missing = latest_received_nonce_at_target.checked_sub(latest_confirmed_nonce_at_source);
match confirmations_missing {
Some(confirmations_missing) if confirmations_missing >= self.max_unconfirmed_nonces_at_target => {
log::debug!(
target: "bridge",
"Cannot deliver any more messages from {} to {}. Too many unconfirmed nonces \
at target: target.latest_received={:?}, source.latest_confirmed={:?}, max={:?}",
MessageDeliveryRace::<P>::source_name(),
MessageDeliveryRace::<P>::target_name(),
latest_received_nonce_at_target,
latest_confirmed_nonce_at_source,
self.max_unconfirmed_nonces_at_target,
);
return None;
}
_ => (),
}
// Ok - we may have new nonces to deliver. But target may still reject new messages, because we haven't
// notified it that (some) messages have been confirmed. So we may want to include updated
// `source.latest_confirmed` in the proof.
//
// Important note: we're including outbound state lane proof whenever there are unconfirmed nonces
// on the target chain. Other strategy is to include it only if it's absolutely necessary.
let latest_confirmed_nonce_at_target = target_nonces.nonces_data.confirmed_nonce;
let outbound_state_proof_required = latest_confirmed_nonce_at_target < latest_confirmed_nonce_at_source;
// The target node would also reject messages if there are too many entries in the
// "unrewarded relayers" set. If we are unable to prove new rewards to the target node, then
// we should wait for confirmations race.
let unrewarded_relayer_entries_limit_reached =
target_nonces.nonces_data.unrewarded_relayers.unrewarded_relayer_entries
>= self.max_unrewarded_relayer_entries_at_target;
if unrewarded_relayer_entries_limit_reached {
// so there are already too many unrewarded relayer entries in the set
//
// => check if we can prove enough rewards. If not, we should wait for more rewards to be paid
let number_of_rewards_being_proved =
latest_confirmed_nonce_at_source.saturating_sub(latest_confirmed_nonce_at_target);
let enough_rewards_being_proved = number_of_rewards_being_proved
>= target_nonces.nonces_data.unrewarded_relayers.messages_in_oldest_entry;
if !enough_rewards_being_proved {
return None;
}
}
// If we're here, then the confirmations race did its job && sending side now knows that messages
// have been delivered. Now let's select nonces that we want to deliver.
//
// We may deliver at most:
//
// max_unconfirmed_nonces_at_target - (latest_received_nonce_at_target - latest_confirmed_nonce_at_target)
//
// messages in the batch. But since we're including outbound state proof in the batch, then it
// may be increased to:
//
// max_unconfirmed_nonces_at_target - (latest_received_nonce_at_target - latest_confirmed_nonce_at_source)
let future_confirmed_nonce_at_target = if outbound_state_proof_required {
latest_confirmed_nonce_at_source
} else {
latest_confirmed_nonce_at_target
};
let max_nonces = latest_received_nonce_at_target
.checked_sub(future_confirmed_nonce_at_target)
.and_then(|diff| self.max_unconfirmed_nonces_at_target.checked_sub(diff))
.unwrap_or_default();
let max_nonces = std::cmp::min(max_nonces, self.max_messages_in_single_batch);
let max_messages_weight_in_single_batch = self.max_messages_weight_in_single_batch;
let max_messages_size_in_single_batch = self.max_messages_size_in_single_batch;
let mut selected_weight: Weight = 0;
let mut selected_size: usize = 0;
let mut selected_count: MessageNonce = 0;
let selected_nonces = self
.strategy
.select_nonces_to_deliver_with_selector(race_state, |range| {
let to_requeue = range
.into_iter()
.skip_while(|(_, weight)| {
// Since we (hopefully) have some reserves in `max_messages_weight_in_single_batch`
// and `max_messages_size_in_single_batch`, we may still try to submit transaction
// with single message if message overflows these limits. The worst case would be if
// transaction will be rejected by the target runtime, but at least we have tried.
// limit messages in the batch by weight
let new_selected_weight = match selected_weight.checked_add(weight.weight) {
Some(new_selected_weight) if new_selected_weight <= max_messages_weight_in_single_batch => {
new_selected_weight
}
new_selected_weight if selected_count == 0 => {
log::warn!(
target: "bridge",
"Going to submit message delivery transaction with declared dispatch \
weight {:?} that overflows maximal configured weight {}",
new_selected_weight,
max_messages_weight_in_single_batch,
);
new_selected_weight.unwrap_or(Weight::MAX)
}
_ => return false,
};
// limit messages in the batch by size
let new_selected_size = match selected_size.checked_add(weight.size) {
Some(new_selected_size) if new_selected_size <= max_messages_size_in_single_batch => {
new_selected_size
}
new_selected_size if selected_count == 0 => {
log::warn!(
target: "bridge",
"Going to submit message delivery transaction with message \
size {:?} that overflows maximal configured size {}",
new_selected_size,
max_messages_size_in_single_batch,
);
new_selected_size.unwrap_or(usize::MAX)
}
_ => return false,
};
// limit number of messages in the batch
let new_selected_count = selected_count + 1;
if new_selected_count > max_nonces {
return false;
}
selected_weight = new_selected_weight;
selected_size = new_selected_size;
selected_count = new_selected_count;
true
})
.collect::<BTreeMap<_, _>>();
if to_requeue.is_empty() {
None
} else {
Some(to_requeue)
}
})?;
Some((
selected_nonces,
MessageProofParameters {
outbound_state_proof_required,
dispatch_weight: selected_weight,
},
))
}
}
impl NoncesRange for MessageWeightsMap {
fn begin(&self) -> MessageNonce {
self.keys().next().cloned().unwrap_or_default()
}
fn end(&self) -> MessageNonce {
self.keys().next_back().cloned().unwrap_or_default()
}
fn greater_than(mut self, nonce: MessageNonce) -> Option<Self> {
let gte = self.split_off(&(nonce + 1));
if gte.is_empty() {
None
} else {
Some(gte)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::message_lane_loop::{
tests::{header_id, TestMessageLane, TestMessagesProof, TestSourceHeaderId, TestTargetHeaderId},
MessageWeights,
};
type TestRaceState = RaceState<TestSourceHeaderId, TestTargetHeaderId, TestMessagesProof>;
type TestStrategy = MessageDeliveryStrategy<TestMessageLane>;
fn prepare_strategy() -> (TestRaceState, TestStrategy) {
let mut race_state = RaceState {
best_finalized_source_header_id_at_source: Some(header_id(1)),
best_finalized_source_header_id_at_best_target: Some(header_id(1)),
best_target_header_id: Some(header_id(1)),
best_finalized_target_header_id: Some(header_id(1)),
nonces_to_submit: None,
nonces_submitted: None,
};
let mut race_strategy = TestStrategy {
max_unrewarded_relayer_entries_at_target: 4,
max_unconfirmed_nonces_at_target: 4,
max_messages_in_single_batch: 4,
max_messages_weight_in_single_batch: 4,
max_messages_size_in_single_batch: 4,
latest_confirmed_nonces_at_source: vec![(header_id(1), 19)].into_iter().collect(),
target_nonces: Some(TargetClientNonces {
latest_nonce: 19,
nonces_data: DeliveryRaceTargetNoncesData {
confirmed_nonce: 19,
unrewarded_relayers: UnrewardedRelayersState {
unrewarded_relayer_entries: 0,
messages_in_oldest_entry: 0,
total_messages: 0,
},
},
}),
strategy: BasicStrategy::new(),
};
race_strategy.strategy.source_nonces_updated(
header_id(1),
SourceClientNonces {
new_nonces: vec![
(20, MessageWeights { weight: 1, size: 1 }),
(21, MessageWeights { weight: 1, size: 1 }),
(22, MessageWeights { weight: 1, size: 1 }),
(23, MessageWeights { weight: 1, size: 1 }),
]
.into_iter()
.collect(),
confirmed_nonce: Some(19),
},
);
let target_nonces = TargetClientNonces {
latest_nonce: 19,
nonces_data: (),
};
race_strategy
.strategy
.best_target_nonces_updated(target_nonces.clone(), &mut race_state);
race_strategy
.strategy
.finalized_target_nonces_updated(target_nonces, &mut race_state);
(race_state, race_strategy)
}
fn proof_parameters(state_required: bool, weight: Weight) -> MessageProofParameters {
MessageProofParameters {
outbound_state_proof_required: state_required,
dispatch_weight: weight,
}
}
#[test]
fn weights_map_works_as_nonces_range() {
fn build_map(range: RangeInclusive<MessageNonce>) -> MessageWeightsMap {
range
.map(|idx| {
(
idx,
MessageWeights {
weight: idx,
size: idx as _,
},
)
})
.collect()
}
let map = build_map(20..=30);
assert_eq!(map.begin(), 20);
assert_eq!(map.end(), 30);
assert_eq!(map.clone().greater_than(10), Some(build_map(20..=30)));
assert_eq!(map.clone().greater_than(19), Some(build_map(20..=30)));
assert_eq!(map.clone().greater_than(20), Some(build_map(21..=30)));
assert_eq!(map.clone().greater_than(25), Some(build_map(26..=30)));
assert_eq!(map.clone().greater_than(29), Some(build_map(30..=30)));
assert_eq!(map.greater_than(30), None);
}
#[test]
fn message_delivery_strategy_selects_messages_to_deliver() {
let (state, mut strategy) = prepare_strategy();
// both sides are ready to relay new messages
assert_eq!(
strategy.select_nonces_to_deliver(&state),
Some(((20..=23), proof_parameters(false, 4)))
);
}
#[test]
fn message_delivery_strategy_selects_nothing_if_too_many_confirmations_missing() {
let (state, mut strategy) = prepare_strategy();
// if there are already `max_unconfirmed_nonces_at_target` messages on target,
// we need to wait until confirmations will be delivered by receiving race
strategy.latest_confirmed_nonces_at_source = vec![(
header_id(1),
strategy.target_nonces.as_ref().unwrap().latest_nonce - strategy.max_unconfirmed_nonces_at_target,
)]
.into_iter()
.collect();
assert_eq!(strategy.select_nonces_to_deliver(&state), None);
}
#[test]
fn message_delivery_strategy_includes_outbound_state_proof_when_new_nonces_are_available() {
let (state, mut strategy) = prepare_strategy();
// if there are new confirmed nonces on source, we want to relay this information
// to target to prune rewards queue
let prev_confirmed_nonce_at_source = strategy.latest_confirmed_nonces_at_source.back().unwrap().1;
strategy.target_nonces.as_mut().unwrap().nonces_data.confirmed_nonce = prev_confirmed_nonce_at_source - 1;
assert_eq!(
strategy.select_nonces_to_deliver(&state),
Some(((20..=23), proof_parameters(true, 4)))
);
}
#[test]
fn message_delivery_strategy_selects_nothing_if_there_are_too_many_unrewarded_relayers() {
let (state, mut strategy) = prepare_strategy();
// if there are already `max_unrewarded_relayer_entries_at_target` entries at target,
// we need to wait until rewards will be paid
{
let mut unrewarded_relayers = &mut strategy.target_nonces.as_mut().unwrap().nonces_data.unrewarded_relayers;
unrewarded_relayers.unrewarded_relayer_entries = strategy.max_unrewarded_relayer_entries_at_target;
unrewarded_relayers.messages_in_oldest_entry = 4;
}
assert_eq!(strategy.select_nonces_to_deliver(&state), None);
}
#[test]
fn message_delivery_strategy_selects_nothing_if_proved_rewards_is_not_enough_to_remove_oldest_unrewarded_entry() {
let (state, mut strategy) = prepare_strategy();
// if there are already `max_unrewarded_relayer_entries_at_target` entries at target,
// we need to prove at least `messages_in_oldest_entry` rewards
let prev_confirmed_nonce_at_source = strategy.latest_confirmed_nonces_at_source.back().unwrap().1;
{
let mut nonces_data = &mut strategy.target_nonces.as_mut().unwrap().nonces_data;
nonces_data.confirmed_nonce = prev_confirmed_nonce_at_source - 1;
let mut unrewarded_relayers = &mut nonces_data.unrewarded_relayers;
unrewarded_relayers.unrewarded_relayer_entries = strategy.max_unrewarded_relayer_entries_at_target;
unrewarded_relayers.messages_in_oldest_entry = 4;
}
assert_eq!(strategy.select_nonces_to_deliver(&state), None);
}
#[test]
fn message_delivery_strategy_includes_outbound_state_proof_if_proved_rewards_is_enough() {
let (state, mut strategy) = prepare_strategy();
// if there are already `max_unrewarded_relayer_entries_at_target` entries at target,
// we need to prove at least `messages_in_oldest_entry` rewards
let prev_confirmed_nonce_at_source = strategy.latest_confirmed_nonces_at_source.back().unwrap().1;
{
let mut nonces_data = &mut strategy.target_nonces.as_mut().unwrap().nonces_data;
nonces_data.confirmed_nonce = prev_confirmed_nonce_at_source - 3;
let mut unrewarded_relayers = &mut nonces_data.unrewarded_relayers;
unrewarded_relayers.unrewarded_relayer_entries = strategy.max_unrewarded_relayer_entries_at_target;
unrewarded_relayers.messages_in_oldest_entry = 3;
}
assert_eq!(
strategy.select_nonces_to_deliver(&state),
Some(((20..=23), proof_parameters(true, 4)))
);
}
#[test]
fn message_delivery_strategy_limits_batch_by_messages_weight() {
let (state, mut strategy) = prepare_strategy();
// not all queued messages may fit in the batch, because batch has max weight
strategy.max_messages_weight_in_single_batch = 3;
assert_eq!(
strategy.select_nonces_to_deliver(&state),
Some(((20..=22), proof_parameters(false, 3)))
);
}
#[test]
fn message_delivery_strategy_accepts_single_message_even_if_its_weight_overflows_maximal_weight() {
let (state, mut strategy) = prepare_strategy();
// first message doesn't fit in the batch, because it has weight (10) that overflows max weight (4)
strategy.strategy.source_queue_mut()[0].1.get_mut(&20).unwrap().weight = 10;
assert_eq!(
strategy.select_nonces_to_deliver(&state),
Some(((20..=20), proof_parameters(false, 10)))
);
}
#[test]
fn message_delivery_strategy_limits_batch_by_messages_size() {
let (state, mut strategy) = prepare_strategy();
// not all queued messages may fit in the batch, because batch has max weight
strategy.max_messages_size_in_single_batch = 3;
assert_eq!(
strategy.select_nonces_to_deliver(&state),
Some(((20..=22), proof_parameters(false, 3)))
);
}
#[test]
fn message_delivery_strategy_accepts_single_message_even_if_its_weight_overflows_maximal_size() {
let (state, mut strategy) = prepare_strategy();
// first message doesn't fit in the batch, because it has weight (10) that overflows max weight (4)
strategy.strategy.source_queue_mut()[0].1.get_mut(&20).unwrap().size = 10;
assert_eq!(
strategy.select_nonces_to_deliver(&state),
Some(((20..=20), proof_parameters(false, 1)))
);
}
#[test]
fn message_delivery_strategy_limits_batch_by_messages_count_when_there_is_upper_limit() {
let (state, mut strategy) = prepare_strategy();
// not all queued messages may fit in the batch, because batch has max number of messages limit
strategy.max_messages_in_single_batch = 3;
assert_eq!(
strategy.select_nonces_to_deliver(&state),
Some(((20..=22), proof_parameters(false, 3)))
);
}
#[test]
fn message_delivery_strategy_limits_batch_by_messages_count_when_there_are_unconfirmed_nonces() {
let (state, mut strategy) = prepare_strategy();
// 1 delivery confirmation from target to source is still missing, so we may only
// relay 3 new messages
let prev_confirmed_nonce_at_source = strategy.latest_confirmed_nonces_at_source.back().unwrap().1;
strategy.latest_confirmed_nonces_at_source = vec![(header_id(1), prev_confirmed_nonce_at_source - 1)]
.into_iter()
.collect();
strategy.target_nonces.as_mut().unwrap().nonces_data.confirmed_nonce = prev_confirmed_nonce_at_source - 1;
assert_eq!(
strategy.select_nonces_to_deliver(&state),
Some(((20..=22), proof_parameters(false, 3)))
);
}
#[test]
fn message_delivery_strategy_waits_for_confirmed_nonce_header_to_appear_on_target() {
// 1 delivery confirmation from target to source is still missing, so we may deliver
// reward confirmation with our message delivery transaction. But the problem is that
// the reward has been paid at header 2 && this header is still unknown to target node.
//
// => so we can't deliver more than 3 messages
let (mut state, mut strategy) = prepare_strategy();
let prev_confirmed_nonce_at_source = strategy.latest_confirmed_nonces_at_source.back().unwrap().1;
strategy.latest_confirmed_nonces_at_source = vec![
(header_id(1), prev_confirmed_nonce_at_source - 1),
(header_id(2), prev_confirmed_nonce_at_source),
]
.into_iter()
.collect();
strategy.target_nonces.as_mut().unwrap().nonces_data.confirmed_nonce = prev_confirmed_nonce_at_source - 1;
state.best_finalized_source_header_id_at_best_target = Some(header_id(1));
assert_eq!(
strategy.select_nonces_to_deliver(&state),
Some(((20..=22), proof_parameters(false, 3)))
);
// the same situation, but the header 2 is known to the target node, so we may deliver reward confirmation
let (mut state, mut strategy) = prepare_strategy();
let prev_confirmed_nonce_at_source = strategy.latest_confirmed_nonces_at_source.back().unwrap().1;
strategy.latest_confirmed_nonces_at_source = vec![
(header_id(1), prev_confirmed_nonce_at_source - 1),
(header_id(2), prev_confirmed_nonce_at_source),
]
.into_iter()
.collect();
strategy.target_nonces.as_mut().unwrap().nonces_data.confirmed_nonce = prev_confirmed_nonce_at_source - 1;
state.best_finalized_source_header_id_at_source = Some(header_id(2));
state.best_finalized_source_header_id_at_best_target = Some(header_id(2));
assert_eq!(
strategy.select_nonces_to_deliver(&state),
Some(((20..=23), proof_parameters(true, 4)))
);
}
}
@@ -0,0 +1,612 @@
// 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.
//! Loop that is serving single race within message lane. This could be
//! message delivery race, receiving confirmations race or processing
//! confirmations race.
//!
//! The idea of the race is simple - we have `nonce`-s on source and target
//! nodes. We're trying to prove that the source node has this nonce (and
//! associated data - like messages, lane state, etc) to the target node by
//! generating and submitting proof.
use crate::message_lane_loop::ClientState;
use async_trait::async_trait;
use bp_message_lane::MessageNonce;
use futures::{
future::FutureExt,
stream::{FusedStream, StreamExt},
};
use relay_utils::{process_future_result, retry_backoff, FailedClient, MaybeConnectionError};
use std::{
fmt::Debug,
ops::RangeInclusive,
time::{Duration, Instant},
};
/// One of races within lane.
pub trait MessageRace {
/// Header id of the race source.
type SourceHeaderId: Debug + Clone + PartialEq;
/// Header id of the race source.
type TargetHeaderId: Debug + Clone + PartialEq;
/// Message nonce used in the race.
type MessageNonce: Debug + Clone;
/// Proof that is generated and delivered in this race.
type Proof: Debug + Clone;
/// Name of the race source.
fn source_name() -> String;
/// Name of the race target.
fn target_name() -> String;
}
/// State of race source client.
type SourceClientState<P> = ClientState<<P as MessageRace>::SourceHeaderId, <P as MessageRace>::TargetHeaderId>;
/// State of race target client.
type TargetClientState<P> = ClientState<<P as MessageRace>::TargetHeaderId, <P as MessageRace>::SourceHeaderId>;
/// Inclusive nonces range.
pub trait NoncesRange: Debug + Sized {
/// Get begin of the range.
fn begin(&self) -> MessageNonce;
/// Get end of the range.
fn end(&self) -> MessageNonce;
/// Returns new range with current range nonces that are greater than the passed `nonce`.
/// If there are no such nonces, `None` is returned.
fn greater_than(self, nonce: MessageNonce) -> Option<Self>;
}
/// Nonces on the race source client.
#[derive(Debug, Clone)]
pub struct SourceClientNonces<NoncesRange> {
/// New nonces range known to the client. `New` here means all nonces generated after
/// `prev_latest_nonce` passed to the `SourceClient::nonces` method.
pub new_nonces: NoncesRange,
/// Latest nonce that is confirmed to the bridged client. This nonce only makes
/// sense in some races. In other races it is `None`.
pub confirmed_nonce: Option<MessageNonce>,
}
/// Nonces on the race target client.
#[derive(Debug, Clone)]
pub struct TargetClientNonces<TargetNoncesData> {
/// Latest nonce that is known to the target client.
pub latest_nonce: MessageNonce,
/// Additional data from target node that may be used by the race.
pub nonces_data: TargetNoncesData,
}
/// One of message lane clients, which is source client for the race.
#[async_trait]
pub trait SourceClient<P: MessageRace> {
/// Type of error this clients returns.
type Error: std::fmt::Debug + MaybeConnectionError;
/// Type of nonces range returned by the source client.
type NoncesRange: NoncesRange;
/// Additional proof parameters required to generate proof.
type ProofParameters;
/// Return nonces that are known to the source client.
async fn nonces(
&self,
at_block: P::SourceHeaderId,
prev_latest_nonce: MessageNonce,
) -> Result<(P::SourceHeaderId, SourceClientNonces<Self::NoncesRange>), Self::Error>;
/// Generate proof for delivering to the target client.
async fn generate_proof(
&self,
at_block: P::SourceHeaderId,
nonces: RangeInclusive<MessageNonce>,
proof_parameters: Self::ProofParameters,
) -> Result<(P::SourceHeaderId, RangeInclusive<MessageNonce>, P::Proof), Self::Error>;
}
/// One of message lane clients, which is target client for the race.
#[async_trait]
pub trait TargetClient<P: MessageRace> {
/// Type of error this clients returns.
type Error: std::fmt::Debug + MaybeConnectionError;
/// Type of the additional data from the target client, used by the race.
type TargetNoncesData: std::fmt::Debug;
/// Return nonces that are known to the target client.
async fn nonces(
&self,
at_block: P::TargetHeaderId,
update_metrics: bool,
) -> Result<(P::TargetHeaderId, TargetClientNonces<Self::TargetNoncesData>), Self::Error>;
/// Submit proof to the target client.
async fn submit_proof(
&self,
generated_at_block: P::SourceHeaderId,
nonces: RangeInclusive<MessageNonce>,
proof: P::Proof,
) -> Result<RangeInclusive<MessageNonce>, Self::Error>;
}
/// Race strategy.
pub trait RaceStrategy<SourceHeaderId, TargetHeaderId, Proof>: Debug {
/// Type of nonces range expected from the source client.
type SourceNoncesRange: NoncesRange;
/// Additional proof parameters required to generate proof.
type ProofParameters;
/// Additional data expected from the target client.
type TargetNoncesData;
/// Should return true if nothing has to be synced.
fn is_empty(&self) -> bool;
/// Return best nonce at source node.
///
/// `Some` is returned only if we are sure that the value is greater or equal
/// than the result of `best_at_target`.
fn best_at_source(&self) -> Option<MessageNonce>;
/// Return best nonce at target node.
///
/// May return `None` if value is yet unknown.
fn best_at_target(&self) -> Option<MessageNonce>;
/// Called when nonces are updated at source node of the race.
fn source_nonces_updated(&mut self, at_block: SourceHeaderId, nonces: SourceClientNonces<Self::SourceNoncesRange>);
/// Called when best nonces are updated at target node of the race.
fn best_target_nonces_updated(
&mut self,
nonces: TargetClientNonces<Self::TargetNoncesData>,
race_state: &mut RaceState<SourceHeaderId, TargetHeaderId, Proof>,
);
/// Called when finalized nonces are updated at target node of the race.
fn finalized_target_nonces_updated(
&mut self,
nonces: TargetClientNonces<Self::TargetNoncesData>,
race_state: &mut RaceState<SourceHeaderId, TargetHeaderId, Proof>,
);
/// Should return `Some(nonces)` if we need to deliver proof of `nonces` (and associated
/// data) from source to target node.
/// Additionally, parameters required to generate proof are returned.
fn select_nonces_to_deliver(
&mut self,
race_state: &RaceState<SourceHeaderId, TargetHeaderId, Proof>,
) -> Option<(RangeInclusive<MessageNonce>, Self::ProofParameters)>;
}
/// State of the race.
#[derive(Debug)]
pub struct RaceState<SourceHeaderId, TargetHeaderId, Proof> {
/// Best finalized source header id at the source client.
pub best_finalized_source_header_id_at_source: Option<SourceHeaderId>,
/// Best finalized source header id at the best block on the target
/// client (at the `best_finalized_source_header_id_at_best_target`).
pub best_finalized_source_header_id_at_best_target: Option<SourceHeaderId>,
/// Best header id at the target client.
pub best_target_header_id: Option<TargetHeaderId>,
/// Best finalized header id at the target client.
pub best_finalized_target_header_id: Option<TargetHeaderId>,
/// Range of nonces that we have selected to submit.
pub nonces_to_submit: Option<(SourceHeaderId, RangeInclusive<MessageNonce>, Proof)>,
/// Range of nonces that is currently submitted.
pub nonces_submitted: Option<RangeInclusive<MessageNonce>>,
}
/// Run race loop until connection with target or source node is lost.
pub async fn run<P: MessageRace, SC: SourceClient<P>, TC: TargetClient<P>>(
race_source: SC,
race_source_updated: impl FusedStream<Item = SourceClientState<P>>,
race_target: TC,
race_target_updated: impl FusedStream<Item = TargetClientState<P>>,
stall_timeout: Duration,
mut strategy: impl RaceStrategy<
P::SourceHeaderId,
P::TargetHeaderId,
P::Proof,
SourceNoncesRange = SC::NoncesRange,
ProofParameters = SC::ProofParameters,
TargetNoncesData = TC::TargetNoncesData,
>,
) -> Result<(), FailedClient> {
let mut progress_context = Instant::now();
let mut race_state = RaceState::default();
let mut stall_countdown = Instant::now();
let mut source_retry_backoff = retry_backoff();
let mut source_client_is_online = true;
let mut source_nonces_required = false;
let source_nonces = futures::future::Fuse::terminated();
let source_generate_proof = futures::future::Fuse::terminated();
let source_go_offline_future = futures::future::Fuse::terminated();
let mut target_retry_backoff = retry_backoff();
let mut target_client_is_online = true;
let mut target_best_nonces_required = false;
let mut target_finalized_nonces_required = false;
let target_best_nonces = futures::future::Fuse::terminated();
let target_finalized_nonces = futures::future::Fuse::terminated();
let target_submit_proof = futures::future::Fuse::terminated();
let target_go_offline_future = futures::future::Fuse::terminated();
futures::pin_mut!(
race_source_updated,
source_nonces,
source_generate_proof,
source_go_offline_future,
race_target_updated,
target_best_nonces,
target_finalized_nonces,
target_submit_proof,
target_go_offline_future,
);
loop {
futures::select! {
// when headers ids are updated
source_state = race_source_updated.next() => {
if let Some(source_state) = source_state {
let is_source_state_updated = race_state.best_finalized_source_header_id_at_source.as_ref()
!= Some(&source_state.best_finalized_self);
if is_source_state_updated {
source_nonces_required = true;
race_state.best_finalized_source_header_id_at_source = Some(source_state.best_finalized_self);
}
}
},
target_state = race_target_updated.next() => {
if let Some(target_state) = target_state {
let is_target_best_state_updated = race_state.best_target_header_id.as_ref()
!= Some(&target_state.best_self);
if is_target_best_state_updated {
target_best_nonces_required = true;
race_state.best_target_header_id = Some(target_state.best_self);
race_state.best_finalized_source_header_id_at_best_target
= Some(target_state.best_finalized_peer_at_best_self);
}
let is_target_finalized_state_updated = race_state.best_finalized_target_header_id.as_ref()
!= Some(&target_state.best_finalized_self);
if is_target_finalized_state_updated {
target_finalized_nonces_required = true;
race_state.best_finalized_target_header_id = Some(target_state.best_finalized_self);
}
}
},
// when nonces are updated
nonces = source_nonces => {
source_nonces_required = false;
source_client_is_online = process_future_result(
nonces,
&mut source_retry_backoff,
|(at_block, nonces)| {
log::debug!(
target: "bridge",
"Received nonces from {}: {:?}",
P::source_name(),
nonces,
);
strategy.source_nonces_updated(at_block, nonces);
},
&mut source_go_offline_future,
async_std::task::sleep,
|| format!("Error retrieving nonces from {}", P::source_name()),
).fail_if_connection_error(FailedClient::Source)?;
},
nonces = target_best_nonces => {
target_best_nonces_required = false;
target_client_is_online = process_future_result(
nonces,
&mut target_retry_backoff,
|(_, nonces)| {
log::debug!(
target: "bridge",
"Received best nonces from {}: {:?}",
P::target_name(),
nonces,
);
let prev_best_at_target = strategy.best_at_target();
strategy.best_target_nonces_updated(nonces, &mut race_state);
if strategy.best_at_target() != prev_best_at_target {
stall_countdown = Instant::now();
}
},
&mut target_go_offline_future,
async_std::task::sleep,
|| format!("Error retrieving best nonces from {}", P::target_name()),
).fail_if_connection_error(FailedClient::Target)?;
},
nonces = target_finalized_nonces => {
target_finalized_nonces_required = false;
target_client_is_online = process_future_result(
nonces,
&mut target_retry_backoff,
|(_, nonces)| {
log::debug!(
target: "bridge",
"Received finalized nonces from {}: {:?}",
P::target_name(),
nonces,
);
strategy.finalized_target_nonces_updated(nonces, &mut race_state);
},
&mut target_go_offline_future,
async_std::task::sleep,
|| format!("Error retrieving finalized nonces from {}", P::target_name()),
).fail_if_connection_error(FailedClient::Target)?;
},
// proof generation and submission
proof = source_generate_proof => {
source_client_is_online = process_future_result(
proof,
&mut source_retry_backoff,
|(at_block, nonces_range, proof)| {
log::debug!(
target: "bridge",
"Received proof for nonces in range {:?} from {}",
nonces_range,
P::source_name(),
);
race_state.nonces_to_submit = Some((at_block, nonces_range, proof));
},
&mut source_go_offline_future,
async_std::task::sleep,
|| format!("Error generating proof at {}", P::source_name()),
).fail_if_connection_error(FailedClient::Source)?;
},
proof_submit_result = target_submit_proof => {
target_client_is_online = process_future_result(
proof_submit_result,
&mut target_retry_backoff,
|nonces_range| {
log::debug!(
target: "bridge",
"Successfully submitted proof of nonces {:?} to {}",
nonces_range,
P::target_name(),
);
race_state.nonces_to_submit = None;
race_state.nonces_submitted = Some(nonces_range);
stall_countdown = Instant::now();
},
&mut target_go_offline_future,
async_std::task::sleep,
|| format!("Error submitting proof {}", P::target_name()),
).fail_if_connection_error(FailedClient::Target)?;
},
// when we're ready to retry request
_ = source_go_offline_future => {
source_client_is_online = true;
},
_ = target_go_offline_future => {
target_client_is_online = true;
},
}
progress_context = print_race_progress::<P, _>(progress_context, &strategy);
if stall_countdown.elapsed() > stall_timeout {
log::warn!(
target: "bridge",
"{} -> {} race has stalled. State: {:?}. Strategy: {:?}",
P::source_name(),
P::target_name(),
race_state,
strategy,
);
return Err(FailedClient::Both);
} else if race_state.nonces_to_submit.is_none() && race_state.nonces_submitted.is_none() && strategy.is_empty()
{
stall_countdown = Instant::now();
}
if source_client_is_online {
source_client_is_online = false;
let nonces_to_deliver = select_nonces_to_deliver(&race_state, &mut strategy);
let best_at_source = strategy.best_at_source();
if let Some((at_block, nonces_range, proof_parameters)) = nonces_to_deliver {
log::debug!(
target: "bridge",
"Asking {} to prove nonces in range {:?} at block {:?}",
P::source_name(),
nonces_range,
at_block,
);
source_generate_proof.set(
race_source
.generate_proof(at_block, nonces_range, proof_parameters)
.fuse(),
);
} else if source_nonces_required && best_at_source.is_some() {
log::debug!(target: "bridge", "Asking {} about message nonces", P::source_name());
let at_block = race_state
.best_finalized_source_header_id_at_source
.as_ref()
.expect(
"source_nonces_required is only true when\
best_finalized_source_header_id_at_source is Some; qed",
)
.clone();
source_nonces.set(
race_source
.nonces(at_block, best_at_source.expect("guaranteed by if condition; qed"))
.fuse(),
);
} else {
source_client_is_online = true;
}
}
if target_client_is_online {
target_client_is_online = false;
if let Some((at_block, nonces_range, proof)) = race_state.nonces_to_submit.as_ref() {
log::debug!(
target: "bridge",
"Going to submit proof of messages in range {:?} to {} node",
nonces_range,
P::target_name(),
);
target_submit_proof.set(
race_target
.submit_proof(at_block.clone(), nonces_range.clone(), proof.clone())
.fuse(),
);
} else if target_best_nonces_required {
log::debug!(target: "bridge", "Asking {} about best message nonces", P::target_name());
let at_block = race_state
.best_target_header_id
.as_ref()
.expect("target_best_nonces_required is only true when best_target_header_id is Some; qed")
.clone();
target_best_nonces.set(race_target.nonces(at_block, false).fuse());
} else if target_finalized_nonces_required {
log::debug!(target: "bridge", "Asking {} about finalized message nonces", P::target_name());
let at_block = race_state
.best_finalized_target_header_id
.as_ref()
.expect(
"target_finalized_nonces_required is only true when\
best_finalized_target_header_id is Some; qed",
)
.clone();
target_finalized_nonces.set(race_target.nonces(at_block, true).fuse());
} else {
target_client_is_online = true;
}
}
}
}
impl<SourceHeaderId, TargetHeaderId, Proof> Default for RaceState<SourceHeaderId, TargetHeaderId, Proof> {
fn default() -> Self {
RaceState {
best_finalized_source_header_id_at_source: None,
best_finalized_source_header_id_at_best_target: None,
best_target_header_id: None,
best_finalized_target_header_id: None,
nonces_to_submit: None,
nonces_submitted: None,
}
}
}
/// Print race progress.
fn print_race_progress<P, S>(prev_time: Instant, strategy: &S) -> Instant
where
P: MessageRace,
S: RaceStrategy<P::SourceHeaderId, P::TargetHeaderId, P::Proof>,
{
let now_time = Instant::now();
let need_update = now_time.saturating_duration_since(prev_time) > Duration::from_secs(10);
if !need_update {
return prev_time;
}
let now_best_nonce_at_source = strategy.best_at_source();
let now_best_nonce_at_target = strategy.best_at_target();
log::info!(
target: "bridge",
"Synced {:?} of {:?} nonces in {} -> {} race",
now_best_nonce_at_target,
now_best_nonce_at_source,
P::source_name(),
P::target_name(),
);
now_time
}
fn select_nonces_to_deliver<SourceHeaderId, TargetHeaderId, Proof, Strategy>(
race_state: &RaceState<SourceHeaderId, TargetHeaderId, Proof>,
strategy: &mut Strategy,
) -> Option<(SourceHeaderId, RangeInclusive<MessageNonce>, Strategy::ProofParameters)>
where
SourceHeaderId: Clone,
Strategy: RaceStrategy<SourceHeaderId, TargetHeaderId, Proof>,
{
race_state
.best_finalized_source_header_id_at_best_target
.as_ref()
.and_then(|best_finalized_source_header_id_at_best_target| {
strategy
.select_nonces_to_deliver(&race_state)
.map(|(nonces_range, proof_parameters)| {
(
best_finalized_source_header_id_at_best_target.clone(),
nonces_range,
proof_parameters,
)
})
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::message_race_strategy::BasicStrategy;
use relay_utils::HeaderId;
#[test]
fn proof_is_generated_at_best_block_known_to_target_node() {
const GENERATED_AT: u64 = 6;
const BEST_AT_SOURCE: u64 = 10;
const BEST_AT_TARGET: u64 = 8;
// target node only knows about source' BEST_AT_TARGET block
// source node has BEST_AT_SOURCE > BEST_AT_TARGET block
let mut race_state = RaceState::<_, _, ()> {
best_finalized_source_header_id_at_source: Some(HeaderId(BEST_AT_SOURCE, BEST_AT_SOURCE)),
best_finalized_source_header_id_at_best_target: Some(HeaderId(BEST_AT_TARGET, BEST_AT_TARGET)),
best_target_header_id: Some(HeaderId(0, 0)),
best_finalized_target_header_id: Some(HeaderId(0, 0)),
nonces_to_submit: None,
nonces_submitted: None,
};
// we have some nonces to deliver and they're generated at GENERATED_AT < BEST_AT_SOURCE
let mut strategy = BasicStrategy::new();
strategy.source_nonces_updated(
HeaderId(GENERATED_AT, GENERATED_AT),
SourceClientNonces {
new_nonces: 0..=10,
confirmed_nonce: None,
},
);
strategy.best_target_nonces_updated(
TargetClientNonces {
latest_nonce: 5u64,
nonces_data: (),
},
&mut race_state,
);
// the proof will be generated on source, but using BEST_AT_TARGET block
assert_eq!(
select_nonces_to_deliver(&race_state, &mut strategy),
Some((HeaderId(BEST_AT_TARGET, BEST_AT_TARGET), 6..=10, (),))
);
}
}
@@ -0,0 +1,232 @@
// 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.
//! Message receiving race delivers proof-of-messages-delivery from lane.target to lane.source.
use crate::message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf};
use crate::message_lane_loop::{
SourceClient as MessageLaneSourceClient, SourceClientState, TargetClient as MessageLaneTargetClient,
TargetClientState,
};
use crate::message_race_loop::{
MessageRace, NoncesRange, SourceClient, SourceClientNonces, TargetClient, TargetClientNonces,
};
use crate::message_race_strategy::BasicStrategy;
use crate::metrics::MessageLaneLoopMetrics;
use async_trait::async_trait;
use bp_message_lane::MessageNonce;
use futures::stream::FusedStream;
use relay_utils::FailedClient;
use std::{marker::PhantomData, ops::RangeInclusive, time::Duration};
/// Message receiving confirmations delivery strategy.
type ReceivingConfirmationsBasicStrategy<P> = BasicStrategy<
<P as MessageLane>::TargetHeaderNumber,
<P as MessageLane>::TargetHeaderHash,
<P as MessageLane>::SourceHeaderNumber,
<P as MessageLane>::SourceHeaderHash,
RangeInclusive<MessageNonce>,
<P as MessageLane>::MessagesReceivingProof,
>;
/// Run receiving confirmations race.
pub async fn run<P: MessageLane>(
source_client: impl MessageLaneSourceClient<P>,
source_state_updates: impl FusedStream<Item = SourceClientState<P>>,
target_client: impl MessageLaneTargetClient<P>,
target_state_updates: impl FusedStream<Item = TargetClientState<P>>,
stall_timeout: Duration,
metrics_msg: Option<MessageLaneLoopMetrics>,
) -> Result<(), FailedClient> {
crate::message_race_loop::run(
ReceivingConfirmationsRaceSource {
client: target_client,
metrics_msg: metrics_msg.clone(),
_phantom: Default::default(),
},
target_state_updates,
ReceivingConfirmationsRaceTarget {
client: source_client,
metrics_msg,
_phantom: Default::default(),
},
source_state_updates,
stall_timeout,
ReceivingConfirmationsBasicStrategy::<P>::new(),
)
.await
}
/// Messages receiving confirmations race.
struct ReceivingConfirmationsRace<P>(std::marker::PhantomData<P>);
impl<P: MessageLane> MessageRace for ReceivingConfirmationsRace<P> {
type SourceHeaderId = TargetHeaderIdOf<P>;
type TargetHeaderId = SourceHeaderIdOf<P>;
type MessageNonce = MessageNonce;
type Proof = P::MessagesReceivingProof;
fn source_name() -> String {
format!("{}::ReceivingConfirmationsDelivery", P::TARGET_NAME)
}
fn target_name() -> String {
format!("{}::ReceivingConfirmationsDelivery", P::SOURCE_NAME)
}
}
/// Message receiving confirmations race source, which is a target of the lane.
struct ReceivingConfirmationsRaceSource<P: MessageLane, C> {
client: C,
metrics_msg: Option<MessageLaneLoopMetrics>,
_phantom: PhantomData<P>,
}
#[async_trait]
impl<P, C> SourceClient<ReceivingConfirmationsRace<P>> for ReceivingConfirmationsRaceSource<P, C>
where
P: MessageLane,
C: MessageLaneTargetClient<P>,
{
type Error = C::Error;
type NoncesRange = RangeInclusive<MessageNonce>;
type ProofParameters = ();
async fn nonces(
&self,
at_block: TargetHeaderIdOf<P>,
prev_latest_nonce: MessageNonce,
) -> Result<(TargetHeaderIdOf<P>, SourceClientNonces<Self::NoncesRange>), Self::Error> {
let (at_block, latest_received_nonce) = self.client.latest_received_nonce(at_block).await?;
if let Some(metrics_msg) = self.metrics_msg.as_ref() {
metrics_msg.update_target_latest_received_nonce::<P>(latest_received_nonce);
}
Ok((
at_block,
SourceClientNonces {
new_nonces: prev_latest_nonce + 1..=latest_received_nonce,
confirmed_nonce: None,
},
))
}
#[allow(clippy::unit_arg)]
async fn generate_proof(
&self,
at_block: TargetHeaderIdOf<P>,
nonces: RangeInclusive<MessageNonce>,
_proof_parameters: Self::ProofParameters,
) -> Result<
(
TargetHeaderIdOf<P>,
RangeInclusive<MessageNonce>,
P::MessagesReceivingProof,
),
Self::Error,
> {
self.client
.prove_messages_receiving(at_block)
.await
.map(|(at_block, proof)| (at_block, nonces, proof))
}
}
/// Message receiving confirmations race target, which is a source of the lane.
struct ReceivingConfirmationsRaceTarget<P: MessageLane, C> {
client: C,
metrics_msg: Option<MessageLaneLoopMetrics>,
_phantom: PhantomData<P>,
}
#[async_trait]
impl<P, C> TargetClient<ReceivingConfirmationsRace<P>> for ReceivingConfirmationsRaceTarget<P, C>
where
P: MessageLane,
C: MessageLaneSourceClient<P>,
{
type Error = C::Error;
type TargetNoncesData = ();
async fn nonces(
&self,
at_block: SourceHeaderIdOf<P>,
update_metrics: bool,
) -> Result<(SourceHeaderIdOf<P>, TargetClientNonces<()>), Self::Error> {
let (at_block, latest_confirmed_nonce) = self.client.latest_confirmed_received_nonce(at_block).await?;
if update_metrics {
if let Some(metrics_msg) = self.metrics_msg.as_ref() {
metrics_msg.update_source_latest_confirmed_nonce::<P>(latest_confirmed_nonce);
}
}
Ok((
at_block,
TargetClientNonces {
latest_nonce: latest_confirmed_nonce,
nonces_data: (),
},
))
}
async fn submit_proof(
&self,
generated_at_block: TargetHeaderIdOf<P>,
nonces: RangeInclusive<MessageNonce>,
proof: P::MessagesReceivingProof,
) -> Result<RangeInclusive<MessageNonce>, Self::Error> {
self.client
.submit_messages_receiving_proof(generated_at_block, proof)
.await?;
Ok(nonces)
}
}
impl NoncesRange for RangeInclusive<MessageNonce> {
fn begin(&self) -> MessageNonce {
*RangeInclusive::<MessageNonce>::start(self)
}
fn end(&self) -> MessageNonce {
*RangeInclusive::<MessageNonce>::end(self)
}
fn greater_than(self, nonce: MessageNonce) -> Option<Self> {
let next_nonce = nonce + 1;
let end = *self.end();
if next_nonce > end {
None
} else {
Some(std::cmp::max(self.begin(), next_nonce)..=end)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn range_inclusive_works_as_nonces_range() {
let range = 20..=30;
assert_eq!(NoncesRange::begin(&range), 20);
assert_eq!(NoncesRange::end(&range), 30);
assert_eq!(range.clone().greater_than(10), Some(20..=30));
assert_eq!(range.clone().greater_than(19), Some(20..=30));
assert_eq!(range.clone().greater_than(20), Some(21..=30));
assert_eq!(range.clone().greater_than(25), Some(26..=30));
assert_eq!(range.clone().greater_than(29), Some(30..=30));
assert_eq!(range.greater_than(30), None);
}
}
@@ -0,0 +1,479 @@
// 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.
//! Basic delivery strategy. The strategy selects nonces if:
//!
//! 1) there are more nonces on the source side than on the target side;
//! 2) new nonces may be proved to target node (i.e. they have appeared at the
//! block, which is known to the target node).
use crate::message_race_loop::{NoncesRange, RaceState, RaceStrategy, SourceClientNonces, TargetClientNonces};
use bp_message_lane::MessageNonce;
use relay_utils::HeaderId;
use std::{collections::VecDeque, fmt::Debug, marker::PhantomData, ops::RangeInclusive};
/// Nonces delivery strategy.
#[derive(Debug)]
pub struct BasicStrategy<
SourceHeaderNumber,
SourceHeaderHash,
TargetHeaderNumber,
TargetHeaderHash,
SourceNoncesRange,
Proof,
> {
/// All queued nonces.
source_queue: VecDeque<(HeaderId<SourceHeaderHash, SourceHeaderNumber>, SourceNoncesRange)>,
/// Best nonce known to target node (at its best block). `None` if it has not been received yet.
best_target_nonce: Option<MessageNonce>,
/// Unused generic types dump.
_phantom: PhantomData<(TargetHeaderNumber, TargetHeaderHash, Proof)>,
}
impl<SourceHeaderNumber, SourceHeaderHash, TargetHeaderNumber, TargetHeaderHash, SourceNoncesRange, Proof>
BasicStrategy<SourceHeaderNumber, SourceHeaderHash, TargetHeaderNumber, TargetHeaderHash, SourceNoncesRange, Proof>
where
SourceHeaderHash: Clone,
SourceHeaderNumber: Clone + Ord,
SourceNoncesRange: NoncesRange,
{
/// Create new delivery strategy.
pub fn new() -> Self {
BasicStrategy {
source_queue: VecDeque::new(),
best_target_nonce: None,
_phantom: Default::default(),
}
}
/// Mutable reference to source queue to use in tests.
#[cfg(test)]
pub(crate) fn source_queue_mut(
&mut self,
) -> &mut VecDeque<(HeaderId<SourceHeaderHash, SourceHeaderNumber>, SourceNoncesRange)> {
&mut self.source_queue
}
/// Should return `Some(nonces)` if we need to deliver proof of `nonces` (and associated
/// data) from source to target node.
///
/// The `selector` function receives range of nonces and should return `None` if the whole
/// range needs to be delivered. If there are some nonces in the range that can't be delivered
/// right now, it should return `Some` with 'undeliverable' nonces. Please keep in mind that
/// this should be the sub-range that the passed range ends with, because nonces are always
/// delivered in-order. Otherwise the function will panic.
pub fn select_nonces_to_deliver_with_selector(
&mut self,
race_state: &RaceState<
HeaderId<SourceHeaderHash, SourceHeaderNumber>,
HeaderId<TargetHeaderHash, TargetHeaderNumber>,
Proof,
>,
mut selector: impl FnMut(SourceNoncesRange) -> Option<SourceNoncesRange>,
) -> Option<RangeInclusive<MessageNonce>> {
// if we do not know best nonce at target node, we can't select anything
let target_nonce = self.best_target_nonce?;
// if we have already selected nonces that we want to submit, do nothing
if race_state.nonces_to_submit.is_some() {
return None;
}
// if we already submitted some nonces, do nothing
if race_state.nonces_submitted.is_some() {
return None;
}
// 1) we want to deliver all nonces, starting from `target_nonce + 1`
// 2) we can't deliver new nonce until header, that has emitted this nonce, is finalized
// by target client
// 3) selector is used for more complicated logic
let best_header_at_target = &race_state.best_finalized_source_header_id_at_best_target.as_ref()?;
let mut nonces_end = None;
while let Some((queued_at, queued_range)) = self.source_queue.pop_front() {
// select (sub) range to deliver
let queued_range_begin = queued_range.begin();
let queued_range_end = queued_range.end();
let range_to_requeue = if queued_at.0 > best_header_at_target.0 {
// if header that has queued the range is not yet finalized at bridged chain,
// we can't prove anything
Some(queued_range)
} else {
// selector returns `Some(range)` if this `range` needs to be requeued
selector(queued_range)
};
// requeue (sub) range and update range to deliver
match range_to_requeue {
Some(range_to_requeue) => {
assert!(
range_to_requeue.begin() <= range_to_requeue.end()
&& range_to_requeue.begin() >= queued_range_begin
&& range_to_requeue.end() == queued_range_end,
"Incorrect implementation of internal `selector` function. Expected original\
range {:?} to end with returned range {:?}",
queued_range_begin..=queued_range_end,
range_to_requeue,
);
if range_to_requeue.begin() != queued_range_begin {
nonces_end = Some(range_to_requeue.begin() - 1);
}
self.source_queue.push_front((queued_at, range_to_requeue));
break;
}
None => {
nonces_end = Some(queued_range_end);
}
}
}
nonces_end.map(|nonces_end| RangeInclusive::new(target_nonce + 1, nonces_end))
}
}
impl<SourceHeaderNumber, SourceHeaderHash, TargetHeaderNumber, TargetHeaderHash, SourceNoncesRange, Proof>
RaceStrategy<HeaderId<SourceHeaderHash, SourceHeaderNumber>, HeaderId<TargetHeaderHash, TargetHeaderNumber>, Proof>
for BasicStrategy<SourceHeaderNumber, SourceHeaderHash, TargetHeaderNumber, TargetHeaderHash, SourceNoncesRange, Proof>
where
SourceHeaderHash: Clone + Debug,
SourceHeaderNumber: Clone + Ord + Debug,
SourceNoncesRange: NoncesRange + Debug,
TargetHeaderHash: Debug,
TargetHeaderNumber: Debug,
Proof: Debug,
{
type SourceNoncesRange = SourceNoncesRange;
type ProofParameters = ();
type TargetNoncesData = ();
fn is_empty(&self) -> bool {
self.source_queue.is_empty()
}
fn best_at_source(&self) -> Option<MessageNonce> {
let best_in_queue = self.source_queue.back().map(|(_, range)| range.end());
match (best_in_queue, self.best_target_nonce) {
(Some(best_in_queue), Some(best_target_nonce)) if best_in_queue > best_target_nonce => Some(best_in_queue),
(_, Some(best_target_nonce)) => Some(best_target_nonce),
(_, None) => None,
}
}
fn best_at_target(&self) -> Option<MessageNonce> {
self.best_target_nonce
}
fn source_nonces_updated(
&mut self,
at_block: HeaderId<SourceHeaderHash, SourceHeaderNumber>,
nonces: SourceClientNonces<SourceNoncesRange>,
) {
let best_in_queue = self
.source_queue
.back()
.map(|(_, range)| range.end())
.or(self.best_target_nonce)
.unwrap_or_default();
self.source_queue.extend(
nonces
.new_nonces
.greater_than(best_in_queue)
.into_iter()
.map(move |range| (at_block.clone(), range)),
)
}
fn best_target_nonces_updated(
&mut self,
nonces: TargetClientNonces<()>,
race_state: &mut RaceState<
HeaderId<SourceHeaderHash, SourceHeaderNumber>,
HeaderId<TargetHeaderHash, TargetHeaderNumber>,
Proof,
>,
) {
let nonce = nonces.latest_nonce;
if let Some(best_target_nonce) = self.best_target_nonce {
if nonce < best_target_nonce {
return;
}
}
while let Some(true) = self.source_queue.front().map(|(_, range)| range.begin() <= nonce) {
let maybe_subrange = self
.source_queue
.pop_front()
.and_then(|(at_block, range)| range.greater_than(nonce).map(|subrange| (at_block, subrange)));
if let Some((at_block, subrange)) = maybe_subrange {
self.source_queue.push_front((at_block, subrange));
break;
}
}
let need_to_select_new_nonces = race_state
.nonces_to_submit
.as_ref()
.map(|(_, nonces, _)| *nonces.end() <= nonce)
.unwrap_or(false);
if need_to_select_new_nonces {
race_state.nonces_to_submit = None;
}
let need_new_nonces_to_submit = race_state
.nonces_submitted
.as_ref()
.map(|nonces| *nonces.end() <= nonce)
.unwrap_or(false);
if need_new_nonces_to_submit {
race_state.nonces_submitted = None;
}
self.best_target_nonce = Some(std::cmp::max(
self.best_target_nonce.unwrap_or(nonces.latest_nonce),
nonce,
));
}
fn finalized_target_nonces_updated(
&mut self,
nonces: TargetClientNonces<()>,
_race_state: &mut RaceState<
HeaderId<SourceHeaderHash, SourceHeaderNumber>,
HeaderId<TargetHeaderHash, TargetHeaderNumber>,
Proof,
>,
) {
self.best_target_nonce = Some(std::cmp::max(
self.best_target_nonce.unwrap_or(nonces.latest_nonce),
nonces.latest_nonce,
));
}
fn select_nonces_to_deliver(
&mut self,
race_state: &RaceState<
HeaderId<SourceHeaderHash, SourceHeaderNumber>,
HeaderId<TargetHeaderHash, TargetHeaderNumber>,
Proof,
>,
) -> Option<(RangeInclusive<MessageNonce>, Self::ProofParameters)> {
self.select_nonces_to_deliver_with_selector(race_state, |_| None)
.map(|range| (range, ()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::message_lane::MessageLane;
use crate::message_lane_loop::tests::{header_id, TestMessageLane, TestMessagesProof};
type SourceNoncesRange = RangeInclusive<MessageNonce>;
type BasicStrategy<P> = super::BasicStrategy<
<P as MessageLane>::SourceHeaderNumber,
<P as MessageLane>::SourceHeaderHash,
<P as MessageLane>::TargetHeaderNumber,
<P as MessageLane>::TargetHeaderHash,
SourceNoncesRange,
<P as MessageLane>::MessagesProof,
>;
fn source_nonces(new_nonces: SourceNoncesRange) -> SourceClientNonces<SourceNoncesRange> {
SourceClientNonces {
new_nonces,
confirmed_nonce: None,
}
}
fn target_nonces(latest_nonce: MessageNonce) -> TargetClientNonces<()> {
TargetClientNonces {
latest_nonce,
nonces_data: (),
}
}
#[test]
fn strategy_is_empty_works() {
let mut strategy = BasicStrategy::<TestMessageLane>::new();
assert_eq!(strategy.is_empty(), true);
strategy.source_nonces_updated(header_id(1), source_nonces(1..=1));
assert_eq!(strategy.is_empty(), false);
}
#[test]
fn best_at_source_is_never_lower_than_target_nonce() {
let mut strategy = BasicStrategy::<TestMessageLane>::new();
assert_eq!(strategy.best_at_source(), None);
strategy.source_nonces_updated(header_id(1), source_nonces(1..=5));
assert_eq!(strategy.best_at_source(), None);
strategy.best_target_nonces_updated(target_nonces(10), &mut Default::default());
assert_eq!(strategy.source_queue, vec![]);
assert_eq!(strategy.best_at_source(), Some(10));
}
#[test]
fn source_nonce_is_never_lower_than_known_target_nonce() {
let mut strategy = BasicStrategy::<TestMessageLane>::new();
strategy.best_target_nonces_updated(target_nonces(10), &mut Default::default());
strategy.source_nonces_updated(header_id(1), source_nonces(1..=5));
assert_eq!(strategy.source_queue, vec![]);
}
#[test]
fn source_nonce_is_never_lower_than_latest_known_source_nonce() {
let mut strategy = BasicStrategy::<TestMessageLane>::new();
strategy.source_nonces_updated(header_id(1), source_nonces(1..=5));
strategy.source_nonces_updated(header_id(2), source_nonces(1..=3));
strategy.source_nonces_updated(header_id(2), source_nonces(1..=5));
assert_eq!(strategy.source_queue, vec![(header_id(1), 1..=5)]);
}
#[test]
fn target_nonce_is_never_lower_than_latest_known_target_nonce() {
let mut strategy = BasicStrategy::<TestMessageLane>::new();
assert_eq!(strategy.best_target_nonce, None);
strategy.best_target_nonces_updated(target_nonces(10), &mut Default::default());
assert_eq!(strategy.best_target_nonce, Some(10));
strategy.best_target_nonces_updated(target_nonces(5), &mut Default::default());
assert_eq!(strategy.best_target_nonce, Some(10));
}
#[test]
fn updated_target_nonce_removes_queued_entries() {
let mut strategy = BasicStrategy::<TestMessageLane>::new();
strategy.source_nonces_updated(header_id(1), source_nonces(1..=5));
strategy.source_nonces_updated(header_id(2), source_nonces(6..=10));
strategy.source_nonces_updated(header_id(3), source_nonces(11..=15));
strategy.source_nonces_updated(header_id(4), source_nonces(16..=20));
strategy.best_target_nonces_updated(target_nonces(15), &mut Default::default());
assert_eq!(strategy.source_queue, vec![(header_id(4), 16..=20)]);
strategy.best_target_nonces_updated(target_nonces(17), &mut Default::default());
assert_eq!(strategy.source_queue, vec![(header_id(4), 18..=20)]);
}
#[test]
fn selected_nonces_are_dropped_on_target_nonce_update() {
let mut state = RaceState::default();
let mut strategy = BasicStrategy::<TestMessageLane>::new();
state.nonces_to_submit = Some((header_id(1), 5..=10, (5..=10, None)));
strategy.best_target_nonces_updated(target_nonces(7), &mut state);
assert!(state.nonces_to_submit.is_some());
strategy.best_target_nonces_updated(target_nonces(10), &mut state);
assert!(state.nonces_to_submit.is_none());
}
#[test]
fn submitted_nonces_are_dropped_on_target_nonce_update() {
let mut state = RaceState::default();
let mut strategy = BasicStrategy::<TestMessageLane>::new();
state.nonces_submitted = Some(5..=10);
strategy.best_target_nonces_updated(target_nonces(7), &mut state);
assert!(state.nonces_submitted.is_some());
strategy.best_target_nonces_updated(target_nonces(10), &mut state);
assert!(state.nonces_submitted.is_none());
}
#[test]
fn nothing_is_selected_if_something_is_already_selected() {
let mut state = RaceState::default();
let mut strategy = BasicStrategy::<TestMessageLane>::new();
state.nonces_to_submit = Some((header_id(1), 1..=10, (1..=10, None)));
strategy.best_target_nonces_updated(target_nonces(0), &mut state);
strategy.source_nonces_updated(header_id(1), source_nonces(1..=10));
assert_eq!(strategy.select_nonces_to_deliver(&state), None);
}
#[test]
fn nothing_is_selected_if_something_is_already_submitted() {
let mut state = RaceState::default();
let mut strategy = BasicStrategy::<TestMessageLane>::new();
state.nonces_submitted = Some(1..=10);
strategy.best_target_nonces_updated(target_nonces(0), &mut state);
strategy.source_nonces_updated(header_id(1), source_nonces(1..=10));
assert_eq!(strategy.select_nonces_to_deliver(&state), None);
}
#[test]
fn select_nonces_to_deliver_works() {
let mut state = RaceState::<_, _, TestMessagesProof>::default();
let mut strategy = BasicStrategy::<TestMessageLane>::new();
strategy.best_target_nonces_updated(target_nonces(0), &mut state);
strategy.source_nonces_updated(header_id(1), source_nonces(1..=1));
strategy.source_nonces_updated(header_id(2), source_nonces(2..=2));
strategy.source_nonces_updated(header_id(3), source_nonces(3..=6));
strategy.source_nonces_updated(header_id(5), source_nonces(7..=8));
state.best_finalized_source_header_id_at_best_target = Some(header_id(4));
assert_eq!(strategy.select_nonces_to_deliver(&state), Some((1..=6, ())));
strategy.best_target_nonces_updated(target_nonces(6), &mut state);
assert_eq!(strategy.select_nonces_to_deliver(&state), None);
state.best_finalized_source_header_id_at_best_target = Some(header_id(5));
assert_eq!(strategy.select_nonces_to_deliver(&state), Some((7..=8, ())));
strategy.best_target_nonces_updated(target_nonces(8), &mut state);
assert_eq!(strategy.select_nonces_to_deliver(&state), None);
}
#[test]
fn select_nonces_to_deliver_able_to_split_ranges_with_selector() {
let mut state = RaceState::<_, _, TestMessagesProof>::default();
let mut strategy = BasicStrategy::<TestMessageLane>::new();
strategy.best_target_nonces_updated(target_nonces(0), &mut state);
strategy.source_nonces_updated(header_id(1), source_nonces(1..=100));
state.best_finalized_source_header_id_at_source = Some(header_id(1));
state.best_finalized_source_header_id_at_best_target = Some(header_id(1));
state.best_target_header_id = Some(header_id(1));
assert_eq!(
strategy.select_nonces_to_deliver_with_selector(&state, |_| Some(50..=100)),
Some(1..=49),
);
}
fn run_panic_test_for_incorrect_selector(
invalid_selector: impl Fn(SourceNoncesRange) -> Option<SourceNoncesRange>,
) {
let mut state = RaceState::<_, _, TestMessagesProof>::default();
let mut strategy = BasicStrategy::<TestMessageLane>::new();
strategy.source_nonces_updated(header_id(1), source_nonces(1..=100));
strategy.best_target_nonces_updated(target_nonces(50), &mut state);
state.best_finalized_source_header_id_at_source = Some(header_id(1));
state.best_finalized_source_header_id_at_best_target = Some(header_id(1));
state.best_target_header_id = Some(header_id(1));
strategy.select_nonces_to_deliver_with_selector(&state, invalid_selector);
}
#[test]
#[should_panic]
fn select_nonces_to_deliver_panics_if_selector_returns_empty_range() {
#[allow(clippy::reversed_empty_ranges)]
run_panic_test_for_incorrect_selector(|_| Some(2..=1))
}
#[test]
#[should_panic]
fn select_nonces_to_deliver_panics_if_selector_returns_range_that_starts_before_passed_range() {
run_panic_test_for_incorrect_selector(|range| Some(range.begin() - 1..=*range.end()))
}
#[test]
#[should_panic]
fn select_nonces_to_deliver_panics_if_selector_returns_range_with_mismatched_end() {
run_panic_test_for_incorrect_selector(|range| Some(range.begin()..=*range.end() + 1))
}
}
@@ -0,0 +1,107 @@
// 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 <http://www.gnu.org/licenses/>.
//! Metrics for message lane relay loop.
use crate::message_lane::MessageLane;
use crate::message_lane_loop::{SourceClientState, TargetClientState};
use bp_message_lane::MessageNonce;
use relay_utils::metrics::{register, GaugeVec, Metrics, Opts, Registry, U64};
/// Message lane relay metrics.
///
/// Cloning only clones references.
#[derive(Clone)]
pub struct MessageLaneLoopMetrics {
/// Best finalized block numbers - "source", "target", "source_at_target", "target_at_source".
best_block_numbers: GaugeVec<U64>,
/// Lane state nonces: "source_latest_generated", "source_latest_confirmed",
/// "target_latest_received", "target_latest_confirmed".
lane_state_nonces: GaugeVec<U64>,
}
impl Metrics for MessageLaneLoopMetrics {
fn register(&self, registry: &Registry) -> Result<(), String> {
register(self.best_block_numbers.clone(), registry).map_err(|e| e.to_string())?;
register(self.lane_state_nonces.clone(), registry).map_err(|e| e.to_string())?;
Ok(())
}
}
impl Default for MessageLaneLoopMetrics {
fn default() -> Self {
MessageLaneLoopMetrics {
best_block_numbers: GaugeVec::new(
Opts::new("best_block_numbers", "Best finalized block numbers"),
&["type"],
)
.expect("metric is static and thus valid; qed"),
lane_state_nonces: GaugeVec::new(Opts::new("lane_state_nonces", "Nonces of the lane state"), &["type"])
.expect("metric is static and thus valid; qed"),
}
}
}
impl MessageLaneLoopMetrics {
/// Update source client state metrics.
pub fn update_source_state<P: MessageLane>(&self, source_client_state: SourceClientState<P>) {
self.best_block_numbers
.with_label_values(&["source"])
.set(source_client_state.best_self.0.into());
self.best_block_numbers
.with_label_values(&["target_at_source"])
.set(source_client_state.best_finalized_peer_at_best_self.0.into());
}
/// Update target client state metrics.
pub fn update_target_state<P: MessageLane>(&self, target_client_state: TargetClientState<P>) {
self.best_block_numbers
.with_label_values(&["target"])
.set(target_client_state.best_self.0.into());
self.best_block_numbers
.with_label_values(&["source_at_target"])
.set(target_client_state.best_finalized_peer_at_best_self.0.into());
}
/// Update latest generated nonce at source.
pub fn update_source_latest_generated_nonce<P: MessageLane>(&self, source_latest_generated_nonce: MessageNonce) {
self.lane_state_nonces
.with_label_values(&["source_latest_generated"])
.set(source_latest_generated_nonce);
}
/// Update latest confirmed nonce at source.
pub fn update_source_latest_confirmed_nonce<P: MessageLane>(&self, source_latest_confirmed_nonce: MessageNonce) {
self.lane_state_nonces
.with_label_values(&["source_latest_confirmed"])
.set(source_latest_confirmed_nonce);
}
/// Update latest received nonce at target.
pub fn update_target_latest_received_nonce<P: MessageLane>(&self, target_latest_generated_nonce: MessageNonce) {
self.lane_state_nonces
.with_label_values(&["target_latest_received"])
.set(target_latest_generated_nonce);
}
/// Update latest confirmed nonce at target.
pub fn update_target_latest_confirmed_nonce<P: MessageLane>(&self, target_latest_confirmed_nonce: MessageNonce) {
self.lane_state_nonces
.with_label_values(&["target_latest_confirmed"])
.set(target_latest_confirmed_nonce);
}
}
@@ -0,0 +1,25 @@
[package]
name = "relay-millau-client"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
codec = { package = "parity-scale-codec", version = "2.0.0" }
headers-relay = { path = "../headers-relay" }
relay-substrate-client = { path = "../substrate-client" }
relay-utils = { path = "../utils" }
# Supported Chains
millau-runtime = { path = "../../bin/millau/runtime" }
# Substrate Dependencies
frame-support = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
frame-system = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
pallet-transaction-payment = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
sp-core = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
sp-keyring = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
sp-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
@@ -0,0 +1,125 @@
// 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 <http://www.gnu.org/licenses/>.
//! Types used to connect to the Millau-Substrate chain.
use codec::Encode;
use relay_substrate_client::{Chain, ChainBase, ChainWithBalances, Client, TransactionSignScheme};
use sp_core::{storage::StorageKey, Pair};
use sp_runtime::{generic::SignedPayload, traits::IdentifyAccount};
use std::time::Duration;
pub use millau_runtime::BridgeRialtoCall;
/// Millau header id.
pub type HeaderId = relay_utils::HeaderId<millau_runtime::Hash, millau_runtime::BlockNumber>;
/// Millau chain definition.
#[derive(Debug, Clone, Copy)]
pub struct Millau;
impl ChainBase for Millau {
type BlockNumber = millau_runtime::BlockNumber;
type Hash = millau_runtime::Hash;
type Hasher = millau_runtime::Hashing;
type Header = millau_runtime::Header;
}
impl Chain for Millau {
const NAME: &'static str = "Millau";
const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_secs(5);
type AccountId = millau_runtime::AccountId;
type Index = millau_runtime::Index;
type SignedBlock = millau_runtime::SignedBlock;
type Call = millau_runtime::Call;
}
impl ChainWithBalances for Millau {
type NativeBalance = millau_runtime::Balance;
fn account_info_storage_key(account_id: &Self::AccountId) -> StorageKey {
use frame_support::storage::generator::StorageMap;
StorageKey(frame_system::Account::<millau_runtime::Runtime>::storage_map_final_key(
account_id,
))
}
}
impl TransactionSignScheme for Millau {
type Chain = Millau;
type AccountKeyPair = sp_core::sr25519::Pair;
type SignedTransaction = millau_runtime::UncheckedExtrinsic;
fn sign_transaction(
client: &Client<Self>,
signer: &Self::AccountKeyPair,
signer_nonce: <Self::Chain as Chain>::Index,
call: <Self::Chain as Chain>::Call,
) -> Self::SignedTransaction {
let raw_payload = SignedPayload::from_raw(
call,
(
frame_system::CheckSpecVersion::<millau_runtime::Runtime>::new(),
frame_system::CheckTxVersion::<millau_runtime::Runtime>::new(),
frame_system::CheckGenesis::<millau_runtime::Runtime>::new(),
frame_system::CheckEra::<millau_runtime::Runtime>::from(sp_runtime::generic::Era::Immortal),
frame_system::CheckNonce::<millau_runtime::Runtime>::from(signer_nonce),
frame_system::CheckWeight::<millau_runtime::Runtime>::new(),
pallet_transaction_payment::ChargeTransactionPayment::<millau_runtime::Runtime>::from(0),
),
(
millau_runtime::VERSION.spec_version,
millau_runtime::VERSION.transaction_version,
*client.genesis_hash(),
*client.genesis_hash(),
(),
(),
(),
),
);
let signature = raw_payload.using_encoded(|payload| signer.sign(payload));
let signer: sp_runtime::MultiSigner = signer.public().into();
let (call, extra, _) = raw_payload.deconstruct();
millau_runtime::UncheckedExtrinsic::new_signed(call, signer.into_account(), signature.into(), extra)
}
}
/// Millau signing params.
#[derive(Clone)]
pub struct SigningParams {
/// Substrate transactions signer.
pub signer: sp_core::sr25519::Pair,
}
impl SigningParams {
/// Create signing params from SURI and password.
pub fn from_suri(suri: &str, password: Option<&str>) -> Result<Self, sp_core::crypto::SecretStringError> {
Ok(SigningParams {
signer: sp_core::sr25519::Pair::from_string(suri, password)?,
})
}
}
impl std::fmt::Debug for SigningParams {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.signer.public())
}
}
/// Millau header type used in headers sync.
pub type SyncHeader = relay_substrate_client::SyncHeader<millau_runtime::Header>;
@@ -0,0 +1,25 @@
[package]
name = "relay-polkadot-client"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
codec = { package = "parity-scale-codec", version = "2.0.0" }
headers-relay = { path = "../headers-relay" }
relay-substrate-client = { path = "../substrate-client" }
relay-utils = { path = "../utils" }
# Bridge dependencies
bp-polkadot = { path = "../../primitives/polkadot" }
# Substrate Dependencies
frame-system = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
frame-support = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
pallet-transaction-payment = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
sp-core = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
sp-keyring = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
sp-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
@@ -0,0 +1,47 @@
// 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 <http://www.gnu.org/licenses/>.
//! Types used to connect to the Polkadot chain.
use relay_substrate_client::{Chain, ChainBase};
use std::time::Duration;
/// Polkadot header id.
pub type HeaderId = relay_utils::HeaderId<bp_polkadot::Hash, bp_polkadot::BlockNumber>;
/// Polkadot chain definition
#[derive(Debug, Clone, Copy)]
pub struct Polkadot;
impl ChainBase for Polkadot {
type BlockNumber = bp_polkadot::BlockNumber;
type Hash = bp_polkadot::Hash;
type Hasher = bp_polkadot::Hasher;
type Header = bp_polkadot::Header;
}
impl Chain for Polkadot {
const NAME: &'static str = "Polkadot";
const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_secs(6);
type AccountId = bp_polkadot::AccountId;
type Index = bp_polkadot::Nonce;
type SignedBlock = bp_polkadot::SignedBlock;
type Call = ();
}
/// Polkadot header type used in headers sync.
pub type SyncHeader = relay_substrate_client::SyncHeader<bp_polkadot::Header>;
@@ -0,0 +1,25 @@
[package]
name = "relay-rialto-client"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
codec = { package = "parity-scale-codec", version = "2.0.0" }
headers-relay = { path = "../headers-relay" }
relay-substrate-client = { path = "../substrate-client" }
relay-utils = { path = "../utils" }
# Bridge dependencies
rialto-runtime = { path = "../../bin/rialto/runtime" }
# Substrate Dependencies
frame-system = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
frame-support = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
pallet-transaction-payment = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
sp-core = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
sp-keyring = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
sp-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
@@ -0,0 +1,133 @@
// 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 <http://www.gnu.org/licenses/>.
//! Types used to connect to the Rialto-Substrate chain.
use codec::Encode;
use relay_substrate_client::{Chain, ChainBase, ChainWithBalances, Client, TransactionSignScheme};
use sp_core::{storage::StorageKey, Pair};
use sp_runtime::{generic::SignedPayload, traits::IdentifyAccount};
use std::time::Duration;
pub use rialto_runtime::BridgeMillauCall;
/// Rialto header id.
pub type HeaderId = relay_utils::HeaderId<rialto_runtime::Hash, rialto_runtime::BlockNumber>;
/// Rialto chain definition
#[derive(Debug, Clone, Copy)]
pub struct Rialto;
impl ChainBase for Rialto {
type BlockNumber = rialto_runtime::BlockNumber;
type Hash = rialto_runtime::Hash;
type Hasher = rialto_runtime::Hashing;
type Header = rialto_runtime::Header;
}
impl Chain for Rialto {
const NAME: &'static str = "Rialto";
const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_secs(5);
type AccountId = rialto_runtime::AccountId;
type Index = rialto_runtime::Index;
type SignedBlock = rialto_runtime::SignedBlock;
type Call = rialto_runtime::Call;
}
impl ChainWithBalances for Rialto {
type NativeBalance = rialto_runtime::Balance;
fn account_info_storage_key(account_id: &Self::AccountId) -> StorageKey {
use frame_support::storage::generator::StorageMap;
StorageKey(frame_system::Account::<rialto_runtime::Runtime>::storage_map_final_key(
account_id,
))
}
}
impl TransactionSignScheme for Rialto {
type Chain = Rialto;
type AccountKeyPair = sp_core::sr25519::Pair;
type SignedTransaction = rialto_runtime::UncheckedExtrinsic;
fn sign_transaction(
client: &Client<Self>,
signer: &Self::AccountKeyPair,
signer_nonce: <Self::Chain as Chain>::Index,
call: <Self::Chain as Chain>::Call,
) -> Self::SignedTransaction {
let raw_payload = SignedPayload::from_raw(
call,
(
frame_system::CheckSpecVersion::<rialto_runtime::Runtime>::new(),
frame_system::CheckTxVersion::<rialto_runtime::Runtime>::new(),
frame_system::CheckGenesis::<rialto_runtime::Runtime>::new(),
frame_system::CheckEra::<rialto_runtime::Runtime>::from(sp_runtime::generic::Era::Immortal),
frame_system::CheckNonce::<rialto_runtime::Runtime>::from(signer_nonce),
frame_system::CheckWeight::<rialto_runtime::Runtime>::new(),
pallet_transaction_payment::ChargeTransactionPayment::<rialto_runtime::Runtime>::from(0),
),
(
rialto_runtime::VERSION.spec_version,
rialto_runtime::VERSION.transaction_version,
*client.genesis_hash(),
*client.genesis_hash(),
(),
(),
(),
),
);
let signature = raw_payload.using_encoded(|payload| signer.sign(payload));
let signer: sp_runtime::MultiSigner = signer.public().into();
let (call, extra, _) = raw_payload.deconstruct();
rialto_runtime::UncheckedExtrinsic::new_signed(call, signer.into_account(), signature.into(), extra)
}
}
/// Rialto signing params.
#[derive(Clone)]
pub struct SigningParams {
/// Substrate transactions signer.
pub signer: sp_core::sr25519::Pair,
}
impl SigningParams {
/// Create signing params from SURI and password.
pub fn from_suri(suri: &str, password: Option<&str>) -> Result<Self, sp_core::crypto::SecretStringError> {
Ok(SigningParams {
signer: sp_core::sr25519::Pair::from_string(suri, password)?,
})
}
}
impl std::fmt::Debug for SigningParams {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.signer.public())
}
}
impl Default for SigningParams {
fn default() -> Self {
SigningParams {
signer: sp_keyring::AccountKeyring::Alice.pair(),
}
}
}
/// Rialto header type used in headers sync.
pub type SyncHeader = relay_substrate_client::SyncHeader<rialto_runtime::Header>;
@@ -0,0 +1,37 @@
[package]
name = "relay-substrate-client"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
async-std = "1.6.5"
async-trait = "0.1.40"
codec = { package = "parity-scale-codec", version = "2.0.0" }
jsonrpsee = { git = "https://github.com/svyatonik/jsonrpsee.git", branch = "shared-client-in-rpc-api", default-features = false, features = ["ws"] }
log = "0.4.11"
num-traits = "0.2"
rand = "0.7"
# Bridge dependencies
bp-message-lane = { path = "../../primitives/message-lane" }
bp-runtime = { path = "../../primitives/runtime" }
headers-relay = { path = "../headers-relay" }
relay-utils = { path = "../utils" }
# Substrate Dependencies
frame-support = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
frame-system = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
pallet-balances = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
sc-rpc-api = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
sp-core = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
sp-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
sp-std = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
sp-trie = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
sp-version = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
#[dev-dependencies]
futures = "0.3.7"
@@ -0,0 +1,103 @@
// 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 <http://www.gnu.org/licenses/>.
use crate::client::Client;
use bp_runtime::Chain as ChainBase;
use frame_support::Parameter;
use jsonrpsee::common::{DeserializeOwned, Serialize};
use num_traits::{CheckedSub, Zero};
use sp_core::{storage::StorageKey, Pair};
use sp_runtime::{
generic::SignedBlock,
traits::{AtLeast32Bit, Dispatchable, MaybeDisplay, MaybeSerialize, MaybeSerializeDeserialize, Member},
Justification,
};
use std::{fmt::Debug, time::Duration};
/// Substrate-based chain from minimal relay-client point of view.
pub trait Chain: ChainBase {
/// Chain name.
const NAME: &'static str;
/// Average block interval.
///
/// How often blocks are produced on that chain. It's suggested to set this value
/// to match the block time of the chain.
const AVERAGE_BLOCK_INTERVAL: Duration;
/// The user account identifier type for the runtime.
type AccountId: Parameter + Member + MaybeSerializeDeserialize + Debug + MaybeDisplay + Ord + Default;
/// Index of a transaction used by the chain.
type Index: Parameter
+ Member
+ MaybeSerialize
+ Debug
+ Default
+ MaybeDisplay
+ DeserializeOwned
+ AtLeast32Bit
+ Copy;
/// Block type.
type SignedBlock: Member + Serialize + DeserializeOwned + BlockWithJustification;
/// The aggregated `Call` type.
type Call: Dispatchable + Debug;
}
/// Substrate-based chain with `frame_system::Config::AccountData` set to
/// the `pallet_balances::AccountData<NativeBalance>`.
pub trait ChainWithBalances: Chain {
/// Balance of an account in native tokens.
type NativeBalance: Parameter + Member + DeserializeOwned + Clone + Copy + CheckedSub + PartialOrd + Zero;
/// Return runtime storage key for getting `frame_system::AccountInfo` of given account.
fn account_info_storage_key(account_id: &Self::AccountId) -> StorageKey;
}
/// Block with justification.
pub trait BlockWithJustification {
/// Return block justification, if known.
fn justification(&self) -> Option<&Justification>;
}
/// Substrate-based chain transactions signing scheme.
pub trait TransactionSignScheme {
/// Chain that this scheme is to be used.
type Chain: Chain;
/// Type of key pairs used to sign transactions.
type AccountKeyPair: Pair;
/// Signed transaction.
type SignedTransaction;
/// Create transaction for given runtime call, signed by given account.
fn sign_transaction(
client: &Client<Self::Chain>,
signer: &Self::AccountKeyPair,
signer_nonce: <Self::Chain as Chain>::Index,
call: <Self::Chain as Chain>::Call,
) -> Self::SignedTransaction;
}
impl BlockWithJustification for () {
fn justification(&self) -> Option<&Justification> {
None
}
}
impl<Block> BlockWithJustification for SignedBlock<Block> {
fn justification(&self) -> Option<&Justification> {
self.justification.as_ref()
}
}
@@ -0,0 +1,269 @@
// 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 <http://www.gnu.org/licenses/>.
//! Substrate node client.
use crate::chain::{Chain, ChainWithBalances};
use crate::rpc::{Substrate, SubstrateMessageLane};
use crate::{ConnectionParams, Error, Result};
use bp_message_lane::{LaneId, MessageNonce};
use bp_runtime::InstanceId;
use codec::Decode;
use frame_system::AccountInfo;
use jsonrpsee::common::DeserializeOwned;
use jsonrpsee::raw::RawClient;
use jsonrpsee::transport::ws::WsTransportClient;
use jsonrpsee::{client::Subscription, Client as RpcClient};
use num_traits::Zero;
use pallet_balances::AccountData;
use sp_core::Bytes;
use sp_trie::StorageProof;
use sp_version::RuntimeVersion;
use std::ops::RangeInclusive;
const SUB_API_GRANDPA_AUTHORITIES: &str = "GrandpaApi_grandpa_authorities";
/// Opaque justifications subscription type.
pub type JustificationsSubscription = Subscription<Bytes>;
/// Opaque GRANDPA authorities set.
pub type OpaqueGrandpaAuthoritiesSet = Vec<u8>;
/// Substrate client type.
///
/// Cloning `Client` is a cheap operation.
pub struct Client<C: Chain> {
/// Client connection params.
params: ConnectionParams,
/// Substrate RPC client.
client: RpcClient,
/// Genesis block hash.
genesis_hash: C::Hash,
}
impl<C: Chain> Clone for Client<C> {
fn clone(&self) -> Self {
Client {
params: self.params.clone(),
client: self.client.clone(),
genesis_hash: self.genesis_hash,
}
}
}
impl<C: Chain> std::fmt::Debug for Client<C> {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
fmt.debug_struct("Client")
.field("genesis_hash", &self.genesis_hash)
.finish()
}
}
impl<C: Chain> Client<C> {
/// Returns client that is able to call RPCs on Substrate node over websocket connection.
pub async fn new(params: ConnectionParams) -> Result<Self> {
let client = Self::build_client(params.clone()).await?;
let number: C::BlockNumber = Zero::zero();
let genesis_hash = Substrate::<C, _, _>::chain_get_block_hash(&client, number).await?;
Ok(Self {
params,
client,
genesis_hash,
})
}
/// Reopen client connection.
pub async fn reconnect(&mut self) -> Result<()> {
self.client = Self::build_client(self.params.clone()).await?;
Ok(())
}
/// Build client to use in connection.
async fn build_client(params: ConnectionParams) -> Result<RpcClient> {
let uri = format!("ws://{}:{}", params.host, params.port);
let transport = WsTransportClient::new(&uri).await?;
let raw_client = RawClient::new(transport);
Ok(raw_client.into())
}
}
impl<C: Chain> Client<C> {
/// Returns true if client is connected to at least one peer and is in synced state.
pub async fn ensure_synced(&self) -> Result<()> {
let health = Substrate::<C, _, _>::system_health(&self.client).await?;
let is_synced = !health.is_syncing && (!health.should_have_peers || health.peers > 0);
if is_synced {
Ok(())
} else {
Err(Error::ClientNotSynced(health))
}
}
/// Return hash of the genesis block.
pub fn genesis_hash(&self) -> &C::Hash {
&self.genesis_hash
}
/// Return hash of the best finalized block.
pub async fn best_finalized_header_hash(&self) -> Result<C::Hash> {
Ok(Substrate::<C, _, _>::chain_get_finalized_head(&self.client).await?)
}
/// Returns the best Substrate header.
pub async fn best_header(&self) -> Result<C::Header>
where
C::Header: DeserializeOwned,
{
Ok(Substrate::<C, _, _>::chain_get_header(&self.client, None).await?)
}
/// Get a Substrate block from its hash.
pub async fn get_block(&self, block_hash: Option<C::Hash>) -> Result<C::SignedBlock> {
Ok(Substrate::<C, _, _>::chain_get_block(&self.client, block_hash).await?)
}
/// Get a Substrate header by its hash.
pub async fn header_by_hash(&self, block_hash: C::Hash) -> Result<C::Header>
where
C::Header: DeserializeOwned,
{
Ok(Substrate::<C, _, _>::chain_get_header(&self.client, block_hash).await?)
}
/// Get a Substrate block hash by its number.
pub async fn block_hash_by_number(&self, number: C::BlockNumber) -> Result<C::Hash> {
Ok(Substrate::<C, _, _>::chain_get_block_hash(&self.client, number).await?)
}
/// Get a Substrate header by its number.
pub async fn header_by_number(&self, block_number: C::BlockNumber) -> Result<C::Header>
where
C::Header: DeserializeOwned,
{
let block_hash = Self::block_hash_by_number(self, block_number).await?;
Ok(Self::header_by_hash(self, block_hash).await?)
}
/// Return runtime version.
pub async fn runtime_version(&self) -> Result<RuntimeVersion> {
Ok(Substrate::<C, _, _>::runtime_version(&self.client).await?)
}
/// Return native tokens balance of the account.
pub async fn free_native_balance(&self, account: C::AccountId) -> Result<C::NativeBalance>
where
C: ChainWithBalances,
{
let storage_key = C::account_info_storage_key(&account);
let encoded_account_data = Substrate::<C, _, _>::get_storage(&self.client, storage_key)
.await?
.ok_or(Error::AccountDoesNotExist)?;
let decoded_account_data =
AccountInfo::<C::Index, AccountData<C::NativeBalance>>::decode(&mut &encoded_account_data.0[..])
.map_err(Error::ResponseParseFailed)?;
Ok(decoded_account_data.data.free)
}
/// Get the nonce of the given Substrate account.
///
/// Note: It's the caller's responsibility to make sure `account` is a valid ss58 address.
pub async fn next_account_index(&self, account: C::AccountId) -> Result<C::Index> {
Ok(Substrate::<C, _, _>::system_account_next_index(&self.client, account).await?)
}
/// Submit an extrinsic for inclusion in a block.
///
/// Note: The given transaction does not need be SCALE encoded beforehand.
pub async fn submit_extrinsic(&self, transaction: Bytes) -> Result<C::Hash> {
let tx_hash = Substrate::<C, _, _>::author_submit_extrinsic(&self.client, transaction).await?;
log::trace!(target: "bridge", "Sent transaction to Substrate node: {:?}", tx_hash);
Ok(tx_hash)
}
/// Get the GRANDPA authority set at given block.
pub async fn grandpa_authorities_set(&self, block: C::Hash) -> Result<OpaqueGrandpaAuthoritiesSet> {
let call = SUB_API_GRANDPA_AUTHORITIES.to_string();
let data = Bytes(Vec::new());
let encoded_response = Substrate::<C, _, _>::state_call(&self.client, call, data, Some(block)).await?;
let authority_list = encoded_response.0;
Ok(authority_list)
}
/// Execute runtime call at given block.
pub async fn state_call(&self, method: String, data: Bytes, at_block: Option<C::Hash>) -> Result<Bytes> {
Substrate::<C, _, _>::state_call(&self.client, method, data, at_block)
.await
.map_err(Into::into)
}
/// Returns proof-of-message(s) in given inclusive range.
pub async fn prove_messages(
&self,
instance: InstanceId,
lane: LaneId,
range: RangeInclusive<MessageNonce>,
include_outbound_lane_state: bool,
at_block: C::Hash,
) -> Result<StorageProof> {
let encoded_trie_nodes = SubstrateMessageLane::<C, _, _>::prove_messages(
&self.client,
instance,
lane,
*range.start(),
*range.end(),
include_outbound_lane_state,
Some(at_block),
)
.await
.map_err(Error::Request)?;
let decoded_trie_nodes: Vec<Vec<u8>> =
Decode::decode(&mut &encoded_trie_nodes[..]).map_err(Error::ResponseParseFailed)?;
Ok(StorageProof::new(decoded_trie_nodes))
}
/// Returns proof-of-message(s) delivery.
pub async fn prove_messages_delivery(
&self,
instance: InstanceId,
lane: LaneId,
at_block: C::Hash,
) -> Result<Vec<Vec<u8>>> {
let encoded_trie_nodes =
SubstrateMessageLane::<C, _, _>::prove_messages_delivery(&self.client, instance, lane, Some(at_block))
.await
.map_err(Error::Request)?;
let decoded_trie_nodes: Vec<Vec<u8>> =
Decode::decode(&mut &encoded_trie_nodes[..]).map_err(Error::ResponseParseFailed)?;
Ok(decoded_trie_nodes)
}
/// Return new justifications stream.
pub async fn subscribe_justifications(self) -> Result<JustificationsSubscription> {
Ok(self
.client
.subscribe(
"grandpa_subscribeJustifications",
jsonrpsee::common::Params::None,
"grandpa_unsubscribeJustifications",
)
.await?)
}
}
@@ -0,0 +1,87 @@
// 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 <http://www.gnu.org/licenses/>.
//! Substrate node RPC errors.
use jsonrpsee::client::RequestError;
use jsonrpsee::transport::ws::WsNewDnsError;
use relay_utils::MaybeConnectionError;
use sc_rpc_api::system::Health;
/// 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)]
pub enum Error {
/// Web socket connection error.
WsConnectionError(WsNewDnsError),
/// An error that can occur when making a request to
/// an JSON-RPC server.
Request(RequestError),
/// The response from the server could not be SCALE decoded.
ResponseParseFailed(codec::Error),
/// The Substrate bridge pallet has not yet been initialized.
UninitializedBridgePallet,
/// Account does not exist on the chain.
AccountDoesNotExist,
/// The client we're connected to is not synced, so we can't rely on its state.
ClientNotSynced(Health),
/// Custom logic error.
Custom(String),
}
impl From<WsNewDnsError> for Error {
fn from(error: WsNewDnsError) -> Self {
Error::WsConnectionError(error)
}
}
impl From<RequestError> for Error {
fn from(error: RequestError) -> Self {
Error::Request(error)
}
}
impl MaybeConnectionError for Error {
fn is_connection_error(&self) -> bool {
matches!(
*self,
Error::Request(RequestError::TransportError(_)) | Error::ClientNotSynced(_)
)
}
}
impl From<Error> for String {
fn from(error: Error) -> String {
error.to_string()
}
}
impl ToString for Error {
fn to_string(&self) -> String {
match self {
Self::WsConnectionError(e) => e.to_string(),
Self::Request(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::ClientNotSynced(health) => format!("Substrate client is not synced: {}", health),
Self::Custom(e) => e.clone(),
}
}
}
@@ -0,0 +1,371 @@
// 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 <http://www.gnu.org/licenses/>.
//! Module 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 async_trait::async_trait;
use num_traits::CheckedSub;
use sp_version::RuntimeVersion;
use std::{
collections::VecDeque,
time::{Duration, Instant},
};
/// Guards environment.
#[async_trait]
pub trait Environment<C: ChainWithBalances>: Send + Sync + 'static {
/// Return current runtime version.
async fn runtime_version(&mut self) -> Result<RuntimeVersion, String>;
/// Return free native balance of the account on the chain.
async fn free_native_balance(&mut self, account: C::AccountId) -> Result<C::NativeBalance, String>;
/// Return current time.
fn now(&self) -> Instant {
Instant::now()
}
/// Sleep given amount of time.
async fn sleep(&mut self, duration: Duration) {
async_std::task::sleep(duration).await
}
/// Abort current process. Called when guard condition check fails.
async fn abort(&mut self) {
std::process::abort();
}
}
/// Abort when runtime spec version is different from specified.
pub fn abort_on_spec_version_change<C: ChainWithBalances>(mut env: impl Environment<C>, expected_spec_version: u32) {
async_std::task::spawn(async move {
loop {
let actual_spec_version = env.runtime_version().await;
match actual_spec_version {
Ok(version) if version.spec_version == expected_spec_version => (),
Ok(version) => {
log::error!(
target: "bridge-guard",
"{} runtime spec version has changed from {} to {}. Aborting relay",
C::NAME,
expected_spec_version,
version.spec_version,
);
env.abort().await;
}
Err(error) => log::warn!(
target: "bridge-guard",
"Failed to read {} runtime version: {:?}. Relay may need to be stopped manually",
C::NAME,
error,
),
}
env.sleep(conditions_check_delay::<C>()).await;
}
});
}
/// Abort if, during a 24 hours, free balance of given account is decreased at least by given value.
/// Other components may increase (or decrease) balance of account and it WILL affect logic of the guard.
pub fn abort_when_account_balance_decreased<C: ChainWithBalances>(
mut env: impl Environment<C>,
account_id: C::AccountId,
maximal_decrease: C::NativeBalance,
) {
const DAY: Duration = Duration::from_secs(60 * 60 * 24);
async_std::task::spawn(async move {
let mut balances = VecDeque::new();
loop {
let current_time = env.now();
// remember balances that are beyound 24h border
let time_border = current_time - DAY;
while balances.front().map(|(time, _)| *time < time_border).unwrap_or(false) {
balances.pop_front();
}
// read balance of the account
let current_balance = env.free_native_balance(account_id.clone()).await;
// remember balance and check difference
match current_balance {
Ok(current_balance) => {
// remember balance
balances.push_back((current_time, current_balance));
// check if difference between current and oldest balance is too large
let (oldest_time, oldest_balance) =
balances.front().expect("pushed to queue couple of lines above; qed");
let balances_difference = oldest_balance.checked_sub(&current_balance);
if balances_difference > Some(maximal_decrease) {
log::error!(
target: "bridge-guard",
"Balance of {} account {:?} has decreased from {:?} to {:?} in {} minutes. Aborting relay",
C::NAME,
account_id,
oldest_balance,
current_balance,
current_time.duration_since(*oldest_time).as_secs() / 60,
);
env.abort().await;
}
}
Err(error) => {
log::warn!(
target: "bridge-guard",
"Failed to read {} account {:?} balance: {:?}. Relay may need to be stopped manually",
C::NAME,
account_id,
error,
);
}
};
env.sleep(conditions_check_delay::<C>()).await;
}
});
}
/// Delay between conditions check.
fn conditions_check_delay<C: Chain>() -> Duration {
C::AVERAGE_BLOCK_INTERVAL * (10 + rand::random::<u32>() % 10)
}
#[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())
}
async fn free_native_balance(&mut self, account: C::AccountId) -> Result<C::NativeBalance, String> {
Client::<C>::free_native_balance(self, account)
.await
.map_err(|e| e.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
use futures::{
channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender},
future::FutureExt,
stream::StreamExt,
SinkExt,
};
struct TestChain;
impl bp_runtime::Chain for TestChain {
type BlockNumber = u32;
type Hash = sp_core::H256;
type Hasher = sp_runtime::traits::BlakeTwo256;
type Header = sp_runtime::generic::Header<u32, sp_runtime::traits::BlakeTwo256>;
}
impl Chain for TestChain {
const NAME: &'static str = "Test";
const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_millis(1);
type AccountId = u32;
type Index = u32;
type SignedBlock = ();
type Call = ();
}
impl ChainWithBalances for TestChain {
type NativeBalance = u32;
fn account_info_storage_key(_account_id: &u32) -> sp_core::storage::StorageKey {
unreachable!()
}
}
struct TestEnvironment {
runtime_version_rx: UnboundedReceiver<RuntimeVersion>,
free_native_balance_rx: UnboundedReceiver<u32>,
slept_tx: UnboundedSender<()>,
aborted_tx: UnboundedSender<()>,
}
#[async_trait]
impl Environment<TestChain> for TestEnvironment {
async fn runtime_version(&mut self) -> Result<RuntimeVersion, String> {
Ok(self.runtime_version_rx.next().await.unwrap_or_default())
}
async fn free_native_balance(&mut self, _account: u32) -> Result<u32, String> {
Ok(self.free_native_balance_rx.next().await.unwrap_or_default())
}
async fn sleep(&mut self, _duration: Duration) {
let _ = self.slept_tx.send(()).await;
}
async fn abort(&mut self) {
let _ = self.aborted_tx.send(()).await;
// simulate process abort :)
async_std::task::sleep(Duration::from_secs(60)).await;
}
}
#[test]
fn aborts_when_spec_version_is_changed() {
async_std::task::block_on(async {
let (
(mut runtime_version_tx, runtime_version_rx),
(_free_native_balance_tx, free_native_balance_rx),
(slept_tx, mut slept_rx),
(aborted_tx, mut aborted_rx),
) = (unbounded(), unbounded(), unbounded(), unbounded());
abort_on_spec_version_change(
TestEnvironment {
runtime_version_rx,
free_native_balance_rx,
slept_tx,
aborted_tx,
},
0,
);
// client responds with wrong version
runtime_version_tx
.send(RuntimeVersion {
spec_version: 42,
..Default::default()
})
.await
.unwrap();
// then the `abort` function is called
aborted_rx.next().await;
// and we do not reach the `sleep` function call
assert!(slept_rx.next().now_or_never().is_none());
});
}
#[test]
fn does_not_aborts_when_spec_version_is_unchanged() {
async_std::task::block_on(async {
let (
(mut runtime_version_tx, runtime_version_rx),
(_free_native_balance_tx, free_native_balance_rx),
(slept_tx, mut slept_rx),
(aborted_tx, mut aborted_rx),
) = (unbounded(), unbounded(), unbounded(), unbounded());
abort_on_spec_version_change(
TestEnvironment {
runtime_version_rx,
free_native_balance_rx,
slept_tx,
aborted_tx,
},
42,
);
// client responds with the same version
runtime_version_tx
.send(RuntimeVersion {
spec_version: 42,
..Default::default()
})
.await
.unwrap();
// then the `sleep` function is called
slept_rx.next().await;
// and the `abort` function is not called
assert!(aborted_rx.next().now_or_never().is_none());
});
}
#[test]
fn aborts_when_balance_is_too_low() {
async_std::task::block_on(async {
let (
(_runtime_version_tx, runtime_version_rx),
(mut free_native_balance_tx, free_native_balance_rx),
(slept_tx, mut slept_rx),
(aborted_tx, mut aborted_rx),
) = (unbounded(), unbounded(), unbounded(), unbounded());
abort_when_account_balance_decreased(
TestEnvironment {
runtime_version_rx,
free_native_balance_rx,
slept_tx,
aborted_tx,
},
0,
100,
);
// client responds with initial balance
free_native_balance_tx.send(1000).await.unwrap();
// then the guard sleeps
slept_rx.next().await;
// and then client responds with updated balance, which is too low
free_native_balance_tx.send(899).await.unwrap();
// then the `abort` function is called
aborted_rx.next().await;
// and we do not reach next `sleep` function call
assert!(slept_rx.next().now_or_never().is_none());
});
}
#[test]
fn does_not_aborts_when_balance_is_enough() {
async_std::task::block_on(async {
let (
(_runtime_version_tx, runtime_version_rx),
(mut free_native_balance_tx, free_native_balance_rx),
(slept_tx, mut slept_rx),
(aborted_tx, mut aborted_rx),
) = (unbounded(), unbounded(), unbounded(), unbounded());
abort_when_account_balance_decreased(
TestEnvironment {
runtime_version_rx,
free_native_balance_rx,
slept_tx,
aborted_tx,
},
0,
100,
);
// client responds with initial balance
free_native_balance_tx.send(1000).await.unwrap();
// then the guard sleeps
slept_rx.next().await;
// and then client responds with updated balance, which is enough
free_native_balance_tx.send(950).await.unwrap();
// then the `sleep` function is called
slept_rx.next().await;
// and `abort` is not called
assert!(aborted_rx.next().now_or_never().is_none());
});
}
}
@@ -0,0 +1,108 @@
// 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 <http://www.gnu.org/licenses/>.
//! Default generic implementation of headers source for basic Substrate client.
use crate::chain::{BlockWithJustification, Chain};
use crate::client::Client;
use crate::error::Error;
use async_trait::async_trait;
use headers_relay::{
sync_loop::SourceClient,
sync_types::{HeaderIdOf, HeadersSyncPipeline, QueuedHeader, SourceHeader},
};
use relay_utils::relay_loop::Client as RelayClient;
use sp_runtime::{traits::Header as HeaderT, Justification};
use std::marker::PhantomData;
/// Substrate node as headers source.
pub struct HeadersSource<C: Chain, P> {
client: Client<C>,
_phantom: PhantomData<P>,
}
impl<C: Chain, P> HeadersSource<C, P> {
/// Create new headers source using given client.
pub fn new(client: Client<C>) -> Self {
HeadersSource {
client,
_phantom: Default::default(),
}
}
}
impl<C: Chain, P> Clone for HeadersSource<C, P> {
fn clone(&self) -> Self {
HeadersSource {
client: self.client.clone(),
_phantom: Default::default(),
}
}
}
#[async_trait]
impl<C: Chain, P: HeadersSyncPipeline> RelayClient for HeadersSource<C, P> {
type Error = Error;
async fn reconnect(&mut self) -> Result<(), Error> {
self.client.reconnect().await
}
}
#[async_trait]
impl<C, P> SourceClient<P> for HeadersSource<C, P>
where
C: Chain,
C::BlockNumber: relay_utils::BlockNumberBase,
C::Header: Into<P::Header>,
P: HeadersSyncPipeline<Extra = (), Completion = Justification, Hash = C::Hash, Number = C::BlockNumber>,
P::Header: SourceHeader<C::Hash, C::BlockNumber>,
{
async fn best_block_number(&self) -> Result<P::Number, Error> {
// we **CAN** continue to relay headers if source node is out of sync, because
// target node may be missing headers that are already available at the source
Ok(*self.client.best_header().await?.number())
}
async fn header_by_hash(&self, hash: P::Hash) -> Result<P::Header, Error> {
self.client
.header_by_hash(hash)
.await
.map(Into::into)
.map_err(Into::into)
}
async fn header_by_number(&self, number: P::Number) -> Result<P::Header, Error> {
self.client
.header_by_number(number)
.await
.map(Into::into)
.map_err(Into::into)
}
async fn header_completion(&self, id: HeaderIdOf<P>) -> Result<(HeaderIdOf<P>, Option<P::Completion>), Error> {
let hash = id.1;
let signed_block = self.client.get_block(Some(hash)).await?;
let grandpa_justification = signed_block.justification().cloned();
Ok((id, grandpa_justification))
}
async fn header_extra(&self, id: HeaderIdOf<P>, _header: QueuedHeader<P>) -> Result<(HeaderIdOf<P>, ()), Error> {
Ok((id, ()))
}
}
@@ -0,0 +1,55 @@
// 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 <http://www.gnu.org/licenses/>.
//! Tools to interact with (Open) Ethereum node using RPC methods.
#![warn(missing_docs)]
mod chain;
mod client;
mod error;
mod rpc;
mod sync_header;
pub mod guard;
pub mod headers_source;
pub use crate::chain::{BlockWithJustification, Chain, ChainWithBalances, TransactionSignScheme};
pub use crate::client::{Client, JustificationsSubscription, OpaqueGrandpaAuthoritiesSet};
pub use crate::error::{Error, Result};
pub use crate::sync_header::SyncHeader;
pub use bp_runtime::{BlockNumberOf, Chain as ChainBase, HashOf, HeaderOf};
/// Header id used by the chain.
pub type HeaderIdOf<C> = relay_utils::HeaderId<HashOf<C>, BlockNumberOf<C>>;
/// Substrate-over-websocket connection params.
#[derive(Debug, Clone)]
pub struct ConnectionParams {
/// Websocket server hostname.
pub host: String,
/// Websocket server TCP port.
pub port: u16,
}
impl Default for ConnectionParams {
fn default() -> Self {
ConnectionParams {
host: "localhost".into(),
port: 9944,
}
}
}
@@ -0,0 +1,77 @@
// 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 <http://www.gnu.org/licenses/>.
//! The most generic Substrate node RPC interface.
// The compiler doesn't think we're using the
// code from rpc_api!
#![allow(dead_code)]
#![allow(unused_variables)]
use crate::chain::Chain;
use bp_message_lane::{LaneId, MessageNonce};
use bp_runtime::InstanceId;
use sc_rpc_api::system::Health;
use sp_core::{
storage::{StorageData, StorageKey},
Bytes,
};
use sp_version::RuntimeVersion;
jsonrpsee::rpc_api! {
pub(crate) Substrate<C: Chain> {
#[rpc(method = "system_health", positional_params)]
fn system_health() -> Health;
#[rpc(method = "chain_getHeader", positional_params)]
fn chain_get_header(block_hash: Option<C::Hash>) -> C::Header;
#[rpc(method = "chain_getFinalizedHead", positional_params)]
fn chain_get_finalized_head() -> C::Hash;
#[rpc(method = "chain_getBlock", positional_params)]
fn chain_get_block(block_hash: Option<C::Hash>) -> C::SignedBlock;
#[rpc(method = "chain_getBlockHash", positional_params)]
fn chain_get_block_hash(block_number: Option<C::BlockNumber>) -> C::Hash;
#[rpc(method = "system_accountNextIndex", positional_params)]
fn system_account_next_index(account_id: C::AccountId) -> C::Index;
#[rpc(method = "author_submitExtrinsic", positional_params)]
fn author_submit_extrinsic(extrinsic: Bytes) -> C::Hash;
#[rpc(method = "state_call", positional_params)]
fn state_call(method: String, data: Bytes, at_block: Option<C::Hash>) -> Bytes;
#[rpc(method = "state_getStorage", positional_params)]
fn get_storage(key: StorageKey) -> Option<StorageData>;
#[rpc(method = "state_getRuntimeVersion", positional_params)]
fn runtime_version() -> RuntimeVersion;
}
pub(crate) SubstrateMessageLane<C: Chain> {
#[rpc(method = "messageLane_proveMessages", positional_params)]
fn prove_messages(
instance: InstanceId,
lane: LaneId,
begin: MessageNonce,
end: MessageNonce,
include_outbound_lane_state: bool,
block: Option<C::Hash>,
) -> Bytes;
#[rpc(method = "messageLane_proveMessagesDelivery", positional_params)]
fn prove_messages_delivery(
instance: InstanceId,
lane: LaneId,
block: Option<C::Hash>,
) -> Bytes;
}
}
@@ -0,0 +1,61 @@
// 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 <http://www.gnu.org/licenses/>.
use headers_relay::sync_types::SourceHeader;
use num_traits::{CheckedSub, One};
use relay_utils::HeaderId;
use sp_runtime::traits::Header as HeaderT;
/// Generic wrapper for `sp_runtime::traits::Header` based headers, that
/// implements `headers_relay::sync_types::SourceHeader` and may be used in headers sync directly.
#[derive(Clone, Debug, PartialEq)]
pub struct SyncHeader<Header>(Header);
impl<Header> SyncHeader<Header> {
/// Extracts wrapped header from self.
pub fn into_inner(self) -> Header {
self.0
}
}
impl<Header> std::ops::Deref for SyncHeader<Header> {
type Target = Header;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<Header> From<Header> for SyncHeader<Header> {
fn from(header: Header) -> Self {
Self(header)
}
}
impl<Header: HeaderT> SourceHeader<Header::Hash, Header::Number> for SyncHeader<Header> {
fn id(&self) -> HeaderId<Header::Hash, Header::Number> {
relay_utils::HeaderId(*self.number(), self.hash())
}
fn parent_id(&self) -> HeaderId<Header::Hash, Header::Number> {
relay_utils::HeaderId(
self.number()
.checked_sub(&One::one())
.expect("should never be called for genesis header"),
*self.parent_hash(),
)
}
}
@@ -0,0 +1,49 @@
[package]
name = "substrate-relay"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
async-std = "1.9.0"
async-trait = "0.1.42"
codec = { package = "parity-scale-codec", version = "2.0.0" }
futures = "0.3.12"
hex = "0.4"
log = "0.4.14"
num-traits = "0.2"
paste = "1.0"
structopt = "0.3"
# Bridge dependencies
bp-header-chain = { path = "../../primitives/header-chain" }
bp-kusama = { path = "../../primitives/kusama" }
bp-message-lane = { path = "../../primitives/message-lane" }
bp-millau = { path = "../../primitives/millau" }
bp-polkadot = { path = "../../primitives/polkadot" }
bp-runtime = { path = "../../primitives/runtime" }
bp-rialto = { path = "../../primitives/rialto" }
bridge-runtime-common = { path = "../../bin/runtime-common" }
headers-relay = { path = "../headers-relay" }
messages-relay = { path = "../messages-relay" }
millau-runtime = { path = "../../bin/millau/runtime" }
pallet-bridge-call-dispatch = { path = "../../modules/call-dispatch" }
pallet-message-lane = { path = "../../modules/message-lane" }
pallet-substrate-bridge = { path = "../../modules/substrate" }
relay-kusama-client = { path = "../kusama-client" }
relay-millau-client = { path = "../millau-client" }
relay-polkadot-client = { path = "../polkadot-client" }
relay-rialto-client = { path = "../rialto-client" }
relay-substrate-client = { path = "../substrate-client" }
relay-utils = { path = "../utils" }
rialto-runtime = { path = "../../bin/rialto/runtime" }
# Substrate Dependencies
frame-support = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
sp-core = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
sp-finality-grandpa = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
sp-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
sp-trie = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
@@ -0,0 +1,360 @@
// 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 <http://www.gnu.org/licenses/>.
//! Deal with CLI args of substrate-to-substrate relay.
use bp_message_lane::LaneId;
use frame_support::weights::Weight;
use sp_core::Bytes;
use sp_finality_grandpa::SetId as GrandpaAuthoritiesSetId;
use structopt::{clap::arg_enum, StructOpt};
/// Parse relay CLI args.
pub fn parse_args() -> Command {
Command::from_args()
}
/// Substrate-to-Substrate bridge utilities.
#[derive(StructOpt)]
#[structopt(about = "Substrate-to-Substrate relay")]
pub enum Command {
/// Start headers relay between two chains.
///
/// The on-chain bridge component should have been already initialized with
/// `init-bridge` sub-command.
RelayHeaders(RelayHeaders),
/// Start messages relay between two chains.
///
/// Ties up to `MessageLane` pallets on both chains and starts relaying messages.
/// Requires the header relay to be already running.
RelayMessages(RelayMessages),
/// Initialize on-chain bridge pallet with current header data.
///
/// Sends initialization transaction to bootstrap the bridge with current finalized block data.
InitBridge(InitBridge),
/// Send custom message over the bridge.
///
/// Allows interacting with the bridge by sending messages over `MessageLane` component.
/// The message is being sent to the source chain, delivered to the target chain and dispatched
/// there.
SendMessage(SendMessage),
}
#[derive(StructOpt)]
pub enum RelayHeaders {
/// Relay Millau headers to Rialto.
MillauToRialto {
#[structopt(flatten)]
millau: MillauConnectionParams,
#[structopt(flatten)]
rialto: RialtoConnectionParams,
#[structopt(flatten)]
rialto_sign: RialtoSigningParams,
#[structopt(flatten)]
prometheus_params: PrometheusParams,
},
/// Relay Rialto headers to Millau.
RialtoToMillau {
#[structopt(flatten)]
rialto: RialtoConnectionParams,
#[structopt(flatten)]
millau: MillauConnectionParams,
#[structopt(flatten)]
millau_sign: MillauSigningParams,
#[structopt(flatten)]
prometheus_params: PrometheusParams,
},
}
#[derive(StructOpt)]
pub enum RelayMessages {
/// Serve given lane of Millau -> Rialto messages.
MillauToRialto {
#[structopt(flatten)]
millau: MillauConnectionParams,
#[structopt(flatten)]
millau_sign: MillauSigningParams,
#[structopt(flatten)]
rialto: RialtoConnectionParams,
#[structopt(flatten)]
rialto_sign: RialtoSigningParams,
#[structopt(flatten)]
prometheus_params: PrometheusParams,
/// Hex-encoded id of lane that should be served by relay.
#[structopt(long)]
lane: HexLaneId,
},
/// Serve given lane of Rialto -> Millau messages.
RialtoToMillau {
#[structopt(flatten)]
rialto: RialtoConnectionParams,
#[structopt(flatten)]
rialto_sign: RialtoSigningParams,
#[structopt(flatten)]
millau: MillauConnectionParams,
#[structopt(flatten)]
millau_sign: MillauSigningParams,
#[structopt(flatten)]
prometheus_params: PrometheusParams,
/// Hex-encoded id of lane that should be served by relay.
#[structopt(long)]
lane: HexLaneId,
},
}
#[derive(StructOpt)]
pub enum InitBridge {
/// Initialize Millau headers bridge in Rialto.
MillauToRialto {
#[structopt(flatten)]
millau: MillauConnectionParams,
#[structopt(flatten)]
rialto: RialtoConnectionParams,
#[structopt(flatten)]
rialto_sign: RialtoSigningParams,
#[structopt(flatten)]
millau_bridge_params: MillauBridgeInitializationParams,
},
/// Initialize Rialto headers bridge in Millau.
RialtoToMillau {
#[structopt(flatten)]
rialto: RialtoConnectionParams,
#[structopt(flatten)]
millau: MillauConnectionParams,
#[structopt(flatten)]
millau_sign: MillauSigningParams,
#[structopt(flatten)]
rialto_bridge_params: RialtoBridgeInitializationParams,
},
}
#[derive(StructOpt)]
pub enum SendMessage {
/// Submit message to given Millau -> Rialto lane.
MillauToRialto {
#[structopt(flatten)]
millau: MillauConnectionParams,
#[structopt(flatten)]
millau_sign: MillauSigningParams,
#[structopt(flatten)]
rialto_sign: RialtoSigningParams,
/// Hex-encoded lane id.
#[structopt(long)]
lane: HexLaneId,
/// Dispatch weight of the message. If not passed, determined automatically.
#[structopt(long)]
dispatch_weight: Option<ExplicitOrMaximal<Weight>>,
/// Delivery and dispatch fee. If not passed, determined automatically.
#[structopt(long)]
fee: Option<bp_millau::Balance>,
/// Message type.
#[structopt(subcommand)]
message: ToRialtoMessage,
/// The origin to use when dispatching the message on the target chain.
#[structopt(long, possible_values = &Origins::variants())]
origin: Origins,
},
/// Submit message to given Rialto -> Millau lane.
RialtoToMillau {
#[structopt(flatten)]
rialto: RialtoConnectionParams,
#[structopt(flatten)]
rialto_sign: RialtoSigningParams,
#[structopt(flatten)]
millau_sign: MillauSigningParams,
/// Hex-encoded lane id.
#[structopt(long)]
lane: HexLaneId,
/// Dispatch weight of the message. If not passed, determined automatically.
#[structopt(long)]
dispatch_weight: Option<ExplicitOrMaximal<Weight>>,
/// Delivery and dispatch fee. If not passed, determined automatically.
#[structopt(long)]
fee: Option<bp_rialto::Balance>,
/// Message type.
#[structopt(subcommand)]
message: ToMillauMessage,
/// The origin to use when dispatching the message on the target chain.
#[structopt(long, possible_values = &Origins::variants())]
origin: Origins,
},
}
/// All possible messages that may be delivered to the Rialto chain.
#[derive(StructOpt, Debug)]
pub enum ToRialtoMessage {
/// Make an on-chain remark (comment).
Remark {
/// Remark size. If not passed, small UTF8-encoded string is generated by relay as remark.
#[structopt(long)]
remark_size: Option<ExplicitOrMaximal<usize>>,
},
/// Transfer the specified `amount` of native tokens to a particular `recipient`.
Transfer {
#[structopt(long)]
recipient: bp_rialto::AccountId,
#[structopt(long)]
amount: bp_rialto::Balance,
},
}
/// All possible messages that may be delivered to the Millau chain.
#[derive(StructOpt, Debug)]
pub enum ToMillauMessage {
/// Make an on-chain remark (comment).
Remark {
/// Size of the remark. If not passed, small UTF8-encoded string is generated by relay as remark.
#[structopt(long)]
remark_size: Option<ExplicitOrMaximal<usize>>,
},
/// Transfer the specified `amount` of native tokens to a particular `recipient`.
Transfer {
#[structopt(long)]
recipient: bp_millau::AccountId,
#[structopt(long)]
amount: bp_millau::Balance,
},
}
arg_enum! {
#[derive(Debug)]
/// The origin to use when dispatching the message on the target chain.
///
/// - `Target` uses account existing on the target chain (requires target private key).
/// - `Origin` uses account derived from the source-chain account.
pub enum Origins {
Target,
Source,
}
}
/// Lane id.
#[derive(Debug)]
pub struct HexLaneId(LaneId);
impl From<HexLaneId> for LaneId {
fn from(lane_id: HexLaneId) -> LaneId {
lane_id.0
}
}
impl std::str::FromStr for HexLaneId {
type Err = hex::FromHexError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut lane_id = LaneId::default();
hex::decode_to_slice(s, &mut lane_id)?;
Ok(HexLaneId(lane_id))
}
}
/// Prometheus metrics params.
#[derive(StructOpt)]
pub struct PrometheusParams {
/// Do not expose a Prometheus metric endpoint.
#[structopt(long)]
pub no_prometheus: bool,
/// Expose Prometheus endpoint at given interface.
#[structopt(long, default_value = "127.0.0.1")]
pub prometheus_host: String,
/// Expose Prometheus endpoint at given port.
#[structopt(long, default_value = "9616")]
pub prometheus_port: u16,
}
impl From<PrometheusParams> for Option<relay_utils::metrics::MetricsParams> {
fn from(cli_params: PrometheusParams) -> Option<relay_utils::metrics::MetricsParams> {
if !cli_params.no_prometheus {
Some(relay_utils::metrics::MetricsParams {
host: cli_params.prometheus_host,
port: cli_params.prometheus_port,
})
} else {
None
}
}
}
/// Either explicit or maximal allowed value.
#[derive(Debug)]
pub enum ExplicitOrMaximal<V> {
/// User has explicitly specified argument value.
Explicit(V),
/// Maximal allowed value for this argument.
Maximal,
}
impl<V: std::str::FromStr> std::str::FromStr for ExplicitOrMaximal<V>
where
V::Err: std::fmt::Debug,
{
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.to_lowercase() == "max" {
return Ok(ExplicitOrMaximal::Maximal);
}
V::from_str(s)
.map(ExplicitOrMaximal::Explicit)
.map_err(|e| format!("Failed to parse '{:?}'. Expected 'max' or explicit value", e))
}
}
macro_rules! declare_chain_options {
($chain:ident, $chain_prefix:ident) => {
paste::item! {
#[doc = $chain " connection params."]
#[derive(StructOpt)]
pub struct [<$chain ConnectionParams>] {
#[doc = "Connect to " $chain " node at given host."]
#[structopt(long)]
pub [<$chain_prefix _host>]: String,
#[doc = "Connect to " $chain " node websocket server at given port."]
#[structopt(long)]
pub [<$chain_prefix _port>]: u16,
}
#[doc = $chain " signing params."]
#[derive(StructOpt)]
pub struct [<$chain SigningParams>] {
#[doc = "The SURI of secret key to use when transactions are submitted to the " $chain " node."]
#[structopt(long)]
pub [<$chain_prefix _signer>]: String,
#[doc = "The password for the SURI of secret key to use when transactions are submitted to the " $chain " node."]
#[structopt(long)]
pub [<$chain_prefix _signer_password>]: Option<String>,
}
#[doc = $chain " headers bridge initialization params."]
#[derive(StructOpt)]
pub struct [<$chain BridgeInitializationParams>] {
#[doc = "Hex-encoded " $chain " header to initialize bridge with. If not specified, genesis header is used."]
#[structopt(long)]
pub [<$chain_prefix _initial_header>]: Option<Bytes>,
#[doc = "Hex-encoded " $chain " GRANDPA authorities set to initialize bridge with. If not specified, set from genesis block is used."]
#[structopt(long)]
pub [<$chain_prefix _initial_authorities>]: Option<Bytes>,
#[doc = "Id of the " $chain " GRANDPA authorities set to initialize bridge with. If not specified, zero is used."]
#[structopt(long)]
pub [<$chain_prefix _initial_authorities_set_id>]: Option<GrandpaAuthoritiesSetId>,
}
}
};
}
declare_chain_options!(Rialto, rialto);
declare_chain_options!(Millau, millau);
@@ -0,0 +1,141 @@
// 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 <http://www.gnu.org/licenses/>.
//! Initialize Substrate -> Substrate headers bridge.
//!
//! Initialization is a transaction that calls `initialize()` function of the
//! `pallet-substrate-bridge` pallet. This transaction brings initial header
//! and authorities set from source to target chain. The headers sync starts
//! with this header.
use codec::Decode;
use pallet_substrate_bridge::InitializationData;
use relay_substrate_client::{Chain, Client};
use sp_core::Bytes;
use sp_finality_grandpa::{AuthorityList as GrandpaAuthoritiesSet, SetId as GrandpaAuthoritiesSetId};
/// Submit headers-bridge initialization transaction.
pub async fn initialize<SourceChain: Chain, TargetChain: Chain>(
source_client: Client<SourceChain>,
target_client: Client<TargetChain>,
raw_initial_header: Option<Bytes>,
raw_initial_authorities_set: Option<Bytes>,
initial_authorities_set_id: Option<GrandpaAuthoritiesSetId>,
prepare_initialize_transaction: impl FnOnce(InitializationData<SourceChain::Header>) -> Result<Bytes, String>,
) {
let result = do_initialize(
source_client,
target_client,
raw_initial_header,
raw_initial_authorities_set,
initial_authorities_set_id,
prepare_initialize_transaction,
)
.await;
match result {
Ok(tx_hash) => log::info!(
target: "bridge",
"Successfully submitted {}-headers bridge initialization transaction to {}: {:?}",
SourceChain::NAME,
TargetChain::NAME,
tx_hash,
),
Err(err) => log::error!(
target: "bridge",
"Failed to submit {}-headers bridge initialization transaction to {}: {:?}",
SourceChain::NAME,
TargetChain::NAME,
err,
),
}
}
/// Craft and submit initialization transaction, returning any error that may occur.
async fn do_initialize<SourceChain: Chain, TargetChain: Chain>(
source_client: Client<SourceChain>,
target_client: Client<TargetChain>,
raw_initial_header: Option<Bytes>,
raw_initial_authorities_set: Option<Bytes>,
initial_authorities_set_id: Option<GrandpaAuthoritiesSetId>,
prepare_initialize_transaction: impl FnOnce(InitializationData<SourceChain::Header>) -> Result<Bytes, String>,
) -> Result<TargetChain::Hash, String> {
let initialization_data = prepare_initialization_data(
source_client,
raw_initial_header,
raw_initial_authorities_set,
initial_authorities_set_id,
)
.await?;
let initialization_tx = prepare_initialize_transaction(initialization_data)?;
let initialization_tx_hash = target_client
.submit_extrinsic(initialization_tx)
.await
.map_err(|err| format!("Failed to submit {} transaction: {:?}", TargetChain::NAME, err))?;
Ok(initialization_tx_hash)
}
/// Prepare initialization data for the headers-bridge pallet.
async fn prepare_initialization_data<SourceChain: Chain>(
source_client: Client<SourceChain>,
raw_initial_header: Option<Bytes>,
raw_initial_authorities_set: Option<Bytes>,
initial_authorities_set_id: Option<GrandpaAuthoritiesSetId>,
) -> Result<InitializationData<SourceChain::Header>, String> {
let source_genesis_hash = *source_client.genesis_hash();
let initial_header = match raw_initial_header {
Some(raw_initial_header) => SourceChain::Header::decode(&mut &raw_initial_header.0[..])
.map_err(|err| format!("Failed to decode {} initial header: {:?}", SourceChain::NAME, err))?,
None => source_client
.header_by_hash(source_genesis_hash)
.await
.map_err(|err| format!("Failed to retrive {} genesis header: {:?}", SourceChain::NAME, err))?,
};
let raw_initial_authorities_set = match raw_initial_authorities_set {
Some(raw_initial_authorities_set) => raw_initial_authorities_set.0,
None => source_client
.grandpa_authorities_set(source_genesis_hash)
.await
.map_err(|err| {
format!(
"Failed to retrive {} authorities set at genesis header: {:?}",
SourceChain::NAME,
err
)
})?,
};
let initial_authorities_set =
GrandpaAuthoritiesSet::decode(&mut &raw_initial_authorities_set[..]).map_err(|err| {
format!(
"Failed to decode {} initial authorities set: {:?}",
SourceChain::NAME,
err
)
})?;
Ok(InitializationData {
header: initial_header,
authority_list: initial_authorities_set,
set_id: initial_authorities_set_id.unwrap_or(0),
// There may be multiple scheduled changes, so on real chains we should select proper
// moment, when there's nothing scheduled. On ephemeral (temporary) chains, it is ok to
// start with genesis.
scheduled_change: None,
is_halted: false,
})
}
@@ -0,0 +1,400 @@
// 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 <http://www.gnu.org/licenses/>.
//! Substrate-to-Substrate headers synchronization maintain procedure.
//!
//! Regular headers synchronization only depends on persistent justifications
//! that are generated when authorities set changes. This happens rarely on
//! real-word chains. So some other way to finalize headers is required.
//!
//! Full nodes are listening to GRANDPA messages, so they may have track authorities
//! votes on their own. They're returning both persistent and ephemeral justifications
//! (justifications that are not stored in the database and not broadcasted over network)
//! throught `grandpa_subscribeJustifications` RPC subscription.
//!
//! The idea of this maintain procedure is that when we see justification that 'improves'
//! best finalized header on the target chain, we submit this justification to the target
//! node.
use crate::headers_pipeline::SubstrateHeadersSyncPipeline;
use async_std::sync::{Arc, Mutex};
use async_trait::async_trait;
use codec::{Decode, Encode};
use futures::future::{poll_fn, FutureExt, TryFutureExt};
use headers_relay::{
sync::HeadersSync,
sync_loop::SyncMaintain,
sync_types::{HeaderIdOf, HeaderStatus},
};
use relay_substrate_client::{Chain, Client, Error as SubstrateError, JustificationsSubscription};
use relay_utils::HeaderId;
use sp_core::Bytes;
use sp_runtime::{traits::Header as HeaderT, Justification};
use std::{collections::VecDeque, marker::PhantomData, task::Poll};
/// Substrate-to-Substrate headers synchronization maintain procedure.
pub struct SubstrateHeadersToSubstrateMaintain<P: SubstrateHeadersSyncPipeline, SourceChain, TargetChain: Chain> {
pipeline: P,
target_client: Client<TargetChain>,
justifications: Arc<Mutex<Justifications<P>>>,
_marker: PhantomData<SourceChain>,
}
/// Future and already received justifications from the source chain.
struct Justifications<P: SubstrateHeadersSyncPipeline> {
/// Justifications stream.
stream: JustificationsSubscription,
/// Justifications that we have read from the stream but have not sent to the
/// target node, because their targets were still not synced.
queue: VecDeque<(HeaderIdOf<P>, Justification)>,
}
impl<P: SubstrateHeadersSyncPipeline, SourceChain, TargetChain: Chain>
SubstrateHeadersToSubstrateMaintain<P, SourceChain, TargetChain>
{
/// Create new maintain procedure.
pub fn new(pipeline: P, target_client: Client<TargetChain>, justifications: JustificationsSubscription) -> Self {
SubstrateHeadersToSubstrateMaintain {
pipeline,
target_client,
justifications: Arc::new(Mutex::new(Justifications {
stream: justifications,
queue: VecDeque::new(),
})),
_marker: Default::default(),
}
}
}
#[async_trait]
impl<P: SubstrateHeadersSyncPipeline, SourceChain, TargetChain: Chain> Clone
for SubstrateHeadersToSubstrateMaintain<P, SourceChain, TargetChain>
{
fn clone(&self) -> Self {
SubstrateHeadersToSubstrateMaintain {
pipeline: self.pipeline.clone(),
target_client: self.target_client.clone(),
justifications: self.justifications.clone(),
_marker: Default::default(),
}
}
}
#[async_trait]
impl<P, SourceChain, TargetChain> SyncMaintain<P> for SubstrateHeadersToSubstrateMaintain<P, SourceChain, TargetChain>
where
SourceChain: Chain,
<SourceChain::Header as HeaderT>::Number: Into<P::Number>,
<SourceChain::Header as HeaderT>::Hash: Into<P::Hash>,
TargetChain: Chain,
P::Number: Decode,
P::Hash: Decode,
P: SubstrateHeadersSyncPipeline<Completion = Justification, Extra = ()>,
{
async fn maintain(&self, sync: &mut HeadersSync<P>) {
// lock justifications before doing anything else
let mut justifications = match self.justifications.try_lock() {
Some(justifications) => justifications,
None => {
// this should never happen, as we use single-thread executor
log::warn!(target: "bridge", "Failed to acquire {} justifications lock", P::SOURCE_NAME);
return;
}
};
// we need to read best finalized header from the target node to be able to
// choose justification to submit
let best_finalized = match best_finalized_header_id::<P, _>(&self.target_client).await {
Ok(best_finalized) => best_finalized,
Err(error) => {
log::warn!(
target: "bridge",
"Failed to read best finalized {} block from maintain: {:?}",
P::SOURCE_NAME,
error,
);
return;
}
};
log::debug!(
target: "bridge",
"Read best finalized {} block from {}: {:?}",
P::SOURCE_NAME,
P::TARGET_NAME,
best_finalized,
);
// Select justification to submit to the target node. We're submitting at most one justification
// on every maintain call. So maintain rate directly affects finalization rate.
let justification_to_submit = poll_fn(|context| {
// read justifications from the stream and push to the queue
justifications.read_from_stream::<SourceChain::Header>(context);
// remove all obsolete justifications from the queue
remove_obsolete::<P>(&mut justifications.queue, best_finalized);
// select justification to submit
Poll::Ready(select_justification(&mut justifications.queue, sync))
})
.await;
// finally - submit selected justification
if let Some((target, justification)) = justification_to_submit {
let submit_result = self
.pipeline
.make_complete_header_transaction(target, justification)
.and_then(|tx| self.target_client.submit_extrinsic(Bytes(tx.encode())))
.await;
match submit_result {
Ok(_) => log::debug!(
target: "bridge",
"Submitted justification received over {} subscription. Target: {:?}",
P::SOURCE_NAME,
target,
),
Err(error) => log::warn!(
target: "bridge",
"Failed to submit justification received over {} subscription for {:?}: {:?}",
P::SOURCE_NAME,
target,
error,
),
}
}
}
}
impl<P> Justifications<P>
where
P::Number: Decode,
P::Hash: Decode,
P: SubstrateHeadersSyncPipeline<Completion = Justification, Extra = ()>,
{
/// Read justifications from the subscription stream without blocking.
fn read_from_stream<'a, SourceHeader>(&mut self, context: &mut std::task::Context<'a>)
where
SourceHeader: HeaderT,
SourceHeader::Number: Into<P::Number>,
SourceHeader::Hash: Into<P::Hash>,
{
loop {
let maybe_next_justification = self.stream.next();
futures::pin_mut!(maybe_next_justification);
let maybe_next_justification = maybe_next_justification.poll_unpin(context);
let justification = match maybe_next_justification {
Poll::Ready(justification) => justification,
Poll::Pending => return,
};
// decode justification target
let target = bp_header_chain::justification::decode_justification_target::<SourceHeader>(&justification);
let target = match target {
Ok((target_hash, target_number)) => HeaderId(target_number.into(), target_hash.into()),
Err(error) => {
log::warn!(
target: "bridge",
"Failed to decode justification from {} subscription: {:?}",
P::SOURCE_NAME,
error,
);
continue;
}
};
log::debug!(
target: "bridge",
"Received {} justification over subscription. Target: {:?}",
P::SOURCE_NAME,
target,
);
self.queue.push_back((target, justification.0));
}
}
}
/// Clean queue of all justifications that are justifying already finalized blocks.
fn remove_obsolete<P: SubstrateHeadersSyncPipeline>(
queue: &mut VecDeque<(HeaderIdOf<P>, Justification)>,
best_finalized: HeaderIdOf<P>,
) {
while queue
.front()
.map(|(target, _)| target.0 <= best_finalized.0)
.unwrap_or(false)
{
queue.pop_front();
}
}
/// Select appropriate justification that would improve best finalized block on target node.
///
/// It is assumed that the selected justification will be submitted to the target node. The
/// justification itself and all preceeding justifications are removed from the queue.
fn select_justification<P>(
queue: &mut VecDeque<(HeaderIdOf<P>, Justification)>,
sync: &mut HeadersSync<P>,
) -> Option<(HeaderIdOf<P>, Justification)>
where
P: SubstrateHeadersSyncPipeline<Completion = Justification>,
{
let mut selected_justification = None;
while let Some((target, justification)) = queue.pop_front() {
// if we're waiting for this justification, report it
if sync.headers().requires_completion_data(&target) {
sync.headers_mut().completion_response(&target, Some(justification));
// we won't submit previous justifications as we going to submit justification for
// next header
selected_justification = None;
// we won't submit next justifications as we need to submit previous justifications
// first
break;
}
// if we know that the header is already synced (it is known to the target node), let's
// select it for submission. We still may select better justification on the next iteration.
if sync.headers().status(&target) == HeaderStatus::Synced {
selected_justification = Some((target, justification));
continue;
}
// finally - return justification back to the queue
queue.push_back((target, justification));
break;
}
selected_justification
}
/// Returns best finalized source header on the target chain.
async fn best_finalized_header_id<P, C>(client: &Client<C>) -> Result<HeaderIdOf<P>, SubstrateError>
where
P: SubstrateHeadersSyncPipeline,
P::Number: Decode,
P::Hash: Decode,
C: Chain,
{
let call = P::FINALIZED_BLOCK_METHOD.into();
let data = Bytes(Vec::new());
let encoded_response = client.state_call(call, data, None).await?;
let decoded_response: (P::Number, P::Hash) =
Decode::decode(&mut &encoded_response.0[..]).map_err(SubstrateError::ResponseParseFailed)?;
let best_header_id = HeaderId(decoded_response.0, decoded_response.1);
Ok(best_header_id)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::headers_pipeline::sync_params;
use crate::millau_headers_to_rialto::MillauHeadersToRialto;
fn parent_hash(index: u8) -> bp_millau::Hash {
if index == 1 {
Default::default()
} else {
header(index - 1).hash()
}
}
fn header_hash(index: u8) -> bp_millau::Hash {
header(index).hash()
}
fn header(index: u8) -> bp_millau::Header {
bp_millau::Header::new(
index as _,
Default::default(),
Default::default(),
parent_hash(index),
Default::default(),
)
}
#[test]
fn obsolete_justifications_are_removed() {
let mut queue = vec![
(HeaderId(1, header_hash(1)), vec![1]),
(HeaderId(2, header_hash(2)), vec![2]),
(HeaderId(3, header_hash(3)), vec![3]),
]
.into_iter()
.collect();
remove_obsolete::<MillauHeadersToRialto>(&mut queue, HeaderId(2, header_hash(2)));
assert_eq!(
queue,
vec![(HeaderId(3, header_hash(3)), vec![3])]
.into_iter()
.collect::<VecDeque<_>>(),
);
}
#[test]
fn latest_justification_is_selected() {
let mut queue = vec![
(HeaderId(1, header_hash(1)), vec![1]),
(HeaderId(2, header_hash(2)), vec![2]),
(HeaderId(3, header_hash(3)), vec![3]),
]
.into_iter()
.collect();
let mut sync = HeadersSync::<MillauHeadersToRialto>::new(sync_params());
sync.headers_mut().header_response(header(1).into());
sync.headers_mut().header_response(header(2).into());
sync.headers_mut().header_response(header(3).into());
sync.target_best_header_response(HeaderId(2, header_hash(2)));
assert_eq!(
select_justification(&mut queue, &mut sync),
Some((HeaderId(2, header_hash(2)), vec![2])),
);
}
#[test]
fn required_justification_is_reported() {
let mut queue = vec![
(HeaderId(1, header_hash(1)), vec![1]),
(HeaderId(2, header_hash(2)), vec![2]),
(HeaderId(3, header_hash(3)), vec![3]),
]
.into_iter()
.collect();
let mut sync = HeadersSync::<MillauHeadersToRialto>::new(sync_params());
sync.headers_mut().header_response(header(1).into());
sync.headers_mut().header_response(header(2).into());
sync.headers_mut().header_response(header(3).into());
sync.headers_mut()
.incomplete_headers_response(vec![HeaderId(2, header_hash(2))].into_iter().collect());
sync.target_best_header_response(HeaderId(2, header_hash(2)));
assert_eq!(sync.headers_mut().header_to_complete(), None,);
assert_eq!(select_justification(&mut queue, &mut sync), None,);
assert_eq!(
sync.headers_mut().header_to_complete(),
Some((HeaderId(2, header_hash(2)), &vec![2])),
);
}
}
@@ -0,0 +1,179 @@
// 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 <http://www.gnu.org/licenses/>.
//! Substrate-to-Substrate headers sync entrypoint.
use crate::{headers_maintain::SubstrateHeadersToSubstrateMaintain, headers_target::SubstrateHeadersTarget};
use async_trait::async_trait;
use codec::Encode;
use headers_relay::{
sync::{HeadersSyncParams, TargetTransactionMode},
sync_types::{HeaderIdOf, HeadersSyncPipeline, QueuedHeader, SourceHeader},
};
use relay_substrate_client::{
headers_source::HeadersSource, BlockNumberOf, Chain, Client, Error as SubstrateError, HashOf,
};
use relay_utils::BlockNumberBase;
use sp_runtime::Justification;
use std::marker::PhantomData;
/// Headers sync pipeline for Substrate <-> Substrate relays.
#[async_trait]
pub trait SubstrateHeadersSyncPipeline: HeadersSyncPipeline {
/// Name of the `best_block` runtime method.
const BEST_BLOCK_METHOD: &'static str;
/// Name of the `finalized_block` runtime method.
const FINALIZED_BLOCK_METHOD: &'static str;
/// Name of the `is_known_block` runtime method.
const IS_KNOWN_BLOCK_METHOD: &'static str;
/// Name of the `incomplete_headers` runtime method.
const INCOMPLETE_HEADERS_METHOD: &'static str;
/// Signed transaction type.
type SignedTransaction: Send + Sync + Encode;
/// Make submit header transaction.
async fn make_submit_header_transaction(
&self,
header: QueuedHeader<Self>,
) -> Result<Self::SignedTransaction, SubstrateError>;
/// Make completion transaction for the header.
async fn make_complete_header_transaction(
&self,
id: HeaderIdOf<Self>,
completion: Justification,
) -> Result<Self::SignedTransaction, SubstrateError>;
}
/// Substrate-to-Substrate headers pipeline.
#[derive(Debug, Clone)]
pub struct SubstrateHeadersToSubstrate<SourceChain, SourceSyncHeader, TargetChain: Chain, TargetSign> {
/// Client for the target chain.
pub(crate) target_client: Client<TargetChain>,
/// Data required to sign target chain transactions.
pub(crate) target_sign: TargetSign,
/// Unused generic arguments dump.
_marker: PhantomData<(SourceChain, SourceSyncHeader)>,
}
impl<SourceChain, SourceSyncHeader, TargetChain: Chain, TargetSign>
SubstrateHeadersToSubstrate<SourceChain, SourceSyncHeader, TargetChain, TargetSign>
{
/// Create new Substrate-to-Substrate headers pipeline.
pub fn new(target_client: Client<TargetChain>, target_sign: TargetSign) -> Self {
SubstrateHeadersToSubstrate {
target_client,
target_sign,
_marker: Default::default(),
}
}
}
impl<SourceChain, SourceSyncHeader, TargetChain, TargetSign> HeadersSyncPipeline
for SubstrateHeadersToSubstrate<SourceChain, SourceSyncHeader, TargetChain, TargetSign>
where
SourceChain: Clone + Chain,
BlockNumberOf<SourceChain>: BlockNumberBase,
SourceSyncHeader:
SourceHeader<HashOf<SourceChain>, BlockNumberOf<SourceChain>> + std::ops::Deref<Target = SourceChain::Header>,
TargetChain: Clone + Chain,
TargetSign: Clone + Send + Sync,
{
const SOURCE_NAME: &'static str = SourceChain::NAME;
const TARGET_NAME: &'static str = TargetChain::NAME;
type Hash = HashOf<SourceChain>;
type Number = BlockNumberOf<SourceChain>;
type Header = SourceSyncHeader;
type Extra = ();
type Completion = Justification;
fn estimate_size(source: &QueuedHeader<Self>) -> usize {
source.header().encode().len()
}
}
/// Return sync parameters for Substrate-to-Substrate headers sync.
pub fn sync_params() -> HeadersSyncParams {
HeadersSyncParams {
max_future_headers_to_download: 32,
max_headers_in_submitted_status: 8,
max_headers_in_single_submit: 1,
max_headers_size_in_single_submit: 1024 * 1024,
prune_depth: 256,
target_tx_mode: TargetTransactionMode::Signed,
}
}
/// Run Substrate-to-Substrate headers sync.
pub async fn run<SourceChain, TargetChain, P>(
pipeline: P,
source_client: Client<SourceChain>,
target_client: Client<TargetChain>,
metrics_params: Option<relay_utils::metrics::MetricsParams>,
) where
P: SubstrateHeadersSyncPipeline<
Hash = HashOf<SourceChain>,
Number = BlockNumberOf<SourceChain>,
Completion = Justification,
Extra = (),
>,
P::Header: SourceHeader<HashOf<SourceChain>, BlockNumberOf<SourceChain>>,
SourceChain: Clone + Chain,
SourceChain::Header: Into<P::Header>,
BlockNumberOf<SourceChain>: BlockNumberBase,
TargetChain: Clone + Chain,
{
let source_justifications = match source_client.clone().subscribe_justifications().await {
Ok(source_justifications) => source_justifications,
Err(error) => {
log::warn!(
target: "bridge",
"Failed to subscribe to {} justifications: {:?}",
SourceChain::NAME,
error,
);
return;
}
};
let sync_maintain = SubstrateHeadersToSubstrateMaintain::<_, SourceChain, _>::new(
pipeline.clone(),
target_client.clone(),
source_justifications,
);
log::info!(
target: "bridge",
"Starting {} -> {} headers relay",
SourceChain::NAME,
TargetChain::NAME,
);
headers_relay::sync_loop::run(
HeadersSource::new(source_client),
SourceChain::AVERAGE_BLOCK_INTERVAL,
SubstrateHeadersTarget::new(target_client, pipeline),
TargetChain::AVERAGE_BLOCK_INTERVAL,
sync_maintain,
sync_params(),
metrics_params,
futures::future::pending(),
);
}
@@ -0,0 +1,168 @@
// 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 <http://www.gnu.org/licenses/>.
//! Substrate client as Substrate headers target. The chain we connect to should have
//! runtime that implements `<BridgedChainName>HeaderApi` to allow bridging with
//! <BridgedName> chain.
use crate::headers_pipeline::SubstrateHeadersSyncPipeline;
use async_trait::async_trait;
use codec::{Decode, Encode};
use futures::TryFutureExt;
use headers_relay::{
sync_loop::TargetClient,
sync_types::{HeaderIdOf, QueuedHeader, SubmittedHeaders},
};
use relay_substrate_client::{Chain, Client, Error as SubstrateError};
use relay_utils::{relay_loop::Client as RelayClient, HeaderId};
use sp_core::Bytes;
use sp_runtime::Justification;
use std::collections::HashSet;
/// Substrate client as Substrate headers target.
pub struct SubstrateHeadersTarget<C: Chain, P> {
client: Client<C>,
pipeline: P,
}
impl<C: Chain, P> SubstrateHeadersTarget<C, P> {
/// Create new Substrate headers target.
pub fn new(client: Client<C>, pipeline: P) -> Self {
SubstrateHeadersTarget { client, pipeline }
}
}
impl<C: Chain, P: SubstrateHeadersSyncPipeline> Clone for SubstrateHeadersTarget<C, P> {
fn clone(&self) -> Self {
SubstrateHeadersTarget {
client: self.client.clone(),
pipeline: self.pipeline.clone(),
}
}
}
#[async_trait]
impl<C: Chain, P: SubstrateHeadersSyncPipeline> RelayClient for SubstrateHeadersTarget<C, P> {
type Error = SubstrateError;
async fn reconnect(&mut self) -> Result<(), SubstrateError> {
self.client.reconnect().await
}
}
#[async_trait]
impl<C, P> TargetClient<P> for SubstrateHeadersTarget<C, P>
where
C: Chain,
P::Number: Decode,
P::Hash: Decode + Encode,
P: SubstrateHeadersSyncPipeline<Completion = Justification, Extra = ()>,
{
async fn best_header_id(&self) -> Result<HeaderIdOf<P>, SubstrateError> {
// we can't continue to relay headers if target node is out of sync, because
// it may have already received (some of) headers that we're going to relay
self.client.ensure_synced().await?;
let call = P::BEST_BLOCK_METHOD.into();
let data = Bytes(Vec::new());
let encoded_response = self.client.state_call(call, data, None).await?;
let decoded_response: Vec<(P::Number, P::Hash)> =
Decode::decode(&mut &encoded_response.0[..]).map_err(SubstrateError::ResponseParseFailed)?;
// If we parse an empty list of headers it means that bridge pallet has not been initalized
// yet. Otherwise we expect to always have at least one header.
decoded_response
.last()
.ok_or(SubstrateError::UninitializedBridgePallet)
.map(|(num, hash)| HeaderId(*num, *hash))
}
async fn is_known_header(&self, id: HeaderIdOf<P>) -> Result<(HeaderIdOf<P>, bool), SubstrateError> {
let call = P::IS_KNOWN_BLOCK_METHOD.into();
let data = Bytes(id.1.encode());
let encoded_response = self.client.state_call(call, data, None).await?;
let is_known_block: bool =
Decode::decode(&mut &encoded_response.0[..]).map_err(SubstrateError::ResponseParseFailed)?;
Ok((id, is_known_block))
}
async fn submit_headers(
&self,
mut headers: Vec<QueuedHeader<P>>,
) -> SubmittedHeaders<HeaderIdOf<P>, SubstrateError> {
debug_assert_eq!(
headers.len(),
1,
"Substrate pallet only supports single header / transaction"
);
let header = headers.remove(0);
let id = header.id();
let submit_transaction_result = self
.pipeline
.make_submit_header_transaction(header)
.and_then(|tx| self.client.submit_extrinsic(Bytes(tx.encode())))
.await;
match submit_transaction_result {
Ok(_) => SubmittedHeaders {
submitted: vec![id],
incomplete: Vec::new(),
rejected: Vec::new(),
fatal_error: None,
},
Err(error) => SubmittedHeaders {
submitted: Vec::new(),
incomplete: Vec::new(),
rejected: vec![id],
fatal_error: Some(error),
},
}
}
async fn incomplete_headers_ids(&self) -> Result<HashSet<HeaderIdOf<P>>, SubstrateError> {
let call = P::INCOMPLETE_HEADERS_METHOD.into();
let data = Bytes(Vec::new());
let encoded_response = self.client.state_call(call, data, None).await?;
let decoded_response: Vec<(P::Number, P::Hash)> =
Decode::decode(&mut &encoded_response.0[..]).map_err(SubstrateError::ResponseParseFailed)?;
let incomplete_headers = decoded_response
.into_iter()
.map(|(number, hash)| HeaderId(number, hash))
.collect();
Ok(incomplete_headers)
}
async fn complete_header(
&self,
id: HeaderIdOf<P>,
completion: Justification,
) -> Result<HeaderIdOf<P>, SubstrateError> {
let tx = self.pipeline.make_complete_header_transaction(id, completion).await?;
self.client.submit_extrinsic(Bytes(tx.encode())).await?;
Ok(id)
}
async fn requires_extra(&self, header: QueuedHeader<P>) -> Result<(HeaderIdOf<P>, bool), SubstrateError> {
Ok((header.id(), false))
}
}
@@ -0,0 +1,709 @@
// 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 <http://www.gnu.org/licenses/>.
//! Substrate-to-substrate relay entrypoint.
#![warn(missing_docs)]
use codec::{Decode, Encode};
use frame_support::weights::{GetDispatchInfo, Weight};
use pallet_bridge_call_dispatch::{CallOrigin, MessagePayload};
use relay_kusama_client::Kusama;
use relay_millau_client::{Millau, SigningParams as MillauSigningParams};
use relay_rialto_client::{Rialto, SigningParams as RialtoSigningParams};
use relay_substrate_client::{Chain, ConnectionParams, TransactionSignScheme};
use relay_utils::initialize::initialize_relay;
use sp_core::{Bytes, Pair};
use sp_runtime::traits::IdentifyAccount;
/// Kusama node client.
pub type KusamaClient = relay_substrate_client::Client<Kusama>;
/// Millau node client.
pub type MillauClient = relay_substrate_client::Client<Millau>;
/// Rialto node client.
pub type RialtoClient = relay_substrate_client::Client<Rialto>;
mod cli;
mod headers_initialize;
mod headers_maintain;
mod headers_pipeline;
mod headers_target;
mod messages_lane;
mod messages_source;
mod messages_target;
mod millau_headers_to_rialto;
mod millau_messages_to_rialto;
mod rialto_headers_to_millau;
mod rialto_messages_to_millau;
fn main() {
initialize_relay();
let result = async_std::task::block_on(run_command(cli::parse_args()));
if let Err(error) = result {
log::error!(target: "bridge", "Failed to start relay: {}", error);
}
}
async fn run_command(command: cli::Command) -> Result<(), String> {
match command {
cli::Command::InitBridge(arg) => run_init_bridge(arg).await,
cli::Command::RelayHeaders(arg) => run_relay_headers(arg).await,
cli::Command::RelayMessages(arg) => run_relay_messages(arg).await,
cli::Command::SendMessage(arg) => run_send_message(arg).await,
}
}
async fn run_init_bridge(command: cli::InitBridge) -> Result<(), String> {
match command {
cli::InitBridge::MillauToRialto {
millau,
rialto,
rialto_sign,
millau_bridge_params,
} => {
let millau_client = millau.into_client().await?;
let rialto_client = rialto.into_client().await?;
let rialto_sign = rialto_sign.parse()?;
let rialto_signer_next_index = rialto_client
.next_account_index(rialto_sign.signer.public().into())
.await?;
headers_initialize::initialize(
millau_client,
rialto_client.clone(),
millau_bridge_params.millau_initial_header,
millau_bridge_params.millau_initial_authorities,
millau_bridge_params.millau_initial_authorities_set_id,
move |initialization_data| {
Ok(Bytes(
Rialto::sign_transaction(
&rialto_client,
&rialto_sign.signer,
rialto_signer_next_index,
rialto_runtime::SudoCall::sudo(Box::new(
rialto_runtime::BridgeMillauCall::initialize(initialization_data).into(),
))
.into(),
)
.encode(),
))
},
)
.await;
}
cli::InitBridge::RialtoToMillau {
rialto,
millau,
millau_sign,
rialto_bridge_params,
} => {
let rialto_client = rialto.into_client().await?;
let millau_client = millau.into_client().await?;
let millau_sign = millau_sign.parse()?;
let millau_signer_next_index = millau_client
.next_account_index(millau_sign.signer.public().into())
.await?;
headers_initialize::initialize(
rialto_client,
millau_client.clone(),
rialto_bridge_params.rialto_initial_header,
rialto_bridge_params.rialto_initial_authorities,
rialto_bridge_params.rialto_initial_authorities_set_id,
move |initialization_data| {
Ok(Bytes(
Millau::sign_transaction(
&millau_client,
&millau_sign.signer,
millau_signer_next_index,
millau_runtime::SudoCall::sudo(Box::new(
millau_runtime::BridgeRialtoCall::initialize(initialization_data).into(),
))
.into(),
)
.encode(),
))
},
)
.await;
}
}
Ok(())
}
async fn run_relay_headers(command: cli::RelayHeaders) -> Result<(), String> {
match command {
cli::RelayHeaders::MillauToRialto {
millau,
rialto,
rialto_sign,
prometheus_params,
} => {
let millau_client = millau.into_client().await?;
let rialto_client = rialto.into_client().await?;
let rialto_sign = rialto_sign.parse()?;
millau_headers_to_rialto::run(millau_client, rialto_client, rialto_sign, prometheus_params.into()).await;
}
cli::RelayHeaders::RialtoToMillau {
rialto,
millau,
millau_sign,
prometheus_params,
} => {
let rialto_client = rialto.into_client().await?;
let millau_client = millau.into_client().await?;
let millau_sign = millau_sign.parse()?;
rialto_headers_to_millau::run(rialto_client, millau_client, millau_sign, prometheus_params.into()).await;
}
}
Ok(())
}
async fn run_relay_messages(command: cli::RelayMessages) -> Result<(), String> {
match command {
cli::RelayMessages::MillauToRialto {
millau,
millau_sign,
rialto,
rialto_sign,
prometheus_params,
lane,
} => {
let millau_client = millau.into_client().await?;
let millau_sign = millau_sign.parse()?;
let rialto_client = rialto.into_client().await?;
let rialto_sign = rialto_sign.parse()?;
millau_messages_to_rialto::run(
millau_client,
millau_sign,
rialto_client,
rialto_sign,
lane.into(),
prometheus_params.into(),
);
}
cli::RelayMessages::RialtoToMillau {
rialto,
rialto_sign,
millau,
millau_sign,
prometheus_params,
lane,
} => {
let rialto_client = rialto.into_client().await?;
let rialto_sign = rialto_sign.parse()?;
let millau_client = millau.into_client().await?;
let millau_sign = millau_sign.parse()?;
rialto_messages_to_millau::run(
rialto_client,
rialto_sign,
millau_client,
millau_sign,
lane.into(),
prometheus_params.into(),
);
}
}
Ok(())
}
async fn run_send_message(command: cli::SendMessage) -> Result<(), String> {
match command {
cli::SendMessage::MillauToRialto {
millau,
millau_sign,
rialto_sign,
lane,
message,
dispatch_weight,
fee,
origin,
..
} => {
let millau_client = millau.into_client().await?;
let millau_sign = millau_sign.parse()?;
let rialto_sign = rialto_sign.parse()?;
let rialto_call = message.into_call();
let payload =
millau_to_rialto_message_payload(&millau_sign, &rialto_sign, &rialto_call, origin, dispatch_weight);
let dispatch_weight = payload.weight;
let lane = lane.into();
let fee = get_fee(fee, || {
estimate_message_delivery_and_dispatch_fee(
&millau_client,
bp_rialto::TO_RIALTO_ESTIMATE_MESSAGE_FEE_METHOD,
lane,
payload.clone(),
)
})
.await?;
let millau_call = millau_runtime::Call::BridgeRialtoMessageLane(
millau_runtime::MessageLaneCall::send_message(lane, payload, fee),
);
let signed_millau_call = Millau::sign_transaction(
&millau_client,
&millau_sign.signer,
millau_client
.next_account_index(millau_sign.signer.public().clone().into())
.await?,
millau_call,
)
.encode();
log::info!(
target: "bridge",
"Sending message to Rialto. Size: {}. Dispatch weight: {}. Fee: {}",
signed_millau_call.len(),
dispatch_weight,
fee,
);
millau_client.submit_extrinsic(Bytes(signed_millau_call)).await?;
}
cli::SendMessage::RialtoToMillau {
rialto,
rialto_sign,
millau_sign,
lane,
message,
dispatch_weight,
fee,
origin,
..
} => {
let rialto_client = rialto.into_client().await?;
let rialto_sign = rialto_sign.parse()?;
let millau_sign = millau_sign.parse()?;
let millau_call = message.into_call();
let payload =
rialto_to_millau_message_payload(&rialto_sign, &millau_sign, &millau_call, origin, dispatch_weight);
let dispatch_weight = payload.weight;
let lane = lane.into();
let fee = get_fee(fee, || {
estimate_message_delivery_and_dispatch_fee(
&rialto_client,
bp_millau::TO_MILLAU_ESTIMATE_MESSAGE_FEE_METHOD,
lane,
payload.clone(),
)
})
.await?;
let rialto_call = rialto_runtime::Call::BridgeMillauMessageLane(
rialto_runtime::MessageLaneCall::send_message(lane, payload, fee),
);
let signed_rialto_call = Rialto::sign_transaction(
&rialto_client,
&rialto_sign.signer,
rialto_client
.next_account_index(rialto_sign.signer.public().clone().into())
.await?,
rialto_call,
)
.encode();
log::info!(
target: "bridge",
"Sending message to Millau. Size: {}. Dispatch weight: {}. Fee: {}",
signed_rialto_call.len(),
dispatch_weight,
fee,
);
rialto_client.submit_extrinsic(Bytes(signed_rialto_call)).await?;
}
}
Ok(())
}
async fn estimate_message_delivery_and_dispatch_fee<Fee: Decode, C: Chain, P: Encode>(
client: &relay_substrate_client::Client<C>,
estimate_fee_method: &str,
lane: bp_message_lane::LaneId,
payload: P,
) -> Result<Option<Fee>, relay_substrate_client::Error> {
let encoded_response = client
.state_call(estimate_fee_method.into(), (lane, payload).encode().into(), None)
.await?;
let decoded_response: Option<Fee> =
Decode::decode(&mut &encoded_response.0[..]).map_err(relay_substrate_client::Error::ResponseParseFailed)?;
Ok(decoded_response)
}
fn remark_payload(remark_size: Option<cli::ExplicitOrMaximal<usize>>, maximal_allowed_size: u32) -> Vec<u8> {
match remark_size {
Some(cli::ExplicitOrMaximal::Explicit(remark_size)) => vec![0; remark_size],
Some(cli::ExplicitOrMaximal::Maximal) => vec![0; maximal_allowed_size as _],
None => format!(
"Unix time: {}",
std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
)
.as_bytes()
.to_vec(),
}
}
fn rialto_to_millau_message_payload(
rialto_sign: &RialtoSigningParams,
millau_sign: &MillauSigningParams,
millau_call: &millau_runtime::Call,
origin: cli::Origins,
user_specified_dispatch_weight: Option<cli::ExplicitOrMaximal<Weight>>,
) -> rialto_runtime::millau_messages::ToMillauMessagePayload {
let millau_call_weight = prepare_call_dispatch_weight(
user_specified_dispatch_weight,
cli::ExplicitOrMaximal::Explicit(millau_call.get_dispatch_info().weight),
compute_maximal_message_dispatch_weight(bp_millau::max_extrinsic_weight()),
);
let rialto_sender_public: bp_rialto::AccountSigner = rialto_sign.signer.public().clone().into();
let rialto_account_id: bp_rialto::AccountId = rialto_sender_public.into_account();
let millau_origin_public = millau_sign.signer.public();
MessagePayload {
spec_version: millau_runtime::VERSION.spec_version,
weight: millau_call_weight,
origin: match origin {
cli::Origins::Source => CallOrigin::SourceAccount(rialto_account_id),
cli::Origins::Target => {
let digest = rialto_runtime::millau_account_ownership_digest(
&millau_call,
rialto_account_id.clone(),
millau_runtime::VERSION.spec_version,
);
let digest_signature = millau_sign.signer.sign(&digest);
CallOrigin::TargetAccount(rialto_account_id, millau_origin_public.into(), digest_signature.into())
}
},
call: millau_call.encode(),
}
}
fn millau_to_rialto_message_payload(
millau_sign: &MillauSigningParams,
rialto_sign: &RialtoSigningParams,
rialto_call: &rialto_runtime::Call,
origin: cli::Origins,
user_specified_dispatch_weight: Option<cli::ExplicitOrMaximal<Weight>>,
) -> millau_runtime::rialto_messages::ToRialtoMessagePayload {
let rialto_call_weight = prepare_call_dispatch_weight(
user_specified_dispatch_weight,
cli::ExplicitOrMaximal::Explicit(rialto_call.get_dispatch_info().weight),
compute_maximal_message_dispatch_weight(bp_rialto::max_extrinsic_weight()),
);
let millau_sender_public: bp_millau::AccountSigner = millau_sign.signer.public().clone().into();
let millau_account_id: bp_millau::AccountId = millau_sender_public.into_account();
let rialto_origin_public = rialto_sign.signer.public();
MessagePayload {
spec_version: rialto_runtime::VERSION.spec_version,
weight: rialto_call_weight,
origin: match origin {
cli::Origins::Source => CallOrigin::SourceAccount(millau_account_id),
cli::Origins::Target => {
let digest = millau_runtime::rialto_account_ownership_digest(
&rialto_call,
millau_account_id.clone(),
rialto_runtime::VERSION.spec_version,
);
let digest_signature = rialto_sign.signer.sign(&digest);
CallOrigin::TargetAccount(millau_account_id, rialto_origin_public.into(), digest_signature.into())
}
},
call: rialto_call.encode(),
}
}
fn prepare_call_dispatch_weight(
user_specified_dispatch_weight: Option<cli::ExplicitOrMaximal<Weight>>,
weight_from_pre_dispatch_call: cli::ExplicitOrMaximal<Weight>,
maximal_allowed_weight: Weight,
) -> Weight {
match user_specified_dispatch_weight.unwrap_or(weight_from_pre_dispatch_call) {
cli::ExplicitOrMaximal::Explicit(weight) => weight,
cli::ExplicitOrMaximal::Maximal => maximal_allowed_weight,
}
}
async fn get_fee<Fee, F, R, E>(fee: Option<Fee>, f: F) -> Result<Fee, String>
where
Fee: Decode,
F: FnOnce() -> R,
R: std::future::Future<Output = Result<Option<Fee>, E>>,
E: std::fmt::Debug,
{
match fee {
Some(fee) => Ok(fee),
None => match f().await {
Ok(Some(fee)) => Ok(fee),
Ok(None) => Err("Failed to estimate message fee. Message is too heavy?".into()),
Err(error) => Err(format!("Failed to estimate message fee: {:?}", error)),
},
}
}
fn compute_maximal_message_dispatch_weight(maximal_extrinsic_weight: Weight) -> Weight {
bridge_runtime_common::messages::target::maximal_incoming_message_dispatch_weight(maximal_extrinsic_weight)
}
fn compute_maximal_message_arguments_size(
maximal_source_extrinsic_size: u32,
maximal_target_extrinsic_size: u32,
) -> u32 {
// assume that both signed extensions and other arguments fit 1KB
let service_tx_bytes_on_source_chain = 1024;
let maximal_source_extrinsic_size = maximal_source_extrinsic_size - service_tx_bytes_on_source_chain;
let maximal_call_size =
bridge_runtime_common::messages::target::maximal_incoming_message_size(maximal_target_extrinsic_size);
let maximal_call_size = if maximal_call_size > maximal_source_extrinsic_size {
maximal_source_extrinsic_size
} else {
maximal_call_size
};
// bytes in Call encoding that are used to encode everything except arguments
let service_bytes = 1 + 1 + 4;
maximal_call_size - service_bytes
}
impl crate::cli::RialtoSigningParams {
/// Parse CLI parameters into typed signing params.
pub fn parse(self) -> Result<RialtoSigningParams, String> {
RialtoSigningParams::from_suri(&self.rialto_signer, self.rialto_signer_password.as_deref())
.map_err(|e| format!("Failed to parse rialto-signer: {:?}", e))
}
}
impl crate::cli::MillauSigningParams {
/// Parse CLI parameters into typed signing params.
pub fn parse(self) -> Result<MillauSigningParams, String> {
MillauSigningParams::from_suri(&self.millau_signer, self.millau_signer_password.as_deref())
.map_err(|e| format!("Failed to parse millau-signer: {:?}", e))
}
}
impl crate::cli::MillauConnectionParams {
/// Convert CLI connection parameters into Millau RPC Client.
pub async fn into_client(self) -> relay_substrate_client::Result<MillauClient> {
MillauClient::new(ConnectionParams {
host: self.millau_host,
port: self.millau_port,
})
.await
}
}
impl crate::cli::RialtoConnectionParams {
/// Convert CLI connection parameters into Rialto RPC Client.
pub async fn into_client(self) -> relay_substrate_client::Result<RialtoClient> {
RialtoClient::new(ConnectionParams {
host: self.rialto_host,
port: self.rialto_port,
})
.await
}
}
impl crate::cli::ToRialtoMessage {
/// Convert CLI call request into runtime `Call` instance.
pub fn into_call(self) -> rialto_runtime::Call {
match self {
cli::ToRialtoMessage::Remark { remark_size } => {
rialto_runtime::Call::System(rialto_runtime::SystemCall::remark(remark_payload(
remark_size,
compute_maximal_message_arguments_size(
bp_millau::max_extrinsic_size(),
bp_rialto::max_extrinsic_size(),
),
)))
}
cli::ToRialtoMessage::Transfer { recipient, amount } => {
rialto_runtime::Call::Balances(rialto_runtime::BalancesCall::transfer(recipient, amount))
}
}
}
}
impl crate::cli::ToMillauMessage {
/// Convert CLI call request into runtime `Call` instance.
pub fn into_call(self) -> millau_runtime::Call {
match self {
cli::ToMillauMessage::Remark { remark_size } => {
millau_runtime::Call::System(millau_runtime::SystemCall::remark(remark_payload(
remark_size,
compute_maximal_message_arguments_size(
bp_rialto::max_extrinsic_size(),
bp_millau::max_extrinsic_size(),
),
)))
}
cli::ToMillauMessage::Transfer { recipient, amount } => {
millau_runtime::Call::Balances(millau_runtime::BalancesCall::transfer(recipient, amount))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use bp_message_lane::source_chain::TargetHeaderChain;
use sp_core::Pair;
use sp_runtime::traits::{IdentifyAccount, Verify};
#[test]
fn millau_signature_is_valid_on_rialto() {
let millau_sign = relay_millau_client::SigningParams::from_suri("//Dave", None).unwrap();
let call = rialto_runtime::Call::System(rialto_runtime::SystemCall::remark(vec![]));
let millau_public: bp_millau::AccountSigner = millau_sign.signer.public().clone().into();
let millau_account_id: bp_millau::AccountId = millau_public.into_account();
let digest = millau_runtime::rialto_account_ownership_digest(
&call,
millau_account_id,
rialto_runtime::VERSION.spec_version,
);
let rialto_signer = relay_rialto_client::SigningParams::from_suri("//Dave", None).unwrap();
let signature = rialto_signer.signer.sign(&digest);
assert!(signature.verify(&digest[..], &rialto_signer.signer.public()));
}
#[test]
fn rialto_signature_is_valid_on_millau() {
let rialto_sign = relay_rialto_client::SigningParams::from_suri("//Dave", None).unwrap();
let call = millau_runtime::Call::System(millau_runtime::SystemCall::remark(vec![]));
let rialto_public: bp_rialto::AccountSigner = rialto_sign.signer.public().clone().into();
let rialto_account_id: bp_rialto::AccountId = rialto_public.into_account();
let digest = rialto_runtime::millau_account_ownership_digest(
&call,
rialto_account_id,
millau_runtime::VERSION.spec_version,
);
let millau_signer = relay_millau_client::SigningParams::from_suri("//Dave", None).unwrap();
let signature = millau_signer.signer.sign(&digest);
assert!(signature.verify(&digest[..], &millau_signer.signer.public()));
}
#[test]
fn maximal_rialto_to_millau_message_arguments_size_is_computed_correctly() {
use rialto_runtime::millau_messages::Millau;
let maximal_remark_size =
compute_maximal_message_arguments_size(bp_rialto::max_extrinsic_size(), bp_millau::max_extrinsic_size());
let call: millau_runtime::Call = millau_runtime::SystemCall::remark(vec![42; maximal_remark_size as _]).into();
let payload = pallet_bridge_call_dispatch::MessagePayload {
spec_version: Default::default(),
weight: call.get_dispatch_info().weight,
origin: pallet_bridge_call_dispatch::CallOrigin::SourceRoot,
call: call.encode(),
};
assert_eq!(Millau::verify_message(&payload), Ok(()));
let call: millau_runtime::Call =
millau_runtime::SystemCall::remark(vec![42; (maximal_remark_size + 1) as _]).into();
let payload = pallet_bridge_call_dispatch::MessagePayload {
spec_version: Default::default(),
weight: call.get_dispatch_info().weight,
origin: pallet_bridge_call_dispatch::CallOrigin::SourceRoot,
call: call.encode(),
};
assert!(Millau::verify_message(&payload).is_err());
}
#[test]
fn maximal_size_remark_to_rialto_is_generated_correctly() {
assert!(
bridge_runtime_common::messages::target::maximal_incoming_message_size(
bp_rialto::max_extrinsic_size()
) > bp_millau::max_extrinsic_size(),
"We can't actually send maximal messages to Rialto from Millau, because Millau extrinsics can't be that large",
)
}
#[test]
fn maximal_rialto_to_millau_message_dispatch_weight_is_computed_correctly() {
use rialto_runtime::millau_messages::Millau;
let maximal_dispatch_weight = compute_maximal_message_dispatch_weight(bp_millau::max_extrinsic_weight());
let call: millau_runtime::Call = rialto_runtime::SystemCall::remark(vec![]).into();
let payload = pallet_bridge_call_dispatch::MessagePayload {
spec_version: Default::default(),
weight: maximal_dispatch_weight,
origin: pallet_bridge_call_dispatch::CallOrigin::SourceRoot,
call: call.encode(),
};
assert_eq!(Millau::verify_message(&payload), Ok(()));
let payload = pallet_bridge_call_dispatch::MessagePayload {
spec_version: Default::default(),
weight: maximal_dispatch_weight + 1,
origin: pallet_bridge_call_dispatch::CallOrigin::SourceRoot,
call: call.encode(),
};
assert!(Millau::verify_message(&payload).is_err());
}
#[test]
fn maximal_weight_fill_block_to_rialto_is_generated_correctly() {
use millau_runtime::rialto_messages::Rialto;
let maximal_dispatch_weight = compute_maximal_message_dispatch_weight(bp_rialto::max_extrinsic_weight());
let call: rialto_runtime::Call = millau_runtime::SystemCall::remark(vec![]).into();
let payload = pallet_bridge_call_dispatch::MessagePayload {
spec_version: Default::default(),
weight: maximal_dispatch_weight,
origin: pallet_bridge_call_dispatch::CallOrigin::SourceRoot,
call: call.encode(),
};
assert_eq!(Rialto::verify_message(&payload), Ok(()));
let payload = pallet_bridge_call_dispatch::MessagePayload {
spec_version: Default::default(),
weight: maximal_dispatch_weight + 1,
origin: pallet_bridge_call_dispatch::CallOrigin::SourceRoot,
call: call.encode(),
};
assert!(Rialto::verify_message(&payload).is_err());
}
}
@@ -0,0 +1,182 @@
// 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 <http://www.gnu.org/licenses/>.
use crate::messages_source::SubstrateMessagesProof;
use crate::messages_target::SubstrateMessagesReceivingProof;
use async_trait::async_trait;
use bp_message_lane::MessageNonce;
use codec::Encode;
use frame_support::weights::Weight;
use messages_relay::message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf};
use relay_substrate_client::{BlockNumberOf, Chain, Client, Error as SubstrateError, HashOf};
use relay_utils::BlockNumberBase;
use std::ops::RangeInclusive;
/// Message sync pipeline for Substrate <-> Substrate relays.
#[async_trait]
pub trait SubstrateMessageLane: MessageLane {
/// Name of the runtime method that returns dispatch weight of outbound messages at the source chain.
const OUTBOUND_LANE_MESSAGES_DISPATCH_WEIGHT_METHOD: &'static str;
/// Name of the runtime method that returns latest generated nonce at the source chain.
const OUTBOUND_LANE_LATEST_GENERATED_NONCE_METHOD: &'static str;
/// Name of the runtime method that returns latest received (confirmed) nonce at the the source chain.
const OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str;
/// Name of the runtime method that returns latest received nonce at the target chain.
const INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str;
/// Name of the runtime method that returns latest confirmed (reward-paid) nonce at the target chain.
const INBOUND_LANE_LATEST_CONFIRMED_NONCE_METHOD: &'static str;
/// Numebr of the runtime method that returns state of "unrewarded relayers" set at the target chain.
const INBOUND_LANE_UNREWARDED_RELAYERS_STATE: &'static str;
/// Name of the runtime method that returns id of best finalized source header at target chain.
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str;
/// Name of the runtime method that returns id of best finalized target header at source chain.
const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str;
/// Signed transaction type of the source chain.
type SourceSignedTransaction: Send + Sync + Encode;
/// Signed transaction type of the target chain.
type TargetSignedTransaction: Send + Sync + Encode;
/// Make messages delivery transaction.
async fn make_messages_delivery_transaction(
&self,
generated_at_header: SourceHeaderIdOf<Self>,
nonces: RangeInclusive<MessageNonce>,
proof: Self::MessagesProof,
) -> Result<Self::TargetSignedTransaction, SubstrateError>;
/// Make messages receiving proof transaction.
async fn make_messages_receiving_proof_transaction(
&self,
generated_at_header: TargetHeaderIdOf<Self>,
proof: Self::MessagesReceivingProof,
) -> Result<Self::SourceSignedTransaction, SubstrateError>;
}
/// Substrate-to-Substrate message lane.
#[derive(Debug)]
pub struct SubstrateMessageLaneToSubstrate<Source: Chain, SourceSignParams, Target: Chain, TargetSignParams> {
/// Client for the source Substrate chain.
pub(crate) source_client: Client<Source>,
/// Parameters required to sign transactions for source chain.
pub(crate) source_sign: SourceSignParams,
/// Client for the target Substrate chain.
pub(crate) target_client: Client<Target>,
/// Parameters required to sign transactions for target chain.
pub(crate) target_sign: TargetSignParams,
/// Account id of relayer at the source chain.
pub(crate) relayer_id_at_source: Source::AccountId,
}
impl<Source: Chain, SourceSignParams: Clone, Target: Chain, TargetSignParams: Clone> Clone
for SubstrateMessageLaneToSubstrate<Source, SourceSignParams, Target, TargetSignParams>
{
fn clone(&self) -> Self {
Self {
source_client: self.source_client.clone(),
source_sign: self.source_sign.clone(),
target_client: self.target_client.clone(),
target_sign: self.target_sign.clone(),
relayer_id_at_source: self.relayer_id_at_source.clone(),
}
}
}
impl<Source: Chain, SourceSignParams, Target: Chain, TargetSignParams> MessageLane
for SubstrateMessageLaneToSubstrate<Source, SourceSignParams, Target, TargetSignParams>
where
SourceSignParams: Clone + Send + Sync + 'static,
TargetSignParams: Clone + Send + Sync + 'static,
BlockNumberOf<Source>: BlockNumberBase,
BlockNumberOf<Target>: BlockNumberBase,
{
const SOURCE_NAME: &'static str = Source::NAME;
const TARGET_NAME: &'static str = Target::NAME;
type MessagesProof = SubstrateMessagesProof<Source>;
type MessagesReceivingProof = SubstrateMessagesReceivingProof<Target>;
type SourceHeaderNumber = BlockNumberOf<Source>;
type SourceHeaderHash = HashOf<Source>;
type TargetHeaderNumber = BlockNumberOf<Target>;
type TargetHeaderHash = HashOf<Target>;
}
/// Returns maximal number of messages and their maximal cumulative dispatch weight, based
/// on given chain parameters.
pub fn select_delivery_transaction_limits<W: pallet_message_lane::WeightInfoExt>(
max_extrinsic_weight: Weight,
max_unconfirmed_messages_at_inbound_lane: MessageNonce,
) -> (MessageNonce, Weight) {
// We may try to guess accurate value, based on maximal number of messages and per-message
// weight overhead, but the relay loop isn't using this info in a super-accurate way anyway.
// So just a rough guess: let's say 1/3 of max tx weight is for tx itself and the rest is
// for messages dispatch.
// Another thing to keep in mind is that our runtimes (when this code was written) accept
// messages with dispatch weight <= max_extrinsic_weight/2. So we can't reserve less than
// that for dispatch.
let weight_for_delivery_tx = max_extrinsic_weight / 3;
let weight_for_messages_dispatch = max_extrinsic_weight - weight_for_delivery_tx;
let delivery_tx_base_weight =
W::receive_messages_proof_overhead() + W::receive_messages_proof_outbound_lane_state_overhead();
let delivery_tx_weight_rest = weight_for_delivery_tx - delivery_tx_base_weight;
let max_number_of_messages = std::cmp::min(
delivery_tx_weight_rest / W::receive_messages_proof_messages_overhead(1),
max_unconfirmed_messages_at_inbound_lane,
);
assert!(
max_number_of_messages > 0,
"Relay should fit at least one message in every delivery transaction",
);
assert!(
weight_for_messages_dispatch >= max_extrinsic_weight / 2,
"Relay shall be able to deliver messages with dispatch weight = max_extrinsic_weight / 2",
);
(max_number_of_messages, weight_for_messages_dispatch)
}
#[cfg(test)]
mod tests {
use super::*;
type RialtoToMillauMessageLaneWeights = pallet_message_lane::weights::RialtoWeight<rialto_runtime::Runtime>;
#[test]
fn select_delivery_transaction_limits_works() {
let (max_count, max_weight) = select_delivery_transaction_limits::<RialtoToMillauMessageLaneWeights>(
bp_millau::max_extrinsic_weight(),
bp_millau::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
);
assert_eq!(
(max_count, max_weight),
// We don't actually care about these values, so feel free to update them whenever test
// fails. The only thing to do before that is to ensure that new values looks sane: i.e. weight
// reserved for messages dispatch allows dispatch of non-trivial messages.
//
// Any significant change in this values should attract additional attention.
(955, 216_583_333_334),
);
}
}
@@ -0,0 +1,373 @@
// 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 <http://www.gnu.org/licenses/>.
//! Substrate client as Substrate messages source. The chain we connect to should have
//! runtime that implements `<BridgedChainName>HeaderApi` to allow bridging with
//! <BridgedName> chain.
use crate::messages_lane::SubstrateMessageLane;
use async_trait::async_trait;
use bp_message_lane::{LaneId, MessageNonce};
use bp_runtime::InstanceId;
use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
use codec::{Decode, Encode};
use frame_support::weights::Weight;
use messages_relay::{
message_lane::{SourceHeaderIdOf, TargetHeaderIdOf},
message_lane_loop::{
ClientState, MessageProofParameters, MessageWeights, MessageWeightsMap, SourceClient, SourceClientState,
},
};
use relay_substrate_client::{Chain, Client, Error as SubstrateError, HashOf, HeaderIdOf};
use relay_utils::{relay_loop::Client as RelayClient, BlockNumberBase, HeaderId};
use sp_core::Bytes;
use sp_runtime::{traits::Header as HeaderT, DeserializeOwned};
use std::ops::RangeInclusive;
/// Intermediate message proof returned by the source Substrate node. Includes everything
/// required to submit to the target node: cumulative dispatch weight of bundled messages and
/// the proof itself.
pub type SubstrateMessagesProof<C> = (Weight, FromBridgedChainMessagesProof<HashOf<C>>);
/// Substrate client as Substrate messages source.
pub struct SubstrateMessagesSource<C: Chain, P> {
client: Client<C>,
lane: P,
lane_id: LaneId,
instance: InstanceId,
}
impl<C: Chain, P> SubstrateMessagesSource<C, P> {
/// Create new Substrate headers source.
pub fn new(client: Client<C>, lane: P, lane_id: LaneId, instance: InstanceId) -> Self {
SubstrateMessagesSource {
client,
lane,
lane_id,
instance,
}
}
}
impl<C: Chain, P: SubstrateMessageLane> Clone for SubstrateMessagesSource<C, P> {
fn clone(&self) -> Self {
Self {
client: self.client.clone(),
lane: self.lane.clone(),
lane_id: self.lane_id,
instance: self.instance,
}
}
}
#[async_trait]
impl<C: Chain, P: SubstrateMessageLane> RelayClient for SubstrateMessagesSource<C, P> {
type Error = SubstrateError;
async fn reconnect(&mut self) -> Result<(), SubstrateError> {
self.client.reconnect().await
}
}
#[async_trait]
impl<C, P> SourceClient<P> for SubstrateMessagesSource<C, P>
where
C: Chain,
C::Header: DeserializeOwned,
C::Index: DeserializeOwned,
C::BlockNumber: BlockNumberBase,
P: SubstrateMessageLane<
MessagesProof = SubstrateMessagesProof<C>,
SourceHeaderNumber = <C::Header as HeaderT>::Number,
SourceHeaderHash = <C::Header as HeaderT>::Hash,
>,
P::TargetHeaderNumber: Decode,
P::TargetHeaderHash: Decode,
{
async fn state(&self) -> Result<SourceClientState<P>, SubstrateError> {
// we can't continue to deliver confirmations if source node is out of sync, because
// it may have already received confirmations that we're going to deliver
self.client.ensure_synced().await?;
read_client_state::<_, P::TargetHeaderHash, P::TargetHeaderNumber>(
&self.client,
P::BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE,
)
.await
}
async fn latest_generated_nonce(
&self,
id: SourceHeaderIdOf<P>,
) -> Result<(SourceHeaderIdOf<P>, MessageNonce), SubstrateError> {
let encoded_response = self
.client
.state_call(
P::OUTBOUND_LANE_LATEST_GENERATED_NONCE_METHOD.into(),
Bytes(self.lane_id.encode()),
Some(id.1),
)
.await?;
let latest_generated_nonce: MessageNonce =
Decode::decode(&mut &encoded_response.0[..]).map_err(SubstrateError::ResponseParseFailed)?;
Ok((id, latest_generated_nonce))
}
async fn latest_confirmed_received_nonce(
&self,
id: SourceHeaderIdOf<P>,
) -> Result<(SourceHeaderIdOf<P>, MessageNonce), SubstrateError> {
let encoded_response = self
.client
.state_call(
P::OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD.into(),
Bytes(self.lane_id.encode()),
Some(id.1),
)
.await?;
let latest_received_nonce: MessageNonce =
Decode::decode(&mut &encoded_response.0[..]).map_err(SubstrateError::ResponseParseFailed)?;
Ok((id, latest_received_nonce))
}
async fn generated_messages_weights(
&self,
id: SourceHeaderIdOf<P>,
nonces: RangeInclusive<MessageNonce>,
) -> Result<MessageWeightsMap, SubstrateError> {
let encoded_response = self
.client
.state_call(
P::OUTBOUND_LANE_MESSAGES_DISPATCH_WEIGHT_METHOD.into(),
Bytes((self.lane_id, nonces.start(), nonces.end()).encode()),
Some(id.1),
)
.await?;
make_message_weights_map::<C>(
Decode::decode(&mut &encoded_response.0[..]).map_err(SubstrateError::ResponseParseFailed)?,
nonces,
)
}
async fn prove_messages(
&self,
id: SourceHeaderIdOf<P>,
nonces: RangeInclusive<MessageNonce>,
proof_parameters: MessageProofParameters,
) -> Result<(SourceHeaderIdOf<P>, RangeInclusive<MessageNonce>, P::MessagesProof), SubstrateError> {
let proof = self
.client
.prove_messages(
self.instance,
self.lane_id,
nonces.clone(),
proof_parameters.outbound_state_proof_required,
id.1,
)
.await?
.iter_nodes()
.collect();
let proof = FromBridgedChainMessagesProof {
bridged_header_hash: id.1,
storage_proof: proof,
lane: self.lane_id,
nonces_start: *nonces.start(),
nonces_end: *nonces.end(),
};
Ok((id, nonces, (proof_parameters.dispatch_weight, proof)))
}
async fn submit_messages_receiving_proof(
&self,
generated_at_block: TargetHeaderIdOf<P>,
proof: P::MessagesReceivingProof,
) -> Result<(), SubstrateError> {
let tx = self
.lane
.make_messages_receiving_proof_transaction(generated_at_block, proof)
.await?;
self.client.submit_extrinsic(Bytes(tx.encode())).await?;
Ok(())
}
}
pub async fn read_client_state<SelfChain, BridgedHeaderHash, BridgedHeaderNumber>(
self_client: &Client<SelfChain>,
best_finalized_header_id_method_name: &str,
) -> Result<ClientState<HeaderIdOf<SelfChain>, HeaderId<BridgedHeaderHash, BridgedHeaderNumber>>, SubstrateError>
where
SelfChain: Chain,
SelfChain::Header: DeserializeOwned,
SelfChain::Index: DeserializeOwned,
BridgedHeaderHash: Decode,
BridgedHeaderNumber: Decode,
{
// let's read our state first: we need best finalized header hash on **this** chain
let self_best_finalized_header_hash = self_client.best_finalized_header_hash().await?;
let self_best_finalized_header = self_client.header_by_hash(self_best_finalized_header_hash).await?;
let self_best_finalized_id = HeaderId(*self_best_finalized_header.number(), self_best_finalized_header_hash);
// now let's read our best header on **this** chain
let self_best_header = self_client.best_header().await?;
let self_best_hash = self_best_header.hash();
let self_best_id = HeaderId(*self_best_header.number(), self_best_hash);
// now let's read id of best finalized peer header at our best finalized block
let encoded_best_finalized_peer_on_self = self_client
.state_call(
best_finalized_header_id_method_name.into(),
Bytes(Vec::new()),
Some(self_best_hash),
)
.await?;
let decoded_best_finalized_peer_on_self: (BridgedHeaderNumber, BridgedHeaderHash) =
Decode::decode(&mut &encoded_best_finalized_peer_on_self.0[..]).map_err(SubstrateError::ResponseParseFailed)?;
let peer_on_self_best_finalized_id = HeaderId(
decoded_best_finalized_peer_on_self.0,
decoded_best_finalized_peer_on_self.1,
);
Ok(ClientState {
best_self: self_best_id,
best_finalized_self: self_best_finalized_id,
best_finalized_peer_at_best_self: peer_on_self_best_finalized_id,
})
}
fn make_message_weights_map<C: Chain>(
weights: Vec<(MessageNonce, Weight, u32)>,
nonces: RangeInclusive<MessageNonce>,
) -> Result<MessageWeightsMap, SubstrateError> {
let make_missing_nonce_error = |expected_nonce| {
Err(SubstrateError::Custom(format!(
"Missing nonce {} in messages_dispatch_weight call result. Expected all nonces from {:?}",
expected_nonce, nonces,
)))
};
let mut weights_map = MessageWeightsMap::new();
// this is actually prevented by external logic
if nonces.is_empty() {
return Ok(weights_map);
}
// check if last nonce is missing - loop below is not checking this
let last_nonce_is_missing = weights
.last()
.map(|(last_nonce, _, _)| last_nonce != nonces.end())
.unwrap_or(true);
if last_nonce_is_missing {
return make_missing_nonce_error(*nonces.end());
}
let mut expected_nonce = *nonces.start();
let mut is_at_head = true;
for (nonce, weight, size) in weights {
match (nonce == expected_nonce, is_at_head) {
(true, _) => (),
(false, true) => {
// this may happen if some messages were already pruned from the source node
//
// this is not critical error and will be auto-resolved by messages lane (and target node)
log::info!(
target: "bridge",
"Some messages are missing from the {} node: {:?}. Target node may be out of sync?",
C::NAME,
expected_nonce..nonce,
);
}
(false, false) => {
// some nonces are missing from the middle/tail of the range
//
// this is critical error, because we can't miss any nonces
return make_missing_nonce_error(expected_nonce);
}
}
weights_map.insert(
nonce,
MessageWeights {
weight,
size: size as _,
},
);
expected_nonce = nonce + 1;
is_at_head = false;
}
Ok(weights_map)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn make_message_weights_map_succeeds_if_no_messages_are_missing() {
assert_eq!(
make_message_weights_map::<relay_rialto_client::Rialto>(vec![(1, 0, 0), (2, 0, 0), (3, 0, 0)], 1..=3,)
.unwrap(),
vec![
(1, MessageWeights { weight: 0, size: 0 }),
(2, MessageWeights { weight: 0, size: 0 }),
(3, MessageWeights { weight: 0, size: 0 }),
]
.into_iter()
.collect(),
);
}
#[test]
fn make_message_weights_map_succeeds_if_head_messages_are_missing() {
assert_eq!(
make_message_weights_map::<relay_rialto_client::Rialto>(vec![(2, 0, 0), (3, 0, 0)], 1..=3,).unwrap(),
vec![
(2, MessageWeights { weight: 0, size: 0 }),
(3, MessageWeights { weight: 0, size: 0 }),
]
.into_iter()
.collect(),
);
}
#[test]
fn make_message_weights_map_fails_if_mid_messages_are_missing() {
assert!(matches!(
make_message_weights_map::<relay_rialto_client::Rialto>(vec![(1, 0, 0), (3, 0, 0)], 1..=3,),
Err(SubstrateError::Custom(_))
));
}
#[test]
fn make_message_weights_map_fails_if_tail_messages_are_missing() {
assert!(matches!(
make_message_weights_map::<relay_rialto_client::Rialto>(vec![(1, 0, 0), (2, 0, 0)], 1..=3,),
Err(SubstrateError::Custom(_))
));
}
#[test]
fn make_message_weights_map_fails_if_all_messages_are_missing() {
assert!(matches!(
make_message_weights_map::<relay_rialto_client::Rialto>(vec![], 1..=3),
Err(SubstrateError::Custom(_))
));
}
}
@@ -0,0 +1,193 @@
// 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 <http://www.gnu.org/licenses/>.
//! Substrate client as Substrate messages target. The chain we connect to should have
//! runtime that implements `<BridgedChainName>HeaderApi` to allow bridging with
//! <BridgedName> chain.
use crate::messages_lane::SubstrateMessageLane;
use crate::messages_source::read_client_state;
use async_trait::async_trait;
use bp_message_lane::{LaneId, MessageNonce, UnrewardedRelayersState};
use bp_runtime::InstanceId;
use bridge_runtime_common::messages::source::FromBridgedChainMessagesDeliveryProof;
use codec::{Decode, Encode};
use messages_relay::{
message_lane::{SourceHeaderIdOf, TargetHeaderIdOf},
message_lane_loop::{TargetClient, TargetClientState},
};
use relay_substrate_client::{Chain, Client, Error as SubstrateError, HashOf};
use relay_utils::{relay_loop::Client as RelayClient, BlockNumberBase};
use sp_core::Bytes;
use sp_runtime::{traits::Header as HeaderT, DeserializeOwned};
use std::ops::RangeInclusive;
/// Message receiving proof returned by the target Substrate node.
pub type SubstrateMessagesReceivingProof<C> = (
UnrewardedRelayersState,
FromBridgedChainMessagesDeliveryProof<HashOf<C>>,
);
/// Substrate client as Substrate messages target.
pub struct SubstrateMessagesTarget<C: Chain, P> {
client: Client<C>,
lane: P,
lane_id: LaneId,
instance: InstanceId,
}
impl<C: Chain, P> SubstrateMessagesTarget<C, P> {
/// Create new Substrate headers target.
pub fn new(client: Client<C>, lane: P, lane_id: LaneId, instance: InstanceId) -> Self {
SubstrateMessagesTarget {
client,
lane,
lane_id,
instance,
}
}
}
impl<C: Chain, P: SubstrateMessageLane> Clone for SubstrateMessagesTarget<C, P> {
fn clone(&self) -> Self {
Self {
client: self.client.clone(),
lane: self.lane.clone(),
lane_id: self.lane_id,
instance: self.instance,
}
}
}
#[async_trait]
impl<C: Chain, P: SubstrateMessageLane> RelayClient for SubstrateMessagesTarget<C, P> {
type Error = SubstrateError;
async fn reconnect(&mut self) -> Result<(), SubstrateError> {
self.client.reconnect().await
}
}
#[async_trait]
impl<C, P> TargetClient<P> for SubstrateMessagesTarget<C, P>
where
C: Chain,
C::Header: DeserializeOwned,
C::Index: DeserializeOwned,
<C::Header as HeaderT>::Number: BlockNumberBase,
P: SubstrateMessageLane<
MessagesReceivingProof = SubstrateMessagesReceivingProof<C>,
TargetHeaderNumber = <C::Header as HeaderT>::Number,
TargetHeaderHash = <C::Header as HeaderT>::Hash,
>,
P::SourceHeaderNumber: Decode,
P::SourceHeaderHash: Decode,
{
async fn state(&self) -> Result<TargetClientState<P>, SubstrateError> {
// we can't continue to deliver messages if target node is out of sync, because
// it may have already received (some of) messages that we're going to deliver
self.client.ensure_synced().await?;
read_client_state::<_, P::SourceHeaderHash, P::SourceHeaderNumber>(
&self.client,
P::BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET,
)
.await
}
async fn latest_received_nonce(
&self,
id: TargetHeaderIdOf<P>,
) -> Result<(TargetHeaderIdOf<P>, MessageNonce), SubstrateError> {
let encoded_response = self
.client
.state_call(
P::INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD.into(),
Bytes(self.lane_id.encode()),
Some(id.1),
)
.await?;
let latest_received_nonce: MessageNonce =
Decode::decode(&mut &encoded_response.0[..]).map_err(SubstrateError::ResponseParseFailed)?;
Ok((id, latest_received_nonce))
}
async fn latest_confirmed_received_nonce(
&self,
id: TargetHeaderIdOf<P>,
) -> Result<(TargetHeaderIdOf<P>, MessageNonce), SubstrateError> {
let encoded_response = self
.client
.state_call(
P::INBOUND_LANE_LATEST_CONFIRMED_NONCE_METHOD.into(),
Bytes(self.lane_id.encode()),
Some(id.1),
)
.await?;
let latest_received_nonce: MessageNonce =
Decode::decode(&mut &encoded_response.0[..]).map_err(SubstrateError::ResponseParseFailed)?;
Ok((id, latest_received_nonce))
}
async fn unrewarded_relayers_state(
&self,
id: TargetHeaderIdOf<P>,
) -> Result<(TargetHeaderIdOf<P>, UnrewardedRelayersState), SubstrateError> {
let encoded_response = self
.client
.state_call(
P::INBOUND_LANE_UNREWARDED_RELAYERS_STATE.into(),
Bytes(self.lane_id.encode()),
Some(id.1),
)
.await?;
let unrewarded_relayers_state: UnrewardedRelayersState =
Decode::decode(&mut &encoded_response.0[..]).map_err(SubstrateError::ResponseParseFailed)?;
Ok((id, unrewarded_relayers_state))
}
async fn prove_messages_receiving(
&self,
id: TargetHeaderIdOf<P>,
) -> Result<(TargetHeaderIdOf<P>, P::MessagesReceivingProof), SubstrateError> {
let (id, relayers_state) = self.unrewarded_relayers_state(id).await?;
let proof = self
.client
.prove_messages_delivery(self.instance, self.lane_id, id.1)
.await?;
let proof = FromBridgedChainMessagesDeliveryProof {
bridged_header_hash: id.1,
storage_proof: proof,
lane: self.lane_id,
};
Ok((id, (relayers_state, proof)))
}
async fn submit_messages_proof(
&self,
generated_at_header: SourceHeaderIdOf<P>,
nonces: RangeInclusive<MessageNonce>,
proof: P::MessagesProof,
) -> Result<RangeInclusive<MessageNonce>, SubstrateError> {
let tx = self
.lane
.make_messages_delivery_transaction(generated_at_header, nonces.clone(), proof)
.await?;
self.client.submit_extrinsic(Bytes(tx.encode())).await?;
Ok(nonces)
}
}
@@ -0,0 +1,89 @@
// 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 <http://www.gnu.org/licenses/>.
//! Millau-to-Rialto headers sync entrypoint.
use crate::{
headers_pipeline::{SubstrateHeadersSyncPipeline, SubstrateHeadersToSubstrate},
MillauClient, RialtoClient,
};
use async_trait::async_trait;
use bp_millau::{
BEST_MILLAU_BLOCKS_METHOD, FINALIZED_MILLAU_BLOCK_METHOD, INCOMPLETE_MILLAU_HEADERS_METHOD,
IS_KNOWN_MILLAU_BLOCK_METHOD,
};
use headers_relay::sync_types::QueuedHeader;
use relay_millau_client::{HeaderId as MillauHeaderId, Millau, SyncHeader as MillauSyncHeader};
use relay_rialto_client::{BridgeMillauCall, Rialto, SigningParams as RialtoSigningParams};
use relay_substrate_client::{Error as SubstrateError, TransactionSignScheme};
use sp_core::Pair;
use sp_runtime::Justification;
/// Millau-to-Rialto headers sync pipeline.
pub(crate) type MillauHeadersToRialto =
SubstrateHeadersToSubstrate<Millau, MillauSyncHeader, Rialto, RialtoSigningParams>;
/// Millau header in-the-queue.
type QueuedMillauHeader = QueuedHeader<MillauHeadersToRialto>;
#[async_trait]
impl SubstrateHeadersSyncPipeline for MillauHeadersToRialto {
const BEST_BLOCK_METHOD: &'static str = BEST_MILLAU_BLOCKS_METHOD;
const FINALIZED_BLOCK_METHOD: &'static str = FINALIZED_MILLAU_BLOCK_METHOD;
const IS_KNOWN_BLOCK_METHOD: &'static str = IS_KNOWN_MILLAU_BLOCK_METHOD;
const INCOMPLETE_HEADERS_METHOD: &'static str = INCOMPLETE_MILLAU_HEADERS_METHOD;
type SignedTransaction = <Rialto as TransactionSignScheme>::SignedTransaction;
async fn make_submit_header_transaction(
&self,
header: QueuedMillauHeader,
) -> Result<Self::SignedTransaction, SubstrateError> {
let account_id = self.target_sign.signer.public().as_array_ref().clone().into();
let nonce = self.target_client.next_account_index(account_id).await?;
let call = BridgeMillauCall::import_signed_header(header.header().clone().into_inner()).into();
let transaction = Rialto::sign_transaction(&self.target_client, &self.target_sign.signer, nonce, call);
Ok(transaction)
}
async fn make_complete_header_transaction(
&self,
id: MillauHeaderId,
completion: Justification,
) -> Result<Self::SignedTransaction, SubstrateError> {
let account_id = self.target_sign.signer.public().as_array_ref().clone().into();
let nonce = self.target_client.next_account_index(account_id).await?;
let call = BridgeMillauCall::finalize_header(id.1, completion).into();
let transaction = Rialto::sign_transaction(&self.target_client, &self.target_sign.signer, nonce, call);
Ok(transaction)
}
}
/// Run Millau-to-Rialto headers sync.
pub async fn run(
millau_client: MillauClient,
rialto_client: RialtoClient,
rialto_sign: RialtoSigningParams,
metrics_params: Option<relay_utils::metrics::MetricsParams>,
) {
crate::headers_pipeline::run(
MillauHeadersToRialto::new(rialto_client.clone(), rialto_sign),
millau_client,
rialto_client,
metrics_params,
)
.await;
}
@@ -0,0 +1,187 @@
// 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 <http://www.gnu.org/licenses/>.
//! Millau-to-Rialto messages sync entrypoint.
use crate::messages_lane::{select_delivery_transaction_limits, SubstrateMessageLane, SubstrateMessageLaneToSubstrate};
use crate::messages_source::SubstrateMessagesSource;
use crate::messages_target::SubstrateMessagesTarget;
use crate::{MillauClient, RialtoClient};
use async_trait::async_trait;
use bp_message_lane::{LaneId, MessageNonce};
use bp_runtime::{MILLAU_BRIDGE_INSTANCE, RIALTO_BRIDGE_INSTANCE};
use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
use codec::Encode;
use frame_support::dispatch::GetDispatchInfo;
use messages_relay::message_lane::MessageLane;
use relay_millau_client::{HeaderId as MillauHeaderId, Millau, SigningParams as MillauSigningParams};
use relay_rialto_client::{HeaderId as RialtoHeaderId, Rialto, SigningParams as RialtoSigningParams};
use relay_substrate_client::{Chain, Error as SubstrateError, TransactionSignScheme};
use relay_utils::metrics::MetricsParams;
use sp_core::Pair;
use std::{ops::RangeInclusive, time::Duration};
/// Millau-to-Rialto message lane.
type MillauMessagesToRialto = SubstrateMessageLaneToSubstrate<Millau, MillauSigningParams, Rialto, RialtoSigningParams>;
#[async_trait]
impl SubstrateMessageLane for MillauMessagesToRialto {
const OUTBOUND_LANE_MESSAGES_DISPATCH_WEIGHT_METHOD: &'static str =
bp_rialto::TO_RIALTO_MESSAGES_DISPATCH_WEIGHT_METHOD;
const OUTBOUND_LANE_LATEST_GENERATED_NONCE_METHOD: &'static str =
bp_rialto::TO_RIALTO_LATEST_GENERATED_NONCE_METHOD;
const OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str = bp_rialto::TO_RIALTO_LATEST_RECEIVED_NONCE_METHOD;
const INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str = bp_millau::FROM_MILLAU_LATEST_RECEIVED_NONCE_METHOD;
const INBOUND_LANE_LATEST_CONFIRMED_NONCE_METHOD: &'static str =
bp_millau::FROM_MILLAU_LATEST_CONFIRMED_NONCE_METHOD;
const INBOUND_LANE_UNREWARDED_RELAYERS_STATE: &'static str = bp_millau::FROM_MILLAU_UNREWARDED_RELAYERS_STATE;
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_millau::FINALIZED_MILLAU_BLOCK_METHOD;
const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str = bp_rialto::FINALIZED_RIALTO_BLOCK_METHOD;
type SourceSignedTransaction = <Millau as TransactionSignScheme>::SignedTransaction;
type TargetSignedTransaction = <Rialto as TransactionSignScheme>::SignedTransaction;
async fn make_messages_receiving_proof_transaction(
&self,
_generated_at_block: RialtoHeaderId,
proof: <Self as MessageLane>::MessagesReceivingProof,
) -> Result<Self::SourceSignedTransaction, SubstrateError> {
let (relayers_state, proof) = proof;
let account_id = self.source_sign.signer.public().as_array_ref().clone().into();
let nonce = self.source_client.next_account_index(account_id).await?;
let call: millau_runtime::Call =
millau_runtime::MessageLaneCall::receive_messages_delivery_proof(proof, relayers_state).into();
let call_weight = call.get_dispatch_info().weight;
let transaction = Millau::sign_transaction(&self.source_client, &self.source_sign.signer, nonce, call);
log::trace!(
target: "bridge",
"Prepared Rialto -> Millau confirmation transaction. Weight: {}/{}, size: {}/{}",
call_weight,
bp_millau::max_extrinsic_weight(),
transaction.encode().len(),
bp_millau::max_extrinsic_size(),
);
Ok(transaction)
}
async fn make_messages_delivery_transaction(
&self,
_generated_at_header: MillauHeaderId,
_nonces: RangeInclusive<MessageNonce>,
proof: <Self as MessageLane>::MessagesProof,
) -> Result<Self::TargetSignedTransaction, SubstrateError> {
let (dispatch_weight, proof) = proof;
let FromBridgedChainMessagesProof {
ref nonces_start,
ref nonces_end,
..
} = proof;
let messages_count = nonces_end - nonces_start + 1;
let account_id = self.target_sign.signer.public().as_array_ref().clone().into();
let nonce = self.target_client.next_account_index(account_id).await?;
let call: rialto_runtime::Call = rialto_runtime::MessageLaneCall::receive_messages_proof(
self.relayer_id_at_source.clone(),
proof,
messages_count as _,
dispatch_weight,
)
.into();
let call_weight = call.get_dispatch_info().weight;
let transaction = Rialto::sign_transaction(&self.target_client, &self.target_sign.signer, nonce, call);
log::trace!(
target: "bridge",
"Prepared Millau -> Rialto delivery transaction. Weight: {}/{}, size: {}/{}",
call_weight,
bp_rialto::max_extrinsic_weight(),
transaction.encode().len(),
bp_rialto::max_extrinsic_size(),
);
Ok(transaction)
}
}
/// Millau node as messages source.
type MillauSourceClient = SubstrateMessagesSource<Millau, MillauMessagesToRialto>;
/// Rialto node as messages target.
type RialtoTargetClient = SubstrateMessagesTarget<Rialto, MillauMessagesToRialto>;
/// Run Millau-to-Rialto messages sync.
pub fn run(
millau_client: MillauClient,
millau_sign: MillauSigningParams,
rialto_client: RialtoClient,
rialto_sign: RialtoSigningParams,
lane_id: LaneId,
metrics_params: Option<MetricsParams>,
) {
let stall_timeout = Duration::from_secs(5 * 60);
let relayer_id_at_millau = millau_sign.signer.public().as_array_ref().clone().into();
let lane = MillauMessagesToRialto {
source_client: millau_client.clone(),
source_sign: millau_sign,
target_client: rialto_client.clone(),
target_sign: rialto_sign,
relayer_id_at_source: relayer_id_at_millau,
};
// 2/3 is reserved for proofs and tx overhead
let max_messages_size_in_single_batch = bp_rialto::max_extrinsic_size() as usize / 3;
// TODO: use Millau weights after https://github.com/paritytech/parity-bridges-common/issues/390
let (max_messages_in_single_batch, max_messages_weight_in_single_batch) =
select_delivery_transaction_limits::<pallet_message_lane::weights::RialtoWeight<millau_runtime::Runtime>>(
bp_rialto::max_extrinsic_weight(),
bp_rialto::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
);
log::info!(
target: "bridge",
"Starting Millau -> Rialto messages relay.\n\t\
Millau relayer account id: {:?}\n\t\
Max messages in single transaction: {}\n\t\
Max messages size in single transaction: {}\n\t\
Max messages weight in single transaction: {}",
lane.relayer_id_at_source,
max_messages_in_single_batch,
max_messages_size_in_single_batch,
max_messages_weight_in_single_batch,
);
messages_relay::message_lane_loop::run(
messages_relay::message_lane_loop::Params {
lane: lane_id,
source_tick: Millau::AVERAGE_BLOCK_INTERVAL,
target_tick: Rialto::AVERAGE_BLOCK_INTERVAL,
reconnect_delay: relay_utils::relay_loop::RECONNECT_DELAY,
stall_timeout,
delivery_params: messages_relay::message_lane_loop::MessageDeliveryParams {
max_unrewarded_relayer_entries_at_target: bp_rialto::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
max_unconfirmed_nonces_at_target: bp_rialto::MAX_UNCONFIRMED_MESSAGES_AT_INBOUND_LANE,
max_messages_in_single_batch,
max_messages_weight_in_single_batch,
max_messages_size_in_single_batch,
},
},
MillauSourceClient::new(millau_client, lane.clone(), lane_id, RIALTO_BRIDGE_INSTANCE),
RialtoTargetClient::new(rialto_client, lane, lane_id, MILLAU_BRIDGE_INSTANCE),
metrics_params,
futures::future::pending(),
);
}
@@ -0,0 +1,88 @@
// 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 <http://www.gnu.org/licenses/>.
//! Rialto-to-Millau headers sync entrypoint.
use crate::{
headers_pipeline::{SubstrateHeadersSyncPipeline, SubstrateHeadersToSubstrate},
MillauClient, RialtoClient,
};
use async_trait::async_trait;
use bp_rialto::{
BEST_RIALTO_BLOCKS_METHOD, FINALIZED_RIALTO_BLOCK_METHOD, INCOMPLETE_RIALTO_HEADERS_METHOD,
IS_KNOWN_RIALTO_BLOCK_METHOD,
};
use headers_relay::sync_types::QueuedHeader;
use relay_millau_client::{BridgeRialtoCall, Millau, SigningParams as MillauSigningParams};
use relay_rialto_client::{HeaderId as RialtoHeaderId, Rialto, SyncHeader as RialtoSyncHeader};
use relay_substrate_client::{Error as SubstrateError, TransactionSignScheme};
use sp_core::Pair;
use sp_runtime::Justification;
/// Rialto-to-Millau headers sync pipeline.
type RialtoHeadersToMillau = SubstrateHeadersToSubstrate<Rialto, RialtoSyncHeader, Millau, MillauSigningParams>;
/// Rialto header in-the-queue.
type QueuedRialtoHeader = QueuedHeader<RialtoHeadersToMillau>;
#[async_trait]
impl SubstrateHeadersSyncPipeline for RialtoHeadersToMillau {
const BEST_BLOCK_METHOD: &'static str = BEST_RIALTO_BLOCKS_METHOD;
const FINALIZED_BLOCK_METHOD: &'static str = FINALIZED_RIALTO_BLOCK_METHOD;
const IS_KNOWN_BLOCK_METHOD: &'static str = IS_KNOWN_RIALTO_BLOCK_METHOD;
const INCOMPLETE_HEADERS_METHOD: &'static str = INCOMPLETE_RIALTO_HEADERS_METHOD;
type SignedTransaction = <Millau as TransactionSignScheme>::SignedTransaction;
async fn make_submit_header_transaction(
&self,
header: QueuedRialtoHeader,
) -> Result<Self::SignedTransaction, SubstrateError> {
let account_id = self.target_sign.signer.public().as_array_ref().clone().into();
let nonce = self.target_client.next_account_index(account_id).await?;
let call = BridgeRialtoCall::import_signed_header(header.header().clone().into_inner()).into();
let transaction = Millau::sign_transaction(&self.target_client, &self.target_sign.signer, nonce, call);
Ok(transaction)
}
async fn make_complete_header_transaction(
&self,
id: RialtoHeaderId,
completion: Justification,
) -> Result<Self::SignedTransaction, SubstrateError> {
let account_id = self.target_sign.signer.public().as_array_ref().clone().into();
let nonce = self.target_client.next_account_index(account_id).await?;
let call = BridgeRialtoCall::finalize_header(id.1, completion).into();
let transaction = Millau::sign_transaction(&self.target_client, &self.target_sign.signer, nonce, call);
Ok(transaction)
}
}
/// Run Rialto-to-Millau headers sync.
pub async fn run(
rialto_client: RialtoClient,
millau_client: MillauClient,
millau_sign: MillauSigningParams,
metrics_params: Option<relay_utils::metrics::MetricsParams>,
) {
crate::headers_pipeline::run(
RialtoHeadersToMillau::new(millau_client.clone(), millau_sign),
rialto_client,
millau_client,
metrics_params,
)
.await;
}
@@ -0,0 +1,186 @@
// 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 <http://www.gnu.org/licenses/>.
//! Rialto-to-Millau messages sync entrypoint.
use crate::messages_lane::{select_delivery_transaction_limits, SubstrateMessageLane, SubstrateMessageLaneToSubstrate};
use crate::messages_source::SubstrateMessagesSource;
use crate::messages_target::SubstrateMessagesTarget;
use crate::{MillauClient, RialtoClient};
use async_trait::async_trait;
use bp_message_lane::{LaneId, MessageNonce};
use bp_runtime::{MILLAU_BRIDGE_INSTANCE, RIALTO_BRIDGE_INSTANCE};
use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
use codec::Encode;
use frame_support::dispatch::GetDispatchInfo;
use messages_relay::message_lane::MessageLane;
use relay_millau_client::{HeaderId as MillauHeaderId, Millau, SigningParams as MillauSigningParams};
use relay_rialto_client::{HeaderId as RialtoHeaderId, Rialto, SigningParams as RialtoSigningParams};
use relay_substrate_client::{Chain, Error as SubstrateError, TransactionSignScheme};
use relay_utils::metrics::MetricsParams;
use sp_core::Pair;
use std::{ops::RangeInclusive, time::Duration};
/// Rialto-to-Millau message lane.
type RialtoMessagesToMillau = SubstrateMessageLaneToSubstrate<Rialto, RialtoSigningParams, Millau, MillauSigningParams>;
#[async_trait]
impl SubstrateMessageLane for RialtoMessagesToMillau {
const OUTBOUND_LANE_MESSAGES_DISPATCH_WEIGHT_METHOD: &'static str =
bp_millau::TO_MILLAU_MESSAGES_DISPATCH_WEIGHT_METHOD;
const OUTBOUND_LANE_LATEST_GENERATED_NONCE_METHOD: &'static str =
bp_millau::TO_MILLAU_LATEST_GENERATED_NONCE_METHOD;
const OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str = bp_millau::TO_MILLAU_LATEST_RECEIVED_NONCE_METHOD;
const INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str = bp_rialto::FROM_RIALTO_LATEST_RECEIVED_NONCE_METHOD;
const INBOUND_LANE_LATEST_CONFIRMED_NONCE_METHOD: &'static str =
bp_rialto::FROM_RIALTO_LATEST_CONFIRMED_NONCE_METHOD;
const INBOUND_LANE_UNREWARDED_RELAYERS_STATE: &'static str = bp_rialto::FROM_RIALTO_UNREWARDED_RELAYERS_STATE;
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_rialto::FINALIZED_RIALTO_BLOCK_METHOD;
const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str = bp_millau::FINALIZED_MILLAU_BLOCK_METHOD;
type SourceSignedTransaction = <Rialto as TransactionSignScheme>::SignedTransaction;
type TargetSignedTransaction = <Millau as TransactionSignScheme>::SignedTransaction;
async fn make_messages_receiving_proof_transaction(
&self,
_generated_at_block: MillauHeaderId,
proof: <Self as MessageLane>::MessagesReceivingProof,
) -> Result<Self::SourceSignedTransaction, SubstrateError> {
let (relayers_state, proof) = proof;
let account_id = self.source_sign.signer.public().as_array_ref().clone().into();
let nonce = self.source_client.next_account_index(account_id).await?;
let call: rialto_runtime::Call =
rialto_runtime::MessageLaneCall::receive_messages_delivery_proof(proof, relayers_state).into();
let call_weight = call.get_dispatch_info().weight;
let transaction = Rialto::sign_transaction(&self.source_client, &self.source_sign.signer, nonce, call);
log::trace!(
target: "bridge",
"Prepared Millau -> Rialto confirmation transaction. Weight: {}/{}, size: {}/{}",
call_weight,
bp_rialto::max_extrinsic_weight(),
transaction.encode().len(),
bp_rialto::max_extrinsic_size(),
);
Ok(transaction)
}
async fn make_messages_delivery_transaction(
&self,
_generated_at_header: RialtoHeaderId,
_nonces: RangeInclusive<MessageNonce>,
proof: <Self as MessageLane>::MessagesProof,
) -> Result<Self::TargetSignedTransaction, SubstrateError> {
let (dispatch_weight, proof) = proof;
let FromBridgedChainMessagesProof {
ref nonces_start,
ref nonces_end,
..
} = proof;
let messages_count = nonces_end - nonces_start + 1;
let account_id = self.target_sign.signer.public().as_array_ref().clone().into();
let nonce = self.target_client.next_account_index(account_id).await?;
let call: millau_runtime::Call = millau_runtime::MessageLaneCall::receive_messages_proof(
self.relayer_id_at_source.clone(),
proof,
messages_count as _,
dispatch_weight,
)
.into();
let call_weight = call.get_dispatch_info().weight;
let transaction = Millau::sign_transaction(&self.target_client, &self.target_sign.signer, nonce, call);
log::trace!(
target: "bridge",
"Prepared Rialto -> Millau delivery transaction. Weight: {}/{}, size: {}/{}",
call_weight,
bp_millau::max_extrinsic_weight(),
transaction.encode().len(),
bp_millau::max_extrinsic_size(),
);
Ok(transaction)
}
}
/// Rialto node as messages source.
type RialtoSourceClient = SubstrateMessagesSource<Rialto, RialtoMessagesToMillau>;
/// Millau node as messages target.
type MillauTargetClient = SubstrateMessagesTarget<Millau, RialtoMessagesToMillau>;
/// Run Rialto-to-Millau messages sync.
pub fn run(
rialto_client: RialtoClient,
rialto_sign: RialtoSigningParams,
millau_client: MillauClient,
millau_sign: MillauSigningParams,
lane_id: LaneId,
metrics_params: Option<MetricsParams>,
) {
let stall_timeout = Duration::from_secs(5 * 60);
let relayer_id_at_rialto = rialto_sign.signer.public().as_array_ref().clone().into();
let lane = RialtoMessagesToMillau {
source_client: rialto_client.clone(),
source_sign: rialto_sign,
target_client: millau_client.clone(),
target_sign: millau_sign,
relayer_id_at_source: relayer_id_at_rialto,
};
// 2/3 is reserved for proofs and tx overhead
let max_messages_size_in_single_batch = bp_millau::max_extrinsic_size() as usize / 3;
let (max_messages_in_single_batch, max_messages_weight_in_single_batch) =
select_delivery_transaction_limits::<pallet_message_lane::weights::RialtoWeight<rialto_runtime::Runtime>>(
bp_millau::max_extrinsic_weight(),
bp_millau::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
);
log::info!(
target: "bridge",
"Starting Rialto -> Millau messages relay.\n\t\
Rialto relayer account id: {:?}\n\t\
Max messages in single transaction: {}\n\t\
Max messages size in single transaction: {}\n\t\
Max messages weight in single transaction: {}",
lane.relayer_id_at_source,
max_messages_in_single_batch,
max_messages_size_in_single_batch,
max_messages_weight_in_single_batch,
);
messages_relay::message_lane_loop::run(
messages_relay::message_lane_loop::Params {
lane: lane_id,
source_tick: Rialto::AVERAGE_BLOCK_INTERVAL,
target_tick: Millau::AVERAGE_BLOCK_INTERVAL,
reconnect_delay: relay_utils::relay_loop::RECONNECT_DELAY,
stall_timeout,
delivery_params: messages_relay::message_lane_loop::MessageDeliveryParams {
max_unrewarded_relayer_entries_at_target: bp_millau::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
max_unconfirmed_nonces_at_target: bp_millau::MAX_UNCONFIRMED_MESSAGES_AT_INBOUND_LANE,
max_messages_in_single_batch,
max_messages_weight_in_single_batch,
max_messages_size_in_single_batch,
},
},
RialtoSourceClient::new(rialto_client, lane.clone(), lane_id, MILLAU_BRIDGE_INSTANCE),
MillauTargetClient::new(millau_client, lane, lane_id, RIALTO_BRIDGE_INSTANCE),
metrics_params,
futures::future::pending(),
);
}
+22
View File
@@ -0,0 +1,22 @@
[package]
name = "relay-utils"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
ansi_term = "0.12"
async-std = "1.6.5"
async-trait = "0.1.40"
backoff = "0.2"
env_logger = "0.8.2"
futures = "0.3.5"
log = "0.4.11"
num-traits = "0.2"
sysinfo = "0.15"
time = "0.2"
# Substrate dependencies
substrate-prometheus-endpoint = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
@@ -0,0 +1,59 @@
// 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 <http://www.gnu.org/licenses/>.
//! Relayer initialization functions.
use std::io::Write;
/// Initialize relay environment.
pub fn initialize_relay() {
let mut builder = env_logger::Builder::new();
let filters = match std::env::var("RUST_LOG") {
Ok(env_filters) => format!("bridge=info,{}", env_filters),
Err(_) => "bridge=info".into(),
};
builder.parse_filters(&filters);
builder.format(move |buf, record| {
writeln!(buf, "{}", {
let timestamp = time::OffsetDateTime::try_now_local()
.unwrap_or_else(|_| time::OffsetDateTime::now_utc())
.format("%Y-%m-%d %H:%M:%S %z");
if cfg!(windows) {
format!("{} {} {} {}", timestamp, record.level(), record.target(), record.args())
} else {
use ansi_term::Colour as Color;
let log_level = match record.level() {
log::Level::Error => Color::Fixed(9).bold().paint(record.level().to_string()),
log::Level::Warn => Color::Fixed(11).bold().paint(record.level().to_string()),
log::Level::Info => Color::Fixed(10).paint(record.level().to_string()),
log::Level::Debug => Color::Fixed(14).paint(record.level().to_string()),
log::Level::Trace => Color::Fixed(12).paint(record.level().to_string()),
};
format!(
"{} {} {} {}",
Color::Fixed(8).bold().paint(timestamp),
log_level,
Color::Fixed(8).paint(record.target()),
record.args()
)
}
})
});
builder.init();
}
+275
View File
@@ -0,0 +1,275 @@
// 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 <http://www.gnu.org/licenses/>.
//! Utilities used by different relays.
use backoff::{backoff::Backoff, ExponentialBackoff};
use futures::future::FutureExt;
use std::time::Duration;
/// Max delay after connection-unrelated error happened before we'll try the
/// same request again.
pub const MAX_BACKOFF_INTERVAL: Duration = Duration::from_secs(60);
/// Delay after connection-related error happened before we'll try
/// reconnection again.
pub const CONNECTION_ERROR_DELAY: Duration = Duration::from_secs(10);
pub mod initialize;
pub mod metrics;
pub mod relay_loop;
/// Block number traits shared by all chains that relay is able to serve.
pub trait BlockNumberBase:
'static
+ From<u32>
+ Into<u64>
+ Ord
+ Clone
+ Copy
+ Default
+ Send
+ Sync
+ std::fmt::Debug
+ std::fmt::Display
+ std::hash::Hash
+ std::ops::Add<Output = Self>
+ std::ops::Sub<Output = Self>
+ num_traits::CheckedSub
+ num_traits::Saturating
+ num_traits::Zero
+ num_traits::One
{
}
impl<T> BlockNumberBase for T where
T: 'static
+ From<u32>
+ Into<u64>
+ Ord
+ Clone
+ Copy
+ Default
+ Send
+ Sync
+ std::fmt::Debug
+ std::fmt::Display
+ std::hash::Hash
+ std::ops::Add<Output = Self>
+ std::ops::Sub<Output = Self>
+ num_traits::CheckedSub
+ num_traits::Saturating
+ num_traits::Zero
+ num_traits::One
{
}
/// Macro that returns (client, Err(error)) tuple from function if result is Err(error).
#[macro_export]
macro_rules! bail_on_error {
($result: expr) => {
match $result {
(client, Ok(result)) => (client, result),
(client, Err(error)) => return (client, Err(error)),
}
};
}
/// Macro that returns (client, Err(error)) tuple from function if result is Err(error).
#[macro_export]
macro_rules! bail_on_arg_error {
($result: expr, $client: ident) => {
match $result {
Ok(result) => result,
Err(error) => return ($client, Err(error)),
}
};
}
/// Ethereum header Id.
#[derive(Debug, Default, Clone, Copy, Eq, Hash, PartialEq)]
pub struct HeaderId<Hash, Number>(pub Number, pub Hash);
/// 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 {
ExponentialBackoff {
// we do not want relayer to stop
max_elapsed_time: None,
max_interval: MAX_BACKOFF_INTERVAL,
..Default::default()
}
}
/// Compact format of IDs vector.
pub fn format_ids<Id: std::fmt::Debug>(mut ids: impl ExactSizeIterator<Item = Id>) -> String {
const NTH_PROOF: &str = "we have checked len; qed";
match ids.len() {
0 => "<nothing>".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)
}
}
}
/// Stream that emits item every `timeout_ms` milliseconds.
pub fn interval(timeout: Duration) -> impl futures::Stream<Item = ()> {
futures::stream::unfold((), move |_| async move {
async_std::task::sleep(timeout).await;
Some(((), ()))
})
}
/// Which client has caused error.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum FailedClient {
/// It is the source client who has caused error.
Source,
/// It is the target client who has caused error.
Target,
/// Both clients are failing, or we just encountered some other error that
/// should be treated like that.
Both,
}
/// Future process result.
#[derive(Debug, Clone, Copy)]
pub enum ProcessFutureResult {
/// Future has been processed successfully.
Success,
/// Future has failed with non-connection error.
Failed,
/// Future has failed with connection error.
ConnectionFailed,
}
impl ProcessFutureResult {
/// Returns true if result is Success.
pub fn is_ok(self) -> bool {
match self {
ProcessFutureResult::Success => true,
ProcessFutureResult::Failed | ProcessFutureResult::ConnectionFailed => false,
}
}
/// Returns Ok(true) if future has succeeded.
/// Returns Ok(false) if future has failed with non-connection error.
/// Returns Err if future is `ConnectionFailed`.
pub fn fail_if_connection_error(self, failed_client: FailedClient) -> Result<bool, FailedClient> {
match self {
ProcessFutureResult::Success => Ok(true),
ProcessFutureResult::Failed => Ok(false),
ProcessFutureResult::ConnectionFailed => Err(failed_client),
}
}
}
/// Process result of the future from a client.
pub fn process_future_result<TResult, TError, TGoOfflineFuture>(
result: Result<TResult, TError>,
retry_backoff: &mut ExponentialBackoff,
on_success: impl FnOnce(TResult),
go_offline_future: &mut std::pin::Pin<&mut futures::future::Fuse<TGoOfflineFuture>>,
go_offline: impl FnOnce(Duration) -> TGoOfflineFuture,
error_pattern: impl FnOnce() -> String,
) -> ProcessFutureResult
where
TError: std::fmt::Debug + MaybeConnectionError,
TGoOfflineFuture: FutureExt,
{
match result {
Ok(result) => {
on_success(result);
retry_backoff.reset();
ProcessFutureResult::Success
}
Err(error) if error.is_connection_error() => {
log::error!(
target: "bridge",
"{}: {:?}. Going to restart",
error_pattern(),
error,
);
retry_backoff.reset();
go_offline_future.set(go_offline(CONNECTION_ERROR_DELAY).fuse());
ProcessFutureResult::ConnectionFailed
}
Err(error) => {
let retry_delay = retry_backoff.next_backoff().unwrap_or(CONNECTION_ERROR_DELAY);
log::error!(
target: "bridge",
"{}: {:?}. Retrying in {}",
error_pattern(),
error,
retry_delay.as_secs_f64(),
);
go_offline_future.set(go_offline(retry_delay).fuse());
ProcessFutureResult::Failed
}
}
}
@@ -0,0 +1,168 @@
// 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 <http://www.gnu.org/licenses/>.
pub use substrate_prometheus_endpoint::{register, Counter, CounterVec, Gauge, GaugeVec, Opts, Registry, F64, U64};
use async_std::sync::{Arc, Mutex};
use std::net::SocketAddr;
use substrate_prometheus_endpoint::init_prometheus;
use sysinfo::{ProcessExt, RefreshKind, System, SystemExt};
/// Prometheus endpoint MetricsParams.
#[derive(Debug, Clone)]
pub struct MetricsParams {
/// Serve HTTP requests at given host.
pub host: String,
/// Serve HTTP requests at given port.
pub port: u16,
}
/// Metrics API.
pub trait Metrics {
/// Register metrics in the registry.
fn register(&self, registry: &Registry) -> Result<(), String>;
}
/// Global Prometheus metrics.
#[derive(Debug, Clone)]
pub struct GlobalMetrics {
system: Arc<Mutex<System>>,
system_average_load: GaugeVec<F64>,
process_cpu_usage_percentage: Gauge<F64>,
process_memory_usage_bytes: Gauge<U64>,
}
/// Start Prometheus endpoint with given metrics registry.
pub fn start(
prefix: String,
params: Option<MetricsParams>,
global_metrics: &GlobalMetrics,
extra_metrics: &impl Metrics,
) {
let params = match params {
Some(params) => params,
None => return,
};
assert!(!prefix.is_empty(), "Metrics prefix can not be empty");
let do_start = move || {
let prometheus_socket_addr = SocketAddr::new(
params
.host
.parse()
.map_err(|err| format!("Invalid Prometheus host {}: {}", params.host, err))?,
params.port,
);
let metrics_registry =
Registry::new_custom(Some(prefix), None).expect("only fails if prefix is empty; prefix is not empty; qed");
global_metrics.register(&metrics_registry)?;
extra_metrics.register(&metrics_registry)?;
async_std::task::spawn(async move {
init_prometheus(prometheus_socket_addr, metrics_registry)
.await
.map_err(|err| format!("Error starting Prometheus endpoint: {}", err))
});
Ok(())
};
let result: Result<(), String> = do_start();
if let Err(err) = result {
log::warn!(
target: "bridge",
"Failed to expose metrics: {}",
err,
);
}
}
impl Default for MetricsParams {
fn default() -> Self {
MetricsParams {
host: "127.0.0.1".into(),
port: 9616,
}
}
}
impl Metrics for GlobalMetrics {
fn register(&self, registry: &Registry) -> Result<(), String> {
register(self.system_average_load.clone(), registry).map_err(|e| e.to_string())?;
register(self.process_cpu_usage_percentage.clone(), registry).map_err(|e| e.to_string())?;
register(self.process_memory_usage_bytes.clone(), registry).map_err(|e| e.to_string())?;
Ok(())
}
}
impl Default for GlobalMetrics {
fn default() -> Self {
GlobalMetrics {
system: Arc::new(Mutex::new(System::new_with_specifics(RefreshKind::everything()))),
system_average_load: GaugeVec::new(Opts::new("system_average_load", "System load average"), &["over"])
.expect("metric is static and thus valid; qed"),
process_cpu_usage_percentage: Gauge::new("process_cpu_usage_percentage", "Process CPU usage")
.expect("metric is static and thus valid; qed"),
process_memory_usage_bytes: Gauge::new(
"process_memory_usage_bytes",
"Process memory (resident set size) usage",
)
.expect("metric is static and thus valid; qed"),
}
}
}
impl GlobalMetrics {
/// Update metrics.
pub async fn update(&self) {
// update system-wide metrics
let mut system = self.system.lock().await;
let load = system.get_load_average();
self.system_average_load.with_label_values(&["1min"]).set(load.one);
self.system_average_load.with_label_values(&["5min"]).set(load.five);
self.system_average_load.with_label_values(&["15min"]).set(load.fifteen);
// update process-related metrics
let pid = sysinfo::get_current_pid().expect(
"only fails where pid is unavailable (os=unknown || arch=wasm32);\
relay is not supposed to run in such MetricsParamss;\
qed",
);
let is_process_refreshed = system.refresh_process(pid);
match (is_process_refreshed, system.get_process(pid)) {
(true, Some(process_info)) => {
let cpu_usage = process_info.cpu_usage() as f64;
let memory_usage = process_info.memory() * 1024;
log::trace!(
target: "bridge-metrics",
"Refreshed process metrics: CPU={}, memory={}",
cpu_usage,
memory_usage,
);
self.process_cpu_usage_percentage
.set(if cpu_usage.is_finite() { cpu_usage } else { 0f64 });
self.process_memory_usage_bytes.set(memory_usage);
}
_ => {
log::warn!(
target: "bridge",
"Failed to refresh process information. Metrics may show obsolete values",
);
}
}
}
}
@@ -0,0 +1,95 @@
// 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 <http://www.gnu.org/licenses/>.
use crate::{FailedClient, MaybeConnectionError};
use async_trait::async_trait;
use std::{fmt::Debug, future::Future, time::Duration};
/// Default pause between reconnect attempts.
pub const RECONNECT_DELAY: Duration = Duration::from_secs(10);
/// Basic blockchain client from relay perspective.
#[async_trait]
pub trait Client: Clone + Send + Sync {
/// Type of error this clients returns.
type Error: Debug + MaybeConnectionError;
/// Try to reconnect to source node.
async fn reconnect(&mut self) -> Result<(), Self::Error>;
}
/// Run relay loop.
///
/// This function represents an outer loop, which in turn calls provided `loop_run` function to do
/// actual job. When `loop_run` returns, this outer loop reconnects to failed client (source,
/// target or both) and calls `loop_run` again.
pub fn run<SC: Client, TC: Client, R, F>(
reconnect_delay: Duration,
mut source_client: SC,
mut target_client: TC,
loop_run: R,
) where
R: Fn(SC, TC) -> F,
F: Future<Output = Result<(), FailedClient>>,
{
let mut local_pool = futures::executor::LocalPool::new();
local_pool.run_until(async move {
loop {
let result = loop_run(source_client.clone(), target_client.clone()).await;
match result {
Ok(()) => break,
Err(failed_client) => loop {
async_std::task::sleep(reconnect_delay).await;
if failed_client == FailedClient::Both || failed_client == FailedClient::Source {
match source_client.reconnect().await {
Ok(()) => (),
Err(error) => {
log::warn!(
target: "bridge",
"Failed to reconnect to source client. Going to retry in {}s: {:?}",
reconnect_delay.as_secs(),
error,
);
continue;
}
}
}
if failed_client == FailedClient::Both || failed_client == FailedClient::Target {
match target_client.reconnect().await {
Ok(()) => (),
Err(error) => {
log::warn!(
target: "bridge",
"Failed to reconnect to target client. Going to retry in {}s: {:?}",
reconnect_delay.as_secs(),
error,
);
continue;
}
}
}
break;
},
}
log::debug!(target: "bridge", "Restarting relay loop");
}
});
}