mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 15:11:02 +00:00
Merge commit '114f487fd9daef4b4cd791446372a9a690c137ac' into update-bridges-subtree-r/w
This commit is contained in:
@@ -1,42 +0,0 @@
|
||||
[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]
|
||||
anyhow = "1.0"
|
||||
async-std = "1.9.0"
|
||||
async-trait = "0.1.42"
|
||||
clap = { version = "2.33.3", features = ["yaml"] }
|
||||
codec = { package = "parity-scale-codec", version = "2.2.0" }
|
||||
ethabi = { git = "https://github.com/svyatonik/ethabi.git", branch = "bump-deps" }
|
||||
ethabi-contract = { git = "https://github.com/svyatonik/ethabi.git", branch = "bump-deps" }
|
||||
ethabi-derive = { git = "https://github.com/svyatonik/ethabi.git", branch = "bump-deps" }
|
||||
futures = "0.3.12"
|
||||
hex = "0.4"
|
||||
hex-literal = "0.3"
|
||||
libsecp256k1 = { version = "0.7", default-features = false, features = ["hmac"] }
|
||||
log = "0.4.14"
|
||||
num-traits = "0.2"
|
||||
serde_json = "1.0.64"
|
||||
thiserror = "1.0.26"
|
||||
|
||||
# Bridge dependencies
|
||||
|
||||
bp-currency-exchange = { path = "../../primitives/currency-exchange" }
|
||||
bp-eth-poa = { path = "../../primitives/ethereum-poa" }
|
||||
exchange-relay = { path = "../exchange" }
|
||||
headers-relay = { path = "../headers" }
|
||||
relay-ethereum-client = { path = "../client-ethereum" }
|
||||
relay-rialto-client = { path = "../client-rialto" }
|
||||
relay-substrate-client = { path = "../client-substrate" }
|
||||
relay-utils = { path = "../utils" }
|
||||
rialto-runtime = { path = "../../bin/rialto/runtime" }
|
||||
|
||||
# Substrate Dependencies
|
||||
|
||||
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
@@ -1,7 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,167 +0,0 @@
|
||||
[
|
||||
{
|
||||
"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
@@ -1,5 +0,0 @@
|
||||
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`
|
||||
@@ -1,166 +0,0 @@
|
||||
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: ð-host
|
||||
long: eth-host
|
||||
value_name: ETH_HOST
|
||||
help: Connect to Ethereum node websocket server at given host.
|
||||
takes_value: true
|
||||
- eth-port: ð-port
|
||||
long: eth-port
|
||||
value_name: ETH_PORT
|
||||
help: Connect to Ethereum node websocket server at given port.
|
||||
takes_value: true
|
||||
- sub-host: &sub-host
|
||||
long: sub-host
|
||||
value_name: SUB_HOST
|
||||
help: Connect to Substrate node websocket server 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: ð-chain-id
|
||||
long: eth-chain-id
|
||||
value_name: ETH_CHAIN_ID
|
||||
help: Chain ID to use for signing.
|
||||
- eth-signer: ð-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
|
||||
@@ -1,38 +0,0 @@
|
||||
// Copyright 2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::rpc_errors::RpcError;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Result type used by PoA relay.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Ethereum PoA relay errors.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
/// Failed to decode initial header.
|
||||
#[error("Error decoding initial header: {0}")]
|
||||
DecodeInitialHeader(codec::Error),
|
||||
/// RPC error.
|
||||
#[error("{0}")]
|
||||
Rpc(#[from] RpcError),
|
||||
/// Failed to read genesis header.
|
||||
#[error("Error reading Substrate genesis header: {0:?}")]
|
||||
ReadGenesisHeader(relay_substrate_client::Error),
|
||||
/// Failed to read initial GRANDPA authorities.
|
||||
#[error("Error reading GRANDPA authorities set: {0:?}")]
|
||||
ReadAuthorities(relay_substrate_client::Error),
|
||||
/// Failed to deploy bridge contract to Ethereum chain.
|
||||
#[error("Error deploying contract: {0:?}")]
|
||||
DeployContract(RpcError),
|
||||
}
|
||||
@@ -1,631 +0,0 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{rpc_errors::RpcError, 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::EncodedJustification;
|
||||
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 the 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: EncodedJustification,
|
||||
) -> 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(¶ms.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: EncodedJustification,
|
||||
) -> RpcResult<RialtoHeaderId> {
|
||||
let _ = self
|
||||
.submit_ethereum_transaction(
|
||||
¶ms,
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{
|
||||
error::{Error, Result},
|
||||
ethereum_client::{bridge_contract, EthereumHighLevelRpc},
|
||||
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 async fn run(params: EthereumDeployContractParams) {
|
||||
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 = async move {
|
||||
let eth_client = EthereumClient::try_connect(eth_params).await.map_err(RpcError::Ethereum)?;
|
||||
let sub_client = SubstrateClient::<Rialto>::try_connect(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(
|
||||
ð_client,
|
||||
ð_sign,
|
||||
eth_contract_code,
|
||||
initial_header,
|
||||
initial_set_id,
|
||||
initial_set,
|
||||
).await
|
||||
}.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>)> {
|
||||
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(Error::DecodeInitialHeader(error)),
|
||||
}
|
||||
},
|
||||
None => {
|
||||
let initial_header = sub_client.header_by_number(Zero::zero()).await;
|
||||
initial_header
|
||||
.map(|header| (HeaderId(Zero::zero(), header.hash()), header.encode()))
|
||||
.map_err(Error::ReadGenesisHeader)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
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::ReadAuthorities)
|
||||
}
|
||||
|
||||
/// 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<()> {
|
||||
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::DeployContract)
|
||||
}
|
||||
@@ -1,402 +0,0 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Relaying proofs of PoA -> Substrate exchange transactions.
|
||||
|
||||
use crate::{
|
||||
instances::BridgeInstance,
|
||||
rialto_client::{SubmitEthereumExchangeTransactionProof, SubstrateHighLevelRpc},
|
||||
rpc_errors::RpcError,
|
||||
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,
|
||||
},
|
||||
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.
|
||||
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: MetricsParams,
|
||||
/// Instance of the bridge pallet being synchronized.
|
||||
pub instance: Arc<dyn BridgeInstance>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for EthereumExchangeParams {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
f.debug_struct("EthereumExchangeParams")
|
||||
.field("eth_params", &self.eth_params)
|
||||
.field("sub_params", &self.sub_params)
|
||||
.field("sub_sign", &sp_core::Pair::public(&self.sub_sign))
|
||||
.field("mode", &self.mode)
|
||||
.field("metrics_params", &self.metrics_params)
|
||||
.field("instance", &self.instance)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// 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().await.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
#[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 async fn run(params: EthereumExchangeParams) {
|
||||
match params.mode {
|
||||
ExchangeRelayMode::Single(eth_tx_hash) => {
|
||||
let result = run_single_transaction_relay(params, 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,
|
||||
),
|
||||
}
|
||||
},
|
||||
ExchangeRelayMode::Auto(eth_start_with_block_number) => {
|
||||
let result =
|
||||
run_auto_transactions_relay_loop(params, eth_start_with_block_number).await;
|
||||
if let Err(err) = result {
|
||||
log::error!(
|
||||
target: "bridge",
|
||||
"Error auto-relaying Ethereum transactions proofs to Substrate node: {}",
|
||||
err,
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Run single transaction proof relay and stop.
|
||||
async fn run_single_transaction_relay(
|
||||
params: EthereumExchangeParams,
|
||||
eth_tx_hash: H256,
|
||||
) -> anyhow::Result<()> {
|
||||
let EthereumExchangeParams { eth_params, sub_params, sub_sign, instance, .. } = params;
|
||||
|
||||
let eth_client = EthereumClient::try_connect(eth_params).await.map_err(RpcError::Ethereum)?;
|
||||
let sub_client = SubstrateClient::<Rialto>::try_connect(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
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn run_auto_transactions_relay_loop(
|
||||
params: EthereumExchangeParams,
|
||||
eth_start_with_block_number: Option<u64>,
|
||||
) -> anyhow::Result<()> {
|
||||
let EthereumExchangeParams {
|
||||
eth_params, sub_params, sub_sign, metrics_params, instance, ..
|
||||
} = params;
|
||||
|
||||
let eth_client = EthereumClient::new(eth_params).await;
|
||||
let sub_client = SubstrateClient::<Rialto>::new(sub_params).await;
|
||||
|
||||
let eth_start_with_block_number = match eth_start_with_block_number {
|
||||
Some(eth_start_with_block_number) => eth_start_with_block_number,
|
||||
None =>
|
||||
sub_client
|
||||
.best_ethereum_finalized_block()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
anyhow::format_err!(
|
||||
"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(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Submitting Ethereum -> Substrate exchange transactions.
|
||||
|
||||
use anyhow::anyhow;
|
||||
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 async fn run(params: EthereumExchangeSubmitParams) {
|
||||
let EthereumExchangeSubmitParams { eth_params, eth_sign, eth_nonce, eth_amount, sub_recipient } =
|
||||
params;
|
||||
|
||||
let result: anyhow::Result<_> = async move {
|
||||
let eth_client = EthereumClient::try_connect(eth_params)
|
||||
.await
|
||||
.map_err(|err| anyhow!("error connecting to Ethereum node: {:?}", err))?;
|
||||
|
||||
let eth_signer_address = secret_to_address(ð_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| anyhow!("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| anyhow!("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(ð_sign.signer, Some(eth_sign.chain_id));
|
||||
eth_client
|
||||
.submit_transaction(eth_tx_signed)
|
||||
.await
|
||||
.map_err(|err| anyhow!("error submitting transaction: {:?}", err))?;
|
||||
|
||||
Ok(eth_tx_unsigned)
|
||||
}
|
||||
.await;
|
||||
|
||||
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,
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,299 +0,0 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Ethereum PoA -> Rialto-Substrate synchronization.
|
||||
|
||||
use crate::{
|
||||
ethereum_client::EthereumHighLevelRpc,
|
||||
instances::BridgeInstance,
|
||||
rialto_client::{SubmitEthereumHeaders, SubstrateHighLevelRpc},
|
||||
rpc_errors::RpcError,
|
||||
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::{collections::HashSet, fmt::Debug, 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.
|
||||
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: MetricsParams,
|
||||
/// Instance of the bridge pallet being synchronized.
|
||||
pub instance: Arc<dyn BridgeInstance>,
|
||||
}
|
||||
|
||||
impl Debug for EthereumSyncParams {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
f.debug_struct("EthereumSyncParams")
|
||||
.field("eth_params", &self.eth_params)
|
||||
.field("sub_params", &self.sub_params)
|
||||
.field("sub_sign", &sp_core::Pair::public(&self.sub_sign))
|
||||
.field("sync_params", &self.sync_params)
|
||||
.field("metrics_params", &self.metrics_params)
|
||||
.field("instance", &self.instance)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// 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().await.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
#[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 async 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).await;
|
||||
let sub_client = 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(),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| RpcError::SyncLoop(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
// Copyright 2020-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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,
|
||||
substrate_types::{into_substrate_ethereum_header, into_substrate_ethereum_receipts},
|
||||
};
|
||||
|
||||
use rialto_runtime::{exchange::EthereumTransactionInclusionProof as Proof, 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_with_receipts: 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 {
|
||||
header: Box::new(into_substrate_ethereum_header(header.header())),
|
||||
receipts: 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_with_receipts: 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 {
|
||||
header: Box::new(into_substrate_ethereum_header(header.header())),
|
||||
receipts: 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)
|
||||
}
|
||||
}
|
||||
@@ -1,424 +0,0 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#![recursion_limit = "1024"]
|
||||
|
||||
mod error;
|
||||
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 anyhow::anyhow;
|
||||
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 libsecp256k1::SecretKey;
|
||||
use relay_utils::{
|
||||
initialize::initialize_relay,
|
||||
metrics::{MetricsAddress, MetricsParams},
|
||||
};
|
||||
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();
|
||||
async_std::task::block_on(run_command(&matches));
|
||||
}
|
||||
|
||||
async fn run_command(matches: &clap::ArgMatches<'_>) {
|
||||
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
|
||||
},
|
||||
})
|
||||
.await
|
||||
.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
|
||||
},
|
||||
})
|
||||
.await
|
||||
.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
|
||||
},
|
||||
},
|
||||
)
|
||||
.await;
|
||||
},
|
||||
("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
|
||||
},
|
||||
},
|
||||
)
|
||||
.await;
|
||||
},
|
||||
("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
|
||||
},
|
||||
})
|
||||
.await;
|
||||
},
|
||||
("", _) => {
|
||||
log::error!(target: "bridge", "No subcommand specified");
|
||||
},
|
||||
_ => unreachable!("all possible subcommands are checked above; qed"),
|
||||
}
|
||||
}
|
||||
|
||||
fn ethereum_connection_params(
|
||||
matches: &clap::ArgMatches,
|
||||
) -> anyhow::Result<EthereumConnectionParams> {
|
||||
let mut params = EthereumConnectionParams::default();
|
||||
if let Some(eth_host) = matches.value_of("eth-host") {
|
||||
params.host = eth_host.into();
|
||||
}
|
||||
if let Some(eth_port) = matches.value_of("eth-port") {
|
||||
params.port = eth_port.parse().map_err(|e| anyhow!("Failed to parse eth-port: {}", e))?;
|
||||
}
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
fn ethereum_signing_params(matches: &clap::ArgMatches) -> anyhow::Result<EthereumSigningParams> {
|
||||
let mut params = EthereumSigningParams::default();
|
||||
if let Some(eth_signer) = matches.value_of("eth-signer") {
|
||||
params.signer = SecretKey::parse_slice(
|
||||
&hex::decode(eth_signer).map_err(|e| anyhow!("Failed to parse eth-signer: {}", e))?,
|
||||
)
|
||||
.map_err(|e| anyhow!("Invalid eth-signer: {}", e))?;
|
||||
}
|
||||
if let Some(eth_chain_id) = matches.value_of("eth-chain-id") {
|
||||
params.chain_id = eth_chain_id
|
||||
.parse::<u64>()
|
||||
.map_err(|e| anyhow!("Failed to parse eth-chain-id: {}", e))?;
|
||||
}
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
fn substrate_connection_params(
|
||||
matches: &clap::ArgMatches,
|
||||
) -> anyhow::Result<SubstrateConnectionParams> {
|
||||
let mut params = SubstrateConnectionParams::default();
|
||||
if let Some(sub_host) = matches.value_of("sub-host") {
|
||||
params.host = sub_host.into();
|
||||
}
|
||||
if let Some(sub_port) = matches.value_of("sub-port") {
|
||||
params.port = sub_port.parse().map_err(|e| anyhow!("Failed to parse sub-port: {}", e))?;
|
||||
}
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
fn rialto_signing_params(matches: &clap::ArgMatches) -> anyhow::Result<RialtoSigningParams> {
|
||||
let mut params = sp_keyring::AccountKeyring::Alice.pair();
|
||||
|
||||
if let Some(sub_signer) = matches.value_of("sub-signer") {
|
||||
let sub_signer_password = matches.value_of("sub-signer-password");
|
||||
params = sp_core::sr25519::Pair::from_string(sub_signer, sub_signer_password)
|
||||
.map_err(|e| anyhow!("Failed to parse sub-signer: {:?}", e))?;
|
||||
}
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
fn ethereum_sync_params(matches: &clap::ArgMatches) -> anyhow::Result<EthereumSyncParams> {
|
||||
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(anyhow!("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) -> anyhow::Result<SubstrateSyncParams> {
|
||||
use crate::substrate_sync_loop::consts::*;
|
||||
|
||||
let eth_contract_address: relay_ethereum_client::types::Address =
|
||||
if let Some(eth_contract) = matches.value_of("eth-contract") {
|
||||
eth_contract.parse()?
|
||||
} 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,
|
||||
) -> anyhow::Result<EthereumDeployContractParams> {
|
||||
let eth_contract_code =
|
||||
parse_hex_argument(matches, "eth-contract-code")?.unwrap_or_else(|| {
|
||||
hex::decode(include_str!("../res/substrate-bridge-bytecode.hex"))
|
||||
.expect("code is hardcoded, thus valid; qed")
|
||||
});
|
||||
let sub_initial_authorities_set_id = matches
|
||||
.value_of("sub-authorities-set-id")
|
||||
.map(|set| {
|
||||
set.parse()
|
||||
.map_err(|e| anyhow!("Failed to parse sub-authorities-set-id: {}", e))
|
||||
})
|
||||
.transpose()?;
|
||||
let sub_initial_authorities_set = parse_hex_argument(matches, "sub-authorities-set")?;
|
||||
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,
|
||||
) -> anyhow::Result<EthereumExchangeSubmitParams> {
|
||||
let eth_nonce = matches
|
||||
.value_of("eth-nonce")
|
||||
.map(|eth_nonce| {
|
||||
relay_ethereum_client::types::U256::from_dec_str(eth_nonce)
|
||||
.map_err(|e| anyhow!("Failed to parse eth-nonce: {}", e))
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
let eth_amount = matches
|
||||
.value_of("eth-amount")
|
||||
.map(|eth_amount| {
|
||||
eth_amount.parse().map_err(|e| anyhow!("Failed to parse eth-amount: {}", e))
|
||||
})
|
||||
.transpose()?
|
||||
.unwrap_or_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| anyhow!("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) -> anyhow::Result<EthereumExchangeParams> {
|
||||
let mode = match matches.value_of("eth-tx-hash") {
|
||||
Some(eth_tx_hash) => ethereum_exchange::ExchangeRelayMode::Single(
|
||||
eth_tx_hash.parse().map_err(|e| anyhow!("Failed to parse eth-tx-hash: {}", e))?,
|
||||
),
|
||||
None => ethereum_exchange::ExchangeRelayMode::Auto(
|
||||
matches
|
||||
.value_of("eth-start-with-block")
|
||||
.map(|eth_start_with_block| {
|
||||
eth_start_with_block
|
||||
.parse()
|
||||
.map_err(|e| anyhow!("Failed to parse eth-start-with-block: {}", e))
|
||||
})
|
||||
.transpose()?,
|
||||
),
|
||||
};
|
||||
|
||||
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) -> anyhow::Result<MetricsParams> {
|
||||
if matches.is_present("no-prometheus") {
|
||||
return Ok(None.into())
|
||||
}
|
||||
|
||||
let mut metrics_params = MetricsAddress::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| anyhow!("Failed to parse prometheus-port: {}", e))?;
|
||||
}
|
||||
|
||||
Ok(Some(metrics_params).into())
|
||||
}
|
||||
|
||||
fn instance_params(matches: &clap::ArgMatches) -> anyhow::Result<Arc<dyn BridgeInstance>> {
|
||||
let instance = if let Some(instance) = matches.value_of("sub-pallet-instance") {
|
||||
match instance.to_lowercase().as_str() {
|
||||
"rialto" => Arc::new(RialtoPoA) as Arc<dyn BridgeInstance>,
|
||||
"kovan" => Arc::new(Kovan),
|
||||
_ => return Err(anyhow!("Unsupported bridge pallet instance")),
|
||||
}
|
||||
} else {
|
||||
unreachable!("CLI config enforces a default instance, can never be None")
|
||||
};
|
||||
|
||||
Ok(instance)
|
||||
}
|
||||
|
||||
fn parse_hex_argument(matches: &clap::ArgMatches, arg: &str) -> anyhow::Result<Option<Vec<u8>>> {
|
||||
match matches.value_of(arg) {
|
||||
Some(value) =>
|
||||
Ok(Some(hex::decode(value).map_err(|e| anyhow!("Failed to parse {}: {}", arg, e))?)),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
@@ -1,300 +0,0 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{
|
||||
ethereum_sync_loop::QueuedEthereumHeader, instances::BridgeInstance, 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, UnsignedTransaction,
|
||||
};
|
||||
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 the 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 transactions receipts are required for Ethereum header submission.
|
||||
async fn ethereum_receipts_required(&self, header: SubstrateEthereumHeader) -> RpcResult<bool>;
|
||||
/// Returns whether 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 genesis_hash = *self.genesis_hash();
|
||||
let submission_result = async {
|
||||
self.submit_signed_extrinsic(
|
||||
(*params.public().as_array_ref()).into(),
|
||||
move |_, transaction_nonce| {
|
||||
Bytes(
|
||||
Rialto::sign_transaction(
|
||||
genesis_hash,
|
||||
¶ms,
|
||||
relay_substrate_client::TransactionEra::immortal(),
|
||||
UnsignedTransaction::new(
|
||||
instance.build_signed_header_call(headers),
|
||||
transaction_nonce,
|
||||
),
|
||||
)
|
||||
.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_unsigned_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 genesis_hash = *self.genesis_hash();
|
||||
self.submit_signed_extrinsic(
|
||||
(*params.public().as_array_ref()).into(),
|
||||
move |_, transaction_nonce| {
|
||||
Bytes(
|
||||
Rialto::sign_transaction(
|
||||
genesis_hash,
|
||||
¶ms,
|
||||
relay_substrate_client::TransactionEra::immortal(),
|
||||
UnsignedTransaction::new(
|
||||
instance.build_currency_exchange_call(proof),
|
||||
transaction_nonce,
|
||||
),
|
||||
)
|
||||
.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)
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use relay_ethereum_client::Error as EthereumNodeError;
|
||||
use relay_substrate_client::Error as SubstrateNodeError;
|
||||
use relay_utils::MaybeConnectionError;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Contains common errors that can occur when
|
||||
/// interacting with a Substrate or Ethereum node
|
||||
/// through RPC.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum RpcError {
|
||||
/// The arguments to the RPC method failed to serialize.
|
||||
#[error("RPC arguments serialization failed: {0}")]
|
||||
Serialization(#[from] serde_json::Error),
|
||||
/// An error occurred when interacting with an Ethereum node.
|
||||
#[error("Ethereum node error: {0}")]
|
||||
Ethereum(#[from] EthereumNodeError),
|
||||
/// An error occurred when interacting with a Substrate node.
|
||||
#[error("Substrate node error: {0}")]
|
||||
Substrate(#[from] SubstrateNodeError),
|
||||
/// Error running relay loop.
|
||||
#[error("{0}")]
|
||||
SyncLoop(String),
|
||||
}
|
||||
|
||||
impl From<RpcError> for String {
|
||||
fn from(err: RpcError) -> Self {
|
||||
format!("{}", 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))
|
||||
}
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Rialto-Substrate -> Ethereum PoA synchronization.
|
||||
|
||||
use crate::{ethereum_client::EthereumHighLevelRpc, 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::EncodedJustification;
|
||||
|
||||
use std::{collections::HashSet, fmt::Debug, 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: 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 = EncodedJustification;
|
||||
|
||||
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().await.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
#[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: EncodedJustification,
|
||||
) -> 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 async 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).await;
|
||||
let sub_client = 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(),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| RpcError::SyncLoop(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
// Copyright 2020-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,8 @@
|
||||
use codec::Decode;
|
||||
use frame_support::weights::{DispatchClass, DispatchInfo, Pays, Weight};
|
||||
use relay_kusama_client::Kusama;
|
||||
use sp_core::storage::StorageKey;
|
||||
use sp_runtime::{FixedPointNumber, FixedU128};
|
||||
use sp_version::RuntimeVersion;
|
||||
|
||||
use crate::cli::{
|
||||
@@ -101,3 +103,14 @@ impl CliChain for Kusama {
|
||||
anyhow::bail!("Sending messages from Kusama is not yet supported.")
|
||||
}
|
||||
}
|
||||
|
||||
/// Storage key and initial value of Polkadot -> Kusama conversion rate.
|
||||
pub(crate) fn polkadot_to_kusama_conversion_rate_params() -> (StorageKey, FixedU128) {
|
||||
(
|
||||
bp_runtime::storage_parameter_key(
|
||||
bp_kusama::POLKADOT_TO_KUSAMA_CONVERSION_RATE_PARAMETER_NAME,
|
||||
),
|
||||
// starting relay before this parameter will be set to some value may cause troubles
|
||||
FixedU128::from_inner(FixedU128::DIV),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -64,10 +64,7 @@ impl SubstrateFinalitySyncPipeline for KusamaFinalityToPolkadot {
|
||||
type TargetChain = Polkadot;
|
||||
|
||||
fn customize_metrics(params: MetricsParams) -> anyhow::Result<MetricsParams> {
|
||||
crate::chains::add_polkadot_kusama_price_metrics::<Self::FinalitySyncPipeline>(
|
||||
Some(finality_relay::metrics_prefix::<Self::FinalitySyncPipeline>()),
|
||||
params,
|
||||
)
|
||||
crate::chains::add_polkadot_kusama_price_metrics::<Self::FinalitySyncPipeline>(params)
|
||||
}
|
||||
|
||||
fn start_relay_guards(&self) {
|
||||
|
||||
@@ -21,7 +21,6 @@ use std::ops::RangeInclusive;
|
||||
use codec::Encode;
|
||||
use frame_support::weights::Weight;
|
||||
use sp_core::{Bytes, Pair};
|
||||
use sp_runtime::{FixedPointNumber, FixedU128};
|
||||
|
||||
use bp_messages::MessageNonce;
|
||||
use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
|
||||
@@ -33,7 +32,6 @@ use relay_polkadot_client::{
|
||||
HeaderId as PolkadotHeaderId, Polkadot, SigningParams as PolkadotSigningParams,
|
||||
};
|
||||
use relay_substrate_client::{Chain, Client, TransactionSignScheme, UnsignedTransaction};
|
||||
use relay_utils::metrics::MetricsParams;
|
||||
use substrate_relay_helper::{
|
||||
messages_lane::{
|
||||
select_delivery_transaction_limits, MessagesRelayParams, StandaloneMessagesMetrics,
|
||||
@@ -196,12 +194,13 @@ pub async fn run(
|
||||
|
||||
let lane_id = params.lane_id;
|
||||
let source_client = params.source_client;
|
||||
let target_client = params.target_client;
|
||||
let lane = KusamaMessagesToPolkadot {
|
||||
message_lane: SubstrateMessageLaneToSubstrate {
|
||||
source_client: source_client.clone(),
|
||||
source_sign: params.source_sign,
|
||||
source_transactions_mortality: params.source_transactions_mortality,
|
||||
target_client: params.target_client.clone(),
|
||||
target_client: target_client.clone(),
|
||||
target_sign: params.target_sign,
|
||||
target_transactions_mortality: params.target_transactions_mortality,
|
||||
relayer_id_at_source: relayer_id_at_kusama,
|
||||
@@ -240,13 +239,10 @@ pub async fn run(
|
||||
stall_timeout,
|
||||
);
|
||||
|
||||
let (metrics_params, metrics_values) = add_standalone_metrics(
|
||||
Some(messages_relay::message_lane_loop::metrics_prefix::<
|
||||
<KusamaMessagesToPolkadot as SubstrateMessageLane>::MessageLane,
|
||||
>(&lane_id)),
|
||||
params.metrics_params,
|
||||
source_client.clone(),
|
||||
)?;
|
||||
let standalone_metrics = params
|
||||
.standalone_metrics
|
||||
.map(Ok)
|
||||
.unwrap_or_else(|| standalone_metrics(source_client.clone(), target_client.clone()))?;
|
||||
messages_relay::message_lane_loop::run(
|
||||
messages_relay::message_lane_loop::Params {
|
||||
lane: lane_id,
|
||||
@@ -272,41 +268,31 @@ pub async fn run(
|
||||
params.target_to_source_headers_relay,
|
||||
),
|
||||
PolkadotTargetClient::new(
|
||||
params.target_client,
|
||||
target_client,
|
||||
lane,
|
||||
lane_id,
|
||||
metrics_values,
|
||||
standalone_metrics.clone(),
|
||||
params.source_to_target_headers_relay,
|
||||
),
|
||||
metrics_params,
|
||||
standalone_metrics.register_and_spawn(params.metrics_params)?,
|
||||
futures::future::pending(),
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Add standalone metrics for the Kusama -> Polkadot messages loop.
|
||||
pub(crate) fn add_standalone_metrics(
|
||||
metrics_prefix: Option<String>,
|
||||
metrics_params: MetricsParams,
|
||||
/// Create standalone metrics for the Kusama -> Polkadot messages loop.
|
||||
pub(crate) fn standalone_metrics(
|
||||
source_client: Client<Kusama>,
|
||||
) -> anyhow::Result<(MetricsParams, StandaloneMessagesMetrics)> {
|
||||
let polkadot_to_kusama_conversion_rate_key = bp_runtime::storage_parameter_key(
|
||||
bp_kusama::POLKADOT_TO_KUSAMA_CONVERSION_RATE_PARAMETER_NAME,
|
||||
)
|
||||
.0;
|
||||
|
||||
substrate_relay_helper::messages_lane::add_standalone_metrics::<KusamaMessagesToPolkadot>(
|
||||
metrics_prefix,
|
||||
metrics_params,
|
||||
target_client: Client<Polkadot>,
|
||||
) -> anyhow::Result<StandaloneMessagesMetrics<Kusama, Polkadot>> {
|
||||
substrate_relay_helper::messages_lane::standalone_metrics(
|
||||
source_client,
|
||||
Some(crate::chains::polkadot::TOKEN_ID),
|
||||
target_client,
|
||||
Some(crate::chains::kusama::TOKEN_ID),
|
||||
Some((
|
||||
sp_core::storage::StorageKey(polkadot_to_kusama_conversion_rate_key),
|
||||
// starting relay before this parameter will be set to some value may cause troubles
|
||||
FixedU128::from_inner(FixedU128::DIV),
|
||||
)),
|
||||
Some(crate::chains::polkadot::TOKEN_ID),
|
||||
Some(crate::chains::polkadot::kusama_to_polkadot_conversion_rate_params()),
|
||||
Some(crate::chains::kusama::polkadot_to_kusama_conversion_rate_params()),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -28,8 +28,17 @@ use bp_message_dispatch::{CallOrigin, MessagePayload};
|
||||
use codec::Decode;
|
||||
use frame_support::weights::{DispatchInfo, GetDispatchInfo, Weight};
|
||||
use relay_millau_client::Millau;
|
||||
use sp_core::storage::StorageKey;
|
||||
use sp_runtime::FixedU128;
|
||||
use sp_version::RuntimeVersion;
|
||||
|
||||
// Millau/Rialto tokens have no any real value, so the conversion rate we use is always 1:1. But we
|
||||
// want to test our code that is intended to work with real-value chains. So to keep it close to
|
||||
// 1:1, we'll be treating Rialto as BTC and Millau as wBTC (only in relayer).
|
||||
|
||||
/// The identifier of token, which value is associated with Millau token value by relayer.
|
||||
pub(crate) const ASSOCIATED_TOKEN_ID: &str = crate::chains::kusama::TOKEN_ID;
|
||||
|
||||
impl CliEncodeCall for Millau {
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
bp_millau::max_extrinsic_size()
|
||||
@@ -123,3 +132,11 @@ impl CliChain for Millau {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Storage key and initial value of Rialto -> Millau conversion rate.
|
||||
pub(crate) fn rialto_to_millau_conversion_rate_params() -> (StorageKey, FixedU128) {
|
||||
(
|
||||
StorageKey(millau_runtime::rialto_messages::RialtoToMillauConversionRate::key().to_vec()),
|
||||
millau_runtime::rialto_messages::INITIAL_RIALTO_TO_MILLAU_CONVERSION_RATE,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ use relay_rialto_client::{
|
||||
HeaderId as RialtoHeaderId, Rialto, SigningParams as RialtoSigningParams,
|
||||
};
|
||||
use relay_substrate_client::{Chain, Client, IndexOf, TransactionSignScheme, UnsignedTransaction};
|
||||
use relay_utils::metrics::MetricsParams;
|
||||
use substrate_relay_helper::{
|
||||
messages_lane::{
|
||||
select_delivery_transaction_limits, MessagesRelayParams, StandaloneMessagesMetrics,
|
||||
@@ -193,12 +192,13 @@ pub async fn run(
|
||||
|
||||
let lane_id = params.lane_id;
|
||||
let source_client = params.source_client;
|
||||
let target_client = params.target_client;
|
||||
let lane = MillauMessagesToRialto {
|
||||
message_lane: SubstrateMessageLaneToSubstrate {
|
||||
source_client: source_client.clone(),
|
||||
source_sign: params.source_sign,
|
||||
source_transactions_mortality: params.source_transactions_mortality,
|
||||
target_client: params.target_client.clone(),
|
||||
target_client: target_client.clone(),
|
||||
target_sign: params.target_sign,
|
||||
target_transactions_mortality: params.target_transactions_mortality,
|
||||
relayer_id_at_source: relayer_id_at_millau,
|
||||
@@ -234,13 +234,10 @@ pub async fn run(
|
||||
stall_timeout,
|
||||
);
|
||||
|
||||
let (metrics_params, metrics_values) = add_standalone_metrics(
|
||||
Some(messages_relay::message_lane_loop::metrics_prefix::<
|
||||
<MillauMessagesToRialto as SubstrateMessageLane>::MessageLane,
|
||||
>(&lane_id)),
|
||||
params.metrics_params,
|
||||
source_client.clone(),
|
||||
)?;
|
||||
let standalone_metrics = params
|
||||
.standalone_metrics
|
||||
.map(Ok)
|
||||
.unwrap_or_else(|| standalone_metrics(source_client.clone(), target_client.clone()))?;
|
||||
messages_relay::message_lane_loop::run(
|
||||
messages_relay::message_lane_loop::Params {
|
||||
lane: lane_id,
|
||||
@@ -266,37 +263,31 @@ pub async fn run(
|
||||
params.target_to_source_headers_relay,
|
||||
),
|
||||
RialtoTargetClient::new(
|
||||
params.target_client,
|
||||
target_client,
|
||||
lane,
|
||||
lane_id,
|
||||
metrics_values,
|
||||
standalone_metrics.clone(),
|
||||
params.source_to_target_headers_relay,
|
||||
),
|
||||
metrics_params,
|
||||
standalone_metrics.register_and_spawn(params.metrics_params)?,
|
||||
futures::future::pending(),
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Add standalone metrics for the Millau -> Rialto messages loop.
|
||||
pub(crate) fn add_standalone_metrics(
|
||||
metrics_prefix: Option<String>,
|
||||
metrics_params: MetricsParams,
|
||||
/// Create standalone metrics for the Millau -> Rialto messages loop.
|
||||
pub(crate) fn standalone_metrics(
|
||||
source_client: Client<Millau>,
|
||||
) -> anyhow::Result<(MetricsParams, StandaloneMessagesMetrics)> {
|
||||
substrate_relay_helper::messages_lane::add_standalone_metrics::<MillauMessagesToRialto>(
|
||||
metrics_prefix,
|
||||
metrics_params,
|
||||
target_client: Client<Rialto>,
|
||||
) -> anyhow::Result<StandaloneMessagesMetrics<Millau, Rialto>> {
|
||||
substrate_relay_helper::messages_lane::standalone_metrics(
|
||||
source_client,
|
||||
Some(crate::chains::MILLAU_ASSOCIATED_TOKEN_ID),
|
||||
Some(crate::chains::RIALTO_ASSOCIATED_TOKEN_ID),
|
||||
Some((
|
||||
sp_core::storage::StorageKey(
|
||||
millau_runtime::rialto_messages::RialtoToMillauConversionRate::key().to_vec(),
|
||||
),
|
||||
millau_runtime::rialto_messages::INITIAL_RIALTO_TO_MILLAU_CONVERSION_RATE,
|
||||
)),
|
||||
target_client,
|
||||
Some(crate::chains::millau::ASSOCIATED_TOKEN_ID),
|
||||
Some(crate::chains::rialto::ASSOCIATED_TOKEN_ID),
|
||||
Some(crate::chains::rialto::millau_to_rialto_conversion_rate_params()),
|
||||
Some(crate::chains::millau::rialto_to_millau_conversion_rate_params()),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -39,31 +39,16 @@ mod rococo;
|
||||
mod westend;
|
||||
mod wococo;
|
||||
|
||||
// Millau/Rialto tokens have no any real value, so the conversion rate we use is always 1:1. But we
|
||||
// want to test our code that is intended to work with real-value chains. So to keep it close to
|
||||
// 1:1, we'll be treating Rialto as BTC and Millau as wBTC (only in relayer).
|
||||
|
||||
/// The identifier of token, which value is associated with Rialto token value by relayer.
|
||||
pub(crate) const RIALTO_ASSOCIATED_TOKEN_ID: &str = polkadot::TOKEN_ID;
|
||||
/// The identifier of token, which value is associated with Millau token value by relayer.
|
||||
pub(crate) const MILLAU_ASSOCIATED_TOKEN_ID: &str = kusama::TOKEN_ID;
|
||||
|
||||
use relay_utils::metrics::MetricsParams;
|
||||
use relay_utils::metrics::{MetricsParams, StandaloneMetric};
|
||||
|
||||
pub(crate) fn add_polkadot_kusama_price_metrics<T: finality_relay::FinalitySyncPipeline>(
|
||||
prefix: Option<String>,
|
||||
params: MetricsParams,
|
||||
) -> anyhow::Result<MetricsParams> {
|
||||
// Polkadot/Kusama prices are added as metrics here, because atm we don't have Polkadot <->
|
||||
// Kusama relays, but we want to test metrics/dashboards in advance
|
||||
Ok(relay_utils::relay_metrics(prefix, params)
|
||||
.standalone_metric(|registry, prefix| {
|
||||
substrate_relay_helper::helpers::token_price_metric(registry, prefix, "polkadot")
|
||||
})?
|
||||
.standalone_metric(|registry, prefix| {
|
||||
substrate_relay_helper::helpers::token_price_metric(registry, prefix, "kusama")
|
||||
})?
|
||||
.into_params())
|
||||
substrate_relay_helper::helpers::token_price_metric(polkadot::TOKEN_ID)?
|
||||
.register_and_spawn(¶ms.registry)?;
|
||||
substrate_relay_helper::helpers::token_price_metric(kusama::TOKEN_ID)?
|
||||
.register_and_spawn(¶ms.registry)?;
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
use codec::Decode;
|
||||
use frame_support::weights::{DispatchClass, DispatchInfo, Pays, Weight};
|
||||
use relay_polkadot_client::Polkadot;
|
||||
use sp_core::storage::StorageKey;
|
||||
use sp_runtime::{FixedPointNumber, FixedU128};
|
||||
use sp_version::RuntimeVersion;
|
||||
|
||||
use crate::cli::{
|
||||
@@ -101,3 +103,14 @@ impl CliChain for Polkadot {
|
||||
anyhow::bail!("Sending messages from Polkadot is not yet supported.")
|
||||
}
|
||||
}
|
||||
|
||||
/// Storage key and initial value of Kusama -> Polkadot conversion rate.
|
||||
pub(crate) fn kusama_to_polkadot_conversion_rate_params() -> (StorageKey, FixedU128) {
|
||||
(
|
||||
bp_runtime::storage_parameter_key(
|
||||
bp_polkadot::KUSAMA_TO_POLKADOT_CONVERSION_RATE_PARAMETER_NAME,
|
||||
),
|
||||
// starting relay before this parameter will be set to some value may cause troubles
|
||||
FixedU128::from_inner(FixedU128::DIV),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -64,10 +64,7 @@ impl SubstrateFinalitySyncPipeline for PolkadotFinalityToKusama {
|
||||
type TargetChain = Kusama;
|
||||
|
||||
fn customize_metrics(params: MetricsParams) -> anyhow::Result<MetricsParams> {
|
||||
crate::chains::add_polkadot_kusama_price_metrics::<Self::FinalitySyncPipeline>(
|
||||
Some(finality_relay::metrics_prefix::<Self::FinalitySyncPipeline>()),
|
||||
params,
|
||||
)
|
||||
crate::chains::add_polkadot_kusama_price_metrics::<Self::FinalitySyncPipeline>(params)
|
||||
}
|
||||
|
||||
fn start_relay_guards(&self) {
|
||||
|
||||
@@ -32,8 +32,6 @@ use relay_polkadot_client::{
|
||||
HeaderId as PolkadotHeaderId, Polkadot, SigningParams as PolkadotSigningParams,
|
||||
};
|
||||
use relay_substrate_client::{Chain, Client, TransactionSignScheme, UnsignedTransaction};
|
||||
use relay_utils::metrics::MetricsParams;
|
||||
use sp_runtime::{FixedPointNumber, FixedU128};
|
||||
use substrate_relay_helper::{
|
||||
messages_lane::{
|
||||
select_delivery_transaction_limits, MessagesRelayParams, StandaloneMessagesMetrics,
|
||||
@@ -195,12 +193,13 @@ pub async fn run(
|
||||
|
||||
let lane_id = params.lane_id;
|
||||
let source_client = params.source_client;
|
||||
let target_client = params.target_client;
|
||||
let lane = PolkadotMessagesToKusama {
|
||||
message_lane: SubstrateMessageLaneToSubstrate {
|
||||
source_client: source_client.clone(),
|
||||
source_sign: params.source_sign,
|
||||
source_transactions_mortality: params.source_transactions_mortality,
|
||||
target_client: params.target_client.clone(),
|
||||
target_client: target_client.clone(),
|
||||
target_sign: params.target_sign,
|
||||
target_transactions_mortality: params.target_transactions_mortality,
|
||||
relayer_id_at_source: relayer_id_at_polkadot,
|
||||
@@ -239,13 +238,10 @@ pub async fn run(
|
||||
stall_timeout,
|
||||
);
|
||||
|
||||
let (metrics_params, metrics_values) = add_standalone_metrics(
|
||||
Some(messages_relay::message_lane_loop::metrics_prefix::<
|
||||
<PolkadotMessagesToKusama as SubstrateMessageLane>::MessageLane,
|
||||
>(&lane_id)),
|
||||
params.metrics_params,
|
||||
source_client.clone(),
|
||||
)?;
|
||||
let standalone_metrics = params
|
||||
.standalone_metrics
|
||||
.map(Ok)
|
||||
.unwrap_or_else(|| standalone_metrics(source_client.clone(), target_client.clone()))?;
|
||||
messages_relay::message_lane_loop::run(
|
||||
messages_relay::message_lane_loop::Params {
|
||||
lane: lane_id,
|
||||
@@ -271,41 +267,31 @@ pub async fn run(
|
||||
params.target_to_source_headers_relay,
|
||||
),
|
||||
KusamaTargetClient::new(
|
||||
params.target_client,
|
||||
target_client,
|
||||
lane,
|
||||
lane_id,
|
||||
metrics_values,
|
||||
standalone_metrics.clone(),
|
||||
params.source_to_target_headers_relay,
|
||||
),
|
||||
metrics_params,
|
||||
standalone_metrics.register_and_spawn(params.metrics_params)?,
|
||||
futures::future::pending(),
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Add standalone metrics for the Polkadot -> Kusama messages loop.
|
||||
pub(crate) fn add_standalone_metrics(
|
||||
metrics_prefix: Option<String>,
|
||||
metrics_params: MetricsParams,
|
||||
/// Create standalone metrics for the Polkadot -> Kusama messages loop.
|
||||
pub(crate) fn standalone_metrics(
|
||||
source_client: Client<Polkadot>,
|
||||
) -> anyhow::Result<(MetricsParams, StandaloneMessagesMetrics)> {
|
||||
let kusama_to_polkadot_conversion_rate_key = bp_runtime::storage_parameter_key(
|
||||
bp_polkadot::KUSAMA_TO_POLKADOT_CONVERSION_RATE_PARAMETER_NAME,
|
||||
)
|
||||
.0;
|
||||
|
||||
substrate_relay_helper::messages_lane::add_standalone_metrics::<PolkadotMessagesToKusama>(
|
||||
metrics_prefix,
|
||||
metrics_params,
|
||||
target_client: Client<Kusama>,
|
||||
) -> anyhow::Result<StandaloneMessagesMetrics<Polkadot, Kusama>> {
|
||||
substrate_relay_helper::messages_lane::standalone_metrics(
|
||||
source_client,
|
||||
Some(crate::chains::kusama::TOKEN_ID),
|
||||
target_client,
|
||||
Some(crate::chains::polkadot::TOKEN_ID),
|
||||
Some((
|
||||
sp_core::storage::StorageKey(kusama_to_polkadot_conversion_rate_key),
|
||||
// starting relay before this parameter will be set to some value may cause troubles
|
||||
FixedU128::from_inner(FixedU128::DIV),
|
||||
)),
|
||||
Some(crate::chains::kusama::TOKEN_ID),
|
||||
Some(crate::chains::kusama::polkadot_to_kusama_conversion_rate_params()),
|
||||
Some(crate::chains::polkadot::kusama_to_polkadot_conversion_rate_params()),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -28,8 +28,17 @@ use bp_message_dispatch::{CallOrigin, MessagePayload};
|
||||
use codec::Decode;
|
||||
use frame_support::weights::{DispatchInfo, GetDispatchInfo, Weight};
|
||||
use relay_rialto_client::Rialto;
|
||||
use sp_core::storage::StorageKey;
|
||||
use sp_runtime::FixedU128;
|
||||
use sp_version::RuntimeVersion;
|
||||
|
||||
// Millau/Rialto tokens have no any real value, so the conversion rate we use is always 1:1. But we
|
||||
// want to test our code that is intended to work with real-value chains. So to keep it close to
|
||||
// 1:1, we'll be treating Rialto as BTC and Millau as wBTC (only in relayer).
|
||||
|
||||
/// The identifier of token, which value is associated with Rialto token value by relayer.
|
||||
pub(crate) const ASSOCIATED_TOKEN_ID: &str = crate::chains::polkadot::TOKEN_ID;
|
||||
|
||||
impl CliEncodeCall for Rialto {
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
bp_rialto::max_extrinsic_size()
|
||||
@@ -122,3 +131,11 @@ impl CliChain for Rialto {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Storage key and initial value of Millau -> Rialto conversion rate.
|
||||
pub(crate) fn millau_to_rialto_conversion_rate_params() -> (StorageKey, FixedU128) {
|
||||
(
|
||||
StorageKey(rialto_runtime::millau_messages::MillauToRialtoConversionRate::key().to_vec()),
|
||||
rialto_runtime::millau_messages::INITIAL_MILLAU_TO_RIALTO_CONVERSION_RATE,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ use relay_rialto_client::{
|
||||
HeaderId as RialtoHeaderId, Rialto, SigningParams as RialtoSigningParams,
|
||||
};
|
||||
use relay_substrate_client::{Chain, Client, IndexOf, TransactionSignScheme, UnsignedTransaction};
|
||||
use relay_utils::metrics::MetricsParams;
|
||||
use substrate_relay_helper::{
|
||||
messages_lane::{
|
||||
select_delivery_transaction_limits, MessagesRelayParams, StandaloneMessagesMetrics,
|
||||
@@ -193,12 +192,13 @@ pub async fn run(
|
||||
|
||||
let lane_id = params.lane_id;
|
||||
let source_client = params.source_client;
|
||||
let target_client = params.target_client;
|
||||
let lane = RialtoMessagesToMillau {
|
||||
message_lane: SubstrateMessageLaneToSubstrate {
|
||||
source_client: source_client.clone(),
|
||||
source_sign: params.source_sign,
|
||||
source_transactions_mortality: params.source_transactions_mortality,
|
||||
target_client: params.target_client.clone(),
|
||||
target_client: target_client.clone(),
|
||||
target_sign: params.target_sign,
|
||||
target_transactions_mortality: params.target_transactions_mortality,
|
||||
relayer_id_at_source: relayer_id_at_rialto,
|
||||
@@ -233,13 +233,10 @@ pub async fn run(
|
||||
stall_timeout,
|
||||
);
|
||||
|
||||
let (metrics_params, metrics_values) = add_standalone_metrics(
|
||||
Some(messages_relay::message_lane_loop::metrics_prefix::<
|
||||
<RialtoMessagesToMillau as SubstrateMessageLane>::MessageLane,
|
||||
>(&lane_id)),
|
||||
params.metrics_params,
|
||||
source_client.clone(),
|
||||
)?;
|
||||
let standalone_metrics = params
|
||||
.standalone_metrics
|
||||
.map(Ok)
|
||||
.unwrap_or_else(|| standalone_metrics(source_client.clone(), target_client.clone()))?;
|
||||
messages_relay::message_lane_loop::run(
|
||||
messages_relay::message_lane_loop::Params {
|
||||
lane: lane_id,
|
||||
@@ -265,37 +262,31 @@ pub async fn run(
|
||||
params.target_to_source_headers_relay,
|
||||
),
|
||||
MillauTargetClient::new(
|
||||
params.target_client,
|
||||
target_client,
|
||||
lane,
|
||||
lane_id,
|
||||
metrics_values,
|
||||
standalone_metrics.clone(),
|
||||
params.source_to_target_headers_relay,
|
||||
),
|
||||
metrics_params,
|
||||
standalone_metrics.register_and_spawn(params.metrics_params)?,
|
||||
futures::future::pending(),
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Add standalone metrics for the Rialto -> Millau messages loop.
|
||||
pub(crate) fn add_standalone_metrics(
|
||||
metrics_prefix: Option<String>,
|
||||
metrics_params: MetricsParams,
|
||||
/// Create standalone metrics for the Rialto -> Millau messages loop.
|
||||
pub(crate) fn standalone_metrics(
|
||||
source_client: Client<Rialto>,
|
||||
) -> anyhow::Result<(MetricsParams, StandaloneMessagesMetrics)> {
|
||||
substrate_relay_helper::messages_lane::add_standalone_metrics::<RialtoMessagesToMillau>(
|
||||
metrics_prefix,
|
||||
metrics_params,
|
||||
target_client: Client<Millau>,
|
||||
) -> anyhow::Result<StandaloneMessagesMetrics<Rialto, Millau>> {
|
||||
substrate_relay_helper::messages_lane::standalone_metrics(
|
||||
source_client,
|
||||
Some(crate::chains::RIALTO_ASSOCIATED_TOKEN_ID),
|
||||
Some(crate::chains::MILLAU_ASSOCIATED_TOKEN_ID),
|
||||
Some((
|
||||
sp_core::storage::StorageKey(
|
||||
rialto_runtime::millau_messages::MillauToRialtoConversionRate::key().to_vec(),
|
||||
),
|
||||
rialto_runtime::millau_messages::INITIAL_MILLAU_TO_RIALTO_CONVERSION_RATE,
|
||||
)),
|
||||
target_client,
|
||||
Some(crate::chains::rialto::ASSOCIATED_TOKEN_ID),
|
||||
Some(crate::chains::millau::ASSOCIATED_TOKEN_ID),
|
||||
Some(crate::chains::millau::rialto_to_millau_conversion_rate_params()),
|
||||
Some(crate::chains::rialto::millau_to_rialto_conversion_rate_params()),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -59,10 +59,7 @@ impl SubstrateFinalitySyncPipeline for RococoFinalityToWococo {
|
||||
type TargetChain = Wococo;
|
||||
|
||||
fn customize_metrics(params: MetricsParams) -> anyhow::Result<MetricsParams> {
|
||||
crate::chains::add_polkadot_kusama_price_metrics::<Self::FinalitySyncPipeline>(
|
||||
Some(finality_relay::metrics_prefix::<Self::FinalitySyncPipeline>()),
|
||||
params,
|
||||
)
|
||||
crate::chains::add_polkadot_kusama_price_metrics::<Self::FinalitySyncPipeline>(params)
|
||||
}
|
||||
|
||||
fn start_relay_guards(&self) {
|
||||
|
||||
@@ -29,7 +29,6 @@ use relay_rococo_client::{
|
||||
HeaderId as RococoHeaderId, Rococo, SigningParams as RococoSigningParams,
|
||||
};
|
||||
use relay_substrate_client::{Chain, Client, IndexOf, TransactionSignScheme, UnsignedTransaction};
|
||||
use relay_utils::metrics::MetricsParams;
|
||||
use relay_wococo_client::{
|
||||
HeaderId as WococoHeaderId, SigningParams as WococoSigningParams, Wococo,
|
||||
};
|
||||
@@ -193,12 +192,13 @@ pub async fn run(
|
||||
|
||||
let lane_id = params.lane_id;
|
||||
let source_client = params.source_client;
|
||||
let target_client = params.target_client;
|
||||
let lane = RococoMessagesToWococo {
|
||||
message_lane: SubstrateMessageLaneToSubstrate {
|
||||
source_client: source_client.clone(),
|
||||
source_sign: params.source_sign,
|
||||
source_transactions_mortality: params.source_transactions_mortality,
|
||||
target_client: params.target_client.clone(),
|
||||
target_client: target_client.clone(),
|
||||
target_sign: params.target_sign,
|
||||
target_transactions_mortality: params.target_transactions_mortality,
|
||||
relayer_id_at_source: relayer_id_at_rococo,
|
||||
@@ -237,13 +237,10 @@ pub async fn run(
|
||||
stall_timeout,
|
||||
);
|
||||
|
||||
let (metrics_params, metrics_values) = add_standalone_metrics(
|
||||
Some(messages_relay::message_lane_loop::metrics_prefix::<
|
||||
<RococoMessagesToWococo as SubstrateMessageLane>::MessageLane,
|
||||
>(&lane_id)),
|
||||
params.metrics_params,
|
||||
source_client.clone(),
|
||||
)?;
|
||||
let standalone_metrics = params
|
||||
.standalone_metrics
|
||||
.map(Ok)
|
||||
.unwrap_or_else(|| standalone_metrics(source_client.clone(), target_client.clone()))?;
|
||||
messages_relay::message_lane_loop::run(
|
||||
messages_relay::message_lane_loop::Params {
|
||||
lane: lane_id,
|
||||
@@ -269,29 +266,28 @@ pub async fn run(
|
||||
params.target_to_source_headers_relay,
|
||||
),
|
||||
WococoTargetClient::new(
|
||||
params.target_client,
|
||||
target_client,
|
||||
lane,
|
||||
lane_id,
|
||||
metrics_values,
|
||||
standalone_metrics.clone(),
|
||||
params.source_to_target_headers_relay,
|
||||
),
|
||||
metrics_params,
|
||||
standalone_metrics.register_and_spawn(params.metrics_params)?,
|
||||
futures::future::pending(),
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Add standalone metrics for the Rococo -> Wococo messages loop.
|
||||
pub(crate) fn add_standalone_metrics(
|
||||
metrics_prefix: Option<String>,
|
||||
metrics_params: MetricsParams,
|
||||
/// Create standalone metrics for the Rococo -> Wococo messages loop.
|
||||
pub(crate) fn standalone_metrics(
|
||||
source_client: Client<Rococo>,
|
||||
) -> anyhow::Result<(MetricsParams, StandaloneMessagesMetrics)> {
|
||||
substrate_relay_helper::messages_lane::add_standalone_metrics::<RococoMessagesToWococo>(
|
||||
metrics_prefix,
|
||||
metrics_params,
|
||||
target_client: Client<Wococo>,
|
||||
) -> anyhow::Result<StandaloneMessagesMetrics<Rococo, Wococo>> {
|
||||
substrate_relay_helper::messages_lane::standalone_metrics(
|
||||
source_client,
|
||||
target_client,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
|
||||
@@ -57,10 +57,7 @@ impl SubstrateFinalitySyncPipeline for WestendFinalityToMillau {
|
||||
type TargetChain = Millau;
|
||||
|
||||
fn customize_metrics(params: MetricsParams) -> anyhow::Result<MetricsParams> {
|
||||
crate::chains::add_polkadot_kusama_price_metrics::<Self::FinalitySyncPipeline>(
|
||||
Some(finality_relay::metrics_prefix::<Self::FinalitySyncPipeline>()),
|
||||
params,
|
||||
)
|
||||
crate::chains::add_polkadot_kusama_price_metrics::<Self::FinalitySyncPipeline>(params)
|
||||
}
|
||||
|
||||
fn transactions_author(&self) -> bp_millau::AccountId {
|
||||
|
||||
@@ -64,10 +64,7 @@ impl SubstrateFinalitySyncPipeline for WococoFinalityToRococo {
|
||||
type TargetChain = Rococo;
|
||||
|
||||
fn customize_metrics(params: MetricsParams) -> anyhow::Result<MetricsParams> {
|
||||
crate::chains::add_polkadot_kusama_price_metrics::<Self::FinalitySyncPipeline>(
|
||||
Some(finality_relay::metrics_prefix::<Self::FinalitySyncPipeline>()),
|
||||
params,
|
||||
)
|
||||
crate::chains::add_polkadot_kusama_price_metrics::<Self::FinalitySyncPipeline>(params)
|
||||
}
|
||||
|
||||
fn start_relay_guards(&self) {
|
||||
|
||||
@@ -29,7 +29,6 @@ use relay_rococo_client::{
|
||||
HeaderId as RococoHeaderId, Rococo, SigningParams as RococoSigningParams,
|
||||
};
|
||||
use relay_substrate_client::{Chain, Client, IndexOf, TransactionSignScheme, UnsignedTransaction};
|
||||
use relay_utils::metrics::MetricsParams;
|
||||
use relay_wococo_client::{
|
||||
HeaderId as WococoHeaderId, SigningParams as WococoSigningParams, Wococo,
|
||||
};
|
||||
@@ -192,12 +191,13 @@ pub async fn run(
|
||||
|
||||
let lane_id = params.lane_id;
|
||||
let source_client = params.source_client;
|
||||
let target_client = params.target_client;
|
||||
let lane = WococoMessagesToRococo {
|
||||
message_lane: SubstrateMessageLaneToSubstrate {
|
||||
source_client: source_client.clone(),
|
||||
source_sign: params.source_sign,
|
||||
source_transactions_mortality: params.source_transactions_mortality,
|
||||
target_client: params.target_client.clone(),
|
||||
target_client: target_client.clone(),
|
||||
target_sign: params.target_sign,
|
||||
target_transactions_mortality: params.target_transactions_mortality,
|
||||
relayer_id_at_source: relayer_id_at_wococo,
|
||||
@@ -236,13 +236,10 @@ pub async fn run(
|
||||
stall_timeout,
|
||||
);
|
||||
|
||||
let (metrics_params, metrics_values) = add_standalone_metrics(
|
||||
Some(messages_relay::message_lane_loop::metrics_prefix::<
|
||||
<WococoMessagesToRococo as SubstrateMessageLane>::MessageLane,
|
||||
>(&lane_id)),
|
||||
params.metrics_params,
|
||||
source_client.clone(),
|
||||
)?;
|
||||
let standalone_metrics = params
|
||||
.standalone_metrics
|
||||
.map(Ok)
|
||||
.unwrap_or_else(|| standalone_metrics(source_client.clone(), target_client.clone()))?;
|
||||
messages_relay::message_lane_loop::run(
|
||||
messages_relay::message_lane_loop::Params {
|
||||
lane: lane_id,
|
||||
@@ -268,29 +265,28 @@ pub async fn run(
|
||||
params.target_to_source_headers_relay,
|
||||
),
|
||||
RococoTargetClient::new(
|
||||
params.target_client,
|
||||
target_client,
|
||||
lane,
|
||||
lane_id,
|
||||
metrics_values,
|
||||
standalone_metrics.clone(),
|
||||
params.source_to_target_headers_relay,
|
||||
),
|
||||
metrics_params,
|
||||
standalone_metrics.register_and_spawn(params.metrics_params)?,
|
||||
futures::future::pending(),
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Add standalone metrics for the Wococo -> Rococo messages loop.
|
||||
pub(crate) fn add_standalone_metrics(
|
||||
metrics_prefix: Option<String>,
|
||||
metrics_params: MetricsParams,
|
||||
/// Create standalone metrics for the Wococo -> Rococo messages loop.
|
||||
pub(crate) fn standalone_metrics(
|
||||
source_client: Client<Wococo>,
|
||||
) -> anyhow::Result<(MetricsParams, StandaloneMessagesMetrics)> {
|
||||
substrate_relay_helper::messages_lane::add_standalone_metrics::<WococoMessagesToRococo>(
|
||||
metrics_prefix,
|
||||
metrics_params,
|
||||
target_client: Client<Rococo>,
|
||||
) -> anyhow::Result<StandaloneMessagesMetrics<Wococo, Rococo>> {
|
||||
substrate_relay_helper::messages_lane::standalone_metrics(
|
||||
source_client,
|
||||
target_client,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
|
||||
@@ -345,7 +345,7 @@ mod tests {
|
||||
|
||||
// then
|
||||
assert!(format!("{:?}", call_hex).starts_with(
|
||||
"0x10030000000001000000381409000000000001d43593c715fdd31c61141abd04a99fd6822c8558854cc\
|
||||
"0x0f030000000001000000381409000000000001d43593c715fdd31c61141abd04a99fd6822c8558854cc\
|
||||
de39a5684e7a56da27d01d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01"
|
||||
))
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
use structopt::StructOpt;
|
||||
use strum::{EnumString, EnumVariantNames, VariantNames};
|
||||
|
||||
use relay_utils::metrics::{GlobalMetrics, StandaloneMetric};
|
||||
use substrate_relay_helper::finality_pipeline::SubstrateFinalitySyncPipeline;
|
||||
|
||||
use crate::cli::{
|
||||
@@ -121,6 +122,8 @@ impl RelayHeaders {
|
||||
let target_transactions_mortality = self.target_sign.target_transactions_mortality;
|
||||
let target_sign = self.target_sign.to_keypair::<Target>()?;
|
||||
let metrics_params = Finality::customize_metrics(self.prometheus_params.into())?;
|
||||
GlobalMetrics::new()?.register_and_spawn(&metrics_params.registry)?;
|
||||
|
||||
let finality = Finality::new(target_client.clone(), target_sign);
|
||||
finality.start_relay_guards();
|
||||
|
||||
|
||||
@@ -34,8 +34,7 @@ use relay_substrate_client::{
|
||||
use relay_utils::metrics::MetricsParams;
|
||||
use sp_core::{Bytes, Pair};
|
||||
use substrate_relay_helper::{
|
||||
messages_lane::{MessagesRelayParams, SubstrateMessageLane},
|
||||
on_demand_headers::OnDemandHeadersRelay,
|
||||
messages_lane::MessagesRelayParams, on_demand_headers::OnDemandHeadersRelay,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -129,11 +128,6 @@ macro_rules! select_bridge {
|
||||
type RightToLeftFinality =
|
||||
crate::chains::rialto_headers_to_millau::RialtoFinalityToMillau;
|
||||
|
||||
type LeftToRightMessages =
|
||||
crate::chains::millau_messages_to_rialto::MillauMessagesToRialto;
|
||||
type RightToLeftMessages =
|
||||
crate::chains::rialto_messages_to_millau::RialtoMessagesToMillau;
|
||||
|
||||
type LeftAccountIdConverter = bp_millau::AccountIdConverter;
|
||||
type RightAccountIdConverter = bp_rialto::AccountIdConverter;
|
||||
|
||||
@@ -144,12 +138,11 @@ macro_rules! select_bridge {
|
||||
|
||||
use crate::chains::{
|
||||
millau_messages_to_rialto::{
|
||||
add_standalone_metrics as add_left_to_right_standalone_metrics,
|
||||
standalone_metrics as left_to_right_standalone_metrics,
|
||||
run as left_to_right_messages,
|
||||
update_rialto_to_millau_conversion_rate as update_right_to_left_conversion_rate,
|
||||
},
|
||||
rialto_messages_to_millau::{
|
||||
add_standalone_metrics as add_right_to_left_standalone_metrics,
|
||||
run as right_to_left_messages,
|
||||
update_millau_to_rialto_conversion_rate as update_left_to_right_conversion_rate,
|
||||
},
|
||||
@@ -184,11 +177,6 @@ macro_rules! select_bridge {
|
||||
type RightToLeftFinality =
|
||||
crate::chains::wococo_headers_to_rococo::WococoFinalityToRococo;
|
||||
|
||||
type LeftToRightMessages =
|
||||
crate::chains::rococo_messages_to_wococo::RococoMessagesToWococo;
|
||||
type RightToLeftMessages =
|
||||
crate::chains::wococo_messages_to_rococo::WococoMessagesToRococo;
|
||||
|
||||
type LeftAccountIdConverter = bp_rococo::AccountIdConverter;
|
||||
type RightAccountIdConverter = bp_wococo::AccountIdConverter;
|
||||
|
||||
@@ -199,11 +187,10 @@ macro_rules! select_bridge {
|
||||
|
||||
use crate::chains::{
|
||||
rococo_messages_to_wococo::{
|
||||
add_standalone_metrics as add_left_to_right_standalone_metrics,
|
||||
standalone_metrics as left_to_right_standalone_metrics,
|
||||
run as left_to_right_messages,
|
||||
},
|
||||
wococo_messages_to_rococo::{
|
||||
add_standalone_metrics as add_right_to_left_standalone_metrics,
|
||||
run as right_to_left_messages,
|
||||
},
|
||||
};
|
||||
@@ -253,11 +240,6 @@ macro_rules! select_bridge {
|
||||
type RightToLeftFinality =
|
||||
crate::chains::polkadot_headers_to_kusama::PolkadotFinalityToKusama;
|
||||
|
||||
type LeftToRightMessages =
|
||||
crate::chains::kusama_messages_to_polkadot::KusamaMessagesToPolkadot;
|
||||
type RightToLeftMessages =
|
||||
crate::chains::polkadot_messages_to_kusama::PolkadotMessagesToKusama;
|
||||
|
||||
type LeftAccountIdConverter = bp_kusama::AccountIdConverter;
|
||||
type RightAccountIdConverter = bp_polkadot::AccountIdConverter;
|
||||
|
||||
@@ -268,12 +250,11 @@ macro_rules! select_bridge {
|
||||
|
||||
use crate::chains::{
|
||||
kusama_messages_to_polkadot::{
|
||||
add_standalone_metrics as add_left_to_right_standalone_metrics,
|
||||
standalone_metrics as left_to_right_standalone_metrics,
|
||||
run as left_to_right_messages,
|
||||
update_polkadot_to_kusama_conversion_rate as update_right_to_left_conversion_rate,
|
||||
},
|
||||
polkadot_messages_to_kusama::{
|
||||
add_standalone_metrics as add_right_to_left_standalone_metrics,
|
||||
run as right_to_left_messages,
|
||||
update_kusama_to_polkadot_conversion_rate as update_left_to_right_conversion_rate,
|
||||
},
|
||||
@@ -378,31 +359,39 @@ impl RelayHeadersAndMessages {
|
||||
let relayer_mode = params.shared.relayer_mode.into();
|
||||
let relay_strategy = MixStrategy::new(relayer_mode);
|
||||
|
||||
const METRIC_IS_SOME_PROOF: &str =
|
||||
"it is `None` when metric has been already registered; \
|
||||
this is the command entrypoint, so nothing has been registered yet; \
|
||||
qed";
|
||||
|
||||
// create metrics registry and register standalone metrics
|
||||
let metrics_params: MetricsParams = params.shared.prometheus_params.into();
|
||||
let metrics_params = relay_utils::relay_metrics(None, metrics_params).into_params();
|
||||
let (metrics_params, left_to_right_metrics) =
|
||||
add_left_to_right_standalone_metrics(None, metrics_params, left_client.clone())?;
|
||||
let (metrics_params, right_to_left_metrics) =
|
||||
add_right_to_left_standalone_metrics(None, metrics_params, right_client.clone())?;
|
||||
let metrics_params = relay_utils::relay_metrics(metrics_params).into_params();
|
||||
let left_to_right_metrics =
|
||||
left_to_right_standalone_metrics(left_client.clone(), right_client.clone())?;
|
||||
let right_to_left_metrics = left_to_right_metrics.clone().reverse();
|
||||
|
||||
// start conversion rate update loops for left/right chains
|
||||
if let Some(left_messages_pallet_owner) = left_messages_pallet_owner {
|
||||
let left_client = left_client.clone();
|
||||
let format_err = || {
|
||||
anyhow::format_err!(
|
||||
"Cannon run conversion rate updater: {} -> {}",
|
||||
Right::NAME,
|
||||
Left::NAME
|
||||
)
|
||||
};
|
||||
substrate_relay_helper::conversion_rate_update::run_conversion_rate_update_loop(
|
||||
left_to_right_metrics
|
||||
.target_to_source_conversion_rate
|
||||
.expect(METRIC_IS_SOME_PROOF),
|
||||
.as_ref()
|
||||
.ok_or_else(format_err)?
|
||||
.shared_value_ref(),
|
||||
left_to_right_metrics
|
||||
.target_to_base_conversion_rate
|
||||
.clone()
|
||||
.expect(METRIC_IS_SOME_PROOF),
|
||||
.as_ref()
|
||||
.ok_or_else(format_err)?
|
||||
.shared_value_ref(),
|
||||
left_to_right_metrics
|
||||
.source_to_base_conversion_rate
|
||||
.clone()
|
||||
.expect(METRIC_IS_SOME_PROOF),
|
||||
.as_ref()
|
||||
.ok_or_else(format_err)?
|
||||
.shared_value_ref(),
|
||||
CONVERSION_RATE_ALLOWED_DIFFERENCE_RATIO,
|
||||
move |new_rate| {
|
||||
log::info!(
|
||||
@@ -423,16 +412,29 @@ impl RelayHeadersAndMessages {
|
||||
}
|
||||
if let Some(right_messages_pallet_owner) = right_messages_pallet_owner {
|
||||
let right_client = right_client.clone();
|
||||
let format_err = || {
|
||||
anyhow::format_err!(
|
||||
"Cannon run conversion rate updater: {} -> {}",
|
||||
Left::NAME,
|
||||
Right::NAME
|
||||
)
|
||||
};
|
||||
substrate_relay_helper::conversion_rate_update::run_conversion_rate_update_loop(
|
||||
right_to_left_metrics
|
||||
.target_to_source_conversion_rate
|
||||
.expect(METRIC_IS_SOME_PROOF),
|
||||
.as_ref()
|
||||
.ok_or_else(format_err)?
|
||||
.shared_value_ref(),
|
||||
left_to_right_metrics
|
||||
.source_to_base_conversion_rate
|
||||
.expect(METRIC_IS_SOME_PROOF),
|
||||
.as_ref()
|
||||
.ok_or_else(format_err)?
|
||||
.shared_value_ref(),
|
||||
left_to_right_metrics
|
||||
.target_to_base_conversion_rate
|
||||
.expect(METRIC_IS_SOME_PROOF),
|
||||
.as_ref()
|
||||
.ok_or_else(format_err)?
|
||||
.shared_value_ref(),
|
||||
CONVERSION_RATE_ALLOWED_DIFFERENCE_RATIO,
|
||||
move |new_rate| {
|
||||
log::info!(
|
||||
@@ -452,6 +454,7 @@ impl RelayHeadersAndMessages {
|
||||
);
|
||||
}
|
||||
|
||||
// optionally, create relayers fund account
|
||||
if params.shared.create_relayers_fund_accounts {
|
||||
let relayer_fund_acount_id = pallet_bridge_messages::relayer_fund_account_id::<
|
||||
AccountIdOf<Left>,
|
||||
@@ -490,6 +493,7 @@ impl RelayHeadersAndMessages {
|
||||
}
|
||||
}
|
||||
|
||||
// start on-demand header relays
|
||||
let left_to_right_on_demand_headers = OnDemandHeadersRelay::new(
|
||||
left_client.clone(),
|
||||
right_client.clone(),
|
||||
@@ -521,11 +525,8 @@ impl RelayHeadersAndMessages {
|
||||
source_to_target_headers_relay: Some(left_to_right_on_demand_headers.clone()),
|
||||
target_to_source_headers_relay: Some(right_to_left_on_demand_headers.clone()),
|
||||
lane_id: lane,
|
||||
metrics_params: metrics_params.clone().disable().metrics_prefix(
|
||||
messages_relay::message_lane_loop::metrics_prefix::<
|
||||
<LeftToRightMessages as SubstrateMessageLane>::MessageLane,
|
||||
>(&lane),
|
||||
),
|
||||
metrics_params: metrics_params.clone().disable(),
|
||||
standalone_metrics: Some(left_to_right_metrics.clone()),
|
||||
relay_strategy: relay_strategy.clone(),
|
||||
})
|
||||
.map_err(|e| anyhow::format_err!("{}", e))
|
||||
@@ -540,11 +541,8 @@ impl RelayHeadersAndMessages {
|
||||
source_to_target_headers_relay: Some(right_to_left_on_demand_headers.clone()),
|
||||
target_to_source_headers_relay: Some(left_to_right_on_demand_headers.clone()),
|
||||
lane_id: lane,
|
||||
metrics_params: metrics_params.clone().disable().metrics_prefix(
|
||||
messages_relay::message_lane_loop::metrics_prefix::<
|
||||
<RightToLeftMessages as SubstrateMessageLane>::MessageLane,
|
||||
>(&lane),
|
||||
),
|
||||
metrics_params: metrics_params.clone().disable(),
|
||||
standalone_metrics: Some(right_to_left_metrics.clone()),
|
||||
relay_strategy: relay_strategy.clone(),
|
||||
})
|
||||
.map_err(|e| anyhow::format_err!("{}", e))
|
||||
@@ -554,7 +552,7 @@ impl RelayHeadersAndMessages {
|
||||
message_relays.push(right_to_left_messages);
|
||||
}
|
||||
|
||||
relay_utils::relay_metrics(None, metrics_params)
|
||||
relay_utils::relay_metrics(metrics_params)
|
||||
.expose()
|
||||
.await
|
||||
.map_err(|e| anyhow::format_err!("{}", e))?;
|
||||
|
||||
@@ -95,6 +95,7 @@ impl RelayMessages {
|
||||
target_to_source_headers_relay: None,
|
||||
lane_id: self.lane.into(),
|
||||
metrics_params: self.prometheus_params.into(),
|
||||
standalone_metrics: None,
|
||||
relay_strategy,
|
||||
})
|
||||
.await
|
||||
|
||||
@@ -401,7 +401,8 @@ impl SwapTokens {
|
||||
.await?;
|
||||
if token_swap_state != None {
|
||||
return Err(anyhow::format_err!(
|
||||
"Confirmed token swap state has been changed to {:?} unexpectedly"
|
||||
"Confirmed token swap state has been changed to {:?} unexpectedly",
|
||||
token_swap_state
|
||||
))
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
[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]
|
||||
async-std = "1.6.5"
|
||||
bp-eth-poa = { path = "../../primitives/ethereum-poa" }
|
||||
headers-relay = { path = "../headers" }
|
||||
hex-literal = "0.3"
|
||||
jsonrpsee-proc-macros = "0.3.1"
|
||||
jsonrpsee-ws-client = "0.3.1"
|
||||
libsecp256k1 = { version = "0.7", default-features = false, features = ["hmac"] }
|
||||
log = "0.4.11"
|
||||
relay-utils = { path = "../utils" }
|
||||
tokio = "1.8"
|
||||
web3 = { git = "https://github.com/svyatonik/rust-web3.git", branch = "bump-deps" }
|
||||
thiserror = "1.0.26"
|
||||
@@ -1,265 +0,0 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{
|
||||
rpc::Ethereum,
|
||||
types::{
|
||||
Address, Bytes, CallRequest, Header, HeaderWithTransactions, Receipt, SignedRawTx,
|
||||
SyncState, Transaction, TransactionHash, H256, U256,
|
||||
},
|
||||
ConnectionParams, Error, Result,
|
||||
};
|
||||
|
||||
use jsonrpsee_ws_client::{WsClient as RpcClient, WsClientBuilder as RpcClientBuilder};
|
||||
use relay_utils::relay_loop::RECONNECT_DELAY;
|
||||
use std::{future::Future, sync::Arc};
|
||||
|
||||
/// 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 {
|
||||
tokio: Arc<tokio::runtime::Runtime>,
|
||||
params: ConnectionParams,
|
||||
client: Arc<RpcClient>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Create a new Ethereum RPC Client.
|
||||
///
|
||||
/// This function will keep connecting to given Ethereum node until connection is established
|
||||
/// and is functional. If attempt fail, it will wait for `RECONNECT_DELAY` and retry again.
|
||||
pub async fn new(params: ConnectionParams) -> Self {
|
||||
loop {
|
||||
match Self::try_connect(params.clone()).await {
|
||||
Ok(client) => return client,
|
||||
Err(error) => log::error!(
|
||||
target: "bridge",
|
||||
"Failed to connect to Ethereum node: {:?}. Going to retry in {}s",
|
||||
error,
|
||||
RECONNECT_DELAY.as_secs(),
|
||||
),
|
||||
}
|
||||
|
||||
async_std::task::sleep(RECONNECT_DELAY).await;
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to connect to Ethereum node. Returns Ethereum RPC client if connection has been
|
||||
/// established or error otherwise.
|
||||
pub async fn try_connect(params: ConnectionParams) -> Result<Self> {
|
||||
let (tokio, client) = Self::build_client(¶ms).await?;
|
||||
Ok(Self { tokio, client, params })
|
||||
}
|
||||
|
||||
/// Build client to use in connection.
|
||||
async fn build_client(
|
||||
params: &ConnectionParams,
|
||||
) -> Result<(Arc<tokio::runtime::Runtime>, Arc<RpcClient>)> {
|
||||
let tokio = tokio::runtime::Runtime::new()?;
|
||||
let uri = format!("ws://{}:{}", params.host, params.port);
|
||||
let client = tokio
|
||||
.spawn(async move { RpcClientBuilder::default().build(&uri).await })
|
||||
.await??;
|
||||
Ok((Arc::new(tokio), Arc::new(client)))
|
||||
}
|
||||
|
||||
/// Reopen client connection.
|
||||
pub async fn reconnect(&mut self) -> Result<()> {
|
||||
let (tokio, client) = Self::build_client(&self.params).await?;
|
||||
self.tokio = tokio;
|
||||
self.client = client;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
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<()> {
|
||||
self.jsonrpsee_execute(move |client| async move {
|
||||
match Ethereum::syncing(&*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(())
|
||||
},
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Estimate gas usage for the given call.
|
||||
pub async fn estimate_gas(&self, call_request: CallRequest) -> Result<U256> {
|
||||
self.jsonrpsee_execute(move |client| async move {
|
||||
Ok(Ethereum::estimate_gas(&*client, call_request).await?)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Retrieve number of the best known block from the Ethereum node.
|
||||
pub async fn best_block_number(&self) -> Result<u64> {
|
||||
self.jsonrpsee_execute(move |client| async move {
|
||||
Ok(Ethereum::block_number(&*client).await?.as_u64())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Retrieve number of the best known block from the Ethereum node.
|
||||
pub async fn header_by_number(&self, block_number: u64) -> Result<Header> {
|
||||
self.jsonrpsee_execute(move |client| async move {
|
||||
let get_full_tx_objects = false;
|
||||
let header =
|
||||
Ethereum::get_block_by_number(&*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),
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Retrieve block header by its hash from Ethereum node.
|
||||
pub async fn header_by_hash(&self, hash: H256) -> Result<Header> {
|
||||
self.jsonrpsee_execute(move |client| async move {
|
||||
let get_full_tx_objects = false;
|
||||
let header = Ethereum::get_block_by_hash(&*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),
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
self.jsonrpsee_execute(move |client| async move {
|
||||
let get_full_tx_objects = true;
|
||||
let header = Ethereum::get_block_by_number_with_transactions(
|
||||
&*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)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
self.jsonrpsee_execute(move |client| async move {
|
||||
let get_full_tx_objects = true;
|
||||
let header =
|
||||
Ethereum::get_block_by_hash_with_transactions(&*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)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Retrieve transaction by its hash from Ethereum node.
|
||||
pub async fn transaction_by_hash(&self, hash: H256) -> Result<Option<Transaction>> {
|
||||
self.jsonrpsee_execute(move |client| async move {
|
||||
Ok(Ethereum::transaction_by_hash(&*client, hash).await?)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Retrieve transaction receipt by transaction hash.
|
||||
pub async fn transaction_receipt(&self, transaction_hash: H256) -> Result<Receipt> {
|
||||
self.jsonrpsee_execute(move |client| async move {
|
||||
Ok(Ethereum::get_transaction_receipt(&*client, transaction_hash).await?)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get the nonce of the given account.
|
||||
pub async fn account_nonce(&self, address: Address) -> Result<U256> {
|
||||
self.jsonrpsee_execute(move |client| async move {
|
||||
Ok(Ethereum::get_transaction_count(&*client, address).await?)
|
||||
})
|
||||
.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> {
|
||||
self.jsonrpsee_execute(move |client| async move {
|
||||
let transaction = Bytes(signed_raw_tx);
|
||||
let tx_hash = Ethereum::submit_transaction(&*client, transaction).await?;
|
||||
log::trace!(target: "bridge", "Sent transaction to Ethereum node: {:?}", tx_hash);
|
||||
Ok(tx_hash)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Call Ethereum smart contract.
|
||||
pub async fn eth_call(&self, call_transaction: CallRequest) -> Result<Bytes> {
|
||||
self.jsonrpsee_execute(move |client| async move {
|
||||
Ok(Ethereum::call(&*client, call_transaction).await?)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Execute jsonrpsee future in tokio context.
|
||||
async fn jsonrpsee_execute<MF, F, T>(&self, make_jsonrpsee_future: MF) -> Result<T>
|
||||
where
|
||||
MF: FnOnce(Arc<RpcClient>) -> F + Send + 'static,
|
||||
F: Future<Output = Result<T>> + Send,
|
||||
T: Send + 'static,
|
||||
{
|
||||
let client = self.client.clone();
|
||||
self.tokio.spawn(async move { make_jsonrpsee_future(client).await }).await?
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Ethereum node RPC errors.
|
||||
|
||||
use crate::types::U256;
|
||||
|
||||
use jsonrpsee_ws_client::types::Error as RpcError;
|
||||
use relay_utils::MaybeConnectionError;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Result type used by Ethereum client.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Errors that can occur only when interacting with
|
||||
/// an Ethereum node through RPC.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
/// IO error.
|
||||
#[error("IO error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
/// An error that can occur when making an HTTP request to
|
||||
/// an JSON-RPC client.
|
||||
#[error("RPC error: {0}")]
|
||||
RpcError(#[from] RpcError),
|
||||
/// Failed to parse response.
|
||||
#[error("Response parse failed: {0}")]
|
||||
ResponseParseFailed(String),
|
||||
/// We have received a header with missing fields.
|
||||
#[error("Incomplete Ethereum Header Received (missing some of required fields - hash, number, logs_bloom).")]
|
||||
IncompleteHeader,
|
||||
/// We have received a transaction missing a `raw` field.
|
||||
#[error("Incomplete Ethereum Transaction (missing required field - raw).")]
|
||||
IncompleteTransaction,
|
||||
/// An invalid Substrate block number was received from
|
||||
/// an Ethereum node.
|
||||
#[error("Received an invalid Substrate block from Ethereum Node.")]
|
||||
InvalidSubstrateBlockNumber,
|
||||
/// An invalid index has been received from an Ethereum node.
|
||||
#[error("Received an invalid incomplete index from Ethereum Node.")]
|
||||
InvalidIncompleteIndex,
|
||||
/// The client we're connected to is not synced, so we can't rely on its state. Contains
|
||||
/// number of unsynced headers.
|
||||
#[error("Ethereum client is not synced: syncing {0} headers.")]
|
||||
ClientNotSynced(U256),
|
||||
/// Custom logic error.
|
||||
#[error("{0}")]
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
impl From<tokio::task::JoinError> for Error {
|
||||
fn from(error: tokio::task::JoinError) -> Self {
|
||||
Error::Custom(format!("Failed to wait tokio task: {}", error))
|
||||
}
|
||||
}
|
||||
|
||||
impl MaybeConnectionError for Error {
|
||||
fn is_connection_error(&self) -> bool {
|
||||
matches!(
|
||||
*self,
|
||||
Error::RpcError(RpcError::Transport(_))
|
||||
// right now if connection to the ws server is dropped (after it is already established),
|
||||
// we're getting this error
|
||||
| Error::RpcError(RpcError::Internal(_))
|
||||
| Error::RpcError(RpcError::RestartNeeded(_))
|
||||
| Error::ClientNotSynced(_),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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,
|
||||
error::{Error, Result},
|
||||
sign::{sign_and_submit_transaction, SigningParams},
|
||||
};
|
||||
|
||||
pub mod types;
|
||||
|
||||
/// Ethereum-over-websocket connection params.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConnectionParams {
|
||||
/// Websocket server host name.
|
||||
pub host: String,
|
||||
/// Websocket server TCP port.
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
impl Default for ConnectionParams {
|
||||
fn default() -> Self {
|
||||
ConnectionParams { host: "localhost".into(), port: 8546 }
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Ethereum node RPC interface.
|
||||
|
||||
use crate::types::{
|
||||
Address, Bytes, CallRequest, Header, HeaderWithTransactions, Receipt, SyncState, Transaction,
|
||||
TransactionHash, H256, U256, U64,
|
||||
};
|
||||
|
||||
jsonrpsee_proc_macros::rpc_client_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;
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{
|
||||
types::{Address, CallRequest, U256},
|
||||
Client, Result,
|
||||
};
|
||||
use bp_eth_poa::signatures::{secret_to_address, SignTransaction};
|
||||
use hex_literal::hex;
|
||||
use libsecp256k1::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 transaction 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(¶ms.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(¶ms.signer, Some(params.chain_id));
|
||||
|
||||
let _ = client.submit_transaction(raw_transaction).await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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)
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,6 @@ thiserror = "1.0.26"
|
||||
bp-header-chain = { path = "../../primitives/header-chain" }
|
||||
bp-runtime = { path = "../../primitives/runtime" }
|
||||
finality-relay = { path = "../finality" }
|
||||
headers-relay = { path = "../headers" }
|
||||
relay-utils = { path = "../utils" }
|
||||
|
||||
# Substrate Dependencies
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Default generic implementation of headers source for basic Substrate client.
|
||||
|
||||
use crate::{
|
||||
chain::{BlockWithJustification, Chain},
|
||||
client::Client,
|
||||
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, EncodedJustification};
|
||||
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 = EncodedJustification,
|
||||
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, ()))
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,6 @@ mod sync_header;
|
||||
|
||||
pub mod finality_source;
|
||||
pub mod guard;
|
||||
pub mod headers_source;
|
||||
pub mod metrics;
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -20,7 +20,8 @@ use async_std::sync::{Arc, RwLock};
|
||||
use async_trait::async_trait;
|
||||
use codec::Decode;
|
||||
use relay_utils::metrics::{
|
||||
metric_name, register, F64SharedRef, Gauge, PrometheusError, Registry, StandaloneMetrics, F64,
|
||||
metric_name, register, F64SharedRef, Gauge, Metric, PrometheusError, Registry,
|
||||
StandaloneMetric, F64,
|
||||
};
|
||||
use sp_core::storage::StorageKey;
|
||||
use sp_runtime::{traits::UniqueSaturatedInto, FixedPointNumber};
|
||||
@@ -42,8 +43,6 @@ pub struct FloatStorageValueMetric<C: Chain, T: Clone> {
|
||||
impl<C: Chain, T: Decode + FixedPointNumber> FloatStorageValueMetric<C, T> {
|
||||
/// Create new metric.
|
||||
pub fn new(
|
||||
registry: &Registry,
|
||||
prefix: Option<&str>,
|
||||
client: Client<C>,
|
||||
storage_key: StorageKey,
|
||||
maybe_default_value: Option<T>,
|
||||
@@ -55,7 +54,7 @@ impl<C: Chain, T: Decode + FixedPointNumber> FloatStorageValueMetric<C, T> {
|
||||
client,
|
||||
storage_key,
|
||||
maybe_default_value,
|
||||
metric: register(Gauge::new(metric_name(prefix, &name), help)?, registry)?,
|
||||
metric: Gauge::new(metric_name(None, &name), help)?,
|
||||
shared_value_ref,
|
||||
})
|
||||
}
|
||||
@@ -66,8 +65,17 @@ impl<C: Chain, T: Decode + FixedPointNumber> FloatStorageValueMetric<C, T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Chain, T> Metric for FloatStorageValueMetric<C, T>
|
||||
where
|
||||
T: 'static + Decode + Send + Sync + FixedPointNumber,
|
||||
{
|
||||
fn register(&self, registry: &Registry) -> Result<(), PrometheusError> {
|
||||
register(self.metric.clone(), registry).map(drop)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C: Chain, T> StandaloneMetrics for FloatStorageValueMetric<C, T>
|
||||
impl<C: Chain, T> StandaloneMetric for FloatStorageValueMetric<C, T>
|
||||
where
|
||||
T: 'static + Decode + Send + Sync + FixedPointNumber,
|
||||
{
|
||||
|
||||
@@ -18,7 +18,7 @@ use crate::{chain::Chain, client::Client, error::Error};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use relay_utils::metrics::{
|
||||
metric_name, register, Gauge, PrometheusError, Registry, StandaloneMetrics, U64,
|
||||
metric_name, register, Gauge, Metric, PrometheusError, Registry, StandaloneMetric, U64,
|
||||
};
|
||||
use sp_core::storage::StorageKey;
|
||||
use sp_runtime::traits::Header as HeaderT;
|
||||
@@ -46,16 +46,10 @@ impl<C: Chain> Clone for StorageProofOverheadMetric<C> {
|
||||
|
||||
impl<C: Chain> StorageProofOverheadMetric<C> {
|
||||
/// Create new metric instance with given name and help.
|
||||
pub fn new(
|
||||
registry: &Registry,
|
||||
prefix: Option<&str>,
|
||||
client: Client<C>,
|
||||
name: String,
|
||||
help: String,
|
||||
) -> Result<Self, PrometheusError> {
|
||||
pub fn new(client: Client<C>, name: String, help: String) -> Result<Self, PrometheusError> {
|
||||
Ok(StorageProofOverheadMetric {
|
||||
client,
|
||||
metric: register(Gauge::new(metric_name(prefix, &name), help)?, registry)?,
|
||||
metric: Gauge::new(metric_name(None, &name), help)?,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -84,8 +78,14 @@ impl<C: Chain> StorageProofOverheadMetric<C> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Chain> Metric for StorageProofOverheadMetric<C> {
|
||||
fn register(&self, registry: &Registry) -> Result<(), PrometheusError> {
|
||||
register(self.metric.clone(), registry).map(drop)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C: Chain> StandaloneMetrics for StorageProofOverheadMetric<C> {
|
||||
impl<C: Chain> StandaloneMetric for StorageProofOverheadMetric<C> {
|
||||
fn update_interval(&self) -> Duration {
|
||||
C::AVERAGE_BLOCK_INTERVAL * UPDATE_INTERVAL_IN_BLOCKS
|
||||
}
|
||||
|
||||
@@ -16,13 +16,10 @@
|
||||
|
||||
use bp_header_chain::find_grandpa_authorities_scheduled_change;
|
||||
use finality_relay::SourceHeader as FinalitySourceHeader;
|
||||
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.
|
||||
/// implements `finality_relay::SourceHeader` and may be used in headers sync directly.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct SyncHeader<Header>(Header);
|
||||
|
||||
@@ -47,21 +44,6 @@ impl<Header> From<Header> for SyncHeader<Header> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<Header: HeaderT> SourceHeader<Header::Hash, Header::Number> for SyncHeader<Header> {
|
||||
fn id(&self) -> HeaderId<Header::Hash, Header::Number> {
|
||||
relay_utils::HeaderId(*self.0.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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Header: HeaderT> FinalitySourceHeader<Header::Number> for SyncHeader<Header> {
|
||||
fn number(&self) -> Header::Number {
|
||||
*self.0.number()
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
[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]
|
||||
anyhow = "1.0"
|
||||
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" }
|
||||
thiserror = "1.0.26"
|
||||
@@ -1,66 +0,0 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Exchange-relay errors.
|
||||
|
||||
use crate::exchange::{BlockHashOf, BlockNumberOf, TransactionHashOf};
|
||||
|
||||
use relay_utils::MaybeConnectionError;
|
||||
use std::fmt::{Debug, Display};
|
||||
use thiserror::Error;
|
||||
|
||||
/// Error type given pipeline.
|
||||
pub type ErrorOf<P> = Error<BlockHashOf<P>, BlockNumberOf<P>, TransactionHashOf<P>>;
|
||||
|
||||
/// Exchange-relay error type.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error<Hash: Display, HeaderNumber: Display, SourceTxHash: Display> {
|
||||
/// Failed to check finality of the requested header on the target node.
|
||||
#[error("Failed to check finality of header {0}/{1} on {2} node: {3:?}")]
|
||||
Finality(HeaderNumber, Hash, &'static str, anyhow::Error),
|
||||
/// Error retrieving block from the source node.
|
||||
#[error("Error retrieving block {0} from {1} node: {2:?}")]
|
||||
RetrievingBlock(Hash, &'static str, anyhow::Error),
|
||||
/// Error retrieving transaction from the source node.
|
||||
#[error("Error retrieving transaction {0} from {1} node: {2:?}")]
|
||||
RetrievingTransaction(SourceTxHash, &'static str, anyhow::Error),
|
||||
/// Failed to check existence of header from the target node.
|
||||
#[error("Failed to check existence of header {0}/{1} on {2} node: {3:?}")]
|
||||
CheckHeaderExistence(HeaderNumber, Hash, &'static str, anyhow::Error),
|
||||
/// Failed to prepare proof for the transaction from the source node.
|
||||
#[error("Error building transaction {0} proof on {1} node: {2:?}")]
|
||||
BuildTransactionProof(String, &'static str, anyhow::Error, bool),
|
||||
/// Failed to submit the transaction proof to the target node.
|
||||
#[error("Error submitting transaction {0} proof to {1} node: {2:?}")]
|
||||
SubmitTransactionProof(String, &'static str, anyhow::Error, bool),
|
||||
/// Transaction filtering failed.
|
||||
#[error("Transaction filtering has failed with {0:?}")]
|
||||
TransactionFiltering(anyhow::Error, bool),
|
||||
/// Utilities/metrics error.
|
||||
#[error("{0}")]
|
||||
Utils(#[from] relay_utils::Error),
|
||||
}
|
||||
|
||||
impl<T: Display, U: Display, V: Display> MaybeConnectionError for Error<T, U, V> {
|
||||
fn is_connection_error(&self) -> bool {
|
||||
match *self {
|
||||
Self::BuildTransactionProof(_, _, _, b) => b,
|
||||
Self::SubmitTransactionProof(_, _, _, b) => b,
|
||||
Self::TransactionFiltering(_, b) => b,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,904 +0,0 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Relaying proofs of exchange transaction.
|
||||
|
||||
use crate::error::{Error, ErrorOf};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use async_trait::async_trait;
|
||||
use relay_utils::{relay_loop::Client as RelayClient, FailedClient, MaybeConnectionError};
|
||||
use std::{
|
||||
fmt::{Debug, Display},
|
||||
string::ToString,
|
||||
};
|
||||
|
||||
/// Transaction proof pipeline.
|
||||
pub trait TransactionProofPipeline: 'static {
|
||||
/// 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: 'static + Send + Sync;
|
||||
}
|
||||
|
||||
/// Block that is participating in exchange.
|
||||
pub trait SourceBlock: 'static + Send + Sync {
|
||||
/// Block hash type.
|
||||
type Hash: 'static + Clone + Send + Sync + Debug + Display;
|
||||
/// Block number type.
|
||||
type Number: 'static
|
||||
+ Debug
|
||||
+ Display
|
||||
+ Clone
|
||||
+ Copy
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 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: 'static + Send {
|
||||
/// Transaction hash type.
|
||||
type Hash: Debug + Display + Clone;
|
||||
|
||||
/// 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>) -> std::result::Result<bool, Self::Error>;
|
||||
/// Returns `Ok(true)` if header is finalized by the target node.
|
||||
async fn is_header_finalized(&self, id: &HeaderId<P>) -> Result<bool, Self::Error>;
|
||||
/// Returns best finalized header id.
|
||||
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,
|
||||
Error::TransactionFiltering(
|
||||
anyhow!("{:?}", err),
|
||||
err.is_connection_error(),
|
||||
),
|
||||
)
|
||||
})?;
|
||||
|
||||
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<(), ErrorOf<P>> {
|
||||
// wait for transaction and header on source node
|
||||
let (source_header_id, source_tx_index) =
|
||||
wait_transaction_mined(source_client, &source_tx_hash).await?;
|
||||
let source_block = source_client.block_by_hash(source_header_id.1.clone()).await;
|
||||
let source_block = source_block.map_err(|err| {
|
||||
Error::RetrievingBlock(source_header_id.1.clone(), P::SOURCE_NAME, anyhow!("{:?}", err))
|
||||
})?;
|
||||
// wait for transaction and header on target node
|
||||
wait_header_imported(target_client, &source_header_id).await?;
|
||||
wait_header_finalized(target_client, &source_header_id).await?;
|
||||
|
||||
// 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?,
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// 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, ErrorOf<P>> {
|
||||
source_client
|
||||
.transaction_proof(source_block, source_tx_index)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
Error::BuildTransactionProof(
|
||||
source_tx_id.to_owned(),
|
||||
P::SOURCE_NAME,
|
||||
anyhow!("{:?}", err),
|
||||
err.is_connection_error(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// 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<(), ErrorOf<P>> {
|
||||
target_client.submit_transaction_proof(source_tx_proof).await.map_err(|err| {
|
||||
Error::SubmitTransactionProof(
|
||||
source_tx_id.to_owned(),
|
||||
P::TARGET_NAME,
|
||||
anyhow!("{:?}", err),
|
||||
err.is_connection_error(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// 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), ErrorOf<P>> {
|
||||
loop {
|
||||
let source_header_and_tx =
|
||||
source_client.transaction_block(source_tx_hash).await.map_err(|err| {
|
||||
Error::RetrievingTransaction(
|
||||
source_tx_hash.clone(),
|
||||
P::SOURCE_NAME,
|
||||
anyhow!("{:?}", 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<(), ErrorOf<P>> {
|
||||
loop {
|
||||
let is_header_known =
|
||||
target_client.is_header_known(source_header_id).await.map_err(|err| {
|
||||
Error::CheckHeaderExistence(
|
||||
source_header_id.0,
|
||||
source_header_id.1.clone(),
|
||||
P::TARGET_NAME,
|
||||
anyhow!("{:?}", 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<(), ErrorOf<P>> {
|
||||
loop {
|
||||
let is_header_finalized =
|
||||
target_client.is_header_finalized(source_header_id).await.map_err(|err| {
|
||||
Error::Finality(
|
||||
source_header_id.0,
|
||||
source_header_id.1.clone(),
|
||||
P::TARGET_NAME,
|
||||
anyhow!("{:?}", 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!(async_std::task::block_on(relay_single_transaction_proof(
|
||||
source,
|
||||
target,
|
||||
test_transaction_hash(0)
|
||||
))
|
||||
.is_ok());
|
||||
assert_eq!(
|
||||
target.data.lock().submitted_proofs,
|
||||
vec![TestTransactionProof(test_transaction_hash(0))],
|
||||
);
|
||||
}
|
||||
|
||||
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))
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,322 +0,0 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Relaying proofs of exchange transactions.
|
||||
|
||||
use crate::{
|
||||
error::Error,
|
||||
exchange::{
|
||||
relay_block_transactions, BlockNumberOf, RelayedBlockTransactions, SourceClient,
|
||||
TargetClient, TransactionProofPipeline,
|
||||
},
|
||||
exchange_loop_metrics::ExchangeLoopMetrics,
|
||||
};
|
||||
|
||||
use crate::error::ErrorOf;
|
||||
use backoff::backoff::Backoff;
|
||||
use futures::{future::FutureExt, select};
|
||||
use num_traits::One;
|
||||
use relay_utils::{
|
||||
metrics::{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: 'static + Clone + Send + Sync {
|
||||
/// Associated block number.
|
||||
type BlockNumber: 'static + Send + Sync;
|
||||
|
||||
/// 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: 'static + Clone + Copy + Send + Sync> 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// Return prefix that will be used by default to expose Prometheus metrics of the exchange loop.
|
||||
pub fn metrics_prefix<P: TransactionProofPipeline>() -> String {
|
||||
format!("{}_to_{}_Exchange", P::SOURCE_NAME, P::TARGET_NAME)
|
||||
}
|
||||
|
||||
/// Run proofs synchronization.
|
||||
pub async fn run<P: TransactionProofPipeline>(
|
||||
storage: impl TransactionProofsRelayStorage<BlockNumber = BlockNumberOf<P>>,
|
||||
source_client: impl SourceClient<P>,
|
||||
target_client: impl TargetClient<P>,
|
||||
metrics_params: MetricsParams,
|
||||
exit_signal: impl Future<Output = ()> + 'static + Send,
|
||||
) -> Result<(), ErrorOf<P>> {
|
||||
let exit_signal = exit_signal.shared();
|
||||
|
||||
relay_utils::relay_loop(source_client, target_client)
|
||||
.with_metrics(Some(metrics_prefix::<P>()), metrics_params)
|
||||
.loop_metric(ExchangeLoopMetrics::new)?
|
||||
.standalone_metric(GlobalMetrics::new)?
|
||||
.expose()
|
||||
.await?
|
||||
.run(metrics_prefix::<P>(), move |source_client, target_client, metrics| {
|
||||
run_until_connection_lost(
|
||||
storage.clone(),
|
||||
source_client,
|
||||
target_client,
|
||||
metrics,
|
||||
exit_signal.clone(),
|
||||
)
|
||||
})
|
||||
.await
|
||||
.map_err(Error::Utils)
|
||||
}
|
||||
|
||||
/// 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_exch: Option<ExchangeLoopMetrics>,
|
||||
exit_signal: impl Future<Output = ()> + Send,
|
||||
) -> 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 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(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));
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}));
|
||||
|
||||
let _ = async_std::task::block_on(run(
|
||||
storage,
|
||||
source,
|
||||
target,
|
||||
MetricsParams::disabled(),
|
||||
exit_receiver.into_future().map(|(_, _)| ()),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Metrics for currency-exchange relay loop.
|
||||
|
||||
use crate::exchange::{BlockNumberOf, RelayedBlockTransactions, TransactionProofPipeline};
|
||||
use relay_utils::metrics::{
|
||||
metric_name, register, Counter, CounterVec, GaugeVec, Opts, PrometheusError, 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 ExchangeLoopMetrics {
|
||||
/// Create and register exchange loop metrics.
|
||||
pub fn new(registry: &Registry, prefix: Option<&str>) -> Result<Self, PrometheusError> {
|
||||
Ok(ExchangeLoopMetrics {
|
||||
best_block_numbers: register(
|
||||
GaugeVec::new(
|
||||
Opts::new(
|
||||
metric_name(prefix, "best_block_numbers"),
|
||||
"Best finalized block numbers",
|
||||
),
|
||||
&["type"],
|
||||
)?,
|
||||
registry,
|
||||
)?,
|
||||
processed_blocks: register(
|
||||
Counter::new(
|
||||
metric_name(prefix, "processed_blocks"),
|
||||
"Total number of processed blocks",
|
||||
)?,
|
||||
registry,
|
||||
)?,
|
||||
processed_transactions: register(
|
||||
CounterVec::new(
|
||||
Opts::new(
|
||||
metric_name(prefix, "processed_transactions"),
|
||||
"Total number of processed transactions",
|
||||
),
|
||||
&["type"],
|
||||
)?,
|
||||
registry,
|
||||
)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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 _);
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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 error;
|
||||
pub mod exchange;
|
||||
pub mod exchange_loop;
|
||||
pub mod exchange_loop_metrics;
|
||||
@@ -12,7 +12,6 @@ async-trait = "0.1.40"
|
||||
backoff = "0.2"
|
||||
bp-header-chain = { path = "../../primitives/header-chain" }
|
||||
futures = "0.3.5"
|
||||
headers-relay = { path = "../headers" }
|
||||
log = "0.4.11"
|
||||
num-traits = "0.2"
|
||||
relay-utils = { path = "../utils" }
|
||||
|
||||
@@ -19,17 +19,17 @@
|
||||
//! is the mandatory headers, which we always submit to the target node. For such headers, we
|
||||
//! assume that the persistent proof either exists, or will eventually become available.
|
||||
|
||||
use crate::{FinalityProof, FinalitySyncPipeline, SourceHeader};
|
||||
use crate::{
|
||||
sync_loop_metrics::SyncLoopMetrics, FinalityProof, FinalitySyncPipeline, SourceHeader,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use backoff::backoff::Backoff;
|
||||
use futures::{select, Future, FutureExt, Stream, StreamExt};
|
||||
use headers_relay::sync_loop_metrics::SyncLoopMetrics;
|
||||
use num_traits::{One, Saturating};
|
||||
use relay_utils::{
|
||||
metrics::{GlobalMetrics, MetricsParams},
|
||||
relay_loop::Client as RelayClient,
|
||||
retry_backoff, FailedClient, MaybeConnectionError,
|
||||
metrics::MetricsParams, relay_loop::Client as RelayClient, retry_backoff, FailedClient,
|
||||
MaybeConnectionError,
|
||||
};
|
||||
use std::{
|
||||
pin::Pin,
|
||||
@@ -113,9 +113,8 @@ pub async fn run<P: FinalitySyncPipeline>(
|
||||
) -> Result<(), relay_utils::Error> {
|
||||
let exit_signal = exit_signal.shared();
|
||||
relay_utils::relay_loop(source_client, target_client)
|
||||
.with_metrics(Some(metrics_prefix::<P>()), metrics_params)
|
||||
.loop_metric(SyncLoopMetrics::new)?
|
||||
.standalone_metric(GlobalMetrics::new)?
|
||||
.with_metrics(metrics_params)
|
||||
.loop_metric(SyncLoopMetrics::new(Some(&metrics_prefix::<P>()))?)?
|
||||
.expose()
|
||||
.await?
|
||||
.run(metrics_prefix::<P>(), move |source_client, target_client, metrics| {
|
||||
|
||||
@@ -28,6 +28,7 @@ use std::fmt::Debug;
|
||||
|
||||
mod finality_loop;
|
||||
mod finality_loop_tests;
|
||||
mod sync_loop_metrics;
|
||||
|
||||
/// Finality proofs synchronization pipeline.
|
||||
pub trait FinalitySyncPipeline: 'static + Clone + Debug + Send + Sync {
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Metrics for headers synchronization relay loop.
|
||||
|
||||
use relay_utils::metrics::{
|
||||
metric_name, register, GaugeVec, Metric, Opts, PrometheusError, Registry, U64,
|
||||
};
|
||||
|
||||
/// Headers sync metrics.
|
||||
#[derive(Clone)]
|
||||
pub struct SyncLoopMetrics {
|
||||
/// Best syncing headers at "source" and "target" nodes.
|
||||
best_block_numbers: GaugeVec<U64>,
|
||||
}
|
||||
|
||||
impl SyncLoopMetrics {
|
||||
/// Create and register headers loop metrics.
|
||||
pub fn new(prefix: Option<&str>) -> Result<Self, PrometheusError> {
|
||||
Ok(SyncLoopMetrics {
|
||||
best_block_numbers: GaugeVec::new(
|
||||
Opts::new(
|
||||
metric_name(prefix, "best_block_numbers"),
|
||||
"Best block numbers on source and target nodes",
|
||||
),
|
||||
&["node"],
|
||||
)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Update best block number at source.
|
||||
pub fn update_best_block_at_source<Number: Into<u64>>(&self, source_best_number: Number) {
|
||||
self.best_block_numbers
|
||||
.with_label_values(&["source"])
|
||||
.set(source_best_number.into());
|
||||
}
|
||||
|
||||
/// Update best block number at target.
|
||||
pub fn update_best_block_at_target<Number: Into<u64>>(&self, target_best_number: Number) {
|
||||
self.best_block_numbers
|
||||
.with_label_values(&["target"])
|
||||
.set(target_best_number.into());
|
||||
}
|
||||
}
|
||||
|
||||
impl Metric for SyncLoopMetrics {
|
||||
fn register(&self, registry: &Registry) -> Result<(), PrometheusError> {
|
||||
register(self.best_block_numbers.clone(), registry)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
[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
@@ -1,33 +0,0 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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. Pallet/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;
|
||||
@@ -1,529 +0,0 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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,
|
||||
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,
|
||||
/// The best header number known to source node.
|
||||
source_best_number: Option<P::Number>,
|
||||
/// The 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
|
||||
}
|
||||
|
||||
/// The 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 reorganization
|
||||
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 reorganizations 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())
|
||||
}
|
||||
|
||||
/// Select orphan header to download.
|
||||
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},
|
||||
sync_loop_tests::{TestHash, TestHeadersSyncPipeline, TestNumber},
|
||||
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)]));
|
||||
}
|
||||
}
|
||||
@@ -1,654 +0,0 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Entrypoint for running headers synchronization loop.
|
||||
|
||||
use crate::{
|
||||
sync::{HeadersSync, HeadersSyncParams},
|
||||
sync_loop_metrics::SyncLoopMetrics,
|
||||
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::{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 reorganization in target chain.
|
||||
/// This reorganization could invalidate our knowledge about sync process (i.e. we have asked if
|
||||
/// HeaderA is known to target, but then reorganization 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) reorganization 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 the 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>: 'static + 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 () {}
|
||||
|
||||
/// Return prefix that will be used by default to expose Prometheus metrics of the finality proofs
|
||||
/// sync loop.
|
||||
pub fn metrics_prefix<P: HeadersSyncPipeline>() -> String {
|
||||
format!("{}_to_{}_Sync", P::SOURCE_NAME, P::TARGET_NAME)
|
||||
}
|
||||
|
||||
/// Run headers synchronization.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async 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: MetricsParams,
|
||||
exit_signal: impl Future<Output = ()> + 'static + Send,
|
||||
) -> Result<(), relay_utils::Error> {
|
||||
let exit_signal = exit_signal.shared();
|
||||
relay_utils::relay_loop(source_client, target_client)
|
||||
.with_metrics(Some(metrics_prefix::<P>()), metrics_params)
|
||||
.loop_metric(SyncLoopMetrics::new)?
|
||||
.standalone_metric(GlobalMetrics::new)?
|
||||
.expose()
|
||||
.await?
|
||||
.run(metrics_prefix::<P>(), move |source_client, target_client, metrics| {
|
||||
run_until_connection_lost(
|
||||
source_client,
|
||||
source_tick,
|
||||
target_client,
|
||||
target_tick,
|
||||
sync_maintain.clone(),
|
||||
sync_params.clone(),
|
||||
metrics,
|
||||
exit_signal.clone(),
|
||||
)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// 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_sync: Option<SyncLoopMetrics>,
|
||||
exit_signal: impl Future<Output = ()> + Send,
|
||||
) -> 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_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).map(|id| id.0), *now_target_header)
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Metrics for headers synchronization relay loop.
|
||||
|
||||
use crate::{
|
||||
sync::HeadersSync,
|
||||
sync_types::{HeaderStatus, HeadersSyncPipeline},
|
||||
};
|
||||
|
||||
use num_traits::Zero;
|
||||
use relay_utils::metrics::{metric_name, register, GaugeVec, Opts, PrometheusError, 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 SyncLoopMetrics {
|
||||
/// Create and register headers loop metrics.
|
||||
pub fn new(registry: &Registry, prefix: Option<&str>) -> Result<Self, PrometheusError> {
|
||||
Ok(SyncLoopMetrics {
|
||||
best_block_numbers: register(
|
||||
GaugeVec::new(
|
||||
Opts::new(
|
||||
metric_name(prefix, "best_block_numbers"),
|
||||
"Best block numbers on source and target nodes",
|
||||
),
|
||||
&["node"],
|
||||
)?,
|
||||
registry,
|
||||
)?,
|
||||
blocks_in_state: register(
|
||||
GaugeVec::new(
|
||||
Opts::new(
|
||||
metric_name(prefix, "blocks_in_state"),
|
||||
"Number of blocks in given state",
|
||||
),
|
||||
&["state"],
|
||||
)?,
|
||||
registry,
|
||||
)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl SyncLoopMetrics {
|
||||
/// Update best block number at source.
|
||||
pub fn update_best_block_at_source<Number: Into<u64>>(&self, source_best_number: Number) {
|
||||
self.best_block_numbers
|
||||
.with_label_values(&["source"])
|
||||
.set(source_best_number.into());
|
||||
}
|
||||
|
||||
/// Update best block number at target.
|
||||
pub fn update_best_block_at_target<Number: Into<u64>>(&self, target_best_number: Number) {
|
||||
self.best_block_numbers
|
||||
.with_label_values(&["target"])
|
||||
.set(target_best_number.into());
|
||||
}
|
||||
|
||||
/// 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.update_best_block_at_source(source_best_number);
|
||||
self.update_best_block_at_target(target_best_number);
|
||||
|
||||
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 _);
|
||||
}
|
||||
}
|
||||
@@ -1,602 +0,0 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use crate::{
|
||||
sync_loop::{run, SourceClient, TargetClient},
|
||||
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::{
|
||||
metrics::MetricsParams, 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;
|
||||
|
||||
let _ = async_std::task::block_on(run(
|
||||
source,
|
||||
test_tick(),
|
||||
target,
|
||||
test_tick(),
|
||||
(),
|
||||
crate::sync::tests::default_sync_params(),
|
||||
MetricsParams::disabled(),
|
||||
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),
|
||||
});
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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: 'static + 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 are either rejected by
|
||||
/// target node immediately, or their descendants of incomplete headers).
|
||||
pub rejected: Vec<Id>,
|
||||
/// Fatal target node error, if it has occurred 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)
|
||||
}
|
||||
}
|
||||
@@ -16,17 +16,11 @@
|
||||
|
||||
//! Substrate relay helpers
|
||||
|
||||
use relay_utils::metrics::{FloatJsonValueMetric, PrometheusError, Registry};
|
||||
use relay_utils::metrics::{FloatJsonValueMetric, PrometheusError};
|
||||
|
||||
/// Creates standalone token price metric.
|
||||
pub fn token_price_metric(
|
||||
registry: &Registry,
|
||||
prefix: Option<&str>,
|
||||
token_id: &str,
|
||||
) -> Result<FloatJsonValueMetric, PrometheusError> {
|
||||
pub fn token_price_metric(token_id: &str) -> Result<FloatJsonValueMetric, PrometheusError> {
|
||||
FloatJsonValueMetric::new(
|
||||
registry,
|
||||
prefix,
|
||||
format!("https://api.coingecko.com/api/v3/simple/price?ids={}&vs_currencies=btc", token_id),
|
||||
format!("$.{}.btc", token_id),
|
||||
format!("{}_to_base_conversion_rate", token_id.replace("-", "_")),
|
||||
|
||||
@@ -34,7 +34,9 @@ use relay_substrate_client::{
|
||||
BlockNumberOf, Chain, Client, HashOf,
|
||||
};
|
||||
use relay_utils::{
|
||||
metrics::{F64SharedRef, MetricsParams},
|
||||
metrics::{
|
||||
FloatJsonValueMetric, GlobalMetrics, MetricsParams, PrometheusError, StandaloneMetric,
|
||||
},
|
||||
BlockNumberBase,
|
||||
};
|
||||
use sp_core::{storage::StorageKey, Bytes};
|
||||
@@ -63,6 +65,8 @@ pub struct MessagesRelayParams<SC: Chain, SS, TC: Chain, TS, Strategy: RelayStra
|
||||
pub lane_id: LaneId,
|
||||
/// Metrics parameters.
|
||||
pub metrics_params: MetricsParams,
|
||||
/// Pre-registered standalone metrics.
|
||||
pub standalone_metrics: Option<StandaloneMessagesMetrics<SC, TC>>,
|
||||
/// Relay strategy
|
||||
pub relay_strategy: Strategy,
|
||||
}
|
||||
@@ -241,110 +245,155 @@ pub fn select_delivery_transaction_limits<W: pallet_bridge_messages::WeightInfoE
|
||||
(max_number_of_messages, weight_for_messages_dispatch)
|
||||
}
|
||||
|
||||
/// Shared references to the values of standalone metrics of the message lane relay loop.
|
||||
/// Shared references to the standalone metrics of the message lane relay loop.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StandaloneMessagesMetrics {
|
||||
/// Shared reference to the actual target -> <base> chain token conversion rate.
|
||||
pub target_to_base_conversion_rate: Option<F64SharedRef>,
|
||||
/// Shared reference to the actual source -> <base> chain token conversion rate.
|
||||
pub source_to_base_conversion_rate: Option<F64SharedRef>,
|
||||
/// Shared reference to the stored (in the source chain runtime storage) target -> source chain
|
||||
/// conversion rate.
|
||||
pub target_to_source_conversion_rate: Option<F64SharedRef>,
|
||||
pub struct StandaloneMessagesMetrics<SC: Chain, TC: Chain> {
|
||||
/// Global metrics.
|
||||
pub global: GlobalMetrics,
|
||||
/// Storage chain proof overhead metric.
|
||||
pub source_storage_proof_overhead: StorageProofOverheadMetric<SC>,
|
||||
/// Target chain proof overhead metric.
|
||||
pub target_storage_proof_overhead: StorageProofOverheadMetric<TC>,
|
||||
/// Source tokens to base conversion rate metric.
|
||||
pub source_to_base_conversion_rate: Option<FloatJsonValueMetric>,
|
||||
/// Target tokens to base conversion rate metric.
|
||||
pub target_to_base_conversion_rate: Option<FloatJsonValueMetric>,
|
||||
/// Source tokens to target tokens conversion rate metric. This rate is stored by the target
|
||||
/// chain.
|
||||
pub source_to_target_conversion_rate:
|
||||
Option<FloatStorageValueMetric<TC, sp_runtime::FixedU128>>,
|
||||
/// Target tokens to source tokens conversion rate metric. This rate is stored by the source
|
||||
/// chain.
|
||||
pub target_to_source_conversion_rate:
|
||||
Option<FloatStorageValueMetric<SC, sp_runtime::FixedU128>>,
|
||||
}
|
||||
|
||||
impl StandaloneMessagesMetrics {
|
||||
impl<SC: Chain, TC: Chain> StandaloneMessagesMetrics<SC, TC> {
|
||||
/// Swap source and target sides.
|
||||
pub fn reverse(self) -> StandaloneMessagesMetrics<TC, SC> {
|
||||
StandaloneMessagesMetrics {
|
||||
global: self.global,
|
||||
source_storage_proof_overhead: self.target_storage_proof_overhead,
|
||||
target_storage_proof_overhead: self.source_storage_proof_overhead,
|
||||
source_to_base_conversion_rate: self.target_to_base_conversion_rate,
|
||||
target_to_base_conversion_rate: self.source_to_base_conversion_rate,
|
||||
source_to_target_conversion_rate: self.target_to_source_conversion_rate,
|
||||
target_to_source_conversion_rate: self.source_to_target_conversion_rate,
|
||||
}
|
||||
}
|
||||
|
||||
/// Register all metrics in the registry.
|
||||
pub fn register_and_spawn(
|
||||
self,
|
||||
metrics: MetricsParams,
|
||||
) -> Result<MetricsParams, PrometheusError> {
|
||||
self.global.register_and_spawn(&metrics.registry)?;
|
||||
self.source_storage_proof_overhead.register_and_spawn(&metrics.registry)?;
|
||||
self.target_storage_proof_overhead.register_and_spawn(&metrics.registry)?;
|
||||
if let Some(m) = self.source_to_base_conversion_rate {
|
||||
m.register_and_spawn(&metrics.registry)?;
|
||||
}
|
||||
if let Some(m) = self.target_to_base_conversion_rate {
|
||||
m.register_and_spawn(&metrics.registry)?;
|
||||
}
|
||||
if let Some(m) = self.target_to_source_conversion_rate {
|
||||
m.register_and_spawn(&metrics.registry)?;
|
||||
}
|
||||
Ok(metrics)
|
||||
}
|
||||
|
||||
/// Return conversion rate from target to source tokens.
|
||||
pub async fn target_to_source_conversion_rate(&self) -> Option<f64> {
|
||||
let target_to_base_conversion_rate =
|
||||
(*self.target_to_base_conversion_rate.as_ref()?.read().await)?;
|
||||
let source_to_base_conversion_rate =
|
||||
(*self.source_to_base_conversion_rate.as_ref()?.read().await)?;
|
||||
Some(source_to_base_conversion_rate / target_to_base_conversion_rate)
|
||||
Self::compute_target_to_source_conversion_rate(
|
||||
*self.target_to_base_conversion_rate.as_ref()?.shared_value_ref().read().await,
|
||||
*self.source_to_base_conversion_rate.as_ref()?.shared_value_ref().read().await,
|
||||
)
|
||||
}
|
||||
|
||||
/// Return conversion rate from target to source tokens, given conversion rates from
|
||||
/// target/source tokens to some base token.
|
||||
fn compute_target_to_source_conversion_rate(
|
||||
target_to_base_conversion_rate: Option<f64>,
|
||||
source_to_base_conversion_rate: Option<f64>,
|
||||
) -> Option<f64> {
|
||||
Some(source_to_base_conversion_rate? / target_to_base_conversion_rate?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Add general standalone metrics for the message lane relay loop.
|
||||
pub fn add_standalone_metrics<P: SubstrateMessageLane>(
|
||||
metrics_prefix: Option<String>,
|
||||
metrics_params: MetricsParams,
|
||||
source_client: Client<P::SourceChain>,
|
||||
/// Create standalone metrics for the message lane relay loop.
|
||||
///
|
||||
/// All metrics returned by this function are exposed by loops that are serving given lane (`P`)
|
||||
/// and by loops that are serving reverse lane (`P` with swapped `TargetChain` and `SourceChain`).
|
||||
pub fn standalone_metrics<SC: Chain, TC: Chain>(
|
||||
source_client: Client<SC>,
|
||||
target_client: Client<TC>,
|
||||
source_chain_token_id: Option<&str>,
|
||||
target_chain_token_id: Option<&str>,
|
||||
source_to_target_conversion_rate_params: Option<(StorageKey, FixedU128)>,
|
||||
target_to_source_conversion_rate_params: Option<(StorageKey, FixedU128)>,
|
||||
) -> anyhow::Result<(MetricsParams, StandaloneMessagesMetrics)> {
|
||||
let mut target_to_source_conversion_rate = None;
|
||||
let mut source_to_base_conversion_rate = None;
|
||||
let mut target_to_base_conversion_rate = None;
|
||||
let mut metrics_params = relay_utils::relay_metrics(metrics_prefix, metrics_params)
|
||||
.standalone_metric(|registry, prefix| {
|
||||
StorageProofOverheadMetric::new(
|
||||
registry,
|
||||
prefix,
|
||||
source_client.clone(),
|
||||
format!("{}_storage_proof_overhead", P::SourceChain::NAME.to_lowercase()),
|
||||
format!("{} storage proof overhead", P::SourceChain::NAME),
|
||||
)
|
||||
})?;
|
||||
if let Some((
|
||||
target_to_source_conversion_rate_storage_key,
|
||||
initial_target_to_source_conversion_rate,
|
||||
)) = target_to_source_conversion_rate_params
|
||||
{
|
||||
metrics_params = metrics_params.standalone_metric(|registry, prefix| {
|
||||
let metric = FloatStorageValueMetric::<_, sp_runtime::FixedU128>::new(
|
||||
registry,
|
||||
prefix,
|
||||
source_client,
|
||||
target_to_source_conversion_rate_storage_key,
|
||||
Some(initial_target_to_source_conversion_rate),
|
||||
format!(
|
||||
"{}_{}_to_{}_conversion_rate",
|
||||
P::SourceChain::NAME,
|
||||
P::TargetChain::NAME,
|
||||
P::SourceChain::NAME
|
||||
),
|
||||
format!(
|
||||
"{} to {} tokens conversion rate (used by {})",
|
||||
P::TargetChain::NAME,
|
||||
P::SourceChain::NAME,
|
||||
P::SourceChain::NAME
|
||||
),
|
||||
)?;
|
||||
target_to_source_conversion_rate = Some(metric.shared_value_ref());
|
||||
Ok(metric)
|
||||
})?;
|
||||
}
|
||||
if let Some(source_chain_token_id) = source_chain_token_id {
|
||||
metrics_params = metrics_params.standalone_metric(|registry, prefix| {
|
||||
let metric =
|
||||
crate::helpers::token_price_metric(registry, prefix, source_chain_token_id)?;
|
||||
source_to_base_conversion_rate = Some(metric.shared_value_ref());
|
||||
Ok(metric)
|
||||
})?;
|
||||
}
|
||||
if let Some(target_chain_token_id) = target_chain_token_id {
|
||||
metrics_params = metrics_params.standalone_metric(|registry, prefix| {
|
||||
let metric =
|
||||
crate::helpers::token_price_metric(registry, prefix, target_chain_token_id)?;
|
||||
target_to_base_conversion_rate = Some(metric.shared_value_ref());
|
||||
Ok(metric)
|
||||
})?;
|
||||
}
|
||||
Ok((
|
||||
metrics_params.into_params(),
|
||||
StandaloneMessagesMetrics {
|
||||
target_to_base_conversion_rate,
|
||||
source_to_base_conversion_rate,
|
||||
target_to_source_conversion_rate,
|
||||
},
|
||||
))
|
||||
) -> anyhow::Result<StandaloneMessagesMetrics<SC, TC>> {
|
||||
Ok(StandaloneMessagesMetrics {
|
||||
global: GlobalMetrics::new()?,
|
||||
source_storage_proof_overhead: StorageProofOverheadMetric::new(
|
||||
source_client.clone(),
|
||||
format!("{}_storage_proof_overhead", SC::NAME.to_lowercase()),
|
||||
format!("{} storage proof overhead", SC::NAME),
|
||||
)?,
|
||||
target_storage_proof_overhead: StorageProofOverheadMetric::new(
|
||||
target_client.clone(),
|
||||
format!("{}_storage_proof_overhead", TC::NAME.to_lowercase()),
|
||||
format!("{} storage proof overhead", TC::NAME),
|
||||
)?,
|
||||
source_to_base_conversion_rate: source_chain_token_id
|
||||
.map(|source_chain_token_id| {
|
||||
crate::helpers::token_price_metric(source_chain_token_id).map(Some)
|
||||
})
|
||||
.unwrap_or(Ok(None))?,
|
||||
target_to_base_conversion_rate: target_chain_token_id
|
||||
.map(|target_chain_token_id| {
|
||||
crate::helpers::token_price_metric(target_chain_token_id).map(Some)
|
||||
})
|
||||
.unwrap_or(Ok(None))?,
|
||||
source_to_target_conversion_rate: source_to_target_conversion_rate_params
|
||||
.map(|(key, rate)| {
|
||||
FloatStorageValueMetric::<_, sp_runtime::FixedU128>::new(
|
||||
target_client,
|
||||
key,
|
||||
Some(rate),
|
||||
format!("{}_{}_to_{}_conversion_rate", TC::NAME, SC::NAME, TC::NAME),
|
||||
format!(
|
||||
"{} to {} tokens conversion rate (used by {})",
|
||||
SC::NAME,
|
||||
TC::NAME,
|
||||
TC::NAME
|
||||
),
|
||||
)
|
||||
.map(Some)
|
||||
})
|
||||
.unwrap_or(Ok(None))?,
|
||||
target_to_source_conversion_rate: target_to_source_conversion_rate_params
|
||||
.map(|(key, rate)| {
|
||||
FloatStorageValueMetric::<_, sp_runtime::FixedU128>::new(
|
||||
source_client,
|
||||
key,
|
||||
Some(rate),
|
||||
format!("{}_{}_to_{}_conversion_rate", SC::NAME, TC::NAME, SC::NAME),
|
||||
format!(
|
||||
"{} to {} tokens conversion rate (used by {})",
|
||||
TC::NAME,
|
||||
SC::NAME,
|
||||
SC::NAME
|
||||
),
|
||||
)
|
||||
.map(Some)
|
||||
})
|
||||
.unwrap_or(Ok(None))?,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use async_std::sync::{Arc, RwLock};
|
||||
|
||||
type RialtoToMillauMessagesWeights =
|
||||
pallet_bridge_messages::weights::RialtoWeight<rialto_runtime::Runtime>;
|
||||
@@ -369,12 +418,9 @@ mod tests {
|
||||
|
||||
#[async_std::test]
|
||||
async fn target_to_source_conversion_rate_works() {
|
||||
let metrics = StandaloneMessagesMetrics {
|
||||
target_to_base_conversion_rate: Some(Arc::new(RwLock::new(Some(183.15)))),
|
||||
source_to_base_conversion_rate: Some(Arc::new(RwLock::new(Some(12.32)))),
|
||||
target_to_source_conversion_rate: None, // we don't care
|
||||
};
|
||||
|
||||
assert_eq!(metrics.target_to_source_conversion_rate().await, Some(12.32 / 183.15),);
|
||||
assert_eq!(
|
||||
StandaloneMessagesMetrics::<relay_rococo_client::Rococo, relay_wococo_client::Wococo>::compute_target_to_source_conversion_rate(Some(183.15), Some(12.32)),
|
||||
Some(12.32 / 183.15),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ pub struct SubstrateMessagesTarget<P: SubstrateMessageLane> {
|
||||
client: Client<P::TargetChain>,
|
||||
lane: P,
|
||||
lane_id: LaneId,
|
||||
metric_values: StandaloneMessagesMetrics,
|
||||
metric_values: StandaloneMessagesMetrics<P::SourceChain, P::TargetChain>,
|
||||
source_to_target_headers_relay: Option<OnDemandHeadersRelay<P::SourceChain>>,
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ impl<P: SubstrateMessageLane> SubstrateMessagesTarget<P> {
|
||||
client: Client<P::TargetChain>,
|
||||
lane: P,
|
||||
lane_id: LaneId,
|
||||
metric_values: StandaloneMessagesMetrics,
|
||||
metric_values: StandaloneMessagesMetrics<P::SourceChain, P::TargetChain>,
|
||||
source_to_target_headers_relay: Option<OnDemandHeadersRelay<P::SourceChain>>,
|
||||
) -> Self {
|
||||
SubstrateMessagesTarget {
|
||||
|
||||
@@ -19,3 +19,5 @@ parking_lot = "0.11.0"
|
||||
bp-messages = { path = "../../primitives/messages" }
|
||||
bp-runtime = { path = "../../primitives/runtime" }
|
||||
relay-utils = { path = "../utils" }
|
||||
|
||||
sp-arithmetic = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
use num_traits::{SaturatingAdd, Zero};
|
||||
use relay_utils::{BlockNumberBase, HeaderId};
|
||||
use sp_arithmetic::traits::AtLeast32BitUnsigned;
|
||||
use std::{fmt::Debug, ops::Sub};
|
||||
|
||||
/// One-way message lane.
|
||||
@@ -40,7 +41,8 @@ pub trait MessageLane: 'static + Clone + Send + Sync {
|
||||
/// 1) pay transaction fees;
|
||||
/// 2) pay message delivery and dispatch fee;
|
||||
/// 3) pay relayer rewards.
|
||||
type SourceChainBalance: Clone
|
||||
type SourceChainBalance: AtLeast32BitUnsigned
|
||||
+ Clone
|
||||
+ Copy
|
||||
+ Debug
|
||||
+ PartialOrd
|
||||
|
||||
@@ -32,10 +32,7 @@ use futures::{channel::mpsc::unbounded, future::FutureExt, stream::StreamExt};
|
||||
use bp_messages::{LaneId, MessageNonce, UnrewardedRelayersState, Weight};
|
||||
use bp_runtime::messages::DispatchFeePayment;
|
||||
use relay_utils::{
|
||||
interval,
|
||||
metrics::{GlobalMetrics, MetricsParams},
|
||||
process_future_result,
|
||||
relay_loop::Client as RelayClient,
|
||||
interval, metrics::MetricsParams, process_future_result, relay_loop::Client as RelayClient,
|
||||
retry_backoff, FailedClient,
|
||||
};
|
||||
|
||||
@@ -270,9 +267,8 @@ pub async fn run<P: MessageLane, Strategy: RelayStrategy>(
|
||||
let exit_signal = exit_signal.shared();
|
||||
relay_utils::relay_loop(source_client, target_client)
|
||||
.reconnect_delay(params.reconnect_delay)
|
||||
.with_metrics(Some(metrics_prefix::<P>(¶ms.lane)), metrics_params)
|
||||
.loop_metric(MessageLaneLoopMetrics::new)?
|
||||
.standalone_metric(GlobalMetrics::new)?
|
||||
.with_metrics(metrics_params)
|
||||
.loop_metric(MessageLaneLoopMetrics::new(Some(&metrics_prefix::<P>(¶ms.lane)))?)?
|
||||
.expose()
|
||||
.await?
|
||||
.run(metrics_prefix::<P>(¶ms.lane), move |source_client, target_client, metrics| {
|
||||
|
||||
@@ -521,7 +521,7 @@ where
|
||||
nonces_queue_range: 0..maximal_source_queue_index + 1,
|
||||
};
|
||||
|
||||
let strategy = EnforcementStrategy::new(self.relay_strategy.clone());
|
||||
let mut strategy = EnforcementStrategy::new(self.relay_strategy.clone());
|
||||
let range_end = strategy.decide(reference).await?;
|
||||
|
||||
let range_begin = source_queue[0].1.begin();
|
||||
|
||||
@@ -22,7 +22,9 @@ use crate::{
|
||||
};
|
||||
|
||||
use bp_messages::MessageNonce;
|
||||
use relay_utils::metrics::{metric_name, register, GaugeVec, Opts, PrometheusError, Registry, U64};
|
||||
use relay_utils::metrics::{
|
||||
metric_name, register, GaugeVec, Metric, Opts, PrometheusError, Registry, U64,
|
||||
};
|
||||
|
||||
/// Message lane relay metrics.
|
||||
///
|
||||
@@ -38,30 +40,22 @@ pub struct MessageLaneLoopMetrics {
|
||||
|
||||
impl MessageLaneLoopMetrics {
|
||||
/// Create and register messages loop metrics.
|
||||
pub fn new(registry: &Registry, prefix: Option<&str>) -> Result<Self, PrometheusError> {
|
||||
pub fn new(prefix: Option<&str>) -> Result<Self, PrometheusError> {
|
||||
Ok(MessageLaneLoopMetrics {
|
||||
best_block_numbers: register(
|
||||
GaugeVec::new(
|
||||
Opts::new(
|
||||
metric_name(prefix, "best_block_numbers"),
|
||||
"Best finalized block numbers",
|
||||
),
|
||||
&["type"],
|
||||
)?,
|
||||
registry,
|
||||
best_block_numbers: GaugeVec::new(
|
||||
Opts::new(
|
||||
metric_name(prefix, "best_block_numbers"),
|
||||
"Best finalized block numbers",
|
||||
),
|
||||
&["type"],
|
||||
)?,
|
||||
lane_state_nonces: register(
|
||||
GaugeVec::new(
|
||||
Opts::new(metric_name(prefix, "lane_state_nonces"), "Nonces of the lane state"),
|
||||
&["type"],
|
||||
)?,
|
||||
registry,
|
||||
lane_state_nonces: GaugeVec::new(
|
||||
Opts::new(metric_name(prefix, "lane_state_nonces"), "Nonces of the lane state"),
|
||||
&["type"],
|
||||
)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageLaneLoopMetrics {
|
||||
/// Update source client state metrics.
|
||||
pub fn update_source_state<P: MessageLane>(&self, source_client_state: SourceClientState<P>) {
|
||||
self.best_block_numbers
|
||||
@@ -122,3 +116,11 @@ impl MessageLaneLoopMetrics {
|
||||
.set(target_latest_confirmed_nonce);
|
||||
}
|
||||
}
|
||||
|
||||
impl Metric for MessageLaneLoopMetrics {
|
||||
fn register(&self, registry: &Registry) -> Result<(), PrometheusError> {
|
||||
register(self.best_block_numbers.clone(), registry)?;
|
||||
register(self.lane_state_nonces.clone(), registry)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ impl RelayStrategy for AltruisticStrategy {
|
||||
SourceClient: MessageLaneSourceClient<P>,
|
||||
TargetClient: MessageLaneTargetClient<P>,
|
||||
>(
|
||||
&self,
|
||||
&mut self,
|
||||
_reference: &mut RelayReference<P, SourceClient, TargetClient>,
|
||||
) -> bool {
|
||||
true
|
||||
|
||||
@@ -49,7 +49,7 @@ impl<Strategy: RelayStrategy> EnforcementStrategy<Strategy> {
|
||||
SourceClient: MessageLaneSourceClient<P>,
|
||||
TargetClient: MessageLaneTargetClient<P>,
|
||||
>(
|
||||
&self,
|
||||
&mut self,
|
||||
reference: RelayMessagesBatchReference<P, SourceClient, TargetClient>,
|
||||
) -> Option<MessageNonce> {
|
||||
let mut hard_selected_count = 0;
|
||||
|
||||
@@ -47,7 +47,7 @@ impl RelayStrategy for MixStrategy {
|
||||
SourceClient: MessageLaneSourceClient<P>,
|
||||
TargetClient: MessageLaneTargetClient<P>,
|
||||
>(
|
||||
&self,
|
||||
&mut self,
|
||||
reference: &mut RelayReference<P, SourceClient, TargetClient>,
|
||||
) -> bool {
|
||||
match self.relayer_mode {
|
||||
|
||||
@@ -52,7 +52,7 @@ pub trait RelayStrategy: 'static + Clone + Send + Sync {
|
||||
SourceClient: MessageLaneSourceClient<P>,
|
||||
TargetClient: MessageLaneTargetClient<P>,
|
||||
>(
|
||||
&self,
|
||||
&mut self,
|
||||
reference: &mut RelayReference<P, SourceClient, TargetClient>,
|
||||
) -> bool;
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ impl RelayStrategy for RationalStrategy {
|
||||
SourceClient: MessageLaneSourceClient<P>,
|
||||
TargetClient: MessageLaneTargetClient<P>,
|
||||
>(
|
||||
&self,
|
||||
&mut self,
|
||||
reference: &mut RelayReference<P, SourceClient, TargetClient>,
|
||||
) -> bool {
|
||||
// technically, multiple confirmations will be delivered in a single transaction,
|
||||
|
||||
@@ -46,28 +46,38 @@ pub struct MetricsParams {
|
||||
/// Interface and TCP port to be used when exposing Prometheus metrics.
|
||||
pub address: Option<MetricsAddress>,
|
||||
/// Metrics registry. May be `Some(_)` if several components share the same endpoint.
|
||||
pub registry: Option<Registry>,
|
||||
/// Prefix that must be used in metric names.
|
||||
pub metrics_prefix: Option<String>,
|
||||
pub registry: Registry,
|
||||
}
|
||||
|
||||
/// Metrics API.
|
||||
pub trait Metrics: Clone + Send + Sync + 'static {}
|
||||
/// Metric API.
|
||||
pub trait Metric: Clone + Send + Sync + 'static {
|
||||
fn register(&self, registry: &Registry) -> Result<(), PrometheusError>;
|
||||
}
|
||||
|
||||
impl<T: Clone + Send + Sync + 'static> Metrics for T {}
|
||||
|
||||
/// Standalone metrics API.
|
||||
/// Standalone metric API.
|
||||
///
|
||||
/// Metrics of this kind know how to update themselves, so we may just spawn and forget the
|
||||
/// asynchronous self-update task.
|
||||
#[async_trait]
|
||||
pub trait StandaloneMetrics: Metrics {
|
||||
pub trait StandaloneMetric: Metric {
|
||||
/// Update metric values.
|
||||
async fn update(&self);
|
||||
|
||||
/// Metrics update interval.
|
||||
fn update_interval(&self) -> Duration;
|
||||
|
||||
/// Register and spawn metric. Metric is only spawned if it is registered for the first time.
|
||||
fn register_and_spawn(self, registry: &Registry) -> Result<(), PrometheusError> {
|
||||
match self.register(registry) {
|
||||
Ok(()) => {
|
||||
self.spawn();
|
||||
Ok(())
|
||||
},
|
||||
Err(PrometheusError::AlreadyReg) => Ok(()),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawn the self update task that will keep update metric value at given intervals.
|
||||
fn spawn(self) {
|
||||
async_std::task::spawn(async move {
|
||||
@@ -89,7 +99,7 @@ impl Default for MetricsAddress {
|
||||
impl MetricsParams {
|
||||
/// Creates metrics params so that metrics are not exposed.
|
||||
pub fn disabled() -> Self {
|
||||
MetricsParams { address: None, registry: None, metrics_prefix: None }
|
||||
MetricsParams { address: None, registry: Registry::new() }
|
||||
}
|
||||
|
||||
/// Do not expose metrics.
|
||||
@@ -97,17 +107,11 @@ impl MetricsParams {
|
||||
self.address = None;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set prefix to use in metric names.
|
||||
pub fn metrics_prefix(mut self, prefix: String) -> Self {
|
||||
self.metrics_prefix = Some(prefix);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<MetricsAddress>> for MetricsParams {
|
||||
fn from(address: Option<MetricsAddress>) -> Self {
|
||||
MetricsParams { address, registry: None, metrics_prefix: None }
|
||||
MetricsParams { address, registry: Registry::new() }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
use crate::{
|
||||
error::{self, Error},
|
||||
metrics::{
|
||||
metric_name, register, F64SharedRef, Gauge, PrometheusError, Registry, StandaloneMetrics,
|
||||
F64,
|
||||
metric_name, register, F64SharedRef, Gauge, Metric, PrometheusError, Registry,
|
||||
StandaloneMetric, F64,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -44,8 +44,6 @@ pub struct FloatJsonValueMetric {
|
||||
impl FloatJsonValueMetric {
|
||||
/// Create new metric instance with given name and help.
|
||||
pub fn new(
|
||||
registry: &Registry,
|
||||
prefix: Option<&str>,
|
||||
url: String,
|
||||
json_path: String,
|
||||
name: String,
|
||||
@@ -55,7 +53,7 @@ impl FloatJsonValueMetric {
|
||||
Ok(FloatJsonValueMetric {
|
||||
url,
|
||||
json_path,
|
||||
metric: register(Gauge::new(metric_name(prefix, &name), help)?, registry)?,
|
||||
metric: Gauge::new(metric_name(None, &name), help)?,
|
||||
shared_value_ref,
|
||||
})
|
||||
}
|
||||
@@ -81,8 +79,14 @@ impl FloatJsonValueMetric {
|
||||
}
|
||||
}
|
||||
|
||||
impl Metric for FloatJsonValueMetric {
|
||||
fn register(&self, registry: &Registry) -> Result<(), PrometheusError> {
|
||||
register(self.metric.clone(), registry).map(drop)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl StandaloneMetrics for FloatJsonValueMetric {
|
||||
impl StandaloneMetric for FloatJsonValueMetric {
|
||||
fn update_interval(&self) -> Duration {
|
||||
UPDATE_INTERVAL
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
//! Global system-wide Prometheus metrics exposed by relays.
|
||||
|
||||
use crate::metrics::{
|
||||
metric_name, register, Gauge, GaugeVec, Opts, PrometheusError, Registry, StandaloneMetrics,
|
||||
F64, U64,
|
||||
metric_name, register, Gauge, GaugeVec, Metric, Opts, PrometheusError, Registry,
|
||||
StandaloneMetric, F64, U64,
|
||||
};
|
||||
|
||||
use async_std::sync::{Arc, Mutex};
|
||||
@@ -40,36 +40,36 @@ pub struct GlobalMetrics {
|
||||
|
||||
impl GlobalMetrics {
|
||||
/// Create and register global metrics.
|
||||
pub fn new(registry: &Registry, prefix: Option<&str>) -> Result<Self, PrometheusError> {
|
||||
pub fn new() -> Result<Self, PrometheusError> {
|
||||
Ok(GlobalMetrics {
|
||||
system: Arc::new(Mutex::new(System::new_with_specifics(RefreshKind::everything()))),
|
||||
system_average_load: register(
|
||||
GaugeVec::new(
|
||||
Opts::new(metric_name(prefix, "system_average_load"), "System load average"),
|
||||
&["over"],
|
||||
)?,
|
||||
registry,
|
||||
system_average_load: GaugeVec::new(
|
||||
Opts::new(metric_name(None, "system_average_load"), "System load average"),
|
||||
&["over"],
|
||||
)?,
|
||||
process_cpu_usage_percentage: register(
|
||||
Gauge::new(
|
||||
metric_name(prefix, "process_cpu_usage_percentage"),
|
||||
"Process CPU usage",
|
||||
)?,
|
||||
registry,
|
||||
process_cpu_usage_percentage: Gauge::new(
|
||||
metric_name(None, "process_cpu_usage_percentage"),
|
||||
"Process CPU usage",
|
||||
)?,
|
||||
process_memory_usage_bytes: register(
|
||||
Gauge::new(
|
||||
metric_name(prefix, "process_memory_usage_bytes"),
|
||||
"Process memory (resident set size) usage",
|
||||
)?,
|
||||
registry,
|
||||
process_memory_usage_bytes: Gauge::new(
|
||||
metric_name(None, "process_memory_usage_bytes"),
|
||||
"Process memory (resident set size) usage",
|
||||
)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Metric for GlobalMetrics {
|
||||
fn register(&self, registry: &Registry) -> Result<(), PrometheusError> {
|
||||
register(self.system_average_load.clone(), registry)?;
|
||||
register(self.process_cpu_usage_percentage.clone(), registry)?;
|
||||
register(self.process_memory_usage_bytes.clone(), registry)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl StandaloneMetrics for GlobalMetrics {
|
||||
impl StandaloneMetric for GlobalMetrics {
|
||||
async fn update(&self) {
|
||||
// update system-wide metrics
|
||||
let mut system = self.system.lock().await;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
use crate::{
|
||||
error::Error,
|
||||
metrics::{Metrics, MetricsAddress, MetricsParams, PrometheusError, StandaloneMetrics},
|
||||
metrics::{Metric, MetricsAddress, MetricsParams},
|
||||
FailedClient, MaybeConnectionError,
|
||||
};
|
||||
|
||||
@@ -53,7 +53,7 @@ pub fn relay_loop<SC, TC>(source_client: SC, target_client: TC) -> Loop<SC, TC,
|
||||
|
||||
/// Returns generic relay loop metrics that may be customized and used in one or several relay
|
||||
/// loops.
|
||||
pub fn relay_metrics(prefix: Option<String>, params: MetricsParams) -> LoopMetrics<(), (), ()> {
|
||||
pub fn relay_metrics(params: MetricsParams) -> LoopMetrics<(), (), ()> {
|
||||
LoopMetrics {
|
||||
relay_loop: Loop {
|
||||
reconnect_delay: RECONNECT_DELAY,
|
||||
@@ -62,8 +62,7 @@ pub fn relay_metrics(prefix: Option<String>, params: MetricsParams) -> LoopMetri
|
||||
loop_metric: None,
|
||||
},
|
||||
address: params.address,
|
||||
registry: params.registry.unwrap_or_else(|| create_metrics_registry(prefix)),
|
||||
metrics_prefix: params.metrics_prefix,
|
||||
registry: params.registry,
|
||||
loop_metric: None,
|
||||
}
|
||||
}
|
||||
@@ -81,7 +80,6 @@ pub struct LoopMetrics<SC, TC, LM> {
|
||||
relay_loop: Loop<SC, TC, ()>,
|
||||
address: Option<MetricsAddress>,
|
||||
registry: Registry,
|
||||
metrics_prefix: Option<String>,
|
||||
loop_metric: Option<LM>,
|
||||
}
|
||||
|
||||
@@ -93,11 +91,7 @@ impl<SC, TC, LM> Loop<SC, TC, LM> {
|
||||
}
|
||||
|
||||
/// Start building loop metrics using given prefix.
|
||||
pub fn with_metrics(
|
||||
self,
|
||||
prefix: Option<String>,
|
||||
params: MetricsParams,
|
||||
) -> LoopMetrics<SC, TC, ()> {
|
||||
pub fn with_metrics(self, params: MetricsParams) -> LoopMetrics<SC, TC, ()> {
|
||||
LoopMetrics {
|
||||
relay_loop: Loop {
|
||||
reconnect_delay: self.reconnect_delay,
|
||||
@@ -106,8 +100,7 @@ impl<SC, TC, LM> Loop<SC, TC, LM> {
|
||||
loop_metric: None,
|
||||
},
|
||||
address: params.address,
|
||||
registry: params.registry.unwrap_or_else(|| create_metrics_registry(prefix)),
|
||||
metrics_prefix: params.metrics_prefix,
|
||||
registry: params.registry,
|
||||
loop_metric: None,
|
||||
}
|
||||
}
|
||||
@@ -160,44 +153,23 @@ impl<SC, TC, LM> LoopMetrics<SC, TC, LM> {
|
||||
/// Add relay loop metrics.
|
||||
///
|
||||
/// Loop metrics will be passed to the loop callback.
|
||||
pub fn loop_metric<NewLM: Metrics>(
|
||||
pub fn loop_metric<NewLM: Metric>(
|
||||
self,
|
||||
create_metric: impl FnOnce(&Registry, Option<&str>) -> Result<NewLM, PrometheusError>,
|
||||
metric: NewLM,
|
||||
) -> Result<LoopMetrics<SC, TC, NewLM>, Error> {
|
||||
let loop_metric = create_metric(&self.registry, self.metrics_prefix.as_deref())?;
|
||||
metric.register(&self.registry)?;
|
||||
|
||||
Ok(LoopMetrics {
|
||||
relay_loop: self.relay_loop,
|
||||
address: self.address,
|
||||
registry: self.registry,
|
||||
metrics_prefix: self.metrics_prefix,
|
||||
loop_metric: Some(loop_metric),
|
||||
loop_metric: Some(metric),
|
||||
})
|
||||
}
|
||||
|
||||
/// Add standalone metrics.
|
||||
pub fn standalone_metric<M: StandaloneMetrics>(
|
||||
self,
|
||||
create_metric: impl FnOnce(&Registry, Option<&str>) -> Result<M, PrometheusError>,
|
||||
) -> Result<Self, Error> {
|
||||
// since standalone metrics are updating themselves, we may just ignore the fact that the
|
||||
// same standalone metric is exposed by several loops && only spawn single metric
|
||||
match create_metric(&self.registry, self.metrics_prefix.as_deref()) {
|
||||
Ok(standalone_metrics) => standalone_metrics.spawn(),
|
||||
Err(PrometheusError::AlreadyReg) => (),
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Convert into `MetricsParams` structure so that metrics registry may be extended later.
|
||||
pub fn into_params(self) -> MetricsParams {
|
||||
MetricsParams {
|
||||
address: self.address,
|
||||
registry: Some(self.registry),
|
||||
metrics_prefix: self.metrics_prefix,
|
||||
}
|
||||
MetricsParams { address: self.address, registry: self.registry }
|
||||
}
|
||||
|
||||
/// Expose metrics using address passed at creation.
|
||||
@@ -274,15 +246,3 @@ pub async fn reconnect_failed_client(
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/// Create new registry with global metrics.
|
||||
fn create_metrics_registry(prefix: Option<String>) -> Registry {
|
||||
match prefix {
|
||||
Some(prefix) => {
|
||||
assert!(!prefix.is_empty(), "Metrics prefix can not be empty");
|
||||
Registry::new_custom(Some(prefix), None)
|
||||
.expect("only fails if prefix is empty; prefix is not empty; qed")
|
||||
},
|
||||
None => Registry::new(),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user