mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 22:11:02 +00:00
Merge commit '392447f5c8f986ded2559a78457f4cd87942f393' into update-bridges-subtree-r/w
This commit is contained in:
@@ -6,24 +6,22 @@ edition = "2018"
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
|
||||
[dependencies]
|
||||
ansi_term = "0.12"
|
||||
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.0.0" }
|
||||
env_logger = "0.8.3"
|
||||
ethabi = { git = "https://github.com/paritytech/ethabi", branch = "td-eth-types-11" }
|
||||
ethabi-contract = { git = "https://github.com/paritytech/ethabi", branch = "td-eth-types-11" }
|
||||
ethabi-derive = { git = "https://github.com/paritytech/ethabi", branch = "td-eth-types-11" }
|
||||
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.3.4", default-features = false, features = ["hmac"] }
|
||||
libsecp256k1 = { version = "0.7", default-features = false, features = ["hmac"] }
|
||||
log = "0.4.14"
|
||||
num-traits = "0.2"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0.64"
|
||||
time = "0.2"
|
||||
thiserror = "1.0.26"
|
||||
|
||||
# Bridge dependencies
|
||||
|
||||
@@ -31,7 +29,6 @@ bp-currency-exchange = { path = "../../primitives/currency-exchange" }
|
||||
bp-eth-poa = { path = "../../primitives/ethereum-poa" }
|
||||
exchange-relay = { path = "../exchange" }
|
||||
headers-relay = { path = "../headers" }
|
||||
messages-relay = { path = "../messages" }
|
||||
relay-ethereum-client = { path = "../client-ethereum" }
|
||||
relay-rialto-client = { path = "../client-rialto" }
|
||||
relay-substrate-client = { path = "../client-substrate" }
|
||||
@@ -40,9 +37,6 @@ rialto-runtime = { path = "../../bin/rialto/runtime" }
|
||||
|
||||
# Substrate Dependencies
|
||||
|
||||
frame-system = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
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" }
|
||||
substrate-prometheus-endpoint = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::rpc_errors::RpcError;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Result type used by PoA relay.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Ethereum PoA relay errors.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
/// Failed to decode initial header.
|
||||
#[error("Error decoding initial header: {0}")]
|
||||
DecodeInitialHeader(codec::Error),
|
||||
/// RPC error.
|
||||
#[error("{0}")]
|
||||
Rpc(#[from] RpcError),
|
||||
/// Failed to read genesis header.
|
||||
#[error("Error reading Substrate genesis header: {0:?}")]
|
||||
ReadGenesisHeader(relay_substrate_client::Error),
|
||||
/// Failed to read initial GRANDPA authorities.
|
||||
#[error("Error reading GRANDPA authorities set: {0:?}")]
|
||||
ReadAuthorities(relay_substrate_client::Error),
|
||||
/// Failed to deploy bridge contract to Ethereum chain.
|
||||
#[error("Error deploying contract: {0:?}")]
|
||||
DeployContract(RpcError),
|
||||
}
|
||||
@@ -14,8 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::rpc_errors::RpcError;
|
||||
use crate::substrate_sync_loop::QueuedRialtoHeader;
|
||||
use crate::{rpc_errors::RpcError, substrate_sync_loop::QueuedRialtoHeader};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bp_eth_poa::signatures::secret_to_address;
|
||||
@@ -41,7 +40,7 @@ type RpcResult<T> = std::result::Result<T, RpcError>;
|
||||
/// interactions involving, for example, an Ethereum contract.
|
||||
#[async_trait]
|
||||
pub trait EthereumHighLevelRpc {
|
||||
/// Returns best Substrate block that PoA chain knows of.
|
||||
/// 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.
|
||||
@@ -60,7 +59,10 @@ pub trait EthereumHighLevelRpc {
|
||||
) -> SubmittedHeaders<RialtoHeaderId, RpcError>;
|
||||
|
||||
/// Returns ids of incomplete Substrate headers.
|
||||
async fn incomplete_substrate_headers(&self, contract_address: Address) -> RpcResult<HashSet<RialtoHeaderId>>;
|
||||
async fn incomplete_substrate_headers(
|
||||
&self,
|
||||
contract_address: Address,
|
||||
) -> RpcResult<HashSet<RialtoHeaderId>>;
|
||||
|
||||
/// Complete Substrate header.
|
||||
async fn complete_substrate_header(
|
||||
@@ -104,7 +106,7 @@ impl EthereumHighLevelRpc for EthereumClient {
|
||||
let hash = rialto_runtime::Hash::decode(&mut &raw_hash[..])?;
|
||||
|
||||
if number != number.low_u32().into() {
|
||||
return Err(RpcError::Ethereum(EthereumNodeError::InvalidSubstrateBlockNumber));
|
||||
return Err(RpcError::Ethereum(EthereumNodeError::InvalidSubstrateBlockNumber))
|
||||
}
|
||||
|
||||
Ok(HeaderId(number.low_u32(), hash))
|
||||
@@ -138,31 +140,28 @@ impl EthereumHighLevelRpc for EthereumClient {
|
||||
let address: Address = secret_to_address(¶ms.signer);
|
||||
let nonce = match self.account_nonce(address).await {
|
||||
Ok(nonce) => nonce,
|
||||
Err(error) => {
|
||||
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,
|
||||
},
|
||||
EthereumHeadersSubmitter { client: self.clone(), params, contract_address, nonce },
|
||||
headers,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn incomplete_substrate_headers(&self, contract_address: Address) -> RpcResult<HashSet<RialtoHeaderId>> {
|
||||
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),
|
||||
@@ -173,13 +172,14 @@ impl EthereumHighLevelRpc for EthereumClient {
|
||||
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_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;
|
||||
return None
|
||||
}
|
||||
|
||||
Some(HeaderId(number.low_u32(), hash))
|
||||
@@ -202,7 +202,11 @@ impl EthereumHighLevelRpc for EthereumClient {
|
||||
Some(contract_address),
|
||||
None,
|
||||
false,
|
||||
bridge_contract::functions::import_finality_proof::encode_input(id.0, id.1, justification),
|
||||
bridge_contract::functions::import_finality_proof::encode_input(
|
||||
id.0,
|
||||
id.1,
|
||||
justification,
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -263,7 +267,7 @@ impl HeadersBatch {
|
||||
) -> Result<(Self, Vec<RialtoHeaderId>), ()> {
|
||||
if headers.len() != ids.len() {
|
||||
log::error!(target: "bridge", "Collection size mismatch ({} vs {})", headers.len(), ids.len());
|
||||
return Err(());
|
||||
return Err(())
|
||||
}
|
||||
|
||||
let header1 = headers.pop().ok_or(())?;
|
||||
@@ -276,27 +280,14 @@ impl HeadersBatch {
|
||||
submitting_ids.extend(ids.pop().iter());
|
||||
}
|
||||
|
||||
Ok((
|
||||
Self {
|
||||
header1,
|
||||
header2,
|
||||
header3,
|
||||
header4,
|
||||
},
|
||||
submitting_ids,
|
||||
))
|
||||
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(),
|
||||
]
|
||||
[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.
|
||||
@@ -323,9 +314,10 @@ impl HeadersBatch {
|
||||
/// or when `idx > HEADERS_BATCH`.
|
||||
pub fn split_off(&mut self, idx: usize) -> Result<(), ()> {
|
||||
if idx == 0 || idx > HEADERS_BATCH {
|
||||
return Err(());
|
||||
return Err(())
|
||||
}
|
||||
let mut vals: [_; HEADERS_BATCH] = [&mut None, &mut self.header2, &mut self.header3, &mut self.header4];
|
||||
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;
|
||||
}
|
||||
@@ -359,7 +351,8 @@ struct EthereumHeadersSubmitter {
|
||||
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 (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()),
|
||||
@@ -369,7 +362,7 @@ impl HeadersSubmitter for EthereumHeadersSubmitter {
|
||||
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));
|
||||
return Err(RpcError::Ethereum(EthereumNodeError::InvalidIncompleteIndex))
|
||||
}
|
||||
|
||||
Ok(incomplete_index.low_u32() as _)
|
||||
@@ -407,17 +400,21 @@ async fn submit_substrate_headers(
|
||||
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");
|
||||
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;
|
||||
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;
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -436,9 +433,11 @@ async fn submit_substrate_headers_batch(
|
||||
// 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) {
|
||||
if submitted_headers.rejected.contains(&parent_id) ||
|
||||
submitted_headers.incomplete.contains(&parent_id)
|
||||
{
|
||||
submitted_headers.rejected.extend(ids);
|
||||
return None;
|
||||
return None
|
||||
}
|
||||
|
||||
// check if headers are incomplete
|
||||
@@ -450,11 +449,11 @@ async fn submit_substrate_headers_batch(
|
||||
// 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);
|
||||
return Some(error)
|
||||
} else {
|
||||
return None;
|
||||
return None
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Modify `ids` and `headers` to only contain values that are going to be accepted.
|
||||
@@ -477,12 +476,12 @@ async fn submit_substrate_headers_batch(
|
||||
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)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -521,11 +520,7 @@ mod tests {
|
||||
number,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
if number == 0 {
|
||||
Default::default()
|
||||
} else {
|
||||
header(number - 1).id().1
|
||||
},
|
||||
if number == 0 { Default::default() } else { header(number - 1).id().1 },
|
||||
Default::default(),
|
||||
)
|
||||
.into(),
|
||||
@@ -535,10 +530,7 @@ mod tests {
|
||||
#[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![],
|
||||
},
|
||||
TestHeadersSubmitter { incomplete: vec![header(5).id()], failed: vec![] },
|
||||
vec![header(5), header(6)],
|
||||
));
|
||||
assert_eq!(submitted_headers.submitted, vec![header(5).id()]);
|
||||
@@ -550,19 +542,8 @@ mod tests {
|
||||
#[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),
|
||||
],
|
||||
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,
|
||||
@@ -583,10 +564,7 @@ mod tests {
|
||||
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()]
|
||||
);
|
||||
assert_eq!(ids, vec![header(1).id(), header(2).id(), header(3).id(), header(4).id()]);
|
||||
headers
|
||||
}
|
||||
|
||||
|
||||
@@ -14,17 +14,22 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::ethereum_client::{bridge_contract, EthereumHighLevelRpc};
|
||||
use crate::rpc_errors::RpcError;
|
||||
use 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,
|
||||
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,
|
||||
Client as SubstrateClient, ConnectionParams as SubstrateConnectionParams,
|
||||
OpaqueGrandpaAuthoritiesSet,
|
||||
};
|
||||
use relay_utils::HeaderId;
|
||||
|
||||
@@ -100,21 +105,21 @@ pub async fn run(params: EthereumDeployContractParams) {
|
||||
async fn prepare_initial_header(
|
||||
sub_client: &SubstrateClient<Rialto>,
|
||||
sub_initial_header: Option<Vec<u8>>,
|
||||
) -> Result<(RialtoHeaderId, Vec<u8>), String> {
|
||||
) -> Result<(RialtoHeaderId, Vec<u8>)> {
|
||||
match sub_initial_header {
|
||||
Some(raw_initial_header) => match rialto_runtime::Header::decode(&mut &raw_initial_header[..]) {
|
||||
Ok(initial_header) => Ok((
|
||||
HeaderId(initial_header.number, initial_header.hash()),
|
||||
raw_initial_header,
|
||||
)),
|
||||
Err(error) => Err(format!("Error decoding initial header: {}", error)),
|
||||
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| format!("Error reading Substrate genesis header: {:?}", error))
|
||||
}
|
||||
.map_err(Error::ReadGenesisHeader)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,13 +128,13 @@ async fn prepare_initial_authorities_set(
|
||||
sub_client: &SubstrateClient<Rialto>,
|
||||
sub_initial_header_hash: rialto_runtime::Hash,
|
||||
sub_initial_authorities_set: Option<Vec<u8>>,
|
||||
) -> Result<OpaqueGrandpaAuthoritiesSet, String> {
|
||||
) -> Result<OpaqueGrandpaAuthoritiesSet> {
|
||||
let initial_authorities_set = match sub_initial_authorities_set {
|
||||
Some(initial_authorities_set) => Ok(initial_authorities_set),
|
||||
None => sub_client.grandpa_authorities_set(sub_initial_header_hash).await,
|
||||
};
|
||||
|
||||
initial_authorities_set.map_err(|error| format!("Error reading GRANDPA authorities set: {:?}", error))
|
||||
initial_authorities_set.map_err(Error::ReadAuthorities)
|
||||
}
|
||||
|
||||
/// Deploy bridge contract to Ethereum chain.
|
||||
@@ -140,15 +145,20 @@ async fn deploy_bridge_contract(
|
||||
initial_header: Vec<u8>,
|
||||
initial_set_id: u64,
|
||||
initial_authorities: Vec<u8>,
|
||||
) -> Result<(), String> {
|
||||
) -> Result<()> {
|
||||
eth_client
|
||||
.submit_ethereum_transaction(
|
||||
params,
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
bridge_contract::constructor(contract_code, initial_header, initial_set_id, initial_authorities),
|
||||
bridge_contract::constructor(
|
||||
contract_code,
|
||||
initial_header,
|
||||
initial_set_id,
|
||||
initial_authorities,
|
||||
),
|
||||
)
|
||||
.await
|
||||
.map_err(|error| format!("Error deploying contract: {:?}", error))
|
||||
.map_err(Error::DeployContract)
|
||||
}
|
||||
|
||||
@@ -16,28 +16,34 @@
|
||||
|
||||
//! Relaying proofs of PoA -> Substrate exchange transactions.
|
||||
|
||||
use crate::instances::BridgeInstance;
|
||||
use crate::rialto_client::{SubmitEthereumExchangeTransactionProof, SubstrateHighLevelRpc};
|
||||
use crate::rpc_errors::RpcError;
|
||||
use crate::substrate_types::into_substrate_ethereum_receipt;
|
||||
use 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,
|
||||
use exchange_relay::{
|
||||
exchange::{
|
||||
relay_single_transaction_proof, SourceBlock, SourceClient, SourceTransaction, TargetClient,
|
||||
TransactionProofPipeline,
|
||||
},
|
||||
exchange_loop::{run as run_loop, InMemoryStorage},
|
||||
};
|
||||
use exchange_relay::exchange_loop::{run as run_loop, InMemoryStorage};
|
||||
use relay_ethereum_client::{
|
||||
types::{
|
||||
HeaderId as EthereumHeaderId, HeaderWithTransactions as EthereumHeaderWithTransactions,
|
||||
Transaction as EthereumTransaction, TransactionHash as EthereumTransactionHash, H256, HEADER_ID_PROOF,
|
||||
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,
|
||||
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;
|
||||
@@ -111,12 +117,7 @@ impl SourceBlock for EthereumSourceBlock {
|
||||
}
|
||||
|
||||
fn transactions(&self) -> Vec<Self::Transaction> {
|
||||
self.0
|
||||
.transactions
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(EthereumSourceTransaction)
|
||||
.collect()
|
||||
self.0.transactions.iter().cloned().map(EthereumSourceTransaction).collect()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,13 +179,12 @@ impl SourceClient<EthereumToSubstrateExchange> for EthereumTransactionsSource {
|
||||
};
|
||||
|
||||
// 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),
|
||||
};
|
||||
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)))
|
||||
}
|
||||
@@ -194,9 +194,11 @@ impl SourceClient<EthereumToSubstrateExchange> for EthereumTransactionsSource {
|
||||
block: &EthereumSourceBlock,
|
||||
tx_index: usize,
|
||||
) -> Result<EthereumTransactionInclusionProof, RpcError> {
|
||||
const TRANSACTION_HAS_RAW_FIELD_PROOF: &str = "RPC level checks that transactions from Ethereum\
|
||||
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";
|
||||
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 {
|
||||
@@ -266,12 +268,15 @@ impl TargetClient<EthereumToSubstrateExchange> for SubstrateTransactionsTarget {
|
||||
self.client.best_ethereum_finalized_block().await
|
||||
}
|
||||
|
||||
async fn filter_transaction_proof(&self, proof: &EthereumTransactionInclusionProof) -> Result<bool, RpcError> {
|
||||
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);
|
||||
return Ok(false)
|
||||
}
|
||||
|
||||
// now let's check if transaction is successful
|
||||
@@ -285,8 +290,12 @@ impl TargetClient<EthereumToSubstrateExchange> for SubstrateTransactionsTarget {
|
||||
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());
|
||||
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
|
||||
@@ -311,9 +320,10 @@ pub async fn run(params: EthereumExchangeParams) {
|
||||
err,
|
||||
),
|
||||
}
|
||||
}
|
||||
},
|
||||
ExchangeRelayMode::Auto(eth_start_with_block_number) => {
|
||||
let result = run_auto_transactions_relay_loop(params, eth_start_with_block_number).await;
|
||||
let result =
|
||||
run_auto_transactions_relay_loop(params, eth_start_with_block_number).await;
|
||||
if let Err(err) = result {
|
||||
log::error!(
|
||||
target: "bridge",
|
||||
@@ -321,23 +331,18 @@ pub async fn run(params: EthereumExchangeParams) {
|
||||
err,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Run single transaction proof relay and stop.
|
||||
async fn run_single_transaction_relay(params: EthereumExchangeParams, eth_tx_hash: H256) -> Result<(), String> {
|
||||
let EthereumExchangeParams {
|
||||
eth_params,
|
||||
sub_params,
|
||||
sub_sign,
|
||||
instance,
|
||||
..
|
||||
} = params;
|
||||
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 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)?;
|
||||
@@ -349,20 +354,17 @@ async fn run_single_transaction_relay(params: EthereumExchangeParams, eth_tx_has
|
||||
bridge_instance: instance,
|
||||
};
|
||||
|
||||
relay_single_transaction_proof(&source, &target, eth_tx_hash).await
|
||||
relay_single_transaction_proof(&source, &target, eth_tx_hash)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn run_auto_transactions_relay_loop(
|
||||
params: EthereumExchangeParams,
|
||||
eth_start_with_block_number: Option<u64>,
|
||||
) -> Result<(), String> {
|
||||
) -> anyhow::Result<()> {
|
||||
let EthereumExchangeParams {
|
||||
eth_params,
|
||||
sub_params,
|
||||
sub_sign,
|
||||
metrics_params,
|
||||
instance,
|
||||
..
|
||||
eth_params, sub_params, sub_sign, metrics_params, instance, ..
|
||||
} = params;
|
||||
|
||||
let eth_client = EthereumClient::new(eth_params).await;
|
||||
@@ -370,18 +372,17 @@ async fn run_auto_transactions_relay_loop(
|
||||
|
||||
let eth_start_with_block_number = match eth_start_with_block_number {
|
||||
Some(eth_start_with_block_number) => eth_start_with_block_number,
|
||||
None => {
|
||||
None =>
|
||||
sub_client
|
||||
.best_ethereum_finalized_block()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
format!(
|
||||
anyhow::format_err!(
|
||||
"Error retrieving best finalized Ethereum block from Substrate node: {:?}",
|
||||
err
|
||||
)
|
||||
})?
|
||||
.0
|
||||
}
|
||||
.0,
|
||||
};
|
||||
|
||||
run_loop(
|
||||
|
||||
@@ -16,13 +16,15 @@
|
||||
|
||||
//! 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,
|
||||
Client as EthereumClient, ConnectionParams as EthereumConnectionParams,
|
||||
SigningParams as EthereumSigningParams,
|
||||
};
|
||||
use rialto_runtime::exchange::LOCK_FUNDS_ADDRESS;
|
||||
|
||||
@@ -43,18 +45,13 @@ pub struct EthereumExchangeSubmitParams {
|
||||
|
||||
/// 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 EthereumExchangeSubmitParams { eth_params, eth_sign, eth_nonce, eth_amount, sub_recipient } =
|
||||
params;
|
||||
|
||||
let result: Result<_, String> = async move {
|
||||
let result: anyhow::Result<_> = async move {
|
||||
let eth_client = EthereumClient::try_connect(eth_params)
|
||||
.await
|
||||
.map_err(|err| format!("error connecting to Ethereum node: {:?}", err))?;
|
||||
.map_err(|err| anyhow!("error connecting to Ethereum node: {:?}", err))?;
|
||||
|
||||
let eth_signer_address = secret_to_address(ð_sign.signer);
|
||||
let sub_recipient_encoded = sub_recipient;
|
||||
@@ -63,7 +60,7 @@ pub async fn run(params: EthereumExchangeSubmitParams) {
|
||||
None => eth_client
|
||||
.account_nonce(eth_signer_address)
|
||||
.await
|
||||
.map_err(|err| format!("error fetching acount nonce: {:?}", err))?,
|
||||
.map_err(|err| anyhow!("error fetching acount nonce: {:?}", err))?,
|
||||
};
|
||||
let gas = eth_client
|
||||
.estimate_gas(CallRequest {
|
||||
@@ -74,7 +71,7 @@ pub async fn run(params: EthereumExchangeSubmitParams) {
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.map_err(|err| format!("error estimating gas requirements: {:?}", err))?;
|
||||
.map_err(|err| anyhow!("error estimating gas requirements: {:?}", err))?;
|
||||
let eth_tx_unsigned = UnsignedTransaction {
|
||||
nonce,
|
||||
gas_price: eth_sign.gas_price,
|
||||
@@ -83,13 +80,12 @@ pub async fn run(params: EthereumExchangeSubmitParams) {
|
||||
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));
|
||||
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| format!("error submitting transaction: {:?}", err))?;
|
||||
.map_err(|err| anyhow!("error submitting transaction: {:?}", err))?;
|
||||
|
||||
Ok(eth_tx_unsigned)
|
||||
}
|
||||
@@ -102,13 +98,13 @@ pub async fn run(params: EthereumExchangeSubmitParams) {
|
||||
"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,
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,11 +16,13 @@
|
||||
|
||||
//! Ethereum PoA -> Rialto-Substrate synchronization.
|
||||
|
||||
use crate::ethereum_client::EthereumHighLevelRpc;
|
||||
use crate::instances::BridgeInstance;
|
||||
use crate::rialto_client::{SubmitEthereumHeaders, SubstrateHighLevelRpc};
|
||||
use crate::rpc_errors::RpcError;
|
||||
use crate::substrate_types::{into_substrate_ethereum_header, into_substrate_ethereum_receipts};
|
||||
use 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;
|
||||
@@ -35,12 +37,12 @@ use relay_ethereum_client::{
|
||||
};
|
||||
use relay_rialto_client::{Rialto, SigningParams as RialtoSigningParams};
|
||||
use relay_substrate_client::{
|
||||
Chain as SubstrateChain, Client as SubstrateClient, ConnectionParams as SubstrateConnectionParams,
|
||||
Chain as SubstrateChain, Client as SubstrateClient,
|
||||
ConnectionParams as SubstrateConnectionParams,
|
||||
};
|
||||
use relay_utils::{metrics::MetricsParams, relay_loop::Client as RelayClient};
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::{collections::HashSet, sync::Arc, time::Duration};
|
||||
use std::{collections::HashSet, fmt::Debug, sync::Arc, time::Duration};
|
||||
|
||||
pub mod consts {
|
||||
use super::*;
|
||||
@@ -57,7 +59,8 @@ pub mod consts {
|
||||
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).
|
||||
/// Max depth of in-memory headers in all states. Past this depth they will be forgotten
|
||||
/// (pruned).
|
||||
pub const PRUNE_DEPTH: u32 = 4096;
|
||||
}
|
||||
|
||||
@@ -106,8 +109,8 @@ impl HeadersSyncPipeline for EthereumHeadersSyncPipeline {
|
||||
type Completion = ();
|
||||
|
||||
fn estimate_size(source: &QueuedHeader<Self>) -> usize {
|
||||
into_substrate_ethereum_header(source.header()).encode().len()
|
||||
+ into_substrate_ethereum_receipts(source.extra())
|
||||
into_substrate_ethereum_header(source.header()).encode().len() +
|
||||
into_substrate_ethereum_receipts(source.extra())
|
||||
.map(|extra| extra.encode().len())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
@@ -148,22 +151,17 @@ impl SourceClient<EthereumHeadersSyncPipeline> for EthereumHeadersSource {
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
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> {
|
||||
async fn header_completion(
|
||||
&self,
|
||||
id: EthereumHeaderId,
|
||||
) -> Result<(EthereumHeaderId, Option<()>), RpcError> {
|
||||
Ok((id, None))
|
||||
}
|
||||
|
||||
@@ -172,9 +170,7 @@ impl SourceClient<EthereumHeadersSyncPipeline> for EthereumHeadersSource {
|
||||
id: EthereumHeaderId,
|
||||
header: QueuedEthereumHeader,
|
||||
) -> Result<(EthereumHeaderId, Vec<Receipt>), RpcError> {
|
||||
self.client
|
||||
.transaction_receipts(id, header.header().transactions.clone())
|
||||
.await
|
||||
self.client.transaction_receipts(id, header.header().transactions.clone()).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,12 +193,7 @@ impl SubstrateHeadersTarget {
|
||||
sign_params: RialtoSigningParams,
|
||||
bridge_instance: Arc<dyn BridgeInstance>,
|
||||
) -> Self {
|
||||
Self {
|
||||
client,
|
||||
sign_transactions,
|
||||
sign_params,
|
||||
bridge_instance,
|
||||
}
|
||||
Self { client, sign_transactions, sign_params, bridge_instance }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,16 +216,19 @@ impl TargetClient<EthereumHeadersSyncPipeline> for SubstrateHeadersTarget {
|
||||
self.client.best_ethereum_block().await
|
||||
}
|
||||
|
||||
async fn is_known_header(&self, id: EthereumHeaderId) -> Result<(EthereumHeaderId, bool), RpcError> {
|
||||
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,
|
||||
);
|
||||
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
|
||||
@@ -245,11 +239,18 @@ impl TargetClient<EthereumHeadersSyncPipeline> for SubstrateHeadersTarget {
|
||||
}
|
||||
|
||||
#[allow(clippy::unit_arg)]
|
||||
async fn complete_header(&self, id: EthereumHeaderId, _completion: ()) -> Result<EthereumHeaderId, RpcError> {
|
||||
async fn complete_header(
|
||||
&self,
|
||||
id: EthereumHeaderId,
|
||||
_completion: (),
|
||||
) -> Result<EthereumHeaderId, RpcError> {
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
async fn requires_extra(&self, header: QueuedEthereumHeader) -> Result<(EthereumHeaderId, bool), RpcError> {
|
||||
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)
|
||||
@@ -292,7 +293,7 @@ pub async fn run(params: EthereumSyncParams) -> Result<(), RpcError> {
|
||||
futures::future::pending(),
|
||||
)
|
||||
.await
|
||||
.map_err(RpcError::SyncLoop)?;
|
||||
.map_err(|e| RpcError::SyncLoop(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -18,16 +18,18 @@
|
||||
//! 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.
|
||||
//! Note that each instance of the bridge pallet is coupled with an instance of the currency
|
||||
//! exchange pallet. We must also have a way to create `Call`s for the correct currency exchange
|
||||
//! instance.
|
||||
//!
|
||||
//! This module helps by preparing the correct `Call`s for each of the different pallet instances.
|
||||
|
||||
use crate::ethereum_sync_loop::QueuedEthereumHeader;
|
||||
use crate::substrate_types::{into_substrate_ethereum_header, into_substrate_ethereum_receipts};
|
||||
use crate::{
|
||||
ethereum_sync_loop::QueuedEthereumHeader,
|
||||
substrate_types::{into_substrate_ethereum_header, into_substrate_ethereum_receipts},
|
||||
};
|
||||
|
||||
use rialto_runtime::exchange::EthereumTransactionInclusionProof as Proof;
|
||||
use rialto_runtime::Call;
|
||||
use rialto_runtime::{exchange::EthereumTransactionInclusionProof as Proof, Call};
|
||||
|
||||
/// Interface for `Calls` which are needed to correctly sync the bridge.
|
||||
///
|
||||
@@ -48,8 +50,8 @@ 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
|
||||
let pallet_call = rialto_runtime::BridgeEthPoACall::import_signed_headers {
|
||||
headers_with_receipts: headers
|
||||
.into_iter()
|
||||
.map(|header| {
|
||||
(
|
||||
@@ -58,22 +60,23 @@ impl BridgeInstance for RialtoPoA {
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
};
|
||||
|
||||
rialto_runtime::Call::BridgeRialtoPoa(pallet_call)
|
||||
}
|
||||
|
||||
fn build_unsigned_header_call(&self, header: QueuedEthereumHeader) -> Call {
|
||||
let pallet_call = rialto_runtime::BridgeEthPoACall::import_unsigned_header(
|
||||
into_substrate_ethereum_header(header.header()),
|
||||
into_substrate_ethereum_receipts(header.extra()),
|
||||
);
|
||||
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);
|
||||
let pallet_call =
|
||||
rialto_runtime::BridgeCurrencyExchangeCall::import_peer_transaction { proof };
|
||||
rialto_runtime::Call::BridgeRialtoCurrencyExchange(pallet_call)
|
||||
}
|
||||
}
|
||||
@@ -84,8 +87,8 @@ 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
|
||||
let pallet_call = rialto_runtime::BridgeEthPoACall::import_signed_headers {
|
||||
headers_with_receipts: headers
|
||||
.into_iter()
|
||||
.map(|header| {
|
||||
(
|
||||
@@ -94,22 +97,23 @@ impl BridgeInstance for Kovan {
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
};
|
||||
|
||||
rialto_runtime::Call::BridgeKovan(pallet_call)
|
||||
}
|
||||
|
||||
fn build_unsigned_header_call(&self, header: QueuedEthereumHeader) -> Call {
|
||||
let pallet_call = rialto_runtime::BridgeEthPoACall::import_unsigned_header(
|
||||
into_substrate_ethereum_header(header.header()),
|
||||
into_substrate_ethereum_receipts(header.extra()),
|
||||
);
|
||||
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);
|
||||
let pallet_call =
|
||||
rialto_runtime::BridgeCurrencyExchangeCall::import_peer_transaction { proof };
|
||||
rialto_runtime::Call::BridgeKovanCurrencyExchange(pallet_call)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#![recursion_limit = "1024"]
|
||||
|
||||
mod error;
|
||||
mod ethereum_client;
|
||||
mod ethereum_deploy_contract;
|
||||
mod ethereum_exchange;
|
||||
@@ -27,6 +28,7 @@ mod rpc_errors;
|
||||
mod substrate_sync_loop;
|
||||
mod substrate_types;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use ethereum_deploy_contract::EthereumDeployContractParams;
|
||||
use ethereum_exchange::EthereumExchangeParams;
|
||||
use ethereum_exchange_submit::EthereumExchangeSubmitParams;
|
||||
@@ -34,16 +36,18 @@ 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 secp256k1::SecretKey;
|
||||
use sp_core::crypto::Pair;
|
||||
use substrate_sync_loop::SubstrateSyncParams;
|
||||
|
||||
use headers_relay::sync::HeadersSyncParams;
|
||||
use relay_ethereum_client::{ConnectionParams as EthereumConnectionParams, SigningParams as EthereumSigningParams};
|
||||
use relay_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;
|
||||
@@ -64,123 +68,128 @@ async fn run_command(matches: &clap::ArgMatches<'_>) {
|
||||
Ok(ethereum_sync_params) => ethereum_sync_params,
|
||||
Err(err) => {
|
||||
log::error!(target: "bridge", "Error parsing parameters: {}", err);
|
||||
return;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
})
|
||||
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;
|
||||
}
|
||||
})
|
||||
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;
|
||||
}
|
||||
return
|
||||
},
|
||||
})
|
||||
.await;
|
||||
}
|
||||
},
|
||||
("", _) => {
|
||||
log::error!(target: "bridge", "No subcommand specified");
|
||||
}
|
||||
},
|
||||
_ => unreachable!("all possible subcommands are checked above; qed"),
|
||||
}
|
||||
}
|
||||
|
||||
fn ethereum_connection_params(matches: &clap::ArgMatches) -> Result<EthereumConnectionParams, String> {
|
||||
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| format!("Failed to parse eth-port: {}", e))?;
|
||||
params.port = eth_port.parse().map_err(|e| anyhow!("Failed to parse eth-port: {}", e))?;
|
||||
}
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
fn ethereum_signing_params(matches: &clap::ArgMatches) -> Result<EthereumSigningParams, String> {
|
||||
fn ethereum_signing_params(matches: &clap::ArgMatches) -> anyhow::Result<EthereumSigningParams> {
|
||||
let mut params = EthereumSigningParams::default();
|
||||
if let Some(eth_signer) = matches.value_of("eth-signer") {
|
||||
params.signer =
|
||||
SecretKey::parse_slice(&hex::decode(eth_signer).map_err(|e| format!("Failed to parse eth-signer: {}", e))?)
|
||||
.map_err(|e| format!("Invalid eth-signer: {}", e))?;
|
||||
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| format!("Failed to parse eth-chain-id: {}", e))?;
|
||||
.map_err(|e| anyhow!("Failed to parse eth-chain-id: {}", e))?;
|
||||
}
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
fn substrate_connection_params(matches: &clap::ArgMatches) -> Result<SubstrateConnectionParams, String> {
|
||||
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| format!("Failed to parse sub-port: {}", e))?;
|
||||
params.port = sub_port.parse().map_err(|e| anyhow!("Failed to parse sub-port: {}", e))?;
|
||||
}
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
fn rialto_signing_params(matches: &clap::ArgMatches) -> Result<RialtoSigningParams, String> {
|
||||
fn rialto_signing_params(matches: &clap::ArgMatches) -> anyhow::Result<RialtoSigningParams> {
|
||||
let mut params = sp_keyring::AccountKeyring::Alice.pair();
|
||||
|
||||
if let Some(sub_signer) = matches.value_of("sub-signer") {
|
||||
let sub_signer_password = matches.value_of("sub-signer-password");
|
||||
params = sp_core::sr25519::Pair::from_string(sub_signer, sub_signer_password)
|
||||
.map_err(|e| format!("Failed to parse sub-signer: {:?}", e))?;
|
||||
.map_err(|e| anyhow!("Failed to parse sub-signer: {:?}", e))?;
|
||||
}
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
fn ethereum_sync_params(matches: &clap::ArgMatches) -> Result<EthereumSyncParams, String> {
|
||||
fn ethereum_sync_params(matches: &clap::ArgMatches) -> anyhow::Result<EthereumSyncParams> {
|
||||
use crate::ethereum_sync_loop::consts::*;
|
||||
|
||||
let mut sync_params = HeadersSyncParams {
|
||||
@@ -199,9 +208,9 @@ fn ethereum_sync_params(matches: &clap::ArgMatches) -> Result<EthereumSyncParams
|
||||
|
||||
// tx pool won't accept too much unsigned transactions
|
||||
sync_params.max_headers_in_submitted_status = 10;
|
||||
}
|
||||
},
|
||||
Some("backup") => sync_params.target_tx_mode = TargetTransactionMode::Backup,
|
||||
Some(mode) => return Err(format!("Invalid sub-tx-mode: {}", mode)),
|
||||
Some(mode) => return Err(anyhow!("Invalid sub-tx-mode: {}", mode)),
|
||||
None => sync_params.target_tx_mode = TargetTransactionMode::Signed,
|
||||
}
|
||||
|
||||
@@ -219,12 +228,12 @@ fn ethereum_sync_params(matches: &clap::ArgMatches) -> Result<EthereumSyncParams
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
fn substrate_sync_params(matches: &clap::ArgMatches) -> Result<SubstrateSyncParams, String> {
|
||||
fn substrate_sync_params(matches: &clap::ArgMatches) -> anyhow::Result<SubstrateSyncParams> {
|
||||
use crate::substrate_sync_loop::consts::*;
|
||||
|
||||
let eth_contract_address: relay_ethereum_client::types::Address =
|
||||
if let Some(eth_contract) = matches.value_of("eth-contract") {
|
||||
eth_contract.parse().map_err(|e| format!("{}", e))?
|
||||
eth_contract.parse()?
|
||||
} else {
|
||||
"731a10897d267e19b34503ad902d0a29173ba4b1"
|
||||
.parse()
|
||||
@@ -252,15 +261,19 @@ fn substrate_sync_params(matches: &clap::ArgMatches) -> Result<SubstrateSyncPara
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
fn ethereum_deploy_contract_params(matches: &clap::ArgMatches) -> Result<EthereumDeployContractParams, String> {
|
||||
let eth_contract_code = parse_hex_argument(matches, "eth-contract-code")?.unwrap_or_else(|| {
|
||||
hex::decode(include_str!("../res/substrate-bridge-bytecode.hex")).expect("code is hardcoded, thus valid; qed")
|
||||
});
|
||||
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| format!("Failed to parse sub-authorities-set-id: {}", e))
|
||||
.map_err(|e| anyhow!("Failed to parse sub-authorities-set-id: {}", e))
|
||||
})
|
||||
.transpose()?;
|
||||
let sub_initial_authorities_set = parse_hex_argument(matches, "sub-authorities-set")?;
|
||||
@@ -281,21 +294,21 @@ fn ethereum_deploy_contract_params(matches: &clap::ArgMatches) -> Result<Ethereu
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
fn ethereum_exchange_submit_params(matches: &clap::ArgMatches) -> Result<EthereumExchangeSubmitParams, String> {
|
||||
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| format!("Failed to parse eth-nonce: {}", e))
|
||||
.map_err(|e| anyhow!("Failed to parse eth-nonce: {}", e))
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
let eth_amount = matches
|
||||
.value_of("eth-amount")
|
||||
.map(|eth_amount| {
|
||||
eth_amount
|
||||
.parse()
|
||||
.map_err(|e| format!("Failed to parse eth-amount: {}", e))
|
||||
eth_amount.parse().map_err(|e| anyhow!("Failed to parse eth-amount: {}", e))
|
||||
})
|
||||
.transpose()?
|
||||
.unwrap_or_else(|| {
|
||||
@@ -304,7 +317,8 @@ fn ethereum_exchange_submit_params(matches: &clap::ArgMatches) -> Result<Ethereu
|
||||
});
|
||||
|
||||
// This is the well-known Substrate account of Ferdie
|
||||
let default_recepient = hex!("1cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c");
|
||||
let default_recepient =
|
||||
hex!("1cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c");
|
||||
|
||||
let sub_recipient = if let Some(sub_recipient) = matches.value_of("sub-recipient") {
|
||||
hex::decode(&sub_recipient)
|
||||
@@ -319,7 +333,7 @@ fn ethereum_exchange_submit_params(matches: &clap::ArgMatches) -> Result<Ethereu
|
||||
Ok(sub_recipient)
|
||||
}
|
||||
})
|
||||
.map_err(|e| format!("Failed to parse sub-recipient: {}", e))?
|
||||
.map_err(|e| anyhow!("Failed to parse sub-recipient: {}", e))?
|
||||
} else {
|
||||
default_recepient
|
||||
};
|
||||
@@ -337,12 +351,10 @@ fn ethereum_exchange_submit_params(matches: &clap::ArgMatches) -> Result<Ethereu
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
fn ethereum_exchange_params(matches: &clap::ArgMatches) -> Result<EthereumExchangeParams, String> {
|
||||
fn ethereum_exchange_params(matches: &clap::ArgMatches) -> anyhow::Result<EthereumExchangeParams> {
|
||||
let mode = match matches.value_of("eth-tx-hash") {
|
||||
Some(eth_tx_hash) => ethereum_exchange::ExchangeRelayMode::Single(
|
||||
eth_tx_hash
|
||||
.parse()
|
||||
.map_err(|e| format!("Failed to parse eth-tx-hash: {}", e))?,
|
||||
eth_tx_hash.parse().map_err(|e| anyhow!("Failed to parse eth-tx-hash: {}", e))?,
|
||||
),
|
||||
None => ethereum_exchange::ExchangeRelayMode::Auto(
|
||||
matches
|
||||
@@ -350,7 +362,7 @@ fn ethereum_exchange_params(matches: &clap::ArgMatches) -> Result<EthereumExchan
|
||||
.map(|eth_start_with_block| {
|
||||
eth_start_with_block
|
||||
.parse()
|
||||
.map_err(|e| format!("Failed to parse eth-start-with-block: {}", e))
|
||||
.map_err(|e| anyhow!("Failed to parse eth-start-with-block: {}", e))
|
||||
})
|
||||
.transpose()?,
|
||||
),
|
||||
@@ -370,9 +382,9 @@ fn ethereum_exchange_params(matches: &clap::ArgMatches) -> Result<EthereumExchan
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
fn metrics_params(matches: &clap::ArgMatches) -> Result<MetricsParams, String> {
|
||||
fn metrics_params(matches: &clap::ArgMatches) -> anyhow::Result<MetricsParams> {
|
||||
if matches.is_present("no-prometheus") {
|
||||
return Ok(None.into());
|
||||
return Ok(None.into())
|
||||
}
|
||||
|
||||
let mut metrics_params = MetricsAddress::default();
|
||||
@@ -383,18 +395,18 @@ fn metrics_params(matches: &clap::ArgMatches) -> Result<MetricsParams, String> {
|
||||
if let Some(prometheus_port) = matches.value_of("prometheus-port") {
|
||||
metrics_params.port = prometheus_port
|
||||
.parse()
|
||||
.map_err(|e| format!("Failed to parse prometheus-port: {}", e))?;
|
||||
.map_err(|e| anyhow!("Failed to parse prometheus-port: {}", e))?;
|
||||
}
|
||||
|
||||
Ok(Some(metrics_params).into())
|
||||
}
|
||||
|
||||
fn instance_params(matches: &clap::ArgMatches) -> Result<Arc<dyn BridgeInstance>, String> {
|
||||
fn instance_params(matches: &clap::ArgMatches) -> anyhow::Result<Arc<dyn BridgeInstance>> {
|
||||
let instance = if let Some(instance) = matches.value_of("sub-pallet-instance") {
|
||||
match instance.to_lowercase().as_str() {
|
||||
"rialto" => Arc::new(RialtoPoA) as Arc<dyn BridgeInstance>,
|
||||
"kovan" => Arc::new(Kovan),
|
||||
_ => return Err("Unsupported bridge pallet instance".to_string()),
|
||||
_ => return Err(anyhow!("Unsupported bridge pallet instance")),
|
||||
}
|
||||
} else {
|
||||
unreachable!("CLI config enforces a default instance, can never be None")
|
||||
@@ -403,11 +415,10 @@ fn instance_params(matches: &clap::ArgMatches) -> Result<Arc<dyn BridgeInstance>
|
||||
Ok(instance)
|
||||
}
|
||||
|
||||
fn parse_hex_argument(matches: &clap::ArgMatches, arg: &str) -> Result<Option<Vec<u8>>, String> {
|
||||
fn parse_hex_argument(matches: &clap::ArgMatches, arg: &str) -> anyhow::Result<Option<Vec<u8>>> {
|
||||
match matches.value_of(arg) {
|
||||
Some(value) => Ok(Some(
|
||||
hex::decode(value).map_err(|e| format!("Failed to parse {}: {}", arg, e))?,
|
||||
)),
|
||||
Some(value) =>
|
||||
Ok(Some(hex::decode(value).map_err(|e| anyhow!("Failed to parse {}: {}", arg, e))?)),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::ethereum_sync_loop::QueuedEthereumHeader;
|
||||
use crate::instances::BridgeInstance;
|
||||
use crate::rpc_errors::RpcError;
|
||||
use crate::{
|
||||
ethereum_sync_loop::QueuedEthereumHeader, instances::BridgeInstance, rpc_errors::RpcError,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bp_eth_poa::AuraHeader as SubstrateEthereumHeader;
|
||||
@@ -24,7 +24,9 @@ use codec::{Decode, Encode};
|
||||
use headers_relay::sync_types::SubmittedHeaders;
|
||||
use relay_ethereum_client::types::HeaderId as EthereumHeaderId;
|
||||
use relay_rialto_client::{Rialto, SigningParams as RialtoSigningParams};
|
||||
use relay_substrate_client::{Client as SubstrateClient, TransactionSignScheme};
|
||||
use relay_substrate_client::{
|
||||
Client as SubstrateClient, TransactionSignScheme, UnsignedTransaction,
|
||||
};
|
||||
use relay_utils::HeaderId;
|
||||
use sp_core::{crypto::Pair, Bytes};
|
||||
use std::{collections::VecDeque, sync::Arc};
|
||||
@@ -33,7 +35,8 @@ const ETH_API_IMPORT_REQUIRES_RECEIPTS: &str = "RialtoPoAHeaderApi_is_import_req
|
||||
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";
|
||||
const EXCH_API_FILTER_TRANSACTION_PROOF: &str =
|
||||
"RialtoCurrencyExchangeApi_filter_transaction_proof";
|
||||
|
||||
type RpcResult<T> = std::result::Result<T, RpcError>;
|
||||
|
||||
@@ -41,13 +44,13 @@ type RpcResult<T> = std::result::Result<T, RpcError>;
|
||||
/// interactions involving, for example, an Ethereum bridge module.
|
||||
#[async_trait]
|
||||
pub trait SubstrateHighLevelRpc {
|
||||
/// Returns best Ethereum block that Substrate runtime knows of.
|
||||
/// 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 or not transactions receipts are required for Ethereum header submission.
|
||||
/// Returns whether transactions receipts are required for Ethereum header submission.
|
||||
async fn ethereum_receipts_required(&self, header: SubstrateEthereumHeader) -> RpcResult<bool>;
|
||||
/// Returns whether or not the given Ethereum header is known to the Substrate runtime.
|
||||
/// Returns whether the given Ethereum header is known to the Substrate runtime.
|
||||
async fn ethereum_header_known(&self, header_id: EthereumHeaderId) -> RpcResult<bool>;
|
||||
}
|
||||
|
||||
@@ -58,7 +61,8 @@ impl SubstrateHighLevelRpc for SubstrateClient<Rialto> {
|
||||
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 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)
|
||||
@@ -69,7 +73,8 @@ impl SubstrateHighLevelRpc for SubstrateClient<Rialto> {
|
||||
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 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)
|
||||
@@ -155,18 +160,25 @@ impl SubmitEthereumHeaders for SubstrateClient<Rialto> {
|
||||
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(), |transaction_nonce| {
|
||||
Bytes(
|
||||
Rialto::sign_transaction(
|
||||
*self.genesis_hash(),
|
||||
¶ms,
|
||||
transaction_nonce,
|
||||
instance.build_signed_header_call(headers),
|
||||
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(),
|
||||
)
|
||||
.encode(),
|
||||
)
|
||||
})
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -208,8 +220,8 @@ impl SubmitEthereumHeaders for SubstrateClient<Rialto> {
|
||||
submitted_headers.rejected.push(id);
|
||||
submitted_headers.rejected.extend(ids);
|
||||
submitted_headers.fatal_error = Some(error.into());
|
||||
break;
|
||||
}
|
||||
break
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,23 +269,32 @@ impl SubmitEthereumExchangeTransactionProof for SubstrateClient<Rialto> {
|
||||
instance: Arc<dyn BridgeInstance>,
|
||||
proof: rialto_runtime::exchange::EthereumTransactionInclusionProof,
|
||||
) -> RpcResult<()> {
|
||||
self.submit_signed_extrinsic((*params.public().as_array_ref()).into(), |transaction_nonce| {
|
||||
Bytes(
|
||||
Rialto::sign_transaction(
|
||||
*self.genesis_hash(),
|
||||
¶ms,
|
||||
transaction_nonce,
|
||||
instance.build_currency_exchange_call(proof),
|
||||
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(),
|
||||
)
|
||||
.encode(),
|
||||
)
|
||||
})
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Create unsigned Substrate transaction for submitting Ethereum header.
|
||||
fn create_unsigned_submit_transaction(call: rialto_runtime::Call) -> rialto_runtime::UncheckedExtrinsic {
|
||||
fn create_unsigned_submit_transaction(
|
||||
call: rialto_runtime::Call,
|
||||
) -> rialto_runtime::UncheckedExtrinsic {
|
||||
rialto_runtime::UncheckedExtrinsic::new_unsigned(call)
|
||||
}
|
||||
|
||||
@@ -17,48 +17,30 @@
|
||||
use relay_ethereum_client::Error as EthereumNodeError;
|
||||
use relay_substrate_client::Error as SubstrateNodeError;
|
||||
use relay_utils::MaybeConnectionError;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Contains common errors that can occur when
|
||||
/// interacting with a Substrate or Ethereum node
|
||||
/// through RPC.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum RpcError {
|
||||
/// The arguments to the RPC method failed to serialize.
|
||||
Serialization(serde_json::Error),
|
||||
/// An error occured when interacting with an Ethereum node.
|
||||
Ethereum(EthereumNodeError),
|
||||
/// An error occured when interacting with a Substrate node.
|
||||
Substrate(SubstrateNodeError),
|
||||
#[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 {
|
||||
match err {
|
||||
RpcError::Serialization(e) => e.to_string(),
|
||||
RpcError::Ethereum(e) => e.to_string(),
|
||||
RpcError::Substrate(e) => e.to_string(),
|
||||
RpcError::SyncLoop(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for RpcError {
|
||||
fn from(err: serde_json::Error) -> Self {
|
||||
Self::Serialization(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EthereumNodeError> for RpcError {
|
||||
fn from(err: EthereumNodeError) -> Self {
|
||||
Self::Ethereum(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SubstrateNodeError> for RpcError {
|
||||
fn from(err: SubstrateNodeError) -> Self {
|
||||
Self::Substrate(err)
|
||||
format!("{}", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,7 @@
|
||||
|
||||
//! Rialto-Substrate -> Ethereum PoA synchronization.
|
||||
|
||||
use crate::ethereum_client::EthereumHighLevelRpc;
|
||||
use crate::rpc_errors::RpcError;
|
||||
use crate::{ethereum_client::EthereumHighLevelRpc, rpc_errors::RpcError};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use codec::Encode;
|
||||
@@ -38,8 +37,7 @@ use relay_substrate_client::{
|
||||
use relay_utils::{metrics::MetricsParams, relay_loop::Client as RelayClient};
|
||||
use sp_runtime::EncodedJustification;
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::{collections::HashSet, time::Duration};
|
||||
use std::{collections::HashSet, fmt::Debug, time::Duration};
|
||||
|
||||
pub mod consts {
|
||||
use super::*;
|
||||
@@ -50,7 +48,8 @@ pub mod consts {
|
||||
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).
|
||||
/// Max depth of in-memory headers in all states. Past this depth they will be forgotten
|
||||
/// (pruned).
|
||||
pub const PRUNE_DEPTH: u32 = 256;
|
||||
}
|
||||
|
||||
@@ -110,11 +109,7 @@ struct EthereumHeadersTarget {
|
||||
|
||||
impl EthereumHeadersTarget {
|
||||
fn new(client: EthereumClient, contract: Address, sign_params: EthereumSigningParams) -> Self {
|
||||
Self {
|
||||
client,
|
||||
contract,
|
||||
sign_params,
|
||||
}
|
||||
Self { client, contract, sign_params }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,11 +132,17 @@ impl TargetClient<SubstrateHeadersSyncPipeline> for EthereumHeadersTarget {
|
||||
self.client.best_substrate_block(self.contract).await
|
||||
}
|
||||
|
||||
async fn is_known_header(&self, id: RialtoHeaderId) -> Result<(RialtoHeaderId, bool), RpcError> {
|
||||
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> {
|
||||
async fn submit_headers(
|
||||
&self,
|
||||
headers: Vec<QueuedRialtoHeader>,
|
||||
) -> SubmittedHeaders<RialtoHeaderId, RpcError> {
|
||||
self.client
|
||||
.submit_substrate_headers(self.sign_params.clone(), self.contract, headers)
|
||||
.await
|
||||
@@ -161,7 +162,10 @@ impl TargetClient<SubstrateHeadersSyncPipeline> for EthereumHeadersTarget {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn requires_extra(&self, header: QueuedRialtoHeader) -> Result<(RialtoHeaderId, bool), RpcError> {
|
||||
async fn requires_extra(
|
||||
&self,
|
||||
header: QueuedRialtoHeader,
|
||||
) -> Result<(RialtoHeaderId, bool), RpcError> {
|
||||
Ok((header.header().id(), false))
|
||||
}
|
||||
}
|
||||
@@ -194,7 +198,7 @@ pub async fn run(params: SubstrateSyncParams) -> Result<(), RpcError> {
|
||||
futures::future::pending(),
|
||||
)
|
||||
.await
|
||||
.map_err(RpcError::SyncLoop)?;
|
||||
.map_err(|e| RpcError::SyncLoop(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -17,11 +17,12 @@
|
||||
//! Converting between Ethereum headers and bridge module types.
|
||||
|
||||
use bp_eth_poa::{
|
||||
AuraHeader as SubstrateEthereumHeader, LogEntry as SubstrateEthereumLogEntry, Receipt as SubstrateEthereumReceipt,
|
||||
TransactionOutcome as SubstrateEthereumTransactionOutcome,
|
||||
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,
|
||||
Header as EthereumHeader, Receipt as EthereumReceipt,
|
||||
HEADER_ID_PROOF as ETHEREUM_HEADER_ID_PROOF,
|
||||
};
|
||||
|
||||
/// Convert Ethereum header into Ethereum header for Substrate.
|
||||
@@ -68,7 +69,8 @@ pub fn into_substrate_ethereum_receipt(receipt: &EthereumReceipt) -> SubstrateEt
|
||||
})
|
||||
.collect(),
|
||||
outcome: match (receipt.status, receipt.root) {
|
||||
(Some(status), None) => SubstrateEthereumTransactionOutcome::StatusCode(status.as_u64() as u8),
|
||||
(Some(status), None) =>
|
||||
SubstrateEthereumTransactionOutcome::StatusCode(status.as_u64() as u8),
|
||||
(None, Some(root)) => SubstrateEthereumTransactionOutcome::StateRoot(root),
|
||||
_ => SubstrateEthereumTransactionOutcome::Unknown,
|
||||
},
|
||||
|
||||
@@ -8,15 +8,16 @@ 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"
|
||||
codec = { package = "parity-scale-codec", version = "2.0.0" }
|
||||
codec = { package = "parity-scale-codec", version = "2.2.0" }
|
||||
futures = "0.3.12"
|
||||
hex = "0.4"
|
||||
log = "0.4.14"
|
||||
num-format = "0.4"
|
||||
num-traits = "0.2"
|
||||
paste = "1.0"
|
||||
rand = "0.8"
|
||||
structopt = "0.3"
|
||||
strum = { version = "0.21.0", features = ["derive"] }
|
||||
|
||||
# Bridge dependencies
|
||||
|
||||
@@ -27,38 +28,52 @@ bp-message-dispatch = { path = "../../primitives/message-dispatch" }
|
||||
bp-millau = { path = "../../primitives/chain-millau" }
|
||||
bp-polkadot = { path = "../../primitives/chain-polkadot" }
|
||||
bp-rialto = { path = "../../primitives/chain-rialto" }
|
||||
bp-rialto-parachain = { path = "../../primitives/chain-rialto-parachain" }
|
||||
bp-rococo = { path = "../../primitives/chain-rococo" }
|
||||
bp-token-swap = { path = "../../primitives/token-swap" }
|
||||
bp-wococo = { path = "../../primitives/chain-wococo" }
|
||||
bp-runtime = { path = "../../primitives/runtime" }
|
||||
bp-westend = { path = "../../primitives/chain-westend" }
|
||||
bridge-runtime-common = { path = "../../bin/runtime-common" }
|
||||
finality-grandpa = "0.14.1"
|
||||
finality-relay = { path = "../finality" }
|
||||
headers-relay = { path = "../headers" }
|
||||
messages-relay = { path = "../messages" }
|
||||
millau-runtime = { path = "../../bin/millau/runtime" }
|
||||
pallet-bridge-dispatch = { path = "../../modules/dispatch" }
|
||||
pallet-bridge-messages = { path = "../../modules/messages" }
|
||||
pallet-bridge-token-swap = { path = "../../modules/token-swap" }
|
||||
relay-kusama-client = { path = "../client-kusama" }
|
||||
relay-millau-client = { path = "../client-millau" }
|
||||
relay-polkadot-client = { path = "../client-polkadot" }
|
||||
relay-rialto-client = { path = "../client-rialto" }
|
||||
relay-rialto-parachain-client = { path = "../client-rialto-parachain" }
|
||||
relay-rococo-client = { path = "../client-rococo" }
|
||||
relay-wococo-client = { path = "../client-wococo" }
|
||||
relay-substrate-client = { path = "../client-substrate" }
|
||||
relay-utils = { path = "../utils" }
|
||||
relay-westend-client = { path = "../client-westend" }
|
||||
rialto-parachain-runtime = { path = "../../bin/rialto-parachain/runtime" }
|
||||
rialto-runtime = { path = "../../bin/rialto/runtime" }
|
||||
substrate-relay-helper = { path = "../lib-substrate-relay" }
|
||||
|
||||
# Substrate Dependencies
|
||||
|
||||
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sp-finality-grandpa = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sp-trie = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sp-version = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
|
||||
# Polkadot Dependencies
|
||||
|
||||
polkadot-parachain = { git = "https://github.com/paritytech/polkadot", branch = "master" }
|
||||
polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "master" }
|
||||
polkadot-runtime-common = { git = "https://github.com/paritytech/polkadot", branch = "master" }
|
||||
polkadot-runtime-parachains = { git = "https://github.com/paritytech/polkadot", branch = "master" }
|
||||
|
||||
[dev-dependencies]
|
||||
hex-literal = "0.3"
|
||||
pallet-bridge-grandpa = { path = "../../modules/grandpa" }
|
||||
sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
tempfile = "3.2"
|
||||
finality-grandpa = { version = "0.14.0" }
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
// 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 codec::Decode;
|
||||
use frame_support::weights::{DispatchClass, DispatchInfo, Pays, Weight};
|
||||
use relay_kusama_client::Kusama;
|
||||
use sp_version::RuntimeVersion;
|
||||
|
||||
use crate::cli::{
|
||||
bridge,
|
||||
encode_call::{Call, CliEncodeCall},
|
||||
encode_message, CliChain,
|
||||
};
|
||||
|
||||
/// Weight of the `system::remark` call at Kusama.
|
||||
///
|
||||
/// This weight is larger (x2) than actual weight at current Kusama runtime to avoid unsuccessful
|
||||
/// calls in the future. But since it is used only in tests (and on test chains), this is ok.
|
||||
pub(crate) const SYSTEM_REMARK_CALL_WEIGHT: Weight = 2 * 1_345_000;
|
||||
|
||||
/// Id of Kusama token that is used to fetch token price.
|
||||
pub(crate) const TOKEN_ID: &str = "kusama";
|
||||
|
||||
impl CliEncodeCall for Kusama {
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
bp_kusama::max_extrinsic_size()
|
||||
}
|
||||
|
||||
fn encode_call(call: &Call) -> anyhow::Result<Self::Call> {
|
||||
Ok(match call {
|
||||
Call::Remark { remark_payload, .. } => relay_kusama_client::runtime::Call::System(
|
||||
relay_kusama_client::runtime::SystemCall::remark(
|
||||
remark_payload.as_ref().map(|x| x.0.clone()).unwrap_or_default(),
|
||||
),
|
||||
),
|
||||
Call::BridgeSendMessage { lane, payload, fee, bridge_instance_index } =>
|
||||
match *bridge_instance_index {
|
||||
bridge::KUSAMA_TO_POLKADOT_INDEX => {
|
||||
let payload = Decode::decode(&mut &*payload.0)?;
|
||||
relay_kusama_client::runtime::Call::BridgePolkadotMessages(
|
||||
relay_kusama_client::runtime::BridgePolkadotMessagesCall::send_message(
|
||||
lane.0, payload, fee.0,
|
||||
),
|
||||
)
|
||||
},
|
||||
_ => anyhow::bail!(
|
||||
"Unsupported target bridge pallet with instance index: {}",
|
||||
bridge_instance_index
|
||||
),
|
||||
},
|
||||
_ => anyhow::bail!("Unsupported Kusama call: {:?}", call),
|
||||
})
|
||||
}
|
||||
|
||||
fn get_dispatch_info(
|
||||
call: &relay_kusama_client::runtime::Call,
|
||||
) -> anyhow::Result<DispatchInfo> {
|
||||
match *call {
|
||||
relay_kusama_client::runtime::Call::System(
|
||||
relay_kusama_client::runtime::SystemCall::remark(_),
|
||||
) => Ok(DispatchInfo {
|
||||
weight: crate::chains::kusama::SYSTEM_REMARK_CALL_WEIGHT,
|
||||
class: DispatchClass::Normal,
|
||||
pays_fee: Pays::Yes,
|
||||
}),
|
||||
_ => anyhow::bail!("Unsupported Kusama call: {:?}", call),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CliChain for Kusama {
|
||||
const RUNTIME_VERSION: RuntimeVersion = bp_kusama::VERSION;
|
||||
|
||||
type KeyPair = sp_core::sr25519::Pair;
|
||||
type MessagePayload = ();
|
||||
|
||||
fn ss58_format() -> u16 {
|
||||
42
|
||||
}
|
||||
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
bp_kusama::max_extrinsic_weight()
|
||||
}
|
||||
|
||||
fn encode_message(
|
||||
_message: encode_message::MessagePayload,
|
||||
) -> anyhow::Result<Self::MessagePayload> {
|
||||
anyhow::bail!("Sending messages from Kusama is not yet supported.")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
// 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/>.
|
||||
|
||||
//! Kusama-to-Polkadot headers sync entrypoint.
|
||||
|
||||
use codec::Encode;
|
||||
use sp_core::{Bytes, Pair};
|
||||
|
||||
use bp_header_chain::justification::GrandpaJustification;
|
||||
use relay_kusama_client::{Kusama, SyncHeader as KusamaSyncHeader};
|
||||
use relay_polkadot_client::{Polkadot, SigningParams as PolkadotSigningParams};
|
||||
use relay_substrate_client::{Client, TransactionSignScheme, UnsignedTransaction};
|
||||
use relay_utils::metrics::MetricsParams;
|
||||
use substrate_relay_helper::finality_pipeline::{
|
||||
SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate,
|
||||
};
|
||||
|
||||
/// Maximal saturating difference between `balance(now)` and `balance(now-24h)` to treat
|
||||
/// relay as gone wild.
|
||||
///
|
||||
/// Actual value, returned by `maximal_balance_decrease_per_day_is_sane` test is approximately 21
|
||||
/// DOT, but let's round up to 30 DOT here.
|
||||
pub(crate) const MAXIMAL_BALANCE_DECREASE_PER_DAY: bp_polkadot::Balance = 30_000_000_000;
|
||||
|
||||
/// Kusama-to-Polkadot finality sync pipeline.
|
||||
pub(crate) type FinalityPipelineKusamaFinalityToPolkadot =
|
||||
SubstrateFinalityToSubstrate<Kusama, Polkadot, PolkadotSigningParams>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct KusamaFinalityToPolkadot {
|
||||
finality_pipeline: FinalityPipelineKusamaFinalityToPolkadot,
|
||||
}
|
||||
|
||||
impl KusamaFinalityToPolkadot {
|
||||
pub fn new(target_client: Client<Polkadot>, target_sign: PolkadotSigningParams) -> Self {
|
||||
Self {
|
||||
finality_pipeline: FinalityPipelineKusamaFinalityToPolkadot::new(
|
||||
target_client,
|
||||
target_sign,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SubstrateFinalitySyncPipeline for KusamaFinalityToPolkadot {
|
||||
type FinalitySyncPipeline = FinalityPipelineKusamaFinalityToPolkadot;
|
||||
|
||||
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str =
|
||||
bp_kusama::BEST_FINALIZED_KUSAMA_HEADER_METHOD;
|
||||
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
||||
fn start_relay_guards(&self) {
|
||||
relay_substrate_client::guard::abort_on_spec_version_change(
|
||||
self.finality_pipeline.target_client.clone(),
|
||||
bp_polkadot::VERSION.spec_version,
|
||||
);
|
||||
relay_substrate_client::guard::abort_when_account_balance_decreased(
|
||||
self.finality_pipeline.target_client.clone(),
|
||||
self.transactions_author(),
|
||||
MAXIMAL_BALANCE_DECREASE_PER_DAY,
|
||||
);
|
||||
}
|
||||
|
||||
fn transactions_author(&self) -> bp_polkadot::AccountId {
|
||||
(*self.finality_pipeline.target_sign.public().as_array_ref()).into()
|
||||
}
|
||||
|
||||
fn make_submit_finality_proof_transaction(
|
||||
&self,
|
||||
era: bp_runtime::TransactionEraOf<Polkadot>,
|
||||
transaction_nonce: bp_runtime::IndexOf<Polkadot>,
|
||||
header: KusamaSyncHeader,
|
||||
proof: GrandpaJustification<bp_kusama::Header>,
|
||||
) -> Bytes {
|
||||
let call = relay_polkadot_client::runtime::Call::BridgeKusamaGrandpa(
|
||||
relay_polkadot_client::runtime::BridgeKusamaGrandpaCall::submit_finality_proof(
|
||||
Box::new(header.into_inner()),
|
||||
proof,
|
||||
),
|
||||
);
|
||||
let genesis_hash = *self.finality_pipeline.target_client.genesis_hash();
|
||||
let transaction = Polkadot::sign_transaction(
|
||||
genesis_hash,
|
||||
&self.finality_pipeline.target_sign,
|
||||
era,
|
||||
UnsignedTransaction::new(call, transaction_nonce),
|
||||
);
|
||||
|
||||
Bytes(transaction.encode())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use frame_support::weights::WeightToFeePolynomial;
|
||||
use pallet_bridge_grandpa::weights::WeightInfo;
|
||||
|
||||
pub fn compute_maximal_balance_decrease_per_day<B, W>(expected_source_headers_per_day: u32) -> B
|
||||
where
|
||||
B: From<u32> + std::ops::Mul<Output = B>,
|
||||
W: WeightToFeePolynomial<Balance = B>,
|
||||
{
|
||||
// we assume that the GRANDPA is not lagging here => ancestry length will be near to 0
|
||||
// (let's round up to 2)
|
||||
const AVG_VOTES_ANCESTRIES_LEN: u32 = 2;
|
||||
// let's assume number of validators is 1024 (more than on any existing well-known chain
|
||||
// atm) => number of precommits is *2/3 + 1
|
||||
const AVG_PRECOMMITS_LEN: u32 = 1024 * 2 / 3 + 1;
|
||||
|
||||
// GRANDPA pallet weights. We're now using Rialto weights everywhere.
|
||||
//
|
||||
// Using Rialto runtime is slightly incorrect, because `DbWeight` of other runtimes may
|
||||
// differ from the `DbWeight` of Rialto runtime. But now (and most probably forever) it is
|
||||
// the same.
|
||||
type GrandpaPalletWeights =
|
||||
pallet_bridge_grandpa::weights::RialtoWeight<rialto_runtime::Runtime>;
|
||||
|
||||
// The following formula shall not be treated as super-accurate - guard is to protect from
|
||||
// mad relays, not to protect from over-average loses.
|
||||
|
||||
// increase number of headers a bit
|
||||
let expected_source_headers_per_day = expected_source_headers_per_day * 110 / 100;
|
||||
let single_source_header_submit_call_weight = GrandpaPalletWeights::submit_finality_proof(
|
||||
AVG_VOTES_ANCESTRIES_LEN,
|
||||
AVG_PRECOMMITS_LEN,
|
||||
);
|
||||
// for simplicity - add extra weight for base tx fee + fee that is paid for the tx size +
|
||||
// adjusted fee
|
||||
let single_source_header_submit_tx_weight = single_source_header_submit_call_weight * 3 / 2;
|
||||
let single_source_header_tx_cost = W::calc(&single_source_header_submit_tx_weight);
|
||||
single_source_header_tx_cost * B::from(expected_source_headers_per_day)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maximal_balance_decrease_per_day_is_sane() {
|
||||
// we expect Kusama -> Polkadot relay to be running in mandatory-headers-only mode
|
||||
// => we expect single header for every Kusama session
|
||||
let maximal_balance_decrease = compute_maximal_balance_decrease_per_day::<
|
||||
bp_polkadot::Balance,
|
||||
bp_polkadot::WeightToFee,
|
||||
>(bp_kusama::DAYS / bp_kusama::SESSION_LENGTH + 1);
|
||||
assert!(
|
||||
MAXIMAL_BALANCE_DECREASE_PER_DAY >= maximal_balance_decrease,
|
||||
"Maximal expected loss per day {} is larger than hardcoded {}",
|
||||
maximal_balance_decrease,
|
||||
MAXIMAL_BALANCE_DECREASE_PER_DAY,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,345 @@
|
||||
// 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/>.
|
||||
|
||||
//! Kusama-to-Polkadot messages sync entrypoint.
|
||||
|
||||
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;
|
||||
use messages_relay::{message_lane::MessageLane, relay_strategy::MixStrategy};
|
||||
use relay_kusama_client::{
|
||||
HeaderId as KusamaHeaderId, Kusama, SigningParams as KusamaSigningParams,
|
||||
};
|
||||
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,
|
||||
SubstrateMessageLane, SubstrateMessageLaneToSubstrate,
|
||||
},
|
||||
messages_source::SubstrateMessagesSource,
|
||||
messages_target::SubstrateMessagesTarget,
|
||||
STALL_TIMEOUT,
|
||||
};
|
||||
|
||||
/// Kusama-to-Polkadot message lane.
|
||||
pub type MessageLaneKusamaMessagesToPolkadot =
|
||||
SubstrateMessageLaneToSubstrate<Kusama, KusamaSigningParams, Polkadot, PolkadotSigningParams>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct KusamaMessagesToPolkadot {
|
||||
message_lane: MessageLaneKusamaMessagesToPolkadot,
|
||||
}
|
||||
|
||||
impl SubstrateMessageLane for KusamaMessagesToPolkadot {
|
||||
type MessageLane = MessageLaneKusamaMessagesToPolkadot;
|
||||
|
||||
const OUTBOUND_LANE_MESSAGE_DETAILS_METHOD: &'static str =
|
||||
bp_polkadot::TO_POLKADOT_MESSAGE_DETAILS_METHOD;
|
||||
const OUTBOUND_LANE_LATEST_GENERATED_NONCE_METHOD: &'static str =
|
||||
bp_polkadot::TO_POLKADOT_LATEST_GENERATED_NONCE_METHOD;
|
||||
const OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str =
|
||||
bp_polkadot::TO_POLKADOT_LATEST_RECEIVED_NONCE_METHOD;
|
||||
|
||||
const INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str =
|
||||
bp_kusama::FROM_KUSAMA_LATEST_RECEIVED_NONCE_METHOD;
|
||||
const INBOUND_LANE_LATEST_CONFIRMED_NONCE_METHOD: &'static str =
|
||||
bp_kusama::FROM_KUSAMA_LATEST_CONFIRMED_NONCE_METHOD;
|
||||
const INBOUND_LANE_UNREWARDED_RELAYERS_STATE: &'static str =
|
||||
bp_kusama::FROM_KUSAMA_UNREWARDED_RELAYERS_STATE;
|
||||
|
||||
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str =
|
||||
bp_kusama::BEST_FINALIZED_KUSAMA_HEADER_METHOD;
|
||||
const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str =
|
||||
bp_polkadot::BEST_FINALIZED_POLKADOT_HEADER_METHOD;
|
||||
|
||||
const MESSAGE_PALLET_NAME_AT_SOURCE: &'static str =
|
||||
bp_kusama::WITH_POLKADOT_MESSAGES_PALLET_NAME;
|
||||
const MESSAGE_PALLET_NAME_AT_TARGET: &'static str =
|
||||
bp_polkadot::WITH_KUSAMA_MESSAGES_PALLET_NAME;
|
||||
|
||||
const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN: Weight =
|
||||
bp_polkadot::PAY_INBOUND_DISPATCH_FEE_WEIGHT;
|
||||
|
||||
type SourceChain = Kusama;
|
||||
type TargetChain = Polkadot;
|
||||
|
||||
fn source_transactions_author(&self) -> bp_kusama::AccountId {
|
||||
(*self.message_lane.source_sign.public().as_array_ref()).into()
|
||||
}
|
||||
|
||||
fn make_messages_receiving_proof_transaction(
|
||||
&self,
|
||||
best_block_id: KusamaHeaderId,
|
||||
transaction_nonce: bp_runtime::IndexOf<Kusama>,
|
||||
_generated_at_block: PolkadotHeaderId,
|
||||
proof: <Self::MessageLane as MessageLane>::MessagesReceivingProof,
|
||||
) -> Bytes {
|
||||
let (relayers_state, proof) = proof;
|
||||
let call = relay_kusama_client::runtime::Call::BridgePolkadotMessages(
|
||||
relay_kusama_client::runtime::BridgePolkadotMessagesCall::receive_messages_delivery_proof(
|
||||
proof,
|
||||
relayers_state,
|
||||
),
|
||||
);
|
||||
let genesis_hash = *self.message_lane.source_client.genesis_hash();
|
||||
let transaction = Kusama::sign_transaction(
|
||||
genesis_hash,
|
||||
&self.message_lane.source_sign,
|
||||
relay_substrate_client::TransactionEra::new(
|
||||
best_block_id,
|
||||
self.message_lane.source_transactions_mortality,
|
||||
),
|
||||
UnsignedTransaction::new(call, transaction_nonce),
|
||||
);
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Prepared Polkadot -> Kusama confirmation transaction. Weight: <unknown>/{}, size: {}/{}",
|
||||
bp_kusama::max_extrinsic_weight(),
|
||||
transaction.encode().len(),
|
||||
bp_kusama::max_extrinsic_size(),
|
||||
);
|
||||
Bytes(transaction.encode())
|
||||
}
|
||||
|
||||
fn target_transactions_author(&self) -> bp_polkadot::AccountId {
|
||||
(*self.message_lane.target_sign.public().as_array_ref()).into()
|
||||
}
|
||||
|
||||
fn make_messages_delivery_transaction(
|
||||
&self,
|
||||
best_block_id: PolkadotHeaderId,
|
||||
transaction_nonce: bp_runtime::IndexOf<Polkadot>,
|
||||
_generated_at_header: KusamaHeaderId,
|
||||
_nonces: RangeInclusive<MessageNonce>,
|
||||
proof: <Self::MessageLane as MessageLane>::MessagesProof,
|
||||
) -> Bytes {
|
||||
let (dispatch_weight, proof) = proof;
|
||||
let FromBridgedChainMessagesProof { ref nonces_start, ref nonces_end, .. } = proof;
|
||||
let messages_count = nonces_end - nonces_start + 1;
|
||||
|
||||
let call = relay_polkadot_client::runtime::Call::BridgeKusamaMessages(
|
||||
relay_polkadot_client::runtime::BridgeKusamaMessagesCall::receive_messages_proof(
|
||||
self.message_lane.relayer_id_at_source.clone(),
|
||||
proof,
|
||||
messages_count as _,
|
||||
dispatch_weight,
|
||||
),
|
||||
);
|
||||
let genesis_hash = *self.message_lane.target_client.genesis_hash();
|
||||
let transaction = Polkadot::sign_transaction(
|
||||
genesis_hash,
|
||||
&self.message_lane.target_sign,
|
||||
relay_substrate_client::TransactionEra::new(
|
||||
best_block_id,
|
||||
self.message_lane.target_transactions_mortality,
|
||||
),
|
||||
UnsignedTransaction::new(call, transaction_nonce),
|
||||
);
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Prepared Kusama -> Polkadot delivery transaction. Weight: <unknown>/{}, size: {}/{}",
|
||||
bp_polkadot::max_extrinsic_weight(),
|
||||
transaction.encode().len(),
|
||||
bp_polkadot::max_extrinsic_size(),
|
||||
);
|
||||
Bytes(transaction.encode())
|
||||
}
|
||||
}
|
||||
|
||||
/// Kusama node as messages source.
|
||||
type KusamaSourceClient = SubstrateMessagesSource<KusamaMessagesToPolkadot>;
|
||||
|
||||
/// Polkadot node as messages target.
|
||||
type PolkadotTargetClient = SubstrateMessagesTarget<KusamaMessagesToPolkadot>;
|
||||
|
||||
/// Run Kusama-to-Polkadot messages sync.
|
||||
pub async fn run(
|
||||
params: MessagesRelayParams<
|
||||
Kusama,
|
||||
KusamaSigningParams,
|
||||
Polkadot,
|
||||
PolkadotSigningParams,
|
||||
MixStrategy,
|
||||
>,
|
||||
) -> anyhow::Result<()> {
|
||||
let stall_timeout = relay_substrate_client::bidirectional_transaction_stall_timeout(
|
||||
params.source_transactions_mortality,
|
||||
params.target_transactions_mortality,
|
||||
Kusama::AVERAGE_BLOCK_INTERVAL,
|
||||
Polkadot::AVERAGE_BLOCK_INTERVAL,
|
||||
STALL_TIMEOUT,
|
||||
);
|
||||
let relayer_id_at_kusama = (*params.source_sign.public().as_array_ref()).into();
|
||||
|
||||
let lane_id = params.lane_id;
|
||||
let source_client = params.source_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_sign: params.target_sign,
|
||||
target_transactions_mortality: params.target_transactions_mortality,
|
||||
relayer_id_at_source: relayer_id_at_kusama,
|
||||
},
|
||||
};
|
||||
|
||||
// 2/3 is reserved for proofs and tx overhead
|
||||
let max_messages_size_in_single_batch = bp_polkadot::max_extrinsic_size() / 3;
|
||||
// we don't know exact weights of the Polkadot runtime. So to guess weights we'll be using
|
||||
// weights from Rialto and then simply dividing it by x2.
|
||||
let (max_messages_in_single_batch, max_messages_weight_in_single_batch) =
|
||||
select_delivery_transaction_limits::<
|
||||
pallet_bridge_messages::weights::RialtoWeight<rialto_runtime::Runtime>,
|
||||
>(
|
||||
bp_polkadot::max_extrinsic_weight(),
|
||||
bp_polkadot::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
|
||||
);
|
||||
let (max_messages_in_single_batch, max_messages_weight_in_single_batch) =
|
||||
(max_messages_in_single_batch / 2, max_messages_weight_in_single_batch / 2);
|
||||
|
||||
log::info!(
|
||||
target: "bridge",
|
||||
"Starting Kusama -> Polkadot messages relay.\n\t\
|
||||
Kusama relayer account id: {:?}\n\t\
|
||||
Max messages in single transaction: {}\n\t\
|
||||
Max messages size in single transaction: {}\n\t\
|
||||
Max messages weight in single transaction: {}\n\t\
|
||||
Tx mortality: {:?}/{:?}\n\t\
|
||||
Stall timeout: {:?}",
|
||||
lane.message_lane.relayer_id_at_source,
|
||||
max_messages_in_single_batch,
|
||||
max_messages_size_in_single_batch,
|
||||
max_messages_weight_in_single_batch,
|
||||
params.source_transactions_mortality,
|
||||
params.target_transactions_mortality,
|
||||
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(),
|
||||
)?;
|
||||
messages_relay::message_lane_loop::run(
|
||||
messages_relay::message_lane_loop::Params {
|
||||
lane: lane_id,
|
||||
source_tick: Kusama::AVERAGE_BLOCK_INTERVAL,
|
||||
target_tick: Polkadot::AVERAGE_BLOCK_INTERVAL,
|
||||
reconnect_delay: relay_utils::relay_loop::RECONNECT_DELAY,
|
||||
stall_timeout,
|
||||
delivery_params: messages_relay::message_lane_loop::MessageDeliveryParams {
|
||||
max_unrewarded_relayer_entries_at_target:
|
||||
bp_polkadot::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
|
||||
max_unconfirmed_nonces_at_target:
|
||||
bp_polkadot::MAX_UNCONFIRMED_MESSAGES_AT_INBOUND_LANE,
|
||||
max_messages_in_single_batch,
|
||||
max_messages_weight_in_single_batch,
|
||||
max_messages_size_in_single_batch,
|
||||
relay_strategy: params.relay_strategy,
|
||||
},
|
||||
},
|
||||
KusamaSourceClient::new(
|
||||
source_client.clone(),
|
||||
lane.clone(),
|
||||
lane_id,
|
||||
params.target_to_source_headers_relay,
|
||||
),
|
||||
PolkadotTargetClient::new(
|
||||
params.target_client,
|
||||
lane,
|
||||
lane_id,
|
||||
metrics_values,
|
||||
params.source_to_target_headers_relay,
|
||||
),
|
||||
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,
|
||||
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,
|
||||
source_client,
|
||||
Some(crate::chains::polkadot::TOKEN_ID),
|
||||
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),
|
||||
)),
|
||||
)
|
||||
}
|
||||
|
||||
/// Update Polkadot -> Kusama conversion rate, stored in Kusama runtime storage.
|
||||
pub(crate) async fn update_polkadot_to_kusama_conversion_rate(
|
||||
client: Client<Kusama>,
|
||||
signer: <Kusama as TransactionSignScheme>::AccountKeyPair,
|
||||
updated_rate: f64,
|
||||
) -> anyhow::Result<()> {
|
||||
let genesis_hash = *client.genesis_hash();
|
||||
let signer_id = (*signer.public().as_array_ref()).into();
|
||||
client
|
||||
.submit_signed_extrinsic(signer_id, move |_, transaction_nonce| {
|
||||
Bytes(
|
||||
Kusama::sign_transaction(
|
||||
genesis_hash,
|
||||
&signer,
|
||||
relay_substrate_client::TransactionEra::immortal(),
|
||||
UnsignedTransaction::new(
|
||||
relay_kusama_client::runtime::Call::BridgePolkadotMessages(
|
||||
relay_kusama_client::runtime::BridgePolkadotMessagesCall::update_pallet_parameter(
|
||||
relay_kusama_client::runtime::BridgePolkadotMessagesParameter::PolkadotToKusamaConversionRate(
|
||||
sp_runtime::FixedU128::from_float(updated_rate),
|
||||
)
|
||||
)
|
||||
),
|
||||
transaction_nonce,
|
||||
),
|
||||
)
|
||||
.encode(),
|
||||
)
|
||||
})
|
||||
.await
|
||||
.map(drop)
|
||||
.map_err(|err| anyhow::format_err!("{:?}", err))
|
||||
}
|
||||
@@ -19,8 +19,11 @@
|
||||
use crate::cli::{
|
||||
bridge,
|
||||
encode_call::{self, Call, CliEncodeCall},
|
||||
encode_message, send_message, CliChain,
|
||||
encode_message,
|
||||
send_message::{self, DispatchFeePayment},
|
||||
CliChain,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use bp_message_dispatch::{CallOrigin, MessagePayload};
|
||||
use codec::Decode;
|
||||
use frame_support::weights::{DispatchInfo, GetDispatchInfo, Weight};
|
||||
@@ -35,31 +38,32 @@ impl CliEncodeCall for Millau {
|
||||
fn encode_call(call: &Call) -> anyhow::Result<Self::Call> {
|
||||
Ok(match call {
|
||||
Call::Raw { data } => Decode::decode(&mut &*data.0)?,
|
||||
Call::Remark { remark_payload, .. } => millau_runtime::Call::System(millau_runtime::SystemCall::remark(
|
||||
remark_payload.as_ref().map(|x| x.0.clone()).unwrap_or_default(),
|
||||
)),
|
||||
Call::Transfer { recipient, amount } => millau_runtime::Call::Balances(
|
||||
millau_runtime::BalancesCall::transfer(recipient.raw_id(), amount.cast()),
|
||||
),
|
||||
Call::BridgeSendMessage {
|
||||
lane,
|
||||
payload,
|
||||
fee,
|
||||
bridge_instance_index,
|
||||
} => match *bridge_instance_index {
|
||||
bridge::MILLAU_TO_RIALTO_INDEX => {
|
||||
let payload = Decode::decode(&mut &*payload.0)?;
|
||||
millau_runtime::Call::BridgeRialtoMessages(millau_runtime::MessagesCall::send_message(
|
||||
lane.0,
|
||||
payload,
|
||||
fee.cast(),
|
||||
))
|
||||
}
|
||||
_ => anyhow::bail!(
|
||||
"Unsupported target bridge pallet with instance index: {}",
|
||||
bridge_instance_index
|
||||
),
|
||||
},
|
||||
Call::Remark { remark_payload, .. } =>
|
||||
millau_runtime::Call::System(millau_runtime::SystemCall::remark {
|
||||
remark: remark_payload.as_ref().map(|x| x.0.clone()).unwrap_or_default(),
|
||||
}),
|
||||
Call::Transfer { recipient, amount } =>
|
||||
millau_runtime::Call::Balances(millau_runtime::BalancesCall::transfer {
|
||||
dest: recipient.raw_id(),
|
||||
value: amount.cast(),
|
||||
}),
|
||||
Call::BridgeSendMessage { lane, payload, fee, bridge_instance_index } =>
|
||||
match *bridge_instance_index {
|
||||
bridge::MILLAU_TO_RIALTO_INDEX => {
|
||||
let payload = Decode::decode(&mut &*payload.0)?;
|
||||
millau_runtime::Call::BridgeRialtoMessages(
|
||||
millau_runtime::MessagesCall::send_message {
|
||||
lane_id: lane.0,
|
||||
payload,
|
||||
delivery_and_dispatch_fee: fee.cast(),
|
||||
},
|
||||
)
|
||||
},
|
||||
_ => anyhow::bail!(
|
||||
"Unsupported target bridge pallet with instance index: {}",
|
||||
bridge_instance_index
|
||||
),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -72,7 +76,12 @@ impl CliChain for Millau {
|
||||
const RUNTIME_VERSION: RuntimeVersion = millau_runtime::VERSION;
|
||||
|
||||
type KeyPair = sp_core::sr25519::Pair;
|
||||
type MessagePayload = MessagePayload<bp_millau::AccountId, bp_rialto::AccountSigner, bp_rialto::Signature, Vec<u8>>;
|
||||
type MessagePayload = MessagePayload<
|
||||
bp_millau::AccountId,
|
||||
bp_rialto::AccountSigner,
|
||||
bp_rialto::Signature,
|
||||
Vec<u8>,
|
||||
>;
|
||||
|
||||
fn ss58_format() -> u16 {
|
||||
millau_runtime::SS58Prefix::get() as u16
|
||||
@@ -83,10 +92,12 @@ impl CliChain for Millau {
|
||||
}
|
||||
|
||||
// TODO [#854|#843] support multiple bridges?
|
||||
fn encode_message(message: encode_message::MessagePayload) -> Result<Self::MessagePayload, String> {
|
||||
fn encode_message(
|
||||
message: encode_message::MessagePayload,
|
||||
) -> anyhow::Result<Self::MessagePayload> {
|
||||
match message {
|
||||
encode_message::MessagePayload::Raw { data } => MessagePayload::decode(&mut &*data.0)
|
||||
.map_err(|e| format!("Failed to decode Millau's MessagePayload: {:?}", e)),
|
||||
.map_err(|e| anyhow!("Failed to decode Millau's MessagePayload: {:?}", e)),
|
||||
encode_message::MessagePayload::Call { mut call, mut sender } => {
|
||||
type Source = Millau;
|
||||
type Target = relay_rialto_client::Rialto;
|
||||
@@ -94,12 +105,21 @@ impl CliChain for Millau {
|
||||
sender.enforce_chain::<Source>();
|
||||
let spec_version = Target::RUNTIME_VERSION.spec_version;
|
||||
let origin = CallOrigin::SourceAccount(sender.raw_id());
|
||||
encode_call::preprocess_call::<Source, Target>(&mut call, bridge::MILLAU_TO_RIALTO_INDEX);
|
||||
let call = Target::encode_call(&call).map_err(|e| e.to_string())?;
|
||||
encode_call::preprocess_call::<Source, Target>(
|
||||
&mut call,
|
||||
bridge::MILLAU_TO_RIALTO_INDEX,
|
||||
);
|
||||
let call = Target::encode_call(&call)?;
|
||||
let weight = call.get_dispatch_info().weight;
|
||||
|
||||
Ok(send_message::message_payload(spec_version, weight, origin, &call))
|
||||
}
|
||||
Ok(send_message::message_payload(
|
||||
spec_version,
|
||||
weight,
|
||||
origin,
|
||||
&call,
|
||||
DispatchFeePayment::AtSourceChain,
|
||||
))
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,37 +16,64 @@
|
||||
|
||||
//! Millau-to-Rialto headers sync entrypoint.
|
||||
|
||||
use crate::finality_pipeline::{SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate};
|
||||
|
||||
use bp_header_chain::justification::GrandpaJustification;
|
||||
use codec::Encode;
|
||||
use relay_millau_client::{Millau, SyncHeader as MillauSyncHeader};
|
||||
use relay_rialto_client::{Rialto, SigningParams as RialtoSigningParams};
|
||||
use relay_substrate_client::{Chain, TransactionSignScheme};
|
||||
use sp_core::{Bytes, Pair};
|
||||
|
||||
use bp_header_chain::justification::GrandpaJustification;
|
||||
use relay_millau_client::{Millau, SyncHeader as MillauSyncHeader};
|
||||
use relay_rialto_client::{Rialto, SigningParams as RialtoSigningParams};
|
||||
use relay_substrate_client::{Client, IndexOf, TransactionSignScheme, UnsignedTransaction};
|
||||
use substrate_relay_helper::finality_pipeline::{
|
||||
SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate,
|
||||
};
|
||||
|
||||
/// Millau-to-Rialto finality sync pipeline.
|
||||
pub(crate) type MillauFinalityToRialto = SubstrateFinalityToSubstrate<Millau, Rialto, RialtoSigningParams>;
|
||||
pub(crate) type FinalityPipelineMillauToRialto =
|
||||
SubstrateFinalityToSubstrate<Millau, Rialto, RialtoSigningParams>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct MillauFinalityToRialto {
|
||||
finality_pipeline: FinalityPipelineMillauToRialto,
|
||||
}
|
||||
|
||||
impl MillauFinalityToRialto {
|
||||
pub fn new(target_client: Client<Rialto>, target_sign: RialtoSigningParams) -> Self {
|
||||
Self { finality_pipeline: FinalityPipelineMillauToRialto::new(target_client, target_sign) }
|
||||
}
|
||||
}
|
||||
|
||||
impl SubstrateFinalitySyncPipeline for MillauFinalityToRialto {
|
||||
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_millau::BEST_FINALIZED_MILLAU_HEADER_METHOD;
|
||||
type FinalitySyncPipeline = FinalityPipelineMillauToRialto;
|
||||
|
||||
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str =
|
||||
bp_millau::BEST_FINALIZED_MILLAU_HEADER_METHOD;
|
||||
|
||||
type TargetChain = Rialto;
|
||||
|
||||
fn transactions_author(&self) -> bp_rialto::AccountId {
|
||||
(*self.target_sign.public().as_array_ref()).into()
|
||||
(*self.finality_pipeline.target_sign.public().as_array_ref()).into()
|
||||
}
|
||||
|
||||
fn make_submit_finality_proof_transaction(
|
||||
&self,
|
||||
transaction_nonce: <Rialto as Chain>::Index,
|
||||
era: bp_runtime::TransactionEraOf<Rialto>,
|
||||
transaction_nonce: IndexOf<Rialto>,
|
||||
header: MillauSyncHeader,
|
||||
proof: GrandpaJustification<bp_millau::Header>,
|
||||
) -> Bytes {
|
||||
let call = rialto_runtime::BridgeGrandpaMillauCall::submit_finality_proof(header.into_inner(), proof).into();
|
||||
let call = rialto_runtime::BridgeGrandpaMillauCall::submit_finality_proof {
|
||||
finality_target: Box::new(header.into_inner()),
|
||||
justification: proof,
|
||||
}
|
||||
.into();
|
||||
|
||||
let genesis_hash = *self.target_client.genesis_hash();
|
||||
let transaction = Rialto::sign_transaction(genesis_hash, &self.target_sign, transaction_nonce, call);
|
||||
let genesis_hash = *self.finality_pipeline.target_client.genesis_hash();
|
||||
let transaction = Rialto::sign_transaction(
|
||||
genesis_hash,
|
||||
&self.finality_pipeline.target_sign,
|
||||
era,
|
||||
UnsignedTransaction::new(call, transaction_nonce),
|
||||
);
|
||||
|
||||
Bytes(transaction.encode())
|
||||
}
|
||||
|
||||
@@ -16,64 +16,100 @@
|
||||
|
||||
//! Millau-to-Rialto messages sync entrypoint.
|
||||
|
||||
use crate::messages_lane::{
|
||||
select_delivery_transaction_limits, MessagesRelayParams, SubstrateMessageLane, SubstrateMessageLaneToSubstrate,
|
||||
};
|
||||
use crate::messages_source::SubstrateMessagesSource;
|
||||
use crate::messages_target::SubstrateMessagesTarget;
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use bp_messages::MessageNonce;
|
||||
use bp_runtime::{MILLAU_CHAIN_ID, RIALTO_CHAIN_ID};
|
||||
use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
|
||||
use codec::Encode;
|
||||
use frame_support::dispatch::GetDispatchInfo;
|
||||
use messages_relay::message_lane::MessageLane;
|
||||
use relay_millau_client::{HeaderId as MillauHeaderId, Millau, SigningParams as MillauSigningParams};
|
||||
use relay_rialto_client::{HeaderId as RialtoHeaderId, Rialto, SigningParams as RialtoSigningParams};
|
||||
use relay_substrate_client::{
|
||||
metrics::{FloatStorageValueMetric, StorageProofOverheadMetric},
|
||||
Chain, TransactionSignScheme,
|
||||
};
|
||||
use sp_core::{Bytes, Pair};
|
||||
use std::{ops::RangeInclusive, time::Duration};
|
||||
|
||||
use bp_messages::MessageNonce;
|
||||
use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
|
||||
use frame_support::weights::Weight;
|
||||
use messages_relay::{message_lane::MessageLane, relay_strategy::MixStrategy};
|
||||
use relay_millau_client::{
|
||||
HeaderId as MillauHeaderId, Millau, SigningParams as MillauSigningParams,
|
||||
};
|
||||
use relay_rialto_client::{
|
||||
HeaderId as RialtoHeaderId, Rialto, SigningParams as RialtoSigningParams,
|
||||
};
|
||||
use relay_substrate_client::{Chain, Client, IndexOf, TransactionSignScheme, UnsignedTransaction};
|
||||
use relay_utils::metrics::MetricsParams;
|
||||
use substrate_relay_helper::{
|
||||
messages_lane::{
|
||||
select_delivery_transaction_limits, MessagesRelayParams, StandaloneMessagesMetrics,
|
||||
SubstrateMessageLane, SubstrateMessageLaneToSubstrate,
|
||||
},
|
||||
messages_source::SubstrateMessagesSource,
|
||||
messages_target::SubstrateMessagesTarget,
|
||||
STALL_TIMEOUT,
|
||||
};
|
||||
|
||||
/// Millau-to-Rialto message lane.
|
||||
pub type MillauMessagesToRialto =
|
||||
pub type MessageLaneMillauMessagesToRialto =
|
||||
SubstrateMessageLaneToSubstrate<Millau, MillauSigningParams, Rialto, RialtoSigningParams>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MillauMessagesToRialto {
|
||||
message_lane: MessageLaneMillauMessagesToRialto,
|
||||
}
|
||||
|
||||
impl SubstrateMessageLane for MillauMessagesToRialto {
|
||||
const OUTBOUND_LANE_MESSAGE_DETAILS_METHOD: &'static str = bp_rialto::TO_RIALTO_MESSAGE_DETAILS_METHOD;
|
||||
type MessageLane = MessageLaneMillauMessagesToRialto;
|
||||
|
||||
const OUTBOUND_LANE_MESSAGE_DETAILS_METHOD: &'static str =
|
||||
bp_rialto::TO_RIALTO_MESSAGE_DETAILS_METHOD;
|
||||
const OUTBOUND_LANE_LATEST_GENERATED_NONCE_METHOD: &'static str =
|
||||
bp_rialto::TO_RIALTO_LATEST_GENERATED_NONCE_METHOD;
|
||||
const OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str = bp_rialto::TO_RIALTO_LATEST_RECEIVED_NONCE_METHOD;
|
||||
const OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str =
|
||||
bp_rialto::TO_RIALTO_LATEST_RECEIVED_NONCE_METHOD;
|
||||
|
||||
const INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str = bp_millau::FROM_MILLAU_LATEST_RECEIVED_NONCE_METHOD;
|
||||
const INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str =
|
||||
bp_millau::FROM_MILLAU_LATEST_RECEIVED_NONCE_METHOD;
|
||||
const INBOUND_LANE_LATEST_CONFIRMED_NONCE_METHOD: &'static str =
|
||||
bp_millau::FROM_MILLAU_LATEST_CONFIRMED_NONCE_METHOD;
|
||||
const INBOUND_LANE_UNREWARDED_RELAYERS_STATE: &'static str = bp_millau::FROM_MILLAU_UNREWARDED_RELAYERS_STATE;
|
||||
const INBOUND_LANE_UNREWARDED_RELAYERS_STATE: &'static str =
|
||||
bp_millau::FROM_MILLAU_UNREWARDED_RELAYERS_STATE;
|
||||
|
||||
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_millau::BEST_FINALIZED_MILLAU_HEADER_METHOD;
|
||||
const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str = bp_rialto::BEST_FINALIZED_RIALTO_HEADER_METHOD;
|
||||
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str =
|
||||
bp_millau::BEST_FINALIZED_MILLAU_HEADER_METHOD;
|
||||
const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str =
|
||||
bp_rialto::BEST_FINALIZED_RIALTO_HEADER_METHOD;
|
||||
|
||||
const MESSAGE_PALLET_NAME_AT_SOURCE: &'static str = bp_millau::WITH_RIALTO_MESSAGES_PALLET_NAME;
|
||||
const MESSAGE_PALLET_NAME_AT_TARGET: &'static str = bp_rialto::WITH_MILLAU_MESSAGES_PALLET_NAME;
|
||||
|
||||
const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN: Weight =
|
||||
bp_rialto::PAY_INBOUND_DISPATCH_FEE_WEIGHT;
|
||||
|
||||
type SourceChain = Millau;
|
||||
type TargetChain = Rialto;
|
||||
|
||||
fn source_transactions_author(&self) -> bp_millau::AccountId {
|
||||
(*self.source_sign.public().as_array_ref()).into()
|
||||
(*self.message_lane.source_sign.public().as_array_ref()).into()
|
||||
}
|
||||
|
||||
fn make_messages_receiving_proof_transaction(
|
||||
&self,
|
||||
transaction_nonce: <Millau as Chain>::Index,
|
||||
best_block_id: MillauHeaderId,
|
||||
transaction_nonce: IndexOf<Millau>,
|
||||
_generated_at_block: RialtoHeaderId,
|
||||
proof: <Self as MessageLane>::MessagesReceivingProof,
|
||||
proof: <Self::MessageLane as MessageLane>::MessagesReceivingProof,
|
||||
) -> Bytes {
|
||||
let (relayers_state, proof) = proof;
|
||||
let call: millau_runtime::Call =
|
||||
millau_runtime::MessagesCall::receive_messages_delivery_proof(proof, relayers_state).into();
|
||||
millau_runtime::MessagesCall::receive_messages_delivery_proof { proof, relayers_state }
|
||||
.into();
|
||||
let call_weight = call.get_dispatch_info().weight;
|
||||
let genesis_hash = *self.source_client.genesis_hash();
|
||||
let transaction = Millau::sign_transaction(genesis_hash, &self.source_sign, transaction_nonce, call);
|
||||
let genesis_hash = *self.message_lane.source_client.genesis_hash();
|
||||
let transaction = Millau::sign_transaction(
|
||||
genesis_hash,
|
||||
&self.message_lane.source_sign,
|
||||
relay_substrate_client::TransactionEra::new(
|
||||
best_block_id,
|
||||
self.message_lane.source_transactions_mortality,
|
||||
),
|
||||
UnsignedTransaction::new(call, transaction_nonce),
|
||||
);
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Prepared Rialto -> Millau confirmation transaction. Weight: {}/{}, size: {}/{}",
|
||||
@@ -86,33 +122,38 @@ impl SubstrateMessageLane for MillauMessagesToRialto {
|
||||
}
|
||||
|
||||
fn target_transactions_author(&self) -> bp_rialto::AccountId {
|
||||
(*self.target_sign.public().as_array_ref()).into()
|
||||
(*self.message_lane.target_sign.public().as_array_ref()).into()
|
||||
}
|
||||
|
||||
fn make_messages_delivery_transaction(
|
||||
&self,
|
||||
transaction_nonce: <Rialto as Chain>::Index,
|
||||
best_block_id: RialtoHeaderId,
|
||||
transaction_nonce: IndexOf<Rialto>,
|
||||
_generated_at_header: MillauHeaderId,
|
||||
_nonces: RangeInclusive<MessageNonce>,
|
||||
proof: <Self as MessageLane>::MessagesProof,
|
||||
proof: <Self::MessageLane as MessageLane>::MessagesProof,
|
||||
) -> Bytes {
|
||||
let (dispatch_weight, proof) = proof;
|
||||
let FromBridgedChainMessagesProof {
|
||||
ref nonces_start,
|
||||
ref nonces_end,
|
||||
..
|
||||
} = proof;
|
||||
let FromBridgedChainMessagesProof { ref nonces_start, ref nonces_end, .. } = proof;
|
||||
let messages_count = nonces_end - nonces_start + 1;
|
||||
let call: rialto_runtime::Call = rialto_runtime::MessagesCall::receive_messages_proof(
|
||||
self.relayer_id_at_source.clone(),
|
||||
let call: rialto_runtime::Call = rialto_runtime::MessagesCall::receive_messages_proof {
|
||||
relayer_id_at_bridged_chain: self.message_lane.relayer_id_at_source.clone(),
|
||||
proof,
|
||||
messages_count as _,
|
||||
messages_count: messages_count as _,
|
||||
dispatch_weight,
|
||||
)
|
||||
}
|
||||
.into();
|
||||
let call_weight = call.get_dispatch_info().weight;
|
||||
let genesis_hash = *self.target_client.genesis_hash();
|
||||
let transaction = Rialto::sign_transaction(genesis_hash, &self.target_sign, transaction_nonce, call);
|
||||
let genesis_hash = *self.message_lane.target_client.genesis_hash();
|
||||
let transaction = Rialto::sign_transaction(
|
||||
genesis_hash,
|
||||
&self.message_lane.target_sign,
|
||||
relay_substrate_client::TransactionEra::new(
|
||||
best_block_id,
|
||||
self.message_lane.target_transactions_mortality,
|
||||
),
|
||||
UnsignedTransaction::new(call, transaction_nonce),
|
||||
);
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Prepared Millau -> Rialto delivery transaction. Weight: {}/{}, size: {}/{}",
|
||||
@@ -126,35 +167,51 @@ impl SubstrateMessageLane for MillauMessagesToRialto {
|
||||
}
|
||||
|
||||
/// Millau node as messages source.
|
||||
type MillauSourceClient =
|
||||
SubstrateMessagesSource<Millau, MillauMessagesToRialto, millau_runtime::WithRialtoMessagesInstance>;
|
||||
type MillauSourceClient = SubstrateMessagesSource<MillauMessagesToRialto>;
|
||||
|
||||
/// Rialto node as messages target.
|
||||
type RialtoTargetClient =
|
||||
SubstrateMessagesTarget<Rialto, MillauMessagesToRialto, rialto_runtime::WithMillauMessagesInstance>;
|
||||
type RialtoTargetClient = SubstrateMessagesTarget<MillauMessagesToRialto>;
|
||||
|
||||
/// Run Millau-to-Rialto messages sync.
|
||||
pub async fn run(
|
||||
params: MessagesRelayParams<Millau, MillauSigningParams, Rialto, RialtoSigningParams>,
|
||||
) -> Result<(), String> {
|
||||
let stall_timeout = Duration::from_secs(5 * 60);
|
||||
params: MessagesRelayParams<
|
||||
Millau,
|
||||
MillauSigningParams,
|
||||
Rialto,
|
||||
RialtoSigningParams,
|
||||
MixStrategy,
|
||||
>,
|
||||
) -> anyhow::Result<()> {
|
||||
let stall_timeout = relay_substrate_client::bidirectional_transaction_stall_timeout(
|
||||
params.source_transactions_mortality,
|
||||
params.target_transactions_mortality,
|
||||
Millau::AVERAGE_BLOCK_INTERVAL,
|
||||
Rialto::AVERAGE_BLOCK_INTERVAL,
|
||||
STALL_TIMEOUT,
|
||||
);
|
||||
let relayer_id_at_millau = (*params.source_sign.public().as_array_ref()).into();
|
||||
|
||||
let lane_id = params.lane_id;
|
||||
let source_client = params.source_client;
|
||||
let lane = MillauMessagesToRialto {
|
||||
source_client: source_client.clone(),
|
||||
source_sign: params.source_sign,
|
||||
target_client: params.target_client.clone(),
|
||||
target_sign: params.target_sign,
|
||||
relayer_id_at_source: relayer_id_at_millau,
|
||||
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_sign: params.target_sign,
|
||||
target_transactions_mortality: params.target_transactions_mortality,
|
||||
relayer_id_at_source: relayer_id_at_millau,
|
||||
},
|
||||
};
|
||||
|
||||
// 2/3 is reserved for proofs and tx overhead
|
||||
let max_messages_size_in_single_batch = bp_rialto::max_extrinsic_size() / 3;
|
||||
// TODO: use Millau weights after https://github.com/paritytech/parity-bridges-common/issues/390
|
||||
let (max_messages_in_single_batch, max_messages_weight_in_single_batch) =
|
||||
select_delivery_transaction_limits::<pallet_bridge_messages::weights::RialtoWeight<millau_runtime::Runtime>>(
|
||||
select_delivery_transaction_limits::<
|
||||
pallet_bridge_messages::weights::RialtoWeight<millau_runtime::Runtime>,
|
||||
>(
|
||||
bp_rialto::max_extrinsic_weight(),
|
||||
bp_rialto::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
|
||||
);
|
||||
@@ -165,13 +222,25 @@ pub async fn run(
|
||||
Millau relayer account id: {:?}\n\t\
|
||||
Max messages in single transaction: {}\n\t\
|
||||
Max messages size in single transaction: {}\n\t\
|
||||
Max messages weight in single transaction: {}",
|
||||
lane.relayer_id_at_source,
|
||||
Max messages weight in single transaction: {}\n\t\
|
||||
Tx mortality: {:?}/{:?}\n\t\
|
||||
Stall timeout: {:?}",
|
||||
lane.message_lane.relayer_id_at_source,
|
||||
max_messages_in_single_batch,
|
||||
max_messages_size_in_single_batch,
|
||||
max_messages_weight_in_single_batch,
|
||||
params.source_transactions_mortality,
|
||||
params.target_transactions_mortality,
|
||||
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(),
|
||||
)?;
|
||||
messages_relay::message_lane_loop::run(
|
||||
messages_relay::message_lane_loop::Params {
|
||||
lane: lane_id,
|
||||
@@ -180,58 +249,86 @@ pub async fn run(
|
||||
reconnect_delay: relay_utils::relay_loop::RECONNECT_DELAY,
|
||||
stall_timeout,
|
||||
delivery_params: messages_relay::message_lane_loop::MessageDeliveryParams {
|
||||
max_unrewarded_relayer_entries_at_target: bp_rialto::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
|
||||
max_unconfirmed_nonces_at_target: bp_rialto::MAX_UNCONFIRMED_MESSAGES_AT_INBOUND_LANE,
|
||||
max_unrewarded_relayer_entries_at_target:
|
||||
bp_rialto::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
|
||||
max_unconfirmed_nonces_at_target:
|
||||
bp_rialto::MAX_UNCONFIRMED_MESSAGES_AT_INBOUND_LANE,
|
||||
max_messages_in_single_batch,
|
||||
max_messages_weight_in_single_batch,
|
||||
max_messages_size_in_single_batch,
|
||||
relayer_mode: messages_relay::message_lane_loop::RelayerMode::Altruistic,
|
||||
relay_strategy: params.relay_strategy,
|
||||
},
|
||||
},
|
||||
MillauSourceClient::new(
|
||||
source_client.clone(),
|
||||
lane.clone(),
|
||||
lane_id,
|
||||
RIALTO_CHAIN_ID,
|
||||
params.target_to_source_headers_relay,
|
||||
),
|
||||
RialtoTargetClient::new(
|
||||
params.target_client,
|
||||
lane,
|
||||
lane_id,
|
||||
MILLAU_CHAIN_ID,
|
||||
metrics_values,
|
||||
params.source_to_target_headers_relay,
|
||||
),
|
||||
relay_utils::relay_metrics(
|
||||
Some(messages_relay::message_lane_loop::metrics_prefix::<
|
||||
MillauMessagesToRialto,
|
||||
>(&lane_id)),
|
||||
params.metrics_params,
|
||||
)
|
||||
.standalone_metric(|registry, prefix| {
|
||||
StorageProofOverheadMetric::new(
|
||||
registry,
|
||||
prefix,
|
||||
source_client.clone(),
|
||||
"millau_storage_proof_overhead".into(),
|
||||
"Millau storage proof overhead".into(),
|
||||
)
|
||||
})?
|
||||
.standalone_metric(|registry, prefix| {
|
||||
FloatStorageValueMetric::<_, sp_runtime::FixedU128>::new(
|
||||
registry,
|
||||
prefix,
|
||||
source_client,
|
||||
sp_core::storage::StorageKey(
|
||||
millau_runtime::rialto_messages::RialtoToMillauConversionRate::key().to_vec(),
|
||||
),
|
||||
Some(millau_runtime::rialto_messages::INITIAL_RIALTO_TO_MILLAU_CONVERSION_RATE),
|
||||
"millau_rialto_to_millau_conversion_rate".into(),
|
||||
"Rialto to Millau tokens conversion rate (used by Rialto)".into(),
|
||||
)
|
||||
})?
|
||||
.into_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,
|
||||
source_client: Client<Millau>,
|
||||
) -> anyhow::Result<(MetricsParams, StandaloneMessagesMetrics)> {
|
||||
substrate_relay_helper::messages_lane::add_standalone_metrics::<MillauMessagesToRialto>(
|
||||
metrics_prefix,
|
||||
metrics_params,
|
||||
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,
|
||||
)),
|
||||
)
|
||||
}
|
||||
|
||||
/// Update Rialto -> Millau conversion rate, stored in Millau runtime storage.
|
||||
pub(crate) async fn update_rialto_to_millau_conversion_rate(
|
||||
client: Client<Millau>,
|
||||
signer: <Millau as TransactionSignScheme>::AccountKeyPair,
|
||||
updated_rate: f64,
|
||||
) -> anyhow::Result<()> {
|
||||
let genesis_hash = *client.genesis_hash();
|
||||
let signer_id = (*signer.public().as_array_ref()).into();
|
||||
client
|
||||
.submit_signed_extrinsic(signer_id, move |_, transaction_nonce| {
|
||||
Bytes(
|
||||
Millau::sign_transaction(
|
||||
genesis_hash,
|
||||
&signer,
|
||||
relay_substrate_client::TransactionEra::immortal(),
|
||||
UnsignedTransaction::new(
|
||||
millau_runtime::MessagesCall::update_pallet_parameter {
|
||||
parameter: millau_runtime::rialto_messages::MillauToRialtoMessagesParameter::RialtoToMillauConversionRate(
|
||||
sp_runtime::FixedU128::from_float(updated_rate),
|
||||
),
|
||||
}
|
||||
.into(),
|
||||
transaction_nonce,
|
||||
),
|
||||
)
|
||||
.encode(),
|
||||
)
|
||||
})
|
||||
.await
|
||||
.map(drop)
|
||||
.map_err(|err| anyhow::format_err!("{:?}", err))
|
||||
}
|
||||
|
||||
@@ -16,8 +16,12 @@
|
||||
|
||||
//! Chain-specific relayer configuration.
|
||||
|
||||
pub mod kusama_headers_to_polkadot;
|
||||
pub mod kusama_messages_to_polkadot;
|
||||
pub mod millau_headers_to_rialto;
|
||||
pub mod millau_messages_to_rialto;
|
||||
pub mod polkadot_headers_to_kusama;
|
||||
pub mod polkadot_messages_to_kusama;
|
||||
pub mod rialto_headers_to_millau;
|
||||
pub mod rialto_messages_to_millau;
|
||||
pub mod rococo_headers_to_wococo;
|
||||
@@ -26,45 +30,40 @@ pub mod westend_headers_to_millau;
|
||||
pub mod wococo_headers_to_rococo;
|
||||
pub mod wococo_messages_to_rococo;
|
||||
|
||||
mod kusama;
|
||||
mod millau;
|
||||
mod polkadot;
|
||||
mod rialto;
|
||||
mod rialto_parachain;
|
||||
mod rococo;
|
||||
mod westend;
|
||||
mod wococo;
|
||||
|
||||
use relay_utils::metrics::{FloatJsonValueMetric, MetricsParams};
|
||||
// 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;
|
||||
|
||||
pub(crate) fn add_polkadot_kusama_price_metrics<T: finality_relay::FinalitySyncPipeline>(
|
||||
prefix: Option<String>,
|
||||
params: MetricsParams,
|
||||
) -> anyhow::Result<MetricsParams> {
|
||||
Ok(
|
||||
relay_utils::relay_metrics(Some(finality_relay::metrics_prefix::<T>()), params)
|
||||
// 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
|
||||
.standalone_metric(|registry, prefix| {
|
||||
FloatJsonValueMetric::new(
|
||||
registry,
|
||||
prefix,
|
||||
"https://api.coingecko.com/api/v3/simple/price?ids=Polkadot&vs_currencies=btc".into(),
|
||||
"$.polkadot.btc".into(),
|
||||
"polkadot_to_base_conversion_rate".into(),
|
||||
"Rate used to convert from DOT to some BASE tokens".into(),
|
||||
)
|
||||
})
|
||||
.map_err(|e| anyhow::format_err!("{}", e))?
|
||||
.standalone_metric(|registry, prefix| {
|
||||
FloatJsonValueMetric::new(
|
||||
registry,
|
||||
prefix,
|
||||
"https://api.coingecko.com/api/v3/simple/price?ids=Kusama&vs_currencies=btc".into(),
|
||||
"$.kusama.btc".into(),
|
||||
"kusama_to_base_conversion_rate".into(),
|
||||
"Rate used to convert from KSM to some BASE tokens".into(),
|
||||
)
|
||||
})
|
||||
.map_err(|e| anyhow::format_err!("{}", e))?
|
||||
.into_params(),
|
||||
)
|
||||
// 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())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -75,7 +74,7 @@ mod tests {
|
||||
use frame_support::dispatch::GetDispatchInfo;
|
||||
use relay_millau_client::Millau;
|
||||
use relay_rialto_client::Rialto;
|
||||
use relay_substrate_client::TransactionSignScheme;
|
||||
use relay_substrate_client::{TransactionSignScheme, UnsignedTransaction};
|
||||
use sp_core::Pair;
|
||||
use sp_runtime::traits::{IdentifyAccount, Verify};
|
||||
|
||||
@@ -83,7 +82,8 @@ mod tests {
|
||||
fn millau_signature_is_valid_on_rialto() {
|
||||
let millau_sign = relay_millau_client::SigningParams::from_string("//Dave", None).unwrap();
|
||||
|
||||
let call = rialto_runtime::Call::System(rialto_runtime::SystemCall::remark(vec![]));
|
||||
let call =
|
||||
rialto_runtime::Call::System(rialto_runtime::SystemCall::remark { remark: vec![] });
|
||||
|
||||
let millau_public: bp_millau::AccountSigner = millau_sign.public().into();
|
||||
let millau_account_id: bp_millau::AccountId = millau_public.into_account();
|
||||
@@ -94,7 +94,8 @@ mod tests {
|
||||
rialto_runtime::VERSION.spec_version,
|
||||
);
|
||||
|
||||
let rialto_signer = relay_rialto_client::SigningParams::from_string("//Dave", None).unwrap();
|
||||
let rialto_signer =
|
||||
relay_rialto_client::SigningParams::from_string("//Dave", None).unwrap();
|
||||
let signature = rialto_signer.sign(&digest);
|
||||
|
||||
assert!(signature.verify(&digest[..], &rialto_signer.public()));
|
||||
@@ -104,7 +105,8 @@ mod tests {
|
||||
fn rialto_signature_is_valid_on_millau() {
|
||||
let rialto_sign = relay_rialto_client::SigningParams::from_string("//Dave", None).unwrap();
|
||||
|
||||
let call = millau_runtime::Call::System(millau_runtime::SystemCall::remark(vec![]));
|
||||
let call =
|
||||
millau_runtime::Call::System(millau_runtime::SystemCall::remark { remark: vec![] });
|
||||
|
||||
let rialto_public: bp_rialto::AccountSigner = rialto_sign.public().into();
|
||||
let rialto_account_id: bp_rialto::AccountId = rialto_public.into_account();
|
||||
@@ -115,7 +117,8 @@ mod tests {
|
||||
millau_runtime::VERSION.spec_version,
|
||||
);
|
||||
|
||||
let millau_signer = relay_millau_client::SigningParams::from_string("//Dave", None).unwrap();
|
||||
let millau_signer =
|
||||
relay_millau_client::SigningParams::from_string("//Dave", None).unwrap();
|
||||
let signature = millau_signer.sign(&digest);
|
||||
|
||||
assert!(signature.verify(&digest[..], &millau_signer.public()));
|
||||
@@ -130,22 +133,27 @@ mod tests {
|
||||
bp_millau::max_extrinsic_size(),
|
||||
);
|
||||
|
||||
let call: millau_runtime::Call = millau_runtime::SystemCall::remark(vec![42; maximal_remark_size as _]).into();
|
||||
let call: millau_runtime::Call =
|
||||
millau_runtime::SystemCall::remark { remark: vec![42; maximal_remark_size as _] }
|
||||
.into();
|
||||
let payload = send_message::message_payload(
|
||||
Default::default(),
|
||||
call.get_dispatch_info().weight,
|
||||
bp_message_dispatch::CallOrigin::SourceRoot,
|
||||
&call,
|
||||
send_message::DispatchFeePayment::AtSourceChain,
|
||||
);
|
||||
assert_eq!(Millau::verify_message(&payload), Ok(()));
|
||||
|
||||
let call: millau_runtime::Call =
|
||||
millau_runtime::SystemCall::remark(vec![42; (maximal_remark_size + 1) as _]).into();
|
||||
millau_runtime::SystemCall::remark { remark: vec![42; (maximal_remark_size + 1) as _] }
|
||||
.into();
|
||||
let payload = send_message::message_payload(
|
||||
Default::default(),
|
||||
call.get_dispatch_info().weight,
|
||||
bp_message_dispatch::CallOrigin::SourceRoot,
|
||||
&call,
|
||||
send_message::DispatchFeePayment::AtSourceChain,
|
||||
);
|
||||
assert!(Millau::verify_message(&payload).is_err());
|
||||
}
|
||||
@@ -164,15 +172,18 @@ mod tests {
|
||||
fn maximal_rialto_to_millau_message_dispatch_weight_is_computed_correctly() {
|
||||
use rialto_runtime::millau_messages::Millau;
|
||||
|
||||
let maximal_dispatch_weight =
|
||||
send_message::compute_maximal_message_dispatch_weight(bp_millau::max_extrinsic_weight());
|
||||
let call: millau_runtime::Call = rialto_runtime::SystemCall::remark(vec![]).into();
|
||||
let maximal_dispatch_weight = send_message::compute_maximal_message_dispatch_weight(
|
||||
bp_millau::max_extrinsic_weight(),
|
||||
);
|
||||
let call: millau_runtime::Call =
|
||||
rialto_runtime::SystemCall::remark { remark: vec![] }.into();
|
||||
|
||||
let payload = send_message::message_payload(
|
||||
Default::default(),
|
||||
maximal_dispatch_weight,
|
||||
bp_message_dispatch::CallOrigin::SourceRoot,
|
||||
&call,
|
||||
send_message::DispatchFeePayment::AtSourceChain,
|
||||
);
|
||||
assert_eq!(Millau::verify_message(&payload), Ok(()));
|
||||
|
||||
@@ -181,6 +192,7 @@ mod tests {
|
||||
maximal_dispatch_weight + 1,
|
||||
bp_message_dispatch::CallOrigin::SourceRoot,
|
||||
&call,
|
||||
send_message::DispatchFeePayment::AtSourceChain,
|
||||
);
|
||||
assert!(Millau::verify_message(&payload).is_err());
|
||||
}
|
||||
@@ -189,15 +201,18 @@ mod tests {
|
||||
fn maximal_weight_fill_block_to_rialto_is_generated_correctly() {
|
||||
use millau_runtime::rialto_messages::Rialto;
|
||||
|
||||
let maximal_dispatch_weight =
|
||||
send_message::compute_maximal_message_dispatch_weight(bp_rialto::max_extrinsic_weight());
|
||||
let call: rialto_runtime::Call = millau_runtime::SystemCall::remark(vec![]).into();
|
||||
let maximal_dispatch_weight = send_message::compute_maximal_message_dispatch_weight(
|
||||
bp_rialto::max_extrinsic_weight(),
|
||||
);
|
||||
let call: rialto_runtime::Call =
|
||||
millau_runtime::SystemCall::remark { remark: vec![] }.into();
|
||||
|
||||
let payload = send_message::message_payload(
|
||||
Default::default(),
|
||||
maximal_dispatch_weight,
|
||||
bp_message_dispatch::CallOrigin::SourceRoot,
|
||||
&call,
|
||||
send_message::DispatchFeePayment::AtSourceChain,
|
||||
);
|
||||
assert_eq!(Rialto::verify_message(&payload), Ok(()));
|
||||
|
||||
@@ -206,18 +221,20 @@ mod tests {
|
||||
maximal_dispatch_weight + 1,
|
||||
bp_message_dispatch::CallOrigin::SourceRoot,
|
||||
&call,
|
||||
send_message::DispatchFeePayment::AtSourceChain,
|
||||
);
|
||||
assert!(Rialto::verify_message(&payload).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rialto_tx_extra_bytes_constant_is_correct() {
|
||||
let rialto_call = rialto_runtime::Call::System(rialto_runtime::SystemCall::remark(vec![]));
|
||||
let rialto_call =
|
||||
rialto_runtime::Call::System(rialto_runtime::SystemCall::remark { remark: vec![] });
|
||||
let rialto_tx = Rialto::sign_transaction(
|
||||
Default::default(),
|
||||
&sp_keyring::AccountKeyring::Alice.pair(),
|
||||
0,
|
||||
rialto_call.clone(),
|
||||
relay_substrate_client::TransactionEra::immortal(),
|
||||
UnsignedTransaction::new(rialto_call.clone(), 0),
|
||||
);
|
||||
let extra_bytes_in_transaction = rialto_tx.encode().len() - rialto_call.encode().len();
|
||||
assert!(
|
||||
@@ -230,12 +247,13 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn millau_tx_extra_bytes_constant_is_correct() {
|
||||
let millau_call = millau_runtime::Call::System(millau_runtime::SystemCall::remark(vec![]));
|
||||
let millau_call =
|
||||
millau_runtime::Call::System(millau_runtime::SystemCall::remark { remark: vec![] });
|
||||
let millau_tx = Millau::sign_transaction(
|
||||
Default::default(),
|
||||
&sp_keyring::AccountKeyring::Alice.pair(),
|
||||
0,
|
||||
millau_call.clone(),
|
||||
relay_substrate_client::TransactionEra::immortal(),
|
||||
UnsignedTransaction::new(millau_call.clone(), 0),
|
||||
);
|
||||
let extra_bytes_in_transaction = millau_tx.encode().len() - millau_call.encode().len();
|
||||
assert!(
|
||||
@@ -274,13 +292,14 @@ mod rococo_tests {
|
||||
};
|
||||
|
||||
let actual = relay_rococo_client::runtime::BridgeGrandpaWococoCall::submit_finality_proof(
|
||||
header.clone(),
|
||||
Box::new(header.clone()),
|
||||
justification.clone(),
|
||||
);
|
||||
let expected = millau_runtime::BridgeGrandpaRialtoCall::<millau_runtime::Runtime>::submit_finality_proof(
|
||||
header,
|
||||
justification,
|
||||
);
|
||||
let expected =
|
||||
millau_runtime::BridgeGrandpaCall::<millau_runtime::Runtime>::submit_finality_proof {
|
||||
finality_target: Box::new(header),
|
||||
justification,
|
||||
};
|
||||
|
||||
// when
|
||||
let actual_encoded = actual.encode();
|
||||
@@ -321,11 +340,15 @@ mod westend_tests {
|
||||
votes_ancestries: vec![],
|
||||
};
|
||||
|
||||
let actual = bp_westend::BridgeGrandpaRococoCall::submit_finality_proof(header.clone(), justification.clone());
|
||||
let expected = millau_runtime::BridgeGrandpaRialtoCall::<millau_runtime::Runtime>::submit_finality_proof(
|
||||
header,
|
||||
justification,
|
||||
let actual = relay_kusama_client::runtime::BridgePolkadotGrandpaCall::submit_finality_proof(
|
||||
Box::new(header.clone()),
|
||||
justification.clone(),
|
||||
);
|
||||
let expected =
|
||||
millau_runtime::BridgeGrandpaCall::<millau_runtime::Runtime>::submit_finality_proof {
|
||||
finality_target: Box::new(header),
|
||||
justification,
|
||||
};
|
||||
|
||||
// when
|
||||
let actual_encoded = actual.encode();
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
// 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 codec::Decode;
|
||||
use frame_support::weights::{DispatchClass, DispatchInfo, Pays, Weight};
|
||||
use relay_polkadot_client::Polkadot;
|
||||
use sp_version::RuntimeVersion;
|
||||
|
||||
use crate::cli::{
|
||||
bridge,
|
||||
encode_call::{Call, CliEncodeCall},
|
||||
encode_message, CliChain,
|
||||
};
|
||||
|
||||
/// Weight of the `system::remark` call at Polkadot.
|
||||
///
|
||||
/// This weight is larger (x2) than actual weight at current Polkadot runtime to avoid unsuccessful
|
||||
/// calls in the future. But since it is used only in tests (and on test chains), this is ok.
|
||||
pub(crate) const SYSTEM_REMARK_CALL_WEIGHT: Weight = 2 * 1_345_000;
|
||||
|
||||
/// Id of Polkadot token that is used to fetch token price.
|
||||
pub(crate) const TOKEN_ID: &str = "polkadot";
|
||||
|
||||
impl CliEncodeCall for Polkadot {
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
bp_polkadot::max_extrinsic_size()
|
||||
}
|
||||
|
||||
fn encode_call(call: &Call) -> anyhow::Result<Self::Call> {
|
||||
Ok(match call {
|
||||
Call::Remark { remark_payload, .. } => relay_polkadot_client::runtime::Call::System(
|
||||
relay_polkadot_client::runtime::SystemCall::remark(
|
||||
remark_payload.as_ref().map(|x| x.0.clone()).unwrap_or_default(),
|
||||
),
|
||||
),
|
||||
Call::BridgeSendMessage { lane, payload, fee, bridge_instance_index } =>
|
||||
match *bridge_instance_index {
|
||||
bridge::POLKADOT_TO_KUSAMA_INDEX => {
|
||||
let payload = Decode::decode(&mut &*payload.0)?;
|
||||
relay_polkadot_client::runtime::Call::BridgeKusamaMessages(
|
||||
relay_polkadot_client::runtime::BridgeKusamaMessagesCall::send_message(
|
||||
lane.0, payload, fee.0,
|
||||
),
|
||||
)
|
||||
},
|
||||
_ => anyhow::bail!(
|
||||
"Unsupported target bridge pallet with instance index: {}",
|
||||
bridge_instance_index
|
||||
),
|
||||
},
|
||||
_ => anyhow::bail!("Unsupported Polkadot call: {:?}", call),
|
||||
})
|
||||
}
|
||||
|
||||
fn get_dispatch_info(
|
||||
call: &relay_polkadot_client::runtime::Call,
|
||||
) -> anyhow::Result<DispatchInfo> {
|
||||
match *call {
|
||||
relay_polkadot_client::runtime::Call::System(
|
||||
relay_polkadot_client::runtime::SystemCall::remark(_),
|
||||
) => Ok(DispatchInfo {
|
||||
weight: crate::chains::polkadot::SYSTEM_REMARK_CALL_WEIGHT,
|
||||
class: DispatchClass::Normal,
|
||||
pays_fee: Pays::Yes,
|
||||
}),
|
||||
_ => anyhow::bail!("Unsupported Polkadot call: {:?}", call),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CliChain for Polkadot {
|
||||
const RUNTIME_VERSION: RuntimeVersion = bp_polkadot::VERSION;
|
||||
|
||||
type KeyPair = sp_core::sr25519::Pair;
|
||||
type MessagePayload = ();
|
||||
|
||||
fn ss58_format() -> u16 {
|
||||
42
|
||||
}
|
||||
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
bp_polkadot::max_extrinsic_weight()
|
||||
}
|
||||
|
||||
fn encode_message(
|
||||
_message: encode_message::MessagePayload,
|
||||
) -> anyhow::Result<Self::MessagePayload> {
|
||||
anyhow::bail!("Sending messages from Polkadot is not yet supported.")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
// 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/>.
|
||||
|
||||
//! Polkadot-to-Kusama headers sync entrypoint.
|
||||
|
||||
use codec::Encode;
|
||||
use sp_core::{Bytes, Pair};
|
||||
|
||||
use bp_header_chain::justification::GrandpaJustification;
|
||||
use relay_kusama_client::{Kusama, SigningParams as KusamaSigningParams};
|
||||
use relay_polkadot_client::{Polkadot, SyncHeader as PolkadotSyncHeader};
|
||||
use relay_substrate_client::{Client, TransactionSignScheme, UnsignedTransaction};
|
||||
use relay_utils::metrics::MetricsParams;
|
||||
use substrate_relay_helper::finality_pipeline::{
|
||||
SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate,
|
||||
};
|
||||
|
||||
/// Maximal saturating difference between `balance(now)` and `balance(now-24h)` to treat
|
||||
/// relay as gone wild.
|
||||
///
|
||||
/// Actual value, returned by `maximal_balance_decrease_per_day_is_sane` test is approximately 0.001
|
||||
/// KSM, but let's round up to 0.1 KSM here.
|
||||
pub(crate) const MAXIMAL_BALANCE_DECREASE_PER_DAY: bp_polkadot::Balance = 100_000_000_000;
|
||||
|
||||
/// Polkadot-to-Kusama finality sync pipeline.
|
||||
pub(crate) type FinalityPipelinePolkadotFinalityToKusama =
|
||||
SubstrateFinalityToSubstrate<Polkadot, Kusama, KusamaSigningParams>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct PolkadotFinalityToKusama {
|
||||
finality_pipeline: FinalityPipelinePolkadotFinalityToKusama,
|
||||
}
|
||||
|
||||
impl PolkadotFinalityToKusama {
|
||||
pub fn new(target_client: Client<Kusama>, target_sign: KusamaSigningParams) -> Self {
|
||||
Self {
|
||||
finality_pipeline: FinalityPipelinePolkadotFinalityToKusama::new(
|
||||
target_client,
|
||||
target_sign,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SubstrateFinalitySyncPipeline for PolkadotFinalityToKusama {
|
||||
type FinalitySyncPipeline = FinalityPipelinePolkadotFinalityToKusama;
|
||||
|
||||
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str =
|
||||
bp_polkadot::BEST_FINALIZED_POLKADOT_HEADER_METHOD;
|
||||
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
||||
fn start_relay_guards(&self) {
|
||||
relay_substrate_client::guard::abort_on_spec_version_change(
|
||||
self.finality_pipeline.target_client.clone(),
|
||||
bp_kusama::VERSION.spec_version,
|
||||
);
|
||||
relay_substrate_client::guard::abort_when_account_balance_decreased(
|
||||
self.finality_pipeline.target_client.clone(),
|
||||
self.transactions_author(),
|
||||
MAXIMAL_BALANCE_DECREASE_PER_DAY,
|
||||
);
|
||||
}
|
||||
|
||||
fn transactions_author(&self) -> bp_kusama::AccountId {
|
||||
(*self.finality_pipeline.target_sign.public().as_array_ref()).into()
|
||||
}
|
||||
|
||||
fn make_submit_finality_proof_transaction(
|
||||
&self,
|
||||
era: bp_runtime::TransactionEraOf<Kusama>,
|
||||
transaction_nonce: bp_runtime::IndexOf<Kusama>,
|
||||
header: PolkadotSyncHeader,
|
||||
proof: GrandpaJustification<bp_polkadot::Header>,
|
||||
) -> Bytes {
|
||||
let call = relay_kusama_client::runtime::Call::BridgePolkadotGrandpa(
|
||||
relay_kusama_client::runtime::BridgePolkadotGrandpaCall::submit_finality_proof(
|
||||
Box::new(header.into_inner()),
|
||||
proof,
|
||||
),
|
||||
);
|
||||
let genesis_hash = *self.finality_pipeline.target_client.genesis_hash();
|
||||
let transaction = Kusama::sign_transaction(
|
||||
genesis_hash,
|
||||
&self.finality_pipeline.target_sign,
|
||||
era,
|
||||
UnsignedTransaction::new(call, transaction_nonce),
|
||||
);
|
||||
|
||||
Bytes(transaction.encode())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::chains::kusama_headers_to_polkadot::tests::compute_maximal_balance_decrease_per_day;
|
||||
|
||||
#[test]
|
||||
fn maximal_balance_decrease_per_day_is_sane() {
|
||||
// we expect Polkadot -> Kusama relay to be running in mandatory-headers-only mode
|
||||
// => we expect single header for every Polkadot session
|
||||
let maximal_balance_decrease = compute_maximal_balance_decrease_per_day::<
|
||||
bp_kusama::Balance,
|
||||
bp_kusama::WeightToFee,
|
||||
>(bp_polkadot::DAYS / bp_polkadot::SESSION_LENGTH + 1);
|
||||
assert!(
|
||||
MAXIMAL_BALANCE_DECREASE_PER_DAY >= maximal_balance_decrease,
|
||||
"Maximal expected loss per day {} is larger than hardcoded {}",
|
||||
maximal_balance_decrease,
|
||||
MAXIMAL_BALANCE_DECREASE_PER_DAY,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,344 @@
|
||||
// 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/>.
|
||||
|
||||
//! Polkadot-to-Kusama messages sync entrypoint.
|
||||
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use codec::Encode;
|
||||
use sp_core::{Bytes, Pair};
|
||||
|
||||
use bp_messages::MessageNonce;
|
||||
use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
|
||||
use frame_support::weights::Weight;
|
||||
use messages_relay::{message_lane::MessageLane, relay_strategy::MixStrategy};
|
||||
use relay_kusama_client::{
|
||||
HeaderId as KusamaHeaderId, Kusama, SigningParams as KusamaSigningParams,
|
||||
};
|
||||
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,
|
||||
SubstrateMessageLane, SubstrateMessageLaneToSubstrate,
|
||||
},
|
||||
messages_source::SubstrateMessagesSource,
|
||||
messages_target::SubstrateMessagesTarget,
|
||||
STALL_TIMEOUT,
|
||||
};
|
||||
|
||||
/// Polkadot-to-Kusama message lane.
|
||||
pub type MessageLanePolkadotMessagesToKusama =
|
||||
SubstrateMessageLaneToSubstrate<Polkadot, PolkadotSigningParams, Kusama, KusamaSigningParams>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PolkadotMessagesToKusama {
|
||||
message_lane: MessageLanePolkadotMessagesToKusama,
|
||||
}
|
||||
|
||||
impl SubstrateMessageLane for PolkadotMessagesToKusama {
|
||||
type MessageLane = MessageLanePolkadotMessagesToKusama;
|
||||
const OUTBOUND_LANE_MESSAGE_DETAILS_METHOD: &'static str =
|
||||
bp_kusama::TO_KUSAMA_MESSAGE_DETAILS_METHOD;
|
||||
const OUTBOUND_LANE_LATEST_GENERATED_NONCE_METHOD: &'static str =
|
||||
bp_kusama::TO_KUSAMA_LATEST_GENERATED_NONCE_METHOD;
|
||||
const OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str =
|
||||
bp_kusama::TO_KUSAMA_LATEST_RECEIVED_NONCE_METHOD;
|
||||
|
||||
const INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str =
|
||||
bp_polkadot::FROM_POLKADOT_LATEST_RECEIVED_NONCE_METHOD;
|
||||
const INBOUND_LANE_LATEST_CONFIRMED_NONCE_METHOD: &'static str =
|
||||
bp_polkadot::FROM_POLKADOT_LATEST_CONFIRMED_NONCE_METHOD;
|
||||
const INBOUND_LANE_UNREWARDED_RELAYERS_STATE: &'static str =
|
||||
bp_polkadot::FROM_POLKADOT_UNREWARDED_RELAYERS_STATE;
|
||||
|
||||
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str =
|
||||
bp_polkadot::BEST_FINALIZED_POLKADOT_HEADER_METHOD;
|
||||
const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str =
|
||||
bp_kusama::BEST_FINALIZED_KUSAMA_HEADER_METHOD;
|
||||
|
||||
const MESSAGE_PALLET_NAME_AT_SOURCE: &'static str =
|
||||
bp_polkadot::WITH_KUSAMA_MESSAGES_PALLET_NAME;
|
||||
const MESSAGE_PALLET_NAME_AT_TARGET: &'static str =
|
||||
bp_kusama::WITH_POLKADOT_MESSAGES_PALLET_NAME;
|
||||
|
||||
const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN: Weight =
|
||||
bp_kusama::PAY_INBOUND_DISPATCH_FEE_WEIGHT;
|
||||
|
||||
type SourceChain = Polkadot;
|
||||
type TargetChain = Kusama;
|
||||
|
||||
fn source_transactions_author(&self) -> bp_polkadot::AccountId {
|
||||
(*self.message_lane.source_sign.public().as_array_ref()).into()
|
||||
}
|
||||
|
||||
fn make_messages_receiving_proof_transaction(
|
||||
&self,
|
||||
best_block_id: PolkadotHeaderId,
|
||||
transaction_nonce: bp_runtime::IndexOf<Polkadot>,
|
||||
_generated_at_block: KusamaHeaderId,
|
||||
proof: <Self::MessageLane as MessageLane>::MessagesReceivingProof,
|
||||
) -> Bytes {
|
||||
let (relayers_state, proof) = proof;
|
||||
let call = relay_polkadot_client::runtime::Call::BridgeKusamaMessages(
|
||||
relay_polkadot_client::runtime::BridgeKusamaMessagesCall::receive_messages_delivery_proof(
|
||||
proof,
|
||||
relayers_state,
|
||||
),
|
||||
);
|
||||
let genesis_hash = *self.message_lane.source_client.genesis_hash();
|
||||
let transaction = Polkadot::sign_transaction(
|
||||
genesis_hash,
|
||||
&self.message_lane.source_sign,
|
||||
relay_substrate_client::TransactionEra::new(
|
||||
best_block_id,
|
||||
self.message_lane.source_transactions_mortality,
|
||||
),
|
||||
UnsignedTransaction::new(call, transaction_nonce),
|
||||
);
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Prepared Kusama -> Polkadot confirmation transaction. Weight: <unknown>/{}, size: {}/{}",
|
||||
bp_polkadot::max_extrinsic_weight(),
|
||||
transaction.encode().len(),
|
||||
bp_polkadot::max_extrinsic_size(),
|
||||
);
|
||||
Bytes(transaction.encode())
|
||||
}
|
||||
|
||||
fn target_transactions_author(&self) -> bp_kusama::AccountId {
|
||||
(*self.message_lane.target_sign.public().as_array_ref()).into()
|
||||
}
|
||||
|
||||
fn make_messages_delivery_transaction(
|
||||
&self,
|
||||
best_block_id: KusamaHeaderId,
|
||||
transaction_nonce: bp_runtime::IndexOf<Kusama>,
|
||||
_generated_at_header: PolkadotHeaderId,
|
||||
_nonces: RangeInclusive<MessageNonce>,
|
||||
proof: <Self::MessageLane as MessageLane>::MessagesProof,
|
||||
) -> Bytes {
|
||||
let (dispatch_weight, proof) = proof;
|
||||
let FromBridgedChainMessagesProof { ref nonces_start, ref nonces_end, .. } = proof;
|
||||
let messages_count = nonces_end - nonces_start + 1;
|
||||
|
||||
let call = relay_kusama_client::runtime::Call::BridgePolkadotMessages(
|
||||
relay_kusama_client::runtime::BridgePolkadotMessagesCall::receive_messages_proof(
|
||||
self.message_lane.relayer_id_at_source.clone(),
|
||||
proof,
|
||||
messages_count as _,
|
||||
dispatch_weight,
|
||||
),
|
||||
);
|
||||
let genesis_hash = *self.message_lane.target_client.genesis_hash();
|
||||
let transaction = Kusama::sign_transaction(
|
||||
genesis_hash,
|
||||
&self.message_lane.target_sign,
|
||||
relay_substrate_client::TransactionEra::new(
|
||||
best_block_id,
|
||||
self.message_lane.target_transactions_mortality,
|
||||
),
|
||||
UnsignedTransaction::new(call, transaction_nonce),
|
||||
);
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Prepared Polkadot -> Kusama delivery transaction. Weight: <unknown>/{}, size: {}/{}",
|
||||
bp_kusama::max_extrinsic_weight(),
|
||||
transaction.encode().len(),
|
||||
bp_kusama::max_extrinsic_size(),
|
||||
);
|
||||
Bytes(transaction.encode())
|
||||
}
|
||||
}
|
||||
|
||||
/// Polkadot node as messages source.
|
||||
type PolkadotSourceClient = SubstrateMessagesSource<PolkadotMessagesToKusama>;
|
||||
|
||||
/// Kusama node as messages target.
|
||||
type KusamaTargetClient = SubstrateMessagesTarget<PolkadotMessagesToKusama>;
|
||||
|
||||
/// Run Polkadot-to-Kusama messages sync.
|
||||
pub async fn run(
|
||||
params: MessagesRelayParams<
|
||||
Polkadot,
|
||||
PolkadotSigningParams,
|
||||
Kusama,
|
||||
KusamaSigningParams,
|
||||
MixStrategy,
|
||||
>,
|
||||
) -> anyhow::Result<()> {
|
||||
let stall_timeout = relay_substrate_client::bidirectional_transaction_stall_timeout(
|
||||
params.source_transactions_mortality,
|
||||
params.target_transactions_mortality,
|
||||
Polkadot::AVERAGE_BLOCK_INTERVAL,
|
||||
Kusama::AVERAGE_BLOCK_INTERVAL,
|
||||
STALL_TIMEOUT,
|
||||
);
|
||||
let relayer_id_at_polkadot = (*params.source_sign.public().as_array_ref()).into();
|
||||
|
||||
let lane_id = params.lane_id;
|
||||
let source_client = params.source_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_sign: params.target_sign,
|
||||
target_transactions_mortality: params.target_transactions_mortality,
|
||||
relayer_id_at_source: relayer_id_at_polkadot,
|
||||
},
|
||||
};
|
||||
|
||||
// 2/3 is reserved for proofs and tx overhead
|
||||
let max_messages_size_in_single_batch = bp_kusama::max_extrinsic_size() / 3;
|
||||
// we don't know exact weights of the Kusama runtime. So to guess weights we'll be using
|
||||
// weights from Rialto and then simply dividing it by x2.
|
||||
let (max_messages_in_single_batch, max_messages_weight_in_single_batch) =
|
||||
select_delivery_transaction_limits::<
|
||||
pallet_bridge_messages::weights::RialtoWeight<rialto_runtime::Runtime>,
|
||||
>(
|
||||
bp_kusama::max_extrinsic_weight(),
|
||||
bp_kusama::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
|
||||
);
|
||||
let (max_messages_in_single_batch, max_messages_weight_in_single_batch) =
|
||||
(max_messages_in_single_batch / 2, max_messages_weight_in_single_batch / 2);
|
||||
|
||||
log::info!(
|
||||
target: "bridge",
|
||||
"Starting Polkadot -> Kusama messages relay.\n\t\
|
||||
Polkadot relayer account id: {:?}\n\t\
|
||||
Max messages in single transaction: {}\n\t\
|
||||
Max messages size in single transaction: {}\n\t\
|
||||
Max messages weight in single transaction: {}\n\t\
|
||||
Tx mortality: {:?}/{:?}\n\t\
|
||||
Stall timeout: {:?}",
|
||||
lane.message_lane.relayer_id_at_source,
|
||||
max_messages_in_single_batch,
|
||||
max_messages_size_in_single_batch,
|
||||
max_messages_weight_in_single_batch,
|
||||
params.source_transactions_mortality,
|
||||
params.target_transactions_mortality,
|
||||
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(),
|
||||
)?;
|
||||
messages_relay::message_lane_loop::run(
|
||||
messages_relay::message_lane_loop::Params {
|
||||
lane: lane_id,
|
||||
source_tick: Polkadot::AVERAGE_BLOCK_INTERVAL,
|
||||
target_tick: Kusama::AVERAGE_BLOCK_INTERVAL,
|
||||
reconnect_delay: relay_utils::relay_loop::RECONNECT_DELAY,
|
||||
stall_timeout,
|
||||
delivery_params: messages_relay::message_lane_loop::MessageDeliveryParams {
|
||||
max_unrewarded_relayer_entries_at_target:
|
||||
bp_kusama::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
|
||||
max_unconfirmed_nonces_at_target:
|
||||
bp_kusama::MAX_UNCONFIRMED_MESSAGES_AT_INBOUND_LANE,
|
||||
max_messages_in_single_batch,
|
||||
max_messages_weight_in_single_batch,
|
||||
max_messages_size_in_single_batch,
|
||||
relay_strategy: params.relay_strategy,
|
||||
},
|
||||
},
|
||||
PolkadotSourceClient::new(
|
||||
source_client.clone(),
|
||||
lane.clone(),
|
||||
lane_id,
|
||||
params.target_to_source_headers_relay,
|
||||
),
|
||||
KusamaTargetClient::new(
|
||||
params.target_client,
|
||||
lane,
|
||||
lane_id,
|
||||
metrics_values,
|
||||
params.source_to_target_headers_relay,
|
||||
),
|
||||
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,
|
||||
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,
|
||||
source_client,
|
||||
Some(crate::chains::kusama::TOKEN_ID),
|
||||
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),
|
||||
)),
|
||||
)
|
||||
}
|
||||
|
||||
/// Update Kusama -> Polkadot conversion rate, stored in Polkadot runtime storage.
|
||||
pub(crate) async fn update_kusama_to_polkadot_conversion_rate(
|
||||
client: Client<Polkadot>,
|
||||
signer: <Polkadot as TransactionSignScheme>::AccountKeyPair,
|
||||
updated_rate: f64,
|
||||
) -> anyhow::Result<()> {
|
||||
let genesis_hash = *client.genesis_hash();
|
||||
let signer_id = (*signer.public().as_array_ref()).into();
|
||||
client
|
||||
.submit_signed_extrinsic(signer_id, move |_, transaction_nonce| {
|
||||
Bytes(
|
||||
Polkadot::sign_transaction(
|
||||
genesis_hash,
|
||||
&signer,
|
||||
relay_substrate_client::TransactionEra::immortal(),
|
||||
UnsignedTransaction::new(
|
||||
relay_polkadot_client::runtime::Call::BridgeKusamaMessages(
|
||||
relay_polkadot_client::runtime::BridgeKusamaMessagesCall::update_pallet_parameter(
|
||||
relay_polkadot_client::runtime::BridgeKusamaMessagesParameter::KusamaToPolkadotConversionRate(
|
||||
sp_runtime::FixedU128::from_float(updated_rate),
|
||||
)
|
||||
)
|
||||
),
|
||||
transaction_nonce,
|
||||
),
|
||||
)
|
||||
.encode(),
|
||||
)
|
||||
})
|
||||
.await
|
||||
.map(drop)
|
||||
.map_err(|err| anyhow::format_err!("{:?}", err))
|
||||
}
|
||||
@@ -19,8 +19,11 @@
|
||||
use crate::cli::{
|
||||
bridge,
|
||||
encode_call::{self, Call, CliEncodeCall},
|
||||
encode_message, send_message, CliChain,
|
||||
encode_message,
|
||||
send_message::{self, DispatchFeePayment},
|
||||
CliChain,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use bp_message_dispatch::{CallOrigin, MessagePayload};
|
||||
use codec::Decode;
|
||||
use frame_support::weights::{DispatchInfo, GetDispatchInfo, Weight};
|
||||
@@ -35,29 +38,32 @@ impl CliEncodeCall for Rialto {
|
||||
fn encode_call(call: &Call) -> anyhow::Result<Self::Call> {
|
||||
Ok(match call {
|
||||
Call::Raw { data } => Decode::decode(&mut &*data.0)?,
|
||||
Call::Remark { remark_payload, .. } => rialto_runtime::Call::System(rialto_runtime::SystemCall::remark(
|
||||
remark_payload.as_ref().map(|x| x.0.clone()).unwrap_or_default(),
|
||||
)),
|
||||
Call::Transfer { recipient, amount } => {
|
||||
rialto_runtime::Call::Balances(rialto_runtime::BalancesCall::transfer(recipient.raw_id(), amount.0))
|
||||
}
|
||||
Call::BridgeSendMessage {
|
||||
lane,
|
||||
payload,
|
||||
fee,
|
||||
bridge_instance_index,
|
||||
} => match *bridge_instance_index {
|
||||
bridge::RIALTO_TO_MILLAU_INDEX => {
|
||||
let payload = Decode::decode(&mut &*payload.0)?;
|
||||
rialto_runtime::Call::BridgeMillauMessages(rialto_runtime::MessagesCall::send_message(
|
||||
lane.0, payload, fee.0,
|
||||
))
|
||||
}
|
||||
_ => anyhow::bail!(
|
||||
"Unsupported target bridge pallet with instance index: {}",
|
||||
bridge_instance_index
|
||||
),
|
||||
},
|
||||
Call::Remark { remark_payload, .. } =>
|
||||
rialto_runtime::Call::System(rialto_runtime::SystemCall::remark {
|
||||
remark: remark_payload.as_ref().map(|x| x.0.clone()).unwrap_or_default(),
|
||||
}),
|
||||
Call::Transfer { recipient, amount } =>
|
||||
rialto_runtime::Call::Balances(rialto_runtime::BalancesCall::transfer {
|
||||
dest: recipient.raw_id().into(),
|
||||
value: amount.0,
|
||||
}),
|
||||
Call::BridgeSendMessage { lane, payload, fee, bridge_instance_index } =>
|
||||
match *bridge_instance_index {
|
||||
bridge::RIALTO_TO_MILLAU_INDEX => {
|
||||
let payload = Decode::decode(&mut &*payload.0)?;
|
||||
rialto_runtime::Call::BridgeMillauMessages(
|
||||
rialto_runtime::MessagesCall::send_message {
|
||||
lane_id: lane.0,
|
||||
payload,
|
||||
delivery_and_dispatch_fee: fee.0,
|
||||
},
|
||||
)
|
||||
},
|
||||
_ => anyhow::bail!(
|
||||
"Unsupported target bridge pallet with instance index: {}",
|
||||
bridge_instance_index
|
||||
),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -70,7 +76,12 @@ impl CliChain for Rialto {
|
||||
const RUNTIME_VERSION: RuntimeVersion = rialto_runtime::VERSION;
|
||||
|
||||
type KeyPair = sp_core::sr25519::Pair;
|
||||
type MessagePayload = MessagePayload<bp_rialto::AccountId, bp_millau::AccountSigner, bp_millau::Signature, Vec<u8>>;
|
||||
type MessagePayload = MessagePayload<
|
||||
bp_rialto::AccountId,
|
||||
bp_millau::AccountSigner,
|
||||
bp_millau::Signature,
|
||||
Vec<u8>,
|
||||
>;
|
||||
|
||||
fn ss58_format() -> u16 {
|
||||
rialto_runtime::SS58Prefix::get() as u16
|
||||
@@ -80,10 +91,12 @@ impl CliChain for Rialto {
|
||||
bp_rialto::max_extrinsic_weight()
|
||||
}
|
||||
|
||||
fn encode_message(message: encode_message::MessagePayload) -> Result<Self::MessagePayload, String> {
|
||||
fn encode_message(
|
||||
message: encode_message::MessagePayload,
|
||||
) -> anyhow::Result<Self::MessagePayload> {
|
||||
match message {
|
||||
encode_message::MessagePayload::Raw { data } => MessagePayload::decode(&mut &*data.0)
|
||||
.map_err(|e| format!("Failed to decode Rialto's MessagePayload: {:?}", e)),
|
||||
.map_err(|e| anyhow!("Failed to decode Rialto's MessagePayload: {:?}", e)),
|
||||
encode_message::MessagePayload::Call { mut call, mut sender } => {
|
||||
type Source = Rialto;
|
||||
type Target = relay_millau_client::Millau;
|
||||
@@ -91,12 +104,21 @@ impl CliChain for Rialto {
|
||||
sender.enforce_chain::<Source>();
|
||||
let spec_version = Target::RUNTIME_VERSION.spec_version;
|
||||
let origin = CallOrigin::SourceAccount(sender.raw_id());
|
||||
encode_call::preprocess_call::<Source, Target>(&mut call, bridge::RIALTO_TO_MILLAU_INDEX);
|
||||
let call = Target::encode_call(&call).map_err(|e| e.to_string())?;
|
||||
encode_call::preprocess_call::<Source, Target>(
|
||||
&mut call,
|
||||
bridge::RIALTO_TO_MILLAU_INDEX,
|
||||
);
|
||||
let call = Target::encode_call(&call)?;
|
||||
let weight = call.get_dispatch_info().weight;
|
||||
|
||||
Ok(send_message::message_payload(spec_version, weight, origin, &call))
|
||||
}
|
||||
Ok(send_message::message_payload(
|
||||
spec_version,
|
||||
weight,
|
||||
origin,
|
||||
&call,
|
||||
DispatchFeePayment::AtSourceChain,
|
||||
))
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,41 +16,72 @@
|
||||
|
||||
//! Rialto-to-Millau headers sync entrypoint.
|
||||
|
||||
use crate::finality_pipeline::{SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate};
|
||||
|
||||
use bp_header_chain::justification::GrandpaJustification;
|
||||
use codec::Encode;
|
||||
use relay_millau_client::{Millau, SigningParams as MillauSigningParams};
|
||||
use relay_rialto_client::{Rialto, SyncHeader as RialtoSyncHeader};
|
||||
use relay_substrate_client::{Chain, TransactionSignScheme};
|
||||
use sp_core::{Bytes, Pair};
|
||||
|
||||
use bp_header_chain::justification::GrandpaJustification;
|
||||
use relay_millau_client::{Millau, SigningParams as MillauSigningParams};
|
||||
use relay_rialto_client::{Rialto, SyncHeader as RialtoSyncHeader};
|
||||
use relay_substrate_client::{Client, IndexOf, TransactionSignScheme, UnsignedTransaction};
|
||||
use substrate_relay_helper::finality_pipeline::{
|
||||
SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate,
|
||||
};
|
||||
|
||||
/// Rialto-to-Millau finality sync pipeline.
|
||||
pub(crate) type RialtoFinalityToMillau = SubstrateFinalityToSubstrate<Rialto, Millau, MillauSigningParams>;
|
||||
pub(crate) type FinalityPipelineRialtoFinalityToMillau =
|
||||
SubstrateFinalityToSubstrate<Rialto, Millau, MillauSigningParams>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RialtoFinalityToMillau {
|
||||
finality_pipeline: FinalityPipelineRialtoFinalityToMillau,
|
||||
}
|
||||
|
||||
impl RialtoFinalityToMillau {
|
||||
pub fn new(target_client: Client<Millau>, target_sign: MillauSigningParams) -> Self {
|
||||
Self {
|
||||
finality_pipeline: FinalityPipelineRialtoFinalityToMillau::new(
|
||||
target_client,
|
||||
target_sign,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SubstrateFinalitySyncPipeline for RialtoFinalityToMillau {
|
||||
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_rialto::BEST_FINALIZED_RIALTO_HEADER_METHOD;
|
||||
type FinalitySyncPipeline = FinalityPipelineRialtoFinalityToMillau;
|
||||
|
||||
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str =
|
||||
bp_rialto::BEST_FINALIZED_RIALTO_HEADER_METHOD;
|
||||
|
||||
type TargetChain = Millau;
|
||||
|
||||
fn transactions_author(&self) -> bp_millau::AccountId {
|
||||
(*self.target_sign.public().as_array_ref()).into()
|
||||
(*self.finality_pipeline.target_sign.public().as_array_ref()).into()
|
||||
}
|
||||
|
||||
fn make_submit_finality_proof_transaction(
|
||||
&self,
|
||||
transaction_nonce: <Millau as Chain>::Index,
|
||||
era: bp_runtime::TransactionEraOf<Millau>,
|
||||
transaction_nonce: IndexOf<Millau>,
|
||||
header: RialtoSyncHeader,
|
||||
proof: GrandpaJustification<bp_rialto::Header>,
|
||||
) -> Bytes {
|
||||
let call = millau_runtime::BridgeGrandpaRialtoCall::<
|
||||
let call = millau_runtime::BridgeGrandpaCall::<
|
||||
millau_runtime::Runtime,
|
||||
millau_runtime::RialtoGrandpaInstance,
|
||||
>::submit_finality_proof(header.into_inner(), proof)
|
||||
>::submit_finality_proof {
|
||||
finality_target: Box::new(header.into_inner()),
|
||||
justification: proof,
|
||||
}
|
||||
.into();
|
||||
|
||||
let genesis_hash = *self.target_client.genesis_hash();
|
||||
let transaction = Millau::sign_transaction(genesis_hash, &self.target_sign, transaction_nonce, call);
|
||||
let genesis_hash = *self.finality_pipeline.target_client.genesis_hash();
|
||||
let transaction = Millau::sign_transaction(
|
||||
genesis_hash,
|
||||
&self.finality_pipeline.target_sign,
|
||||
era,
|
||||
UnsignedTransaction::new(call, transaction_nonce),
|
||||
);
|
||||
|
||||
Bytes(transaction.encode())
|
||||
}
|
||||
|
||||
@@ -16,64 +16,100 @@
|
||||
|
||||
//! Rialto-to-Millau messages sync entrypoint.
|
||||
|
||||
use crate::messages_lane::{
|
||||
select_delivery_transaction_limits, MessagesRelayParams, SubstrateMessageLane, SubstrateMessageLaneToSubstrate,
|
||||
};
|
||||
use crate::messages_source::SubstrateMessagesSource;
|
||||
use crate::messages_target::SubstrateMessagesTarget;
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use bp_messages::MessageNonce;
|
||||
use bp_runtime::{MILLAU_CHAIN_ID, RIALTO_CHAIN_ID};
|
||||
use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
|
||||
use codec::Encode;
|
||||
use frame_support::dispatch::GetDispatchInfo;
|
||||
use messages_relay::message_lane::MessageLane;
|
||||
use relay_millau_client::{HeaderId as MillauHeaderId, Millau, SigningParams as MillauSigningParams};
|
||||
use relay_rialto_client::{HeaderId as RialtoHeaderId, Rialto, SigningParams as RialtoSigningParams};
|
||||
use relay_substrate_client::{
|
||||
metrics::{FloatStorageValueMetric, StorageProofOverheadMetric},
|
||||
Chain, TransactionSignScheme,
|
||||
};
|
||||
use sp_core::{Bytes, Pair};
|
||||
use std::{ops::RangeInclusive, time::Duration};
|
||||
|
||||
use bp_messages::MessageNonce;
|
||||
use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
|
||||
use frame_support::weights::Weight;
|
||||
use messages_relay::{message_lane::MessageLane, relay_strategy::MixStrategy};
|
||||
use relay_millau_client::{
|
||||
HeaderId as MillauHeaderId, Millau, SigningParams as MillauSigningParams,
|
||||
};
|
||||
use relay_rialto_client::{
|
||||
HeaderId as RialtoHeaderId, Rialto, SigningParams as RialtoSigningParams,
|
||||
};
|
||||
use relay_substrate_client::{Chain, Client, IndexOf, TransactionSignScheme, UnsignedTransaction};
|
||||
use relay_utils::metrics::MetricsParams;
|
||||
use substrate_relay_helper::{
|
||||
messages_lane::{
|
||||
select_delivery_transaction_limits, MessagesRelayParams, StandaloneMessagesMetrics,
|
||||
SubstrateMessageLane, SubstrateMessageLaneToSubstrate,
|
||||
},
|
||||
messages_source::SubstrateMessagesSource,
|
||||
messages_target::SubstrateMessagesTarget,
|
||||
STALL_TIMEOUT,
|
||||
};
|
||||
|
||||
/// Rialto-to-Millau message lane.
|
||||
pub type RialtoMessagesToMillau =
|
||||
pub type MessageLaneRialtoMessagesToMillau =
|
||||
SubstrateMessageLaneToSubstrate<Rialto, RialtoSigningParams, Millau, MillauSigningParams>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RialtoMessagesToMillau {
|
||||
message_lane: MessageLaneRialtoMessagesToMillau,
|
||||
}
|
||||
|
||||
impl SubstrateMessageLane for RialtoMessagesToMillau {
|
||||
const OUTBOUND_LANE_MESSAGE_DETAILS_METHOD: &'static str = bp_millau::TO_MILLAU_MESSAGE_DETAILS_METHOD;
|
||||
type MessageLane = MessageLaneRialtoMessagesToMillau;
|
||||
|
||||
const OUTBOUND_LANE_MESSAGE_DETAILS_METHOD: &'static str =
|
||||
bp_millau::TO_MILLAU_MESSAGE_DETAILS_METHOD;
|
||||
const OUTBOUND_LANE_LATEST_GENERATED_NONCE_METHOD: &'static str =
|
||||
bp_millau::TO_MILLAU_LATEST_GENERATED_NONCE_METHOD;
|
||||
const OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str = bp_millau::TO_MILLAU_LATEST_RECEIVED_NONCE_METHOD;
|
||||
const OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str =
|
||||
bp_millau::TO_MILLAU_LATEST_RECEIVED_NONCE_METHOD;
|
||||
|
||||
const INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str = bp_rialto::FROM_RIALTO_LATEST_RECEIVED_NONCE_METHOD;
|
||||
const INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str =
|
||||
bp_rialto::FROM_RIALTO_LATEST_RECEIVED_NONCE_METHOD;
|
||||
const INBOUND_LANE_LATEST_CONFIRMED_NONCE_METHOD: &'static str =
|
||||
bp_rialto::FROM_RIALTO_LATEST_CONFIRMED_NONCE_METHOD;
|
||||
const INBOUND_LANE_UNREWARDED_RELAYERS_STATE: &'static str = bp_rialto::FROM_RIALTO_UNREWARDED_RELAYERS_STATE;
|
||||
const INBOUND_LANE_UNREWARDED_RELAYERS_STATE: &'static str =
|
||||
bp_rialto::FROM_RIALTO_UNREWARDED_RELAYERS_STATE;
|
||||
|
||||
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_rialto::BEST_FINALIZED_RIALTO_HEADER_METHOD;
|
||||
const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str = bp_millau::BEST_FINALIZED_MILLAU_HEADER_METHOD;
|
||||
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str =
|
||||
bp_rialto::BEST_FINALIZED_RIALTO_HEADER_METHOD;
|
||||
const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str =
|
||||
bp_millau::BEST_FINALIZED_MILLAU_HEADER_METHOD;
|
||||
|
||||
const MESSAGE_PALLET_NAME_AT_SOURCE: &'static str = bp_rialto::WITH_MILLAU_MESSAGES_PALLET_NAME;
|
||||
const MESSAGE_PALLET_NAME_AT_TARGET: &'static str = bp_millau::WITH_RIALTO_MESSAGES_PALLET_NAME;
|
||||
|
||||
const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN: Weight =
|
||||
bp_millau::PAY_INBOUND_DISPATCH_FEE_WEIGHT;
|
||||
|
||||
type SourceChain = Rialto;
|
||||
type TargetChain = Millau;
|
||||
|
||||
fn source_transactions_author(&self) -> bp_rialto::AccountId {
|
||||
(*self.source_sign.public().as_array_ref()).into()
|
||||
(*self.message_lane.source_sign.public().as_array_ref()).into()
|
||||
}
|
||||
|
||||
fn make_messages_receiving_proof_transaction(
|
||||
&self,
|
||||
transaction_nonce: <Rialto as Chain>::Index,
|
||||
best_block_id: RialtoHeaderId,
|
||||
transaction_nonce: IndexOf<Rialto>,
|
||||
_generated_at_block: MillauHeaderId,
|
||||
proof: <Self as MessageLane>::MessagesReceivingProof,
|
||||
proof: <Self::MessageLane as MessageLane>::MessagesReceivingProof,
|
||||
) -> Bytes {
|
||||
let (relayers_state, proof) = proof;
|
||||
let call: rialto_runtime::Call =
|
||||
rialto_runtime::MessagesCall::receive_messages_delivery_proof(proof, relayers_state).into();
|
||||
rialto_runtime::MessagesCall::receive_messages_delivery_proof { proof, relayers_state }
|
||||
.into();
|
||||
let call_weight = call.get_dispatch_info().weight;
|
||||
let genesis_hash = *self.source_client.genesis_hash();
|
||||
let transaction = Rialto::sign_transaction(genesis_hash, &self.source_sign, transaction_nonce, call);
|
||||
let genesis_hash = *self.message_lane.source_client.genesis_hash();
|
||||
let transaction = Rialto::sign_transaction(
|
||||
genesis_hash,
|
||||
&self.message_lane.source_sign,
|
||||
relay_substrate_client::TransactionEra::new(
|
||||
best_block_id,
|
||||
self.message_lane.source_transactions_mortality,
|
||||
),
|
||||
UnsignedTransaction::new(call, transaction_nonce),
|
||||
);
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Prepared Millau -> Rialto confirmation transaction. Weight: {}/{}, size: {}/{}",
|
||||
@@ -86,33 +122,38 @@ impl SubstrateMessageLane for RialtoMessagesToMillau {
|
||||
}
|
||||
|
||||
fn target_transactions_author(&self) -> bp_millau::AccountId {
|
||||
(*self.target_sign.public().as_array_ref()).into()
|
||||
(*self.message_lane.target_sign.public().as_array_ref()).into()
|
||||
}
|
||||
|
||||
fn make_messages_delivery_transaction(
|
||||
&self,
|
||||
transaction_nonce: <Millau as Chain>::Index,
|
||||
best_block_id: MillauHeaderId,
|
||||
transaction_nonce: IndexOf<Millau>,
|
||||
_generated_at_header: RialtoHeaderId,
|
||||
_nonces: RangeInclusive<MessageNonce>,
|
||||
proof: <Self as MessageLane>::MessagesProof,
|
||||
proof: <Self::MessageLane as MessageLane>::MessagesProof,
|
||||
) -> Bytes {
|
||||
let (dispatch_weight, proof) = proof;
|
||||
let FromBridgedChainMessagesProof {
|
||||
ref nonces_start,
|
||||
ref nonces_end,
|
||||
..
|
||||
} = proof;
|
||||
let FromBridgedChainMessagesProof { ref nonces_start, ref nonces_end, .. } = proof;
|
||||
let messages_count = nonces_end - nonces_start + 1;
|
||||
let call: millau_runtime::Call = millau_runtime::MessagesCall::receive_messages_proof(
|
||||
self.relayer_id_at_source.clone(),
|
||||
let call: millau_runtime::Call = millau_runtime::MessagesCall::receive_messages_proof {
|
||||
relayer_id_at_bridged_chain: self.message_lane.relayer_id_at_source.clone(),
|
||||
proof,
|
||||
messages_count as _,
|
||||
messages_count: messages_count as _,
|
||||
dispatch_weight,
|
||||
)
|
||||
}
|
||||
.into();
|
||||
let call_weight = call.get_dispatch_info().weight;
|
||||
let genesis_hash = *self.target_client.genesis_hash();
|
||||
let transaction = Millau::sign_transaction(genesis_hash, &self.target_sign, transaction_nonce, call);
|
||||
let genesis_hash = *self.message_lane.target_client.genesis_hash();
|
||||
let transaction = Millau::sign_transaction(
|
||||
genesis_hash,
|
||||
&self.message_lane.target_sign,
|
||||
relay_substrate_client::TransactionEra::new(
|
||||
best_block_id,
|
||||
self.message_lane.target_transactions_mortality,
|
||||
),
|
||||
UnsignedTransaction::new(call, transaction_nonce),
|
||||
);
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Prepared Rialto -> Millau delivery transaction. Weight: {}/{}, size: {}/{}",
|
||||
@@ -126,34 +167,50 @@ impl SubstrateMessageLane for RialtoMessagesToMillau {
|
||||
}
|
||||
|
||||
/// Rialto node as messages source.
|
||||
type RialtoSourceClient =
|
||||
SubstrateMessagesSource<Rialto, RialtoMessagesToMillau, rialto_runtime::WithMillauMessagesInstance>;
|
||||
type RialtoSourceClient = SubstrateMessagesSource<RialtoMessagesToMillau>;
|
||||
|
||||
/// Millau node as messages target.
|
||||
type MillauTargetClient =
|
||||
SubstrateMessagesTarget<Millau, RialtoMessagesToMillau, millau_runtime::WithRialtoMessagesInstance>;
|
||||
type MillauTargetClient = SubstrateMessagesTarget<RialtoMessagesToMillau>;
|
||||
|
||||
/// Run Rialto-to-Millau messages sync.
|
||||
pub async fn run(
|
||||
params: MessagesRelayParams<Rialto, RialtoSigningParams, Millau, MillauSigningParams>,
|
||||
) -> Result<(), String> {
|
||||
let stall_timeout = Duration::from_secs(5 * 60);
|
||||
params: MessagesRelayParams<
|
||||
Rialto,
|
||||
RialtoSigningParams,
|
||||
Millau,
|
||||
MillauSigningParams,
|
||||
MixStrategy,
|
||||
>,
|
||||
) -> anyhow::Result<()> {
|
||||
let stall_timeout = relay_substrate_client::bidirectional_transaction_stall_timeout(
|
||||
params.source_transactions_mortality,
|
||||
params.target_transactions_mortality,
|
||||
Rialto::AVERAGE_BLOCK_INTERVAL,
|
||||
Millau::AVERAGE_BLOCK_INTERVAL,
|
||||
STALL_TIMEOUT,
|
||||
);
|
||||
let relayer_id_at_rialto = (*params.source_sign.public().as_array_ref()).into();
|
||||
|
||||
let lane_id = params.lane_id;
|
||||
let source_client = params.source_client;
|
||||
let lane = RialtoMessagesToMillau {
|
||||
source_client: source_client.clone(),
|
||||
source_sign: params.source_sign,
|
||||
target_client: params.target_client.clone(),
|
||||
target_sign: params.target_sign,
|
||||
relayer_id_at_source: relayer_id_at_rialto,
|
||||
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_sign: params.target_sign,
|
||||
target_transactions_mortality: params.target_transactions_mortality,
|
||||
relayer_id_at_source: relayer_id_at_rialto,
|
||||
},
|
||||
};
|
||||
|
||||
// 2/3 is reserved for proofs and tx overhead
|
||||
let max_messages_size_in_single_batch = bp_millau::max_extrinsic_size() / 3;
|
||||
let (max_messages_in_single_batch, max_messages_weight_in_single_batch) =
|
||||
select_delivery_transaction_limits::<pallet_bridge_messages::weights::RialtoWeight<rialto_runtime::Runtime>>(
|
||||
select_delivery_transaction_limits::<
|
||||
pallet_bridge_messages::weights::RialtoWeight<rialto_runtime::Runtime>,
|
||||
>(
|
||||
bp_millau::max_extrinsic_weight(),
|
||||
bp_millau::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
|
||||
);
|
||||
@@ -164,13 +221,25 @@ pub async fn run(
|
||||
Rialto relayer account id: {:?}\n\t\
|
||||
Max messages in single transaction: {}\n\t\
|
||||
Max messages size in single transaction: {}\n\t\
|
||||
Max messages weight in single transaction: {}",
|
||||
lane.relayer_id_at_source,
|
||||
Max messages weight in single transaction: {}\n\t\
|
||||
Tx mortality: {:?}/{:?}\n\t\
|
||||
Stall timeout: {:?}",
|
||||
lane.message_lane.relayer_id_at_source,
|
||||
max_messages_in_single_batch,
|
||||
max_messages_size_in_single_batch,
|
||||
max_messages_weight_in_single_batch,
|
||||
params.source_transactions_mortality,
|
||||
params.target_transactions_mortality,
|
||||
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(),
|
||||
)?;
|
||||
messages_relay::message_lane_loop::run(
|
||||
messages_relay::message_lane_loop::Params {
|
||||
lane: lane_id,
|
||||
@@ -179,58 +248,86 @@ pub async fn run(
|
||||
reconnect_delay: relay_utils::relay_loop::RECONNECT_DELAY,
|
||||
stall_timeout,
|
||||
delivery_params: messages_relay::message_lane_loop::MessageDeliveryParams {
|
||||
max_unrewarded_relayer_entries_at_target: bp_millau::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
|
||||
max_unconfirmed_nonces_at_target: bp_millau::MAX_UNCONFIRMED_MESSAGES_AT_INBOUND_LANE,
|
||||
max_unrewarded_relayer_entries_at_target:
|
||||
bp_millau::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
|
||||
max_unconfirmed_nonces_at_target:
|
||||
bp_millau::MAX_UNCONFIRMED_MESSAGES_AT_INBOUND_LANE,
|
||||
max_messages_in_single_batch,
|
||||
max_messages_weight_in_single_batch,
|
||||
max_messages_size_in_single_batch,
|
||||
relayer_mode: messages_relay::message_lane_loop::RelayerMode::Altruistic,
|
||||
relay_strategy: params.relay_strategy,
|
||||
},
|
||||
},
|
||||
RialtoSourceClient::new(
|
||||
source_client.clone(),
|
||||
lane.clone(),
|
||||
lane_id,
|
||||
MILLAU_CHAIN_ID,
|
||||
params.target_to_source_headers_relay,
|
||||
),
|
||||
MillauTargetClient::new(
|
||||
params.target_client,
|
||||
lane,
|
||||
lane_id,
|
||||
RIALTO_CHAIN_ID,
|
||||
metrics_values,
|
||||
params.source_to_target_headers_relay,
|
||||
),
|
||||
relay_utils::relay_metrics(
|
||||
Some(messages_relay::message_lane_loop::metrics_prefix::<
|
||||
RialtoMessagesToMillau,
|
||||
>(&lane_id)),
|
||||
params.metrics_params,
|
||||
)
|
||||
.standalone_metric(|registry, prefix| {
|
||||
StorageProofOverheadMetric::new(
|
||||
registry,
|
||||
prefix,
|
||||
source_client.clone(),
|
||||
"rialto_storage_proof_overhead".into(),
|
||||
"Rialto storage proof overhead".into(),
|
||||
)
|
||||
})?
|
||||
.standalone_metric(|registry, prefix| {
|
||||
FloatStorageValueMetric::<_, sp_runtime::FixedU128>::new(
|
||||
registry,
|
||||
prefix,
|
||||
source_client,
|
||||
sp_core::storage::StorageKey(
|
||||
rialto_runtime::millau_messages::MillauToRialtoConversionRate::key().to_vec(),
|
||||
),
|
||||
Some(rialto_runtime::millau_messages::INITIAL_MILLAU_TO_RIALTO_CONVERSION_RATE),
|
||||
"rialto_millau_to_rialto_conversion_rate".into(),
|
||||
"Millau to Rialto tokens conversion rate (used by Millau)".into(),
|
||||
)
|
||||
})?
|
||||
.into_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,
|
||||
source_client: Client<Rialto>,
|
||||
) -> anyhow::Result<(MetricsParams, StandaloneMessagesMetrics)> {
|
||||
substrate_relay_helper::messages_lane::add_standalone_metrics::<RialtoMessagesToMillau>(
|
||||
metrics_prefix,
|
||||
metrics_params,
|
||||
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,
|
||||
)),
|
||||
)
|
||||
}
|
||||
|
||||
/// Update Millau -> Rialto conversion rate, stored in Rialto runtime storage.
|
||||
pub(crate) async fn update_millau_to_rialto_conversion_rate(
|
||||
client: Client<Rialto>,
|
||||
signer: <Rialto as TransactionSignScheme>::AccountKeyPair,
|
||||
updated_rate: f64,
|
||||
) -> anyhow::Result<()> {
|
||||
let genesis_hash = *client.genesis_hash();
|
||||
let signer_id = (*signer.public().as_array_ref()).into();
|
||||
client
|
||||
.submit_signed_extrinsic(signer_id, move |_, transaction_nonce| {
|
||||
Bytes(
|
||||
Rialto::sign_transaction(
|
||||
genesis_hash,
|
||||
&signer,
|
||||
relay_substrate_client::TransactionEra::immortal(),
|
||||
UnsignedTransaction::new(
|
||||
rialto_runtime::MessagesCall::update_pallet_parameter {
|
||||
parameter: rialto_runtime::millau_messages::RialtoToMillauMessagesParameter::MillauToRialtoConversionRate(
|
||||
sp_runtime::FixedU128::from_float(updated_rate),
|
||||
),
|
||||
}
|
||||
.into(),
|
||||
transaction_nonce,
|
||||
),
|
||||
)
|
||||
.encode(),
|
||||
)
|
||||
})
|
||||
.await
|
||||
.map(drop)
|
||||
.map_err(|err| anyhow::format_err!("{:?}", err))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
// 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 parachain specification for CLI.
|
||||
|
||||
use crate::cli::{
|
||||
encode_call::{Call, CliEncodeCall},
|
||||
encode_message, CliChain,
|
||||
};
|
||||
use bp_message_dispatch::MessagePayload;
|
||||
use codec::Decode;
|
||||
use frame_support::weights::{DispatchInfo, GetDispatchInfo, Weight};
|
||||
use relay_rialto_parachain_client::RialtoParachain;
|
||||
use sp_version::RuntimeVersion;
|
||||
|
||||
impl CliEncodeCall for RialtoParachain {
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
bp_rialto_parachain::max_extrinsic_size()
|
||||
}
|
||||
|
||||
fn encode_call(call: &Call) -> anyhow::Result<Self::Call> {
|
||||
Ok(match call {
|
||||
Call::Raw { data } => Decode::decode(&mut &*data.0)?,
|
||||
Call::Remark { remark_payload, .. } => rialto_parachain_runtime::Call::System(
|
||||
rialto_parachain_runtime::SystemCall::remark {
|
||||
remark: remark_payload.as_ref().map(|x| x.0.clone()).unwrap_or_default(),
|
||||
},
|
||||
),
|
||||
Call::Transfer { recipient, amount } => rialto_parachain_runtime::Call::Balances(
|
||||
rialto_parachain_runtime::BalancesCall::transfer {
|
||||
dest: recipient.raw_id().into(),
|
||||
value: amount.0,
|
||||
},
|
||||
),
|
||||
Call::BridgeSendMessage { .. } =>
|
||||
anyhow::bail!("Bridge messages are not (yet) supported here",),
|
||||
})
|
||||
}
|
||||
|
||||
fn get_dispatch_info(call: &rialto_parachain_runtime::Call) -> anyhow::Result<DispatchInfo> {
|
||||
Ok(call.get_dispatch_info())
|
||||
}
|
||||
}
|
||||
|
||||
impl CliChain for RialtoParachain {
|
||||
const RUNTIME_VERSION: RuntimeVersion = rialto_parachain_runtime::VERSION;
|
||||
|
||||
type KeyPair = sp_core::sr25519::Pair;
|
||||
type MessagePayload = MessagePayload<
|
||||
bp_rialto_parachain::AccountId,
|
||||
bp_millau::AccountSigner,
|
||||
bp_millau::Signature,
|
||||
Vec<u8>,
|
||||
>;
|
||||
|
||||
fn ss58_format() -> u16 {
|
||||
rialto_parachain_runtime::SS58Prefix::get() as u16
|
||||
}
|
||||
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
bp_rialto_parachain::max_extrinsic_weight()
|
||||
}
|
||||
|
||||
fn encode_message(
|
||||
_message: encode_message::MessagePayload,
|
||||
) -> anyhow::Result<Self::MessagePayload> {
|
||||
anyhow::bail!("Not supported")
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use anyhow::anyhow;
|
||||
use codec::Decode;
|
||||
use frame_support::weights::{DispatchClass, DispatchInfo, Pays, Weight};
|
||||
use relay_rococo_client::Rococo;
|
||||
@@ -27,7 +28,7 @@ use crate::cli::{
|
||||
|
||||
/// Weight of the `system::remark` call at Rococo.
|
||||
///
|
||||
/// This weight is larger (x2) than actual weight at current Rooco runtime to avoid unsuccessful
|
||||
/// This weight is larger (x2) than actual weight at current Rococo runtime to avoid unsuccessful
|
||||
/// calls in the future. But since it is used only in tests (and on test chains), this is ok.
|
||||
pub(crate) const SYSTEM_REMARK_CALL_WEIGHT: Weight = 2 * 1_345_000;
|
||||
|
||||
@@ -38,41 +39,41 @@ impl CliEncodeCall for Rococo {
|
||||
|
||||
fn encode_call(call: &Call) -> anyhow::Result<Self::Call> {
|
||||
Ok(match call {
|
||||
Call::Remark { remark_payload, .. } => {
|
||||
relay_rococo_client::runtime::Call::System(relay_rococo_client::runtime::SystemCall::remark(
|
||||
Call::Remark { remark_payload, .. } => relay_rococo_client::runtime::Call::System(
|
||||
relay_rococo_client::runtime::SystemCall::remark(
|
||||
remark_payload.as_ref().map(|x| x.0.clone()).unwrap_or_default(),
|
||||
))
|
||||
}
|
||||
Call::BridgeSendMessage {
|
||||
lane,
|
||||
payload,
|
||||
fee,
|
||||
bridge_instance_index,
|
||||
} => match *bridge_instance_index {
|
||||
bridge::ROCOCO_TO_WOCOCO_INDEX => {
|
||||
let payload = Decode::decode(&mut &*payload.0)?;
|
||||
relay_rococo_client::runtime::Call::BridgeMessagesWococo(
|
||||
relay_rococo_client::runtime::BridgeMessagesWococoCall::send_message(lane.0, payload, fee.0),
|
||||
)
|
||||
}
|
||||
_ => anyhow::bail!(
|
||||
"Unsupported target bridge pallet with instance index: {}",
|
||||
bridge_instance_index
|
||||
),
|
||||
},
|
||||
),
|
||||
Call::BridgeSendMessage { lane, payload, fee, bridge_instance_index } =>
|
||||
match *bridge_instance_index {
|
||||
bridge::ROCOCO_TO_WOCOCO_INDEX => {
|
||||
let payload = Decode::decode(&mut &*payload.0)?;
|
||||
relay_rococo_client::runtime::Call::BridgeMessagesWococo(
|
||||
relay_rococo_client::runtime::BridgeMessagesWococoCall::send_message(
|
||||
lane.0, payload, fee.0,
|
||||
),
|
||||
)
|
||||
},
|
||||
_ => anyhow::bail!(
|
||||
"Unsupported target bridge pallet with instance index: {}",
|
||||
bridge_instance_index
|
||||
),
|
||||
},
|
||||
_ => anyhow::bail!("The call is not supported"),
|
||||
})
|
||||
}
|
||||
|
||||
fn get_dispatch_info(call: &relay_rococo_client::runtime::Call) -> anyhow::Result<DispatchInfo> {
|
||||
fn get_dispatch_info(
|
||||
call: &relay_rococo_client::runtime::Call,
|
||||
) -> anyhow::Result<DispatchInfo> {
|
||||
match *call {
|
||||
relay_rococo_client::runtime::Call::System(relay_rococo_client::runtime::SystemCall::remark(_)) => {
|
||||
Ok(DispatchInfo {
|
||||
weight: SYSTEM_REMARK_CALL_WEIGHT,
|
||||
class: DispatchClass::Normal,
|
||||
pays_fee: Pays::Yes,
|
||||
})
|
||||
}
|
||||
relay_rococo_client::runtime::Call::System(
|
||||
relay_rococo_client::runtime::SystemCall::remark(_),
|
||||
) => Ok(DispatchInfo {
|
||||
weight: SYSTEM_REMARK_CALL_WEIGHT,
|
||||
class: DispatchClass::Normal,
|
||||
pays_fee: Pays::Yes,
|
||||
}),
|
||||
_ => anyhow::bail!("Unsupported Rococo call: {:?}", call),
|
||||
}
|
||||
}
|
||||
@@ -92,7 +93,9 @@ impl CliChain for Rococo {
|
||||
bp_wococo::max_extrinsic_weight()
|
||||
}
|
||||
|
||||
fn encode_message(_message: encode_message::MessagePayload) -> Result<Self::MessagePayload, String> {
|
||||
Err("Sending messages from Rococo is not yet supported.".into())
|
||||
fn encode_message(
|
||||
_message: encode_message::MessagePayload,
|
||||
) -> anyhow::Result<Self::MessagePayload> {
|
||||
Err(anyhow!("Sending messages from Rococo is not yet supported."))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,56 +16,91 @@
|
||||
|
||||
//! Rococo-to-Wococo headers sync entrypoint.
|
||||
|
||||
use crate::chains::wococo_headers_to_rococo::MAXIMAL_BALANCE_DECREASE_PER_DAY;
|
||||
use crate::finality_pipeline::{SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate};
|
||||
|
||||
use bp_header_chain::justification::GrandpaJustification;
|
||||
use codec::Encode;
|
||||
use relay_rococo_client::{Rococo, SyncHeader as RococoSyncHeader};
|
||||
use relay_substrate_client::{Chain, TransactionSignScheme};
|
||||
use relay_utils::metrics::MetricsParams;
|
||||
use relay_wococo_client::{SigningParams as WococoSigningParams, Wococo};
|
||||
use sp_core::{Bytes, Pair};
|
||||
|
||||
use bp_header_chain::justification::GrandpaJustification;
|
||||
use relay_rococo_client::{Rococo, SyncHeader as RococoSyncHeader};
|
||||
use relay_substrate_client::{Client, IndexOf, TransactionSignScheme, UnsignedTransaction};
|
||||
use relay_utils::metrics::MetricsParams;
|
||||
use relay_wococo_client::{SigningParams as WococoSigningParams, Wococo};
|
||||
use substrate_relay_helper::finality_pipeline::{
|
||||
SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate,
|
||||
};
|
||||
|
||||
use crate::chains::wococo_headers_to_rococo::MAXIMAL_BALANCE_DECREASE_PER_DAY;
|
||||
|
||||
/// Rococo-to-Wococo finality sync pipeline.
|
||||
pub(crate) type RococoFinalityToWococo = SubstrateFinalityToSubstrate<Rococo, Wococo, WococoSigningParams>;
|
||||
pub(crate) type FinalityPipelineRococoFinalityToWococo =
|
||||
SubstrateFinalityToSubstrate<Rococo, Wococo, WococoSigningParams>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct RococoFinalityToWococo {
|
||||
finality_pipeline: FinalityPipelineRococoFinalityToWococo,
|
||||
}
|
||||
|
||||
impl RococoFinalityToWococo {
|
||||
pub fn new(target_client: Client<Wococo>, target_sign: WococoSigningParams) -> Self {
|
||||
Self {
|
||||
finality_pipeline: FinalityPipelineRococoFinalityToWococo::new(
|
||||
target_client,
|
||||
target_sign,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SubstrateFinalitySyncPipeline for RococoFinalityToWococo {
|
||||
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_rococo::BEST_FINALIZED_ROCOCO_HEADER_METHOD;
|
||||
type FinalitySyncPipeline = FinalityPipelineRococoFinalityToWococo;
|
||||
|
||||
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str =
|
||||
bp_rococo::BEST_FINALIZED_ROCOCO_HEADER_METHOD;
|
||||
|
||||
type TargetChain = Wococo;
|
||||
|
||||
fn customize_metrics(params: MetricsParams) -> anyhow::Result<MetricsParams> {
|
||||
crate::chains::add_polkadot_kusama_price_metrics::<Self>(params)
|
||||
crate::chains::add_polkadot_kusama_price_metrics::<Self::FinalitySyncPipeline>(
|
||||
Some(finality_relay::metrics_prefix::<Self::FinalitySyncPipeline>()),
|
||||
params,
|
||||
)
|
||||
}
|
||||
|
||||
fn start_relay_guards(&self) {
|
||||
relay_substrate_client::guard::abort_on_spec_version_change(
|
||||
self.target_client.clone(),
|
||||
self.finality_pipeline.target_client.clone(),
|
||||
bp_wococo::VERSION.spec_version,
|
||||
);
|
||||
relay_substrate_client::guard::abort_when_account_balance_decreased(
|
||||
self.target_client.clone(),
|
||||
self.finality_pipeline.target_client.clone(),
|
||||
self.transactions_author(),
|
||||
MAXIMAL_BALANCE_DECREASE_PER_DAY,
|
||||
);
|
||||
}
|
||||
|
||||
fn transactions_author(&self) -> bp_wococo::AccountId {
|
||||
(*self.target_sign.public().as_array_ref()).into()
|
||||
(*self.finality_pipeline.target_sign.public().as_array_ref()).into()
|
||||
}
|
||||
|
||||
fn make_submit_finality_proof_transaction(
|
||||
&self,
|
||||
transaction_nonce: <Wococo as Chain>::Index,
|
||||
era: bp_runtime::TransactionEraOf<Wococo>,
|
||||
transaction_nonce: IndexOf<Wococo>,
|
||||
header: RococoSyncHeader,
|
||||
proof: GrandpaJustification<bp_rococo::Header>,
|
||||
) -> Bytes {
|
||||
let call = relay_wococo_client::runtime::Call::BridgeGrandpaRococo(
|
||||
relay_wococo_client::runtime::BridgeGrandpaRococoCall::submit_finality_proof(header.into_inner(), proof),
|
||||
relay_wococo_client::runtime::BridgeGrandpaRococoCall::submit_finality_proof(
|
||||
Box::new(header.into_inner()),
|
||||
proof,
|
||||
),
|
||||
);
|
||||
let genesis_hash = *self.finality_pipeline.target_client.genesis_hash();
|
||||
let transaction = Wococo::sign_transaction(
|
||||
genesis_hash,
|
||||
&self.finality_pipeline.target_sign,
|
||||
era,
|
||||
UnsignedTransaction::new(call, transaction_nonce),
|
||||
);
|
||||
let genesis_hash = *self.target_client.genesis_hash();
|
||||
let transaction = Wococo::sign_transaction(genesis_hash, &self.target_sign, transaction_nonce, call);
|
||||
|
||||
Bytes(transaction.encode())
|
||||
}
|
||||
|
||||
@@ -16,53 +16,83 @@
|
||||
|
||||
//! Rococo-to-Wococo messages sync entrypoint.
|
||||
|
||||
use crate::messages_lane::{
|
||||
select_delivery_transaction_limits, MessagesRelayParams, SubstrateMessageLane, SubstrateMessageLaneToSubstrate,
|
||||
};
|
||||
use crate::messages_source::SubstrateMessagesSource;
|
||||
use crate::messages_target::SubstrateMessagesTarget;
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use codec::Encode;
|
||||
use sp_core::{Bytes, Pair};
|
||||
|
||||
use bp_messages::MessageNonce;
|
||||
use bp_runtime::{ROCOCO_CHAIN_ID, WOCOCO_CHAIN_ID};
|
||||
use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
|
||||
use codec::Encode;
|
||||
use messages_relay::message_lane::MessageLane;
|
||||
use relay_rococo_client::{HeaderId as RococoHeaderId, Rococo, SigningParams as RococoSigningParams};
|
||||
use relay_substrate_client::{metrics::StorageProofOverheadMetric, Chain, TransactionSignScheme};
|
||||
use relay_wococo_client::{HeaderId as WococoHeaderId, SigningParams as WococoSigningParams, Wococo};
|
||||
use sp_core::{Bytes, Pair};
|
||||
use std::{ops::RangeInclusive, time::Duration};
|
||||
use frame_support::weights::Weight;
|
||||
use messages_relay::{message_lane::MessageLane, relay_strategy::MixStrategy};
|
||||
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,
|
||||
};
|
||||
use substrate_relay_helper::{
|
||||
messages_lane::{
|
||||
select_delivery_transaction_limits, MessagesRelayParams, StandaloneMessagesMetrics,
|
||||
SubstrateMessageLane, SubstrateMessageLaneToSubstrate,
|
||||
},
|
||||
messages_source::SubstrateMessagesSource,
|
||||
messages_target::SubstrateMessagesTarget,
|
||||
STALL_TIMEOUT,
|
||||
};
|
||||
|
||||
/// Rococo-to-Wococo message lane.
|
||||
pub type RococoMessagesToWococo =
|
||||
pub type MessageLaneRococoMessagesToWococo =
|
||||
SubstrateMessageLaneToSubstrate<Rococo, RococoSigningParams, Wococo, WococoSigningParams>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RococoMessagesToWococo {
|
||||
message_lane: MessageLaneRococoMessagesToWococo,
|
||||
}
|
||||
|
||||
impl SubstrateMessageLane for RococoMessagesToWococo {
|
||||
const OUTBOUND_LANE_MESSAGE_DETAILS_METHOD: &'static str = bp_wococo::TO_WOCOCO_MESSAGE_DETAILS_METHOD;
|
||||
type MessageLane = MessageLaneRococoMessagesToWococo;
|
||||
|
||||
const OUTBOUND_LANE_MESSAGE_DETAILS_METHOD: &'static str =
|
||||
bp_wococo::TO_WOCOCO_MESSAGE_DETAILS_METHOD;
|
||||
const OUTBOUND_LANE_LATEST_GENERATED_NONCE_METHOD: &'static str =
|
||||
bp_wococo::TO_WOCOCO_LATEST_GENERATED_NONCE_METHOD;
|
||||
const OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str = bp_wococo::TO_WOCOCO_LATEST_RECEIVED_NONCE_METHOD;
|
||||
const OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str =
|
||||
bp_wococo::TO_WOCOCO_LATEST_RECEIVED_NONCE_METHOD;
|
||||
|
||||
const INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str = bp_rococo::FROM_ROCOCO_LATEST_RECEIVED_NONCE_METHOD;
|
||||
const INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str =
|
||||
bp_rococo::FROM_ROCOCO_LATEST_RECEIVED_NONCE_METHOD;
|
||||
const INBOUND_LANE_LATEST_CONFIRMED_NONCE_METHOD: &'static str =
|
||||
bp_rococo::FROM_ROCOCO_LATEST_CONFIRMED_NONCE_METHOD;
|
||||
const INBOUND_LANE_UNREWARDED_RELAYERS_STATE: &'static str = bp_rococo::FROM_ROCOCO_UNREWARDED_RELAYERS_STATE;
|
||||
const INBOUND_LANE_UNREWARDED_RELAYERS_STATE: &'static str =
|
||||
bp_rococo::FROM_ROCOCO_UNREWARDED_RELAYERS_STATE;
|
||||
|
||||
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_rococo::BEST_FINALIZED_ROCOCO_HEADER_METHOD;
|
||||
const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str = bp_wococo::BEST_FINALIZED_WOCOCO_HEADER_METHOD;
|
||||
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str =
|
||||
bp_rococo::BEST_FINALIZED_ROCOCO_HEADER_METHOD;
|
||||
const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str =
|
||||
bp_wococo::BEST_FINALIZED_WOCOCO_HEADER_METHOD;
|
||||
|
||||
const MESSAGE_PALLET_NAME_AT_SOURCE: &'static str = bp_rococo::WITH_WOCOCO_MESSAGES_PALLET_NAME;
|
||||
const MESSAGE_PALLET_NAME_AT_TARGET: &'static str = bp_wococo::WITH_ROCOCO_MESSAGES_PALLET_NAME;
|
||||
|
||||
const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN: Weight =
|
||||
bp_wococo::PAY_INBOUND_DISPATCH_FEE_WEIGHT;
|
||||
|
||||
type SourceChain = Rococo;
|
||||
type TargetChain = Wococo;
|
||||
|
||||
fn source_transactions_author(&self) -> bp_rococo::AccountId {
|
||||
(*self.source_sign.public().as_array_ref()).into()
|
||||
(*self.message_lane.source_sign.public().as_array_ref()).into()
|
||||
}
|
||||
|
||||
fn make_messages_receiving_proof_transaction(
|
||||
&self,
|
||||
transaction_nonce: <Rococo as Chain>::Index,
|
||||
best_block_id: RococoHeaderId,
|
||||
transaction_nonce: IndexOf<Rococo>,
|
||||
_generated_at_block: WococoHeaderId,
|
||||
proof: <Self as MessageLane>::MessagesReceivingProof,
|
||||
proof: <Self::MessageLane as MessageLane>::MessagesReceivingProof,
|
||||
) -> Bytes {
|
||||
let (relayers_state, proof) = proof;
|
||||
let call = relay_rococo_client::runtime::Call::BridgeMessagesWococo(
|
||||
@@ -71,8 +101,16 @@ impl SubstrateMessageLane for RococoMessagesToWococo {
|
||||
relayers_state,
|
||||
),
|
||||
);
|
||||
let genesis_hash = *self.source_client.genesis_hash();
|
||||
let transaction = Rococo::sign_transaction(genesis_hash, &self.source_sign, transaction_nonce, call);
|
||||
let genesis_hash = *self.message_lane.source_client.genesis_hash();
|
||||
let transaction = Rococo::sign_transaction(
|
||||
genesis_hash,
|
||||
&self.message_lane.source_sign,
|
||||
relay_substrate_client::TransactionEra::new(
|
||||
best_block_id,
|
||||
self.message_lane.source_transactions_mortality,
|
||||
),
|
||||
UnsignedTransaction::new(call, transaction_nonce),
|
||||
);
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Prepared Wococo -> Rococo confirmation transaction. Weight: <unknown>/{}, size: {}/{}",
|
||||
@@ -84,34 +122,39 @@ impl SubstrateMessageLane for RococoMessagesToWococo {
|
||||
}
|
||||
|
||||
fn target_transactions_author(&self) -> bp_wococo::AccountId {
|
||||
(*self.target_sign.public().as_array_ref()).into()
|
||||
(*self.message_lane.target_sign.public().as_array_ref()).into()
|
||||
}
|
||||
|
||||
fn make_messages_delivery_transaction(
|
||||
&self,
|
||||
transaction_nonce: <Wococo as Chain>::Index,
|
||||
best_block_id: WococoHeaderId,
|
||||
transaction_nonce: IndexOf<Wococo>,
|
||||
_generated_at_header: RococoHeaderId,
|
||||
_nonces: RangeInclusive<MessageNonce>,
|
||||
proof: <Self as MessageLane>::MessagesProof,
|
||||
proof: <Self::MessageLane as MessageLane>::MessagesProof,
|
||||
) -> Bytes {
|
||||
let (dispatch_weight, proof) = proof;
|
||||
let FromBridgedChainMessagesProof {
|
||||
ref nonces_start,
|
||||
ref nonces_end,
|
||||
..
|
||||
} = proof;
|
||||
let FromBridgedChainMessagesProof { ref nonces_start, ref nonces_end, .. } = proof;
|
||||
let messages_count = nonces_end - nonces_start + 1;
|
||||
|
||||
let call = relay_wococo_client::runtime::Call::BridgeMessagesRococo(
|
||||
relay_wococo_client::runtime::BridgeMessagesRococoCall::receive_messages_proof(
|
||||
self.relayer_id_at_source.clone(),
|
||||
self.message_lane.relayer_id_at_source.clone(),
|
||||
proof,
|
||||
messages_count as _,
|
||||
dispatch_weight,
|
||||
),
|
||||
);
|
||||
let genesis_hash = *self.target_client.genesis_hash();
|
||||
let transaction = Wococo::sign_transaction(genesis_hash, &self.target_sign, transaction_nonce, call);
|
||||
let genesis_hash = *self.message_lane.target_client.genesis_hash();
|
||||
let transaction = Wococo::sign_transaction(
|
||||
genesis_hash,
|
||||
&self.message_lane.target_sign,
|
||||
relay_substrate_client::TransactionEra::new(
|
||||
best_block_id,
|
||||
self.message_lane.target_transactions_mortality,
|
||||
),
|
||||
UnsignedTransaction::new(call, transaction_nonce),
|
||||
);
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Prepared Rococo -> Wococo delivery transaction. Weight: <unknown>/{}, size: {}/{}",
|
||||
@@ -124,28 +167,42 @@ impl SubstrateMessageLane for RococoMessagesToWococo {
|
||||
}
|
||||
|
||||
/// Rococo node as messages source.
|
||||
type RococoSourceClient =
|
||||
SubstrateMessagesSource<Rococo, RococoMessagesToWococo, relay_rococo_client::runtime::WithWococoMessagesInstance>;
|
||||
type RococoSourceClient = SubstrateMessagesSource<RococoMessagesToWococo>;
|
||||
|
||||
/// Wococo node as messages target.
|
||||
type WococoTargetClient =
|
||||
SubstrateMessagesTarget<Wococo, RococoMessagesToWococo, relay_wococo_client::runtime::WithRococoMessagesInstance>;
|
||||
type WococoTargetClient = SubstrateMessagesTarget<RococoMessagesToWococo>;
|
||||
|
||||
/// Run Rococo-to-Wococo messages sync.
|
||||
pub async fn run(
|
||||
params: MessagesRelayParams<Rococo, RococoSigningParams, Wococo, WococoSigningParams>,
|
||||
) -> Result<(), String> {
|
||||
let stall_timeout = Duration::from_secs(5 * 60);
|
||||
params: MessagesRelayParams<
|
||||
Rococo,
|
||||
RococoSigningParams,
|
||||
Wococo,
|
||||
WococoSigningParams,
|
||||
MixStrategy,
|
||||
>,
|
||||
) -> anyhow::Result<()> {
|
||||
let stall_timeout = relay_substrate_client::bidirectional_transaction_stall_timeout(
|
||||
params.source_transactions_mortality,
|
||||
params.target_transactions_mortality,
|
||||
Rococo::AVERAGE_BLOCK_INTERVAL,
|
||||
Wococo::AVERAGE_BLOCK_INTERVAL,
|
||||
STALL_TIMEOUT,
|
||||
);
|
||||
let relayer_id_at_rococo = (*params.source_sign.public().as_array_ref()).into();
|
||||
|
||||
let lane_id = params.lane_id;
|
||||
let source_client = params.source_client;
|
||||
let lane = RococoMessagesToWococo {
|
||||
source_client: source_client.clone(),
|
||||
source_sign: params.source_sign,
|
||||
target_client: params.target_client.clone(),
|
||||
target_sign: params.target_sign,
|
||||
relayer_id_at_source: relayer_id_at_rococo,
|
||||
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_sign: params.target_sign,
|
||||
target_transactions_mortality: params.target_transactions_mortality,
|
||||
relayer_id_at_source: relayer_id_at_rococo,
|
||||
},
|
||||
};
|
||||
|
||||
// 2/3 is reserved for proofs and tx overhead
|
||||
@@ -153,14 +210,14 @@ pub async fn run(
|
||||
// we don't know exact weights of the Wococo runtime. So to guess weights we'll be using
|
||||
// weights from Rialto and then simply dividing it by x2.
|
||||
let (max_messages_in_single_batch, max_messages_weight_in_single_batch) =
|
||||
select_delivery_transaction_limits::<pallet_bridge_messages::weights::RialtoWeight<rialto_runtime::Runtime>>(
|
||||
select_delivery_transaction_limits::<
|
||||
pallet_bridge_messages::weights::RialtoWeight<rialto_runtime::Runtime>,
|
||||
>(
|
||||
bp_wococo::max_extrinsic_weight(),
|
||||
bp_wococo::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
|
||||
);
|
||||
let (max_messages_in_single_batch, max_messages_weight_in_single_batch) = (
|
||||
max_messages_in_single_batch / 2,
|
||||
max_messages_weight_in_single_batch / 2,
|
||||
);
|
||||
let (max_messages_in_single_batch, max_messages_weight_in_single_batch) =
|
||||
(max_messages_in_single_batch / 2, max_messages_weight_in_single_batch / 2);
|
||||
|
||||
log::info!(
|
||||
target: "bridge",
|
||||
@@ -168,13 +225,25 @@ pub async fn run(
|
||||
Rococo relayer account id: {:?}\n\t\
|
||||
Max messages in single transaction: {}\n\t\
|
||||
Max messages size in single transaction: {}\n\t\
|
||||
Max messages weight in single transaction: {}",
|
||||
lane.relayer_id_at_source,
|
||||
Max messages weight in single transaction: {}\n\t\
|
||||
Tx mortality: {:?}/{:?}\n\t\
|
||||
Stall timeout: {:?}",
|
||||
lane.message_lane.relayer_id_at_source,
|
||||
max_messages_in_single_batch,
|
||||
max_messages_size_in_single_batch,
|
||||
max_messages_weight_in_single_batch,
|
||||
params.source_transactions_mortality,
|
||||
params.target_transactions_mortality,
|
||||
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(),
|
||||
)?;
|
||||
messages_relay::message_lane_loop::run(
|
||||
messages_relay::message_lane_loop::Params {
|
||||
lane: lane_id,
|
||||
@@ -183,45 +252,48 @@ pub async fn run(
|
||||
reconnect_delay: relay_utils::relay_loop::RECONNECT_DELAY,
|
||||
stall_timeout,
|
||||
delivery_params: messages_relay::message_lane_loop::MessageDeliveryParams {
|
||||
max_unrewarded_relayer_entries_at_target: bp_wococo::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
|
||||
max_unconfirmed_nonces_at_target: bp_wococo::MAX_UNCONFIRMED_MESSAGES_AT_INBOUND_LANE,
|
||||
max_unrewarded_relayer_entries_at_target:
|
||||
bp_wococo::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
|
||||
max_unconfirmed_nonces_at_target:
|
||||
bp_wococo::MAX_UNCONFIRMED_MESSAGES_AT_INBOUND_LANE,
|
||||
max_messages_in_single_batch,
|
||||
max_messages_weight_in_single_batch,
|
||||
max_messages_size_in_single_batch,
|
||||
relayer_mode: messages_relay::message_lane_loop::RelayerMode::Altruistic,
|
||||
relay_strategy: params.relay_strategy,
|
||||
},
|
||||
},
|
||||
RococoSourceClient::new(
|
||||
source_client.clone(),
|
||||
lane.clone(),
|
||||
lane_id,
|
||||
WOCOCO_CHAIN_ID,
|
||||
params.target_to_source_headers_relay,
|
||||
),
|
||||
WococoTargetClient::new(
|
||||
params.target_client,
|
||||
lane,
|
||||
lane_id,
|
||||
ROCOCO_CHAIN_ID,
|
||||
metrics_values,
|
||||
params.source_to_target_headers_relay,
|
||||
),
|
||||
relay_utils::relay_metrics(
|
||||
Some(messages_relay::message_lane_loop::metrics_prefix::<
|
||||
RococoMessagesToWococo,
|
||||
>(&lane_id)),
|
||||
params.metrics_params,
|
||||
)
|
||||
.standalone_metric(|registry, prefix| {
|
||||
StorageProofOverheadMetric::new(
|
||||
registry,
|
||||
prefix,
|
||||
source_client.clone(),
|
||||
"rococo_storage_proof_overhead".into(),
|
||||
"Rococo storage proof overhead".into(),
|
||||
)
|
||||
})?
|
||||
.into_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,
|
||||
source_client: Client<Rococo>,
|
||||
) -> anyhow::Result<(MetricsParams, StandaloneMessagesMetrics)> {
|
||||
substrate_relay_helper::messages_lane::add_standalone_metrics::<RococoMessagesToWococo>(
|
||||
metrics_prefix,
|
||||
metrics_params,
|
||||
source_client,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
//! Westend chain specification for CLI.
|
||||
|
||||
use crate::cli::{encode_message, CliChain};
|
||||
use anyhow::anyhow;
|
||||
use frame_support::weights::Weight;
|
||||
use relay_westend_client::Westend;
|
||||
use sp_version::RuntimeVersion;
|
||||
@@ -35,7 +36,9 @@ impl CliChain for Westend {
|
||||
0
|
||||
}
|
||||
|
||||
fn encode_message(_message: encode_message::MessagePayload) -> Result<Self::MessagePayload, String> {
|
||||
Err("Sending messages from Westend is not yet supported.".into())
|
||||
fn encode_message(
|
||||
_message: encode_message::MessagePayload,
|
||||
) -> anyhow::Result<Self::MessagePayload> {
|
||||
Err(anyhow!("Sending messages from Westend is not yet supported."))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,46 +16,80 @@
|
||||
|
||||
//! Westend-to-Millau headers sync entrypoint.
|
||||
|
||||
use crate::finality_pipeline::{SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate};
|
||||
|
||||
use bp_header_chain::justification::GrandpaJustification;
|
||||
use codec::Encode;
|
||||
use relay_millau_client::{Millau, SigningParams as MillauSigningParams};
|
||||
use relay_substrate_client::{Chain, TransactionSignScheme};
|
||||
use relay_utils::metrics::MetricsParams;
|
||||
use relay_westend_client::{SyncHeader as WestendSyncHeader, Westend};
|
||||
use sp_core::{Bytes, Pair};
|
||||
|
||||
use bp_header_chain::justification::GrandpaJustification;
|
||||
use relay_millau_client::{Millau, SigningParams as MillauSigningParams};
|
||||
use relay_substrate_client::{Client, IndexOf, TransactionSignScheme, UnsignedTransaction};
|
||||
use relay_utils::metrics::MetricsParams;
|
||||
use relay_westend_client::{SyncHeader as WestendSyncHeader, Westend};
|
||||
use substrate_relay_helper::finality_pipeline::{
|
||||
SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate,
|
||||
};
|
||||
|
||||
/// Westend-to-Millau finality sync pipeline.
|
||||
pub(crate) type WestendFinalityToMillau = SubstrateFinalityToSubstrate<Westend, Millau, MillauSigningParams>;
|
||||
pub(crate) type FinalityPipelineWestendFinalityToMillau =
|
||||
SubstrateFinalityToSubstrate<Westend, Millau, MillauSigningParams>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct WestendFinalityToMillau {
|
||||
finality_pipeline: FinalityPipelineWestendFinalityToMillau,
|
||||
}
|
||||
|
||||
impl WestendFinalityToMillau {
|
||||
pub fn new(target_client: Client<Millau>, target_sign: MillauSigningParams) -> Self {
|
||||
Self {
|
||||
finality_pipeline: FinalityPipelineWestendFinalityToMillau::new(
|
||||
target_client,
|
||||
target_sign,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SubstrateFinalitySyncPipeline for WestendFinalityToMillau {
|
||||
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_westend::BEST_FINALIZED_WESTEND_HEADER_METHOD;
|
||||
type FinalitySyncPipeline = FinalityPipelineWestendFinalityToMillau;
|
||||
|
||||
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str =
|
||||
bp_westend::BEST_FINALIZED_WESTEND_HEADER_METHOD;
|
||||
|
||||
type TargetChain = Millau;
|
||||
|
||||
fn customize_metrics(params: MetricsParams) -> anyhow::Result<MetricsParams> {
|
||||
crate::chains::add_polkadot_kusama_price_metrics::<Self>(params)
|
||||
crate::chains::add_polkadot_kusama_price_metrics::<Self::FinalitySyncPipeline>(
|
||||
Some(finality_relay::metrics_prefix::<Self::FinalitySyncPipeline>()),
|
||||
params,
|
||||
)
|
||||
}
|
||||
|
||||
fn transactions_author(&self) -> bp_millau::AccountId {
|
||||
(*self.target_sign.public().as_array_ref()).into()
|
||||
(*self.finality_pipeline.target_sign.public().as_array_ref()).into()
|
||||
}
|
||||
|
||||
fn make_submit_finality_proof_transaction(
|
||||
&self,
|
||||
transaction_nonce: <Millau as Chain>::Index,
|
||||
era: bp_runtime::TransactionEraOf<Millau>,
|
||||
transaction_nonce: IndexOf<Millau>,
|
||||
header: WestendSyncHeader,
|
||||
proof: GrandpaJustification<bp_westend::Header>,
|
||||
) -> Bytes {
|
||||
let call = millau_runtime::BridgeGrandpaWestendCall::<
|
||||
let call = millau_runtime::BridgeGrandpaCall::<
|
||||
millau_runtime::Runtime,
|
||||
millau_runtime::WestendGrandpaInstance,
|
||||
>::submit_finality_proof(header.into_inner(), proof)
|
||||
>::submit_finality_proof {
|
||||
finality_target: Box::new(header.into_inner()),
|
||||
justification: proof,
|
||||
}
|
||||
.into();
|
||||
|
||||
let genesis_hash = *self.target_client.genesis_hash();
|
||||
let transaction = Millau::sign_transaction(genesis_hash, &self.target_sign, transaction_nonce, call);
|
||||
let genesis_hash = *self.finality_pipeline.target_client.genesis_hash();
|
||||
let transaction = Millau::sign_transaction(
|
||||
genesis_hash,
|
||||
&self.finality_pipeline.target_sign,
|
||||
era,
|
||||
UnsignedTransaction::new(call, transaction_nonce),
|
||||
);
|
||||
|
||||
Bytes(transaction.encode())
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use anyhow::anyhow;
|
||||
use codec::Decode;
|
||||
use frame_support::weights::{DispatchClass, DispatchInfo, Pays, Weight};
|
||||
use relay_wococo_client::Wococo;
|
||||
@@ -32,41 +33,41 @@ impl CliEncodeCall for Wococo {
|
||||
|
||||
fn encode_call(call: &Call) -> anyhow::Result<Self::Call> {
|
||||
Ok(match call {
|
||||
Call::Remark { remark_payload, .. } => {
|
||||
relay_wococo_client::runtime::Call::System(relay_wococo_client::runtime::SystemCall::remark(
|
||||
Call::Remark { remark_payload, .. } => relay_wococo_client::runtime::Call::System(
|
||||
relay_wococo_client::runtime::SystemCall::remark(
|
||||
remark_payload.as_ref().map(|x| x.0.clone()).unwrap_or_default(),
|
||||
))
|
||||
}
|
||||
Call::BridgeSendMessage {
|
||||
lane,
|
||||
payload,
|
||||
fee,
|
||||
bridge_instance_index,
|
||||
} => match *bridge_instance_index {
|
||||
bridge::WOCOCO_TO_ROCOCO_INDEX => {
|
||||
let payload = Decode::decode(&mut &*payload.0)?;
|
||||
relay_wococo_client::runtime::Call::BridgeMessagesRococo(
|
||||
relay_wococo_client::runtime::BridgeMessagesRococoCall::send_message(lane.0, payload, fee.0),
|
||||
)
|
||||
}
|
||||
_ => anyhow::bail!(
|
||||
"Unsupported target bridge pallet with instance index: {}",
|
||||
bridge_instance_index
|
||||
),
|
||||
},
|
||||
),
|
||||
Call::BridgeSendMessage { lane, payload, fee, bridge_instance_index } =>
|
||||
match *bridge_instance_index {
|
||||
bridge::WOCOCO_TO_ROCOCO_INDEX => {
|
||||
let payload = Decode::decode(&mut &*payload.0)?;
|
||||
relay_wococo_client::runtime::Call::BridgeMessagesRococo(
|
||||
relay_wococo_client::runtime::BridgeMessagesRococoCall::send_message(
|
||||
lane.0, payload, fee.0,
|
||||
),
|
||||
)
|
||||
},
|
||||
_ => anyhow::bail!(
|
||||
"Unsupported target bridge pallet with instance index: {}",
|
||||
bridge_instance_index
|
||||
),
|
||||
},
|
||||
_ => anyhow::bail!("The call is not supported"),
|
||||
})
|
||||
}
|
||||
|
||||
fn get_dispatch_info(call: &relay_wococo_client::runtime::Call) -> anyhow::Result<DispatchInfo> {
|
||||
fn get_dispatch_info(
|
||||
call: &relay_wococo_client::runtime::Call,
|
||||
) -> anyhow::Result<DispatchInfo> {
|
||||
match *call {
|
||||
relay_wococo_client::runtime::Call::System(relay_wococo_client::runtime::SystemCall::remark(_)) => {
|
||||
Ok(DispatchInfo {
|
||||
weight: crate::chains::rococo::SYSTEM_REMARK_CALL_WEIGHT,
|
||||
class: DispatchClass::Normal,
|
||||
pays_fee: Pays::Yes,
|
||||
})
|
||||
}
|
||||
relay_wococo_client::runtime::Call::System(
|
||||
relay_wococo_client::runtime::SystemCall::remark(_),
|
||||
) => Ok(DispatchInfo {
|
||||
weight: crate::chains::rococo::SYSTEM_REMARK_CALL_WEIGHT,
|
||||
class: DispatchClass::Normal,
|
||||
pays_fee: Pays::Yes,
|
||||
}),
|
||||
_ => anyhow::bail!("Unsupported Rococo call: {:?}", call),
|
||||
}
|
||||
}
|
||||
@@ -86,7 +87,9 @@ impl CliChain for Wococo {
|
||||
bp_wococo::max_extrinsic_weight()
|
||||
}
|
||||
|
||||
fn encode_message(_message: encode_message::MessagePayload) -> Result<Self::MessagePayload, String> {
|
||||
Err("Sending messages from Wococo is not yet supported.".into())
|
||||
fn encode_message(
|
||||
_message: encode_message::MessagePayload,
|
||||
) -> anyhow::Result<Self::MessagePayload> {
|
||||
Err(anyhow!("Sending messages from Wococo is not yet supported."))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,15 +16,17 @@
|
||||
|
||||
//! Wococo-to-Rococo headers sync entrypoint.
|
||||
|
||||
use crate::finality_pipeline::{SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate};
|
||||
use codec::Encode;
|
||||
use sp_core::{Bytes, Pair};
|
||||
|
||||
use bp_header_chain::justification::GrandpaJustification;
|
||||
use codec::Encode;
|
||||
use relay_rococo_client::{Rococo, SigningParams as RococoSigningParams};
|
||||
use relay_substrate_client::{Chain, TransactionSignScheme};
|
||||
use relay_substrate_client::{Client, IndexOf, TransactionSignScheme, UnsignedTransaction};
|
||||
use relay_utils::metrics::MetricsParams;
|
||||
use relay_wococo_client::{SyncHeader as WococoSyncHeader, Wococo};
|
||||
use sp_core::{Bytes, Pair};
|
||||
use substrate_relay_helper::finality_pipeline::{
|
||||
SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate,
|
||||
};
|
||||
|
||||
/// Maximal saturating difference between `balance(now)` and `balance(now-24h)` to treat
|
||||
/// relay as gone wild.
|
||||
@@ -34,44 +36,76 @@ use sp_core::{Bytes, Pair};
|
||||
pub(crate) const MAXIMAL_BALANCE_DECREASE_PER_DAY: bp_rococo::Balance = 1_500_000_000_000_000;
|
||||
|
||||
/// Wococo-to-Rococo finality sync pipeline.
|
||||
pub(crate) type WococoFinalityToRococo = SubstrateFinalityToSubstrate<Wococo, Rococo, RococoSigningParams>;
|
||||
pub(crate) type FinalityPipelineWococoFinalityToRococo =
|
||||
SubstrateFinalityToSubstrate<Wococo, Rococo, RococoSigningParams>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct WococoFinalityToRococo {
|
||||
finality_pipeline: FinalityPipelineWococoFinalityToRococo,
|
||||
}
|
||||
|
||||
impl WococoFinalityToRococo {
|
||||
pub fn new(target_client: Client<Rococo>, target_sign: RococoSigningParams) -> Self {
|
||||
Self {
|
||||
finality_pipeline: FinalityPipelineWococoFinalityToRococo::new(
|
||||
target_client,
|
||||
target_sign,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SubstrateFinalitySyncPipeline for WococoFinalityToRococo {
|
||||
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_wococo::BEST_FINALIZED_WOCOCO_HEADER_METHOD;
|
||||
type FinalitySyncPipeline = FinalityPipelineWococoFinalityToRococo;
|
||||
|
||||
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str =
|
||||
bp_wococo::BEST_FINALIZED_WOCOCO_HEADER_METHOD;
|
||||
|
||||
type TargetChain = Rococo;
|
||||
|
||||
fn customize_metrics(params: MetricsParams) -> anyhow::Result<MetricsParams> {
|
||||
crate::chains::add_polkadot_kusama_price_metrics::<Self>(params)
|
||||
crate::chains::add_polkadot_kusama_price_metrics::<Self::FinalitySyncPipeline>(
|
||||
Some(finality_relay::metrics_prefix::<Self::FinalitySyncPipeline>()),
|
||||
params,
|
||||
)
|
||||
}
|
||||
|
||||
fn start_relay_guards(&self) {
|
||||
relay_substrate_client::guard::abort_on_spec_version_change(
|
||||
self.target_client.clone(),
|
||||
self.finality_pipeline.target_client.clone(),
|
||||
bp_rococo::VERSION.spec_version,
|
||||
);
|
||||
relay_substrate_client::guard::abort_when_account_balance_decreased(
|
||||
self.target_client.clone(),
|
||||
self.finality_pipeline.target_client.clone(),
|
||||
self.transactions_author(),
|
||||
MAXIMAL_BALANCE_DECREASE_PER_DAY,
|
||||
);
|
||||
}
|
||||
|
||||
fn transactions_author(&self) -> bp_rococo::AccountId {
|
||||
(*self.target_sign.public().as_array_ref()).into()
|
||||
(*self.finality_pipeline.target_sign.public().as_array_ref()).into()
|
||||
}
|
||||
|
||||
fn make_submit_finality_proof_transaction(
|
||||
&self,
|
||||
transaction_nonce: <Rococo as Chain>::Index,
|
||||
era: bp_runtime::TransactionEraOf<Rococo>,
|
||||
transaction_nonce: IndexOf<Rococo>,
|
||||
header: WococoSyncHeader,
|
||||
proof: GrandpaJustification<bp_wococo::Header>,
|
||||
) -> Bytes {
|
||||
let call = relay_rococo_client::runtime::Call::BridgeGrandpaWococo(
|
||||
relay_rococo_client::runtime::BridgeGrandpaWococoCall::submit_finality_proof(header.into_inner(), proof),
|
||||
relay_rococo_client::runtime::BridgeGrandpaWococoCall::submit_finality_proof(
|
||||
Box::new(header.into_inner()),
|
||||
proof,
|
||||
),
|
||||
);
|
||||
let genesis_hash = *self.finality_pipeline.target_client.genesis_hash();
|
||||
let transaction = Rococo::sign_transaction(
|
||||
genesis_hash,
|
||||
&self.finality_pipeline.target_sign,
|
||||
era,
|
||||
UnsignedTransaction::new(call, transaction_nonce),
|
||||
);
|
||||
let genesis_hash = *self.target_client.genesis_hash();
|
||||
let transaction = Rococo::sign_transaction(genesis_hash, &self.target_sign, transaction_nonce, call);
|
||||
|
||||
Bytes(transaction.encode())
|
||||
}
|
||||
@@ -80,36 +114,19 @@ impl SubstrateFinalitySyncPipeline for WococoFinalityToRococo {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use frame_support::weights::WeightToFeePolynomial;
|
||||
use pallet_bridge_grandpa::weights::WeightInfo;
|
||||
use crate::chains::kusama_headers_to_polkadot::tests::compute_maximal_balance_decrease_per_day;
|
||||
|
||||
#[test]
|
||||
fn maximal_balance_decrease_per_day_is_sane() {
|
||||
// Rococo/Wococo GRANDPA pallet weights. They're now using Rialto weights => using `RialtoWeight` is justified.
|
||||
//
|
||||
// Using Rialto runtime this is slightly incorrect, because `DbWeight` of Rococo/Wococo runtime may differ
|
||||
// from the `DbWeight` of Rialto runtime. But now (and most probably forever) it is the same.
|
||||
type RococoGrandpaPalletWeights = pallet_bridge_grandpa::weights::RialtoWeight<rialto_runtime::Runtime>;
|
||||
|
||||
// The following formula shall not be treated as super-accurate - guard is to protect from mad relays,
|
||||
// not to protect from over-average loses.
|
||||
//
|
||||
// Worst case: we're submitting proof for every source header. Since we submit every header, the number of
|
||||
// headers in ancestry proof is near to 0 (let's round up to 2). And the number of authorities is 1024,
|
||||
// which is (now) larger than on any existing chain => normally there'll be ~1024*2/3+1 commits.
|
||||
const AVG_VOTES_ANCESTRIES_LEN: u32 = 2;
|
||||
const AVG_PRECOMMITS_LEN: u32 = 1024 * 2 / 3 + 1;
|
||||
let number_of_source_headers_per_day: bp_wococo::Balance = bp_wococo::DAYS as _;
|
||||
let single_source_header_submit_call_weight =
|
||||
RococoGrandpaPalletWeights::submit_finality_proof(AVG_VOTES_ANCESTRIES_LEN, AVG_PRECOMMITS_LEN);
|
||||
// for simplicity - add extra weight for base tx fee + fee that is paid for the tx size + adjusted fee
|
||||
let single_source_header_submit_tx_weight = single_source_header_submit_call_weight * 3 / 2;
|
||||
let single_source_header_tx_cost = bp_rococo::WeightToFee::calc(&single_source_header_submit_tx_weight);
|
||||
let maximal_expected_decrease = single_source_header_tx_cost * number_of_source_headers_per_day;
|
||||
// we expect Wococo -> Rococo relay to be running in all-headers mode
|
||||
let maximal_balance_decrease = compute_maximal_balance_decrease_per_day::<
|
||||
bp_kusama::Balance,
|
||||
bp_kusama::WeightToFee,
|
||||
>(bp_wococo::DAYS);
|
||||
assert!(
|
||||
MAXIMAL_BALANCE_DECREASE_PER_DAY >= maximal_expected_decrease,
|
||||
MAXIMAL_BALANCE_DECREASE_PER_DAY >= maximal_balance_decrease,
|
||||
"Maximal expected loss per day {} is larger than hardcoded {}",
|
||||
maximal_expected_decrease,
|
||||
maximal_balance_decrease,
|
||||
MAXIMAL_BALANCE_DECREASE_PER_DAY,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,53 +16,82 @@
|
||||
|
||||
//! Wococo-to-Rococo messages sync entrypoint.
|
||||
|
||||
use crate::messages_lane::{
|
||||
select_delivery_transaction_limits, MessagesRelayParams, SubstrateMessageLane, SubstrateMessageLaneToSubstrate,
|
||||
};
|
||||
use crate::messages_source::SubstrateMessagesSource;
|
||||
use crate::messages_target::SubstrateMessagesTarget;
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use codec::Encode;
|
||||
use sp_core::{Bytes, Pair};
|
||||
|
||||
use bp_messages::MessageNonce;
|
||||
use bp_runtime::{ROCOCO_CHAIN_ID, WOCOCO_CHAIN_ID};
|
||||
use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
|
||||
use codec::Encode;
|
||||
use messages_relay::message_lane::MessageLane;
|
||||
use relay_rococo_client::{HeaderId as RococoHeaderId, Rococo, SigningParams as RococoSigningParams};
|
||||
use relay_substrate_client::{metrics::StorageProofOverheadMetric, Chain, TransactionSignScheme};
|
||||
use relay_wococo_client::{HeaderId as WococoHeaderId, SigningParams as WococoSigningParams, Wococo};
|
||||
use sp_core::{Bytes, Pair};
|
||||
use std::{ops::RangeInclusive, time::Duration};
|
||||
use frame_support::weights::Weight;
|
||||
use messages_relay::{message_lane::MessageLane, relay_strategy::MixStrategy};
|
||||
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,
|
||||
};
|
||||
use substrate_relay_helper::{
|
||||
messages_lane::{
|
||||
select_delivery_transaction_limits, MessagesRelayParams, StandaloneMessagesMetrics,
|
||||
SubstrateMessageLane, SubstrateMessageLaneToSubstrate,
|
||||
},
|
||||
messages_source::SubstrateMessagesSource,
|
||||
messages_target::SubstrateMessagesTarget,
|
||||
STALL_TIMEOUT,
|
||||
};
|
||||
|
||||
/// Wococo-to-Rococo message lane.
|
||||
pub type WococoMessagesToRococo =
|
||||
pub type MessageLaneWococoMessagesToRococo =
|
||||
SubstrateMessageLaneToSubstrate<Wococo, WococoSigningParams, Rococo, RococoSigningParams>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WococoMessagesToRococo {
|
||||
message_lane: MessageLaneWococoMessagesToRococo,
|
||||
}
|
||||
|
||||
impl SubstrateMessageLane for WococoMessagesToRococo {
|
||||
const OUTBOUND_LANE_MESSAGE_DETAILS_METHOD: &'static str = bp_rococo::TO_ROCOCO_MESSAGE_DETAILS_METHOD;
|
||||
type MessageLane = MessageLaneWococoMessagesToRococo;
|
||||
const OUTBOUND_LANE_MESSAGE_DETAILS_METHOD: &'static str =
|
||||
bp_rococo::TO_ROCOCO_MESSAGE_DETAILS_METHOD;
|
||||
const OUTBOUND_LANE_LATEST_GENERATED_NONCE_METHOD: &'static str =
|
||||
bp_rococo::TO_ROCOCO_LATEST_GENERATED_NONCE_METHOD;
|
||||
const OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str = bp_rococo::TO_ROCOCO_LATEST_RECEIVED_NONCE_METHOD;
|
||||
const OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str =
|
||||
bp_rococo::TO_ROCOCO_LATEST_RECEIVED_NONCE_METHOD;
|
||||
|
||||
const INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str = bp_wococo::FROM_WOCOCO_LATEST_RECEIVED_NONCE_METHOD;
|
||||
const INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str =
|
||||
bp_wococo::FROM_WOCOCO_LATEST_RECEIVED_NONCE_METHOD;
|
||||
const INBOUND_LANE_LATEST_CONFIRMED_NONCE_METHOD: &'static str =
|
||||
bp_wococo::FROM_WOCOCO_LATEST_CONFIRMED_NONCE_METHOD;
|
||||
const INBOUND_LANE_UNREWARDED_RELAYERS_STATE: &'static str = bp_wococo::FROM_WOCOCO_UNREWARDED_RELAYERS_STATE;
|
||||
const INBOUND_LANE_UNREWARDED_RELAYERS_STATE: &'static str =
|
||||
bp_wococo::FROM_WOCOCO_UNREWARDED_RELAYERS_STATE;
|
||||
|
||||
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_wococo::BEST_FINALIZED_WOCOCO_HEADER_METHOD;
|
||||
const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str = bp_rococo::BEST_FINALIZED_ROCOCO_HEADER_METHOD;
|
||||
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str =
|
||||
bp_wococo::BEST_FINALIZED_WOCOCO_HEADER_METHOD;
|
||||
const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str =
|
||||
bp_rococo::BEST_FINALIZED_ROCOCO_HEADER_METHOD;
|
||||
|
||||
const MESSAGE_PALLET_NAME_AT_SOURCE: &'static str = bp_wococo::WITH_ROCOCO_MESSAGES_PALLET_NAME;
|
||||
const MESSAGE_PALLET_NAME_AT_TARGET: &'static str = bp_rococo::WITH_WOCOCO_MESSAGES_PALLET_NAME;
|
||||
|
||||
const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN: Weight =
|
||||
bp_rococo::PAY_INBOUND_DISPATCH_FEE_WEIGHT;
|
||||
|
||||
type SourceChain = Wococo;
|
||||
type TargetChain = Rococo;
|
||||
|
||||
fn source_transactions_author(&self) -> bp_wococo::AccountId {
|
||||
(*self.source_sign.public().as_array_ref()).into()
|
||||
(*self.message_lane.source_sign.public().as_array_ref()).into()
|
||||
}
|
||||
|
||||
fn make_messages_receiving_proof_transaction(
|
||||
&self,
|
||||
transaction_nonce: <Wococo as Chain>::Index,
|
||||
best_block_id: WococoHeaderId,
|
||||
transaction_nonce: IndexOf<Wococo>,
|
||||
_generated_at_block: RococoHeaderId,
|
||||
proof: <Self as MessageLane>::MessagesReceivingProof,
|
||||
proof: <Self::MessageLane as MessageLane>::MessagesReceivingProof,
|
||||
) -> Bytes {
|
||||
let (relayers_state, proof) = proof;
|
||||
let call = relay_wococo_client::runtime::Call::BridgeMessagesRococo(
|
||||
@@ -71,8 +100,16 @@ impl SubstrateMessageLane for WococoMessagesToRococo {
|
||||
relayers_state,
|
||||
),
|
||||
);
|
||||
let genesis_hash = *self.source_client.genesis_hash();
|
||||
let transaction = Wococo::sign_transaction(genesis_hash, &self.source_sign, transaction_nonce, call);
|
||||
let genesis_hash = *self.message_lane.source_client.genesis_hash();
|
||||
let transaction = Wococo::sign_transaction(
|
||||
genesis_hash,
|
||||
&self.message_lane.source_sign,
|
||||
relay_substrate_client::TransactionEra::new(
|
||||
best_block_id,
|
||||
self.message_lane.source_transactions_mortality,
|
||||
),
|
||||
UnsignedTransaction::new(call, transaction_nonce),
|
||||
);
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Prepared Rococo -> Wococo confirmation transaction. Weight: <unknown>/{}, size: {}/{}",
|
||||
@@ -84,34 +121,39 @@ impl SubstrateMessageLane for WococoMessagesToRococo {
|
||||
}
|
||||
|
||||
fn target_transactions_author(&self) -> bp_rococo::AccountId {
|
||||
(*self.target_sign.public().as_array_ref()).into()
|
||||
(*self.message_lane.target_sign.public().as_array_ref()).into()
|
||||
}
|
||||
|
||||
fn make_messages_delivery_transaction(
|
||||
&self,
|
||||
transaction_nonce: <Rococo as Chain>::Index,
|
||||
best_block_id: WococoHeaderId,
|
||||
transaction_nonce: IndexOf<Rococo>,
|
||||
_generated_at_header: WococoHeaderId,
|
||||
_nonces: RangeInclusive<MessageNonce>,
|
||||
proof: <Self as MessageLane>::MessagesProof,
|
||||
proof: <Self::MessageLane as MessageLane>::MessagesProof,
|
||||
) -> Bytes {
|
||||
let (dispatch_weight, proof) = proof;
|
||||
let FromBridgedChainMessagesProof {
|
||||
ref nonces_start,
|
||||
ref nonces_end,
|
||||
..
|
||||
} = proof;
|
||||
let FromBridgedChainMessagesProof { ref nonces_start, ref nonces_end, .. } = proof;
|
||||
let messages_count = nonces_end - nonces_start + 1;
|
||||
|
||||
let call = relay_rococo_client::runtime::Call::BridgeMessagesWococo(
|
||||
relay_rococo_client::runtime::BridgeMessagesWococoCall::receive_messages_proof(
|
||||
self.relayer_id_at_source.clone(),
|
||||
self.message_lane.relayer_id_at_source.clone(),
|
||||
proof,
|
||||
messages_count as _,
|
||||
dispatch_weight,
|
||||
),
|
||||
);
|
||||
let genesis_hash = *self.target_client.genesis_hash();
|
||||
let transaction = Rococo::sign_transaction(genesis_hash, &self.target_sign, transaction_nonce, call);
|
||||
let genesis_hash = *self.message_lane.target_client.genesis_hash();
|
||||
let transaction = Rococo::sign_transaction(
|
||||
genesis_hash,
|
||||
&self.message_lane.target_sign,
|
||||
relay_substrate_client::TransactionEra::new(
|
||||
best_block_id,
|
||||
self.message_lane.target_transactions_mortality,
|
||||
),
|
||||
UnsignedTransaction::new(call, transaction_nonce),
|
||||
);
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Prepared Wococo -> Rococo delivery transaction. Weight: <unknown>/{}, size: {}/{}",
|
||||
@@ -124,28 +166,42 @@ impl SubstrateMessageLane for WococoMessagesToRococo {
|
||||
}
|
||||
|
||||
/// Wococo node as messages source.
|
||||
type WococoSourceClient =
|
||||
SubstrateMessagesSource<Wococo, WococoMessagesToRococo, relay_wococo_client::runtime::WithRococoMessagesInstance>;
|
||||
type WococoSourceClient = SubstrateMessagesSource<WococoMessagesToRococo>;
|
||||
|
||||
/// Rococo node as messages target.
|
||||
type RococoTargetClient =
|
||||
SubstrateMessagesTarget<Rococo, WococoMessagesToRococo, relay_rococo_client::runtime::WithWococoMessagesInstance>;
|
||||
type RococoTargetClient = SubstrateMessagesTarget<WococoMessagesToRococo>;
|
||||
|
||||
/// Run Wococo-to-Rococo messages sync.
|
||||
pub async fn run(
|
||||
params: MessagesRelayParams<Wococo, WococoSigningParams, Rococo, RococoSigningParams>,
|
||||
) -> Result<(), String> {
|
||||
let stall_timeout = Duration::from_secs(5 * 60);
|
||||
params: MessagesRelayParams<
|
||||
Wococo,
|
||||
WococoSigningParams,
|
||||
Rococo,
|
||||
RococoSigningParams,
|
||||
MixStrategy,
|
||||
>,
|
||||
) -> anyhow::Result<()> {
|
||||
let stall_timeout = relay_substrate_client::bidirectional_transaction_stall_timeout(
|
||||
params.source_transactions_mortality,
|
||||
params.target_transactions_mortality,
|
||||
Wococo::AVERAGE_BLOCK_INTERVAL,
|
||||
Rococo::AVERAGE_BLOCK_INTERVAL,
|
||||
STALL_TIMEOUT,
|
||||
);
|
||||
let relayer_id_at_wococo = (*params.source_sign.public().as_array_ref()).into();
|
||||
|
||||
let lane_id = params.lane_id;
|
||||
let source_client = params.source_client;
|
||||
let lane = WococoMessagesToRococo {
|
||||
source_client: source_client.clone(),
|
||||
source_sign: params.source_sign,
|
||||
target_client: params.target_client.clone(),
|
||||
target_sign: params.target_sign,
|
||||
relayer_id_at_source: relayer_id_at_wococo,
|
||||
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_sign: params.target_sign,
|
||||
target_transactions_mortality: params.target_transactions_mortality,
|
||||
relayer_id_at_source: relayer_id_at_wococo,
|
||||
},
|
||||
};
|
||||
|
||||
// 2/3 is reserved for proofs and tx overhead
|
||||
@@ -153,14 +209,14 @@ pub async fn run(
|
||||
// we don't know exact weights of the Rococo runtime. So to guess weights we'll be using
|
||||
// weights from Rialto and then simply dividing it by x2.
|
||||
let (max_messages_in_single_batch, max_messages_weight_in_single_batch) =
|
||||
select_delivery_transaction_limits::<pallet_bridge_messages::weights::RialtoWeight<rialto_runtime::Runtime>>(
|
||||
select_delivery_transaction_limits::<
|
||||
pallet_bridge_messages::weights::RialtoWeight<rialto_runtime::Runtime>,
|
||||
>(
|
||||
bp_rococo::max_extrinsic_weight(),
|
||||
bp_rococo::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
|
||||
);
|
||||
let (max_messages_in_single_batch, max_messages_weight_in_single_batch) = (
|
||||
max_messages_in_single_batch / 2,
|
||||
max_messages_weight_in_single_batch / 2,
|
||||
);
|
||||
let (max_messages_in_single_batch, max_messages_weight_in_single_batch) =
|
||||
(max_messages_in_single_batch / 2, max_messages_weight_in_single_batch / 2);
|
||||
|
||||
log::info!(
|
||||
target: "bridge",
|
||||
@@ -168,13 +224,25 @@ pub async fn run(
|
||||
Wococo relayer account id: {:?}\n\t\
|
||||
Max messages in single transaction: {}\n\t\
|
||||
Max messages size in single transaction: {}\n\t\
|
||||
Max messages weight in single transaction: {}",
|
||||
lane.relayer_id_at_source,
|
||||
Max messages weight in single transaction: {}\n\t\
|
||||
Tx mortality: {:?}/{:?}\n\t\
|
||||
Stall timeout: {:?}",
|
||||
lane.message_lane.relayer_id_at_source,
|
||||
max_messages_in_single_batch,
|
||||
max_messages_size_in_single_batch,
|
||||
max_messages_weight_in_single_batch,
|
||||
params.source_transactions_mortality,
|
||||
params.target_transactions_mortality,
|
||||
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(),
|
||||
)?;
|
||||
messages_relay::message_lane_loop::run(
|
||||
messages_relay::message_lane_loop::Params {
|
||||
lane: lane_id,
|
||||
@@ -183,45 +251,48 @@ pub async fn run(
|
||||
reconnect_delay: relay_utils::relay_loop::RECONNECT_DELAY,
|
||||
stall_timeout,
|
||||
delivery_params: messages_relay::message_lane_loop::MessageDeliveryParams {
|
||||
max_unrewarded_relayer_entries_at_target: bp_rococo::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
|
||||
max_unconfirmed_nonces_at_target: bp_rococo::MAX_UNCONFIRMED_MESSAGES_AT_INBOUND_LANE,
|
||||
max_unrewarded_relayer_entries_at_target:
|
||||
bp_rococo::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
|
||||
max_unconfirmed_nonces_at_target:
|
||||
bp_rococo::MAX_UNCONFIRMED_MESSAGES_AT_INBOUND_LANE,
|
||||
max_messages_in_single_batch,
|
||||
max_messages_weight_in_single_batch,
|
||||
max_messages_size_in_single_batch,
|
||||
relayer_mode: messages_relay::message_lane_loop::RelayerMode::Altruistic,
|
||||
relay_strategy: params.relay_strategy,
|
||||
},
|
||||
},
|
||||
WococoSourceClient::new(
|
||||
source_client.clone(),
|
||||
lane.clone(),
|
||||
lane_id,
|
||||
ROCOCO_CHAIN_ID,
|
||||
params.target_to_source_headers_relay,
|
||||
),
|
||||
RococoTargetClient::new(
|
||||
params.target_client,
|
||||
lane,
|
||||
lane_id,
|
||||
WOCOCO_CHAIN_ID,
|
||||
metrics_values,
|
||||
params.source_to_target_headers_relay,
|
||||
),
|
||||
relay_utils::relay_metrics(
|
||||
Some(messages_relay::message_lane_loop::metrics_prefix::<
|
||||
WococoMessagesToRococo,
|
||||
>(&lane_id)),
|
||||
params.metrics_params,
|
||||
)
|
||||
.standalone_metric(|registry, prefix| {
|
||||
StorageProofOverheadMetric::new(
|
||||
registry,
|
||||
prefix,
|
||||
source_client.clone(),
|
||||
"wococo_storage_proof_overhead".into(),
|
||||
"Wococo storage proof overhead".into(),
|
||||
)
|
||||
})?
|
||||
.into_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,
|
||||
source_client: Client<Wococo>,
|
||||
) -> anyhow::Result<(MetricsParams, StandaloneMessagesMetrics)> {
|
||||
substrate_relay_helper::messages_lane::add_standalone_metrics::<WococoMessagesToRococo>(
|
||||
metrics_prefix,
|
||||
metrics_params,
|
||||
source_client,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -14,17 +14,18 @@
|
||||
// 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 structopt::clap::arg_enum;
|
||||
use strum::{EnumString, EnumVariantNames};
|
||||
|
||||
arg_enum! {
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
/// Supported full bridges (headers + messages).
|
||||
pub enum FullBridge {
|
||||
MillauToRialto,
|
||||
RialtoToMillau,
|
||||
RococoToWococo,
|
||||
WococoToRococo,
|
||||
}
|
||||
#[derive(Debug, PartialEq, Eq, EnumString, EnumVariantNames)]
|
||||
#[strum(serialize_all = "kebab_case")]
|
||||
/// Supported full bridges (headers + messages).
|
||||
pub enum FullBridge {
|
||||
MillauToRialto,
|
||||
RialtoToMillau,
|
||||
RococoToWococo,
|
||||
WococoToRococo,
|
||||
KusamaToPolkadot,
|
||||
PolkadotToKusama,
|
||||
}
|
||||
|
||||
impl FullBridge {
|
||||
@@ -35,6 +36,8 @@ impl FullBridge {
|
||||
Self::RialtoToMillau => RIALTO_TO_MILLAU_INDEX,
|
||||
Self::RococoToWococo => ROCOCO_TO_WOCOCO_INDEX,
|
||||
Self::WococoToRococo => WOCOCO_TO_ROCOCO_INDEX,
|
||||
Self::KusamaToPolkadot => KUSAMA_TO_POLKADOT_INDEX,
|
||||
Self::PolkadotToKusama => POLKADOT_TO_KUSAMA_INDEX,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,6 +46,8 @@ pub const RIALTO_TO_MILLAU_INDEX: u8 = 0;
|
||||
pub const MILLAU_TO_RIALTO_INDEX: u8 = 0;
|
||||
pub const ROCOCO_TO_WOCOCO_INDEX: u8 = 0;
|
||||
pub const WOCOCO_TO_ROCOCO_INDEX: u8 = 0;
|
||||
pub const KUSAMA_TO_POLKADOT_INDEX: u8 = 0;
|
||||
pub const POLKADOT_TO_KUSAMA_INDEX: u8 = 0;
|
||||
|
||||
/// The macro allows executing bridge-specific code without going fully generic.
|
||||
///
|
||||
@@ -139,6 +144,50 @@ macro_rules! select_full_bridge {
|
||||
#[allow(unused_imports)]
|
||||
use relay_wococo_client::runtime::wococo_to_rococo_account_ownership_digest as account_ownership_digest;
|
||||
|
||||
$generic
|
||||
}
|
||||
FullBridge::KusamaToPolkadot => {
|
||||
type Source = relay_kusama_client::Kusama;
|
||||
#[allow(dead_code)]
|
||||
type Target = relay_polkadot_client::Polkadot;
|
||||
|
||||
// Derive-account
|
||||
#[allow(unused_imports)]
|
||||
use bp_polkadot::derive_account_from_kusama_id as derive_account;
|
||||
|
||||
// Relay-messages
|
||||
#[allow(unused_imports)]
|
||||
use crate::chains::kusama_messages_to_polkadot::run as relay_messages;
|
||||
|
||||
// Send-message / Estimate-fee
|
||||
#[allow(unused_imports)]
|
||||
use bp_polkadot::TO_POLKADOT_ESTIMATE_MESSAGE_FEE_METHOD as ESTIMATE_MESSAGE_FEE_METHOD;
|
||||
// Send-message
|
||||
#[allow(unused_imports)]
|
||||
use relay_kusama_client::runtime::kusama_to_polkadot_account_ownership_digest as account_ownership_digest;
|
||||
|
||||
$generic
|
||||
}
|
||||
FullBridge::PolkadotToKusama => {
|
||||
type Source = relay_polkadot_client::Polkadot;
|
||||
#[allow(dead_code)]
|
||||
type Target = relay_kusama_client::Kusama;
|
||||
|
||||
// Derive-account
|
||||
#[allow(unused_imports)]
|
||||
use bp_kusama::derive_account_from_polkadot_id as derive_account;
|
||||
|
||||
// Relay-messages
|
||||
#[allow(unused_imports)]
|
||||
use crate::chains::polkadot_messages_to_kusama::run as relay_messages;
|
||||
|
||||
// Send-message / Estimate-fee
|
||||
#[allow(unused_imports)]
|
||||
use bp_kusama::TO_KUSAMA_ESTIMATE_MESSAGE_FEE_METHOD as ESTIMATE_MESSAGE_FEE_METHOD;
|
||||
// Send-message
|
||||
#[allow(unused_imports)]
|
||||
use relay_polkadot_client::runtime::polkadot_to_kusama_account_ownership_digest as account_ownership_digest;
|
||||
|
||||
$generic
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,10 +14,13 @@
|
||||
// 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::cli::{bridge::FullBridge, AccountId};
|
||||
use crate::select_full_bridge;
|
||||
use crate::{
|
||||
cli::{bridge::FullBridge, AccountId},
|
||||
select_full_bridge,
|
||||
};
|
||||
use relay_substrate_client::Chain;
|
||||
use structopt::StructOpt;
|
||||
use strum::VariantNames;
|
||||
|
||||
/// Given a source chain `AccountId`, derive the corresponding `AccountId` for the target chain.
|
||||
///
|
||||
@@ -28,7 +31,7 @@ use structopt::StructOpt;
|
||||
#[derive(StructOpt)]
|
||||
pub struct DeriveAccount {
|
||||
/// A bridge instance to initialize.
|
||||
#[structopt(possible_values = &FullBridge::variants(), case_insensitive = true)]
|
||||
#[structopt(possible_values = FullBridge::VARIANTS, case_insensitive = true)]
|
||||
bridge: FullBridge,
|
||||
/// Source-chain address to derive Target-chain address from.
|
||||
account: AccountId,
|
||||
@@ -54,11 +57,7 @@ impl DeriveAccount {
|
||||
select_full_bridge!(self.bridge, {
|
||||
let (account, derived_account) = self.derive_account();
|
||||
println!("Source address:\n{} ({})", account, Source::NAME);
|
||||
println!(
|
||||
"->Corresponding (derived) address:\n{} ({})",
|
||||
derived_account,
|
||||
Target::NAME,
|
||||
);
|
||||
println!("->Corresponding (derived) address:\n{} ({})", derived_account, Target::NAME,);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
@@ -80,9 +79,9 @@ mod tests {
|
||||
let millau = "752paRyW1EGfq9YLTSSqcSJ5hqnBDidBmaftGhBo8fy6ypW9";
|
||||
|
||||
// when
|
||||
let (rialto_parsed, rialto_derived) = derive_account_cli("RialtoToMillau", rialto);
|
||||
let (millau_parsed, millau_derived) = derive_account_cli("MillauToRialto", millau);
|
||||
let (millau2_parsed, millau2_derived) = derive_account_cli("MillauToRialto", rialto);
|
||||
let (rialto_parsed, rialto_derived) = derive_account_cli("rialto-to-millau", rialto);
|
||||
let (millau_parsed, millau_derived) = derive_account_cli("millau-to-rialto", millau);
|
||||
let (millau2_parsed, millau2_derived) = derive_account_cli("millau-to-rialto", rialto);
|
||||
|
||||
// then
|
||||
assert_eq!(format!("{}", rialto_parsed), rialto);
|
||||
|
||||
@@ -14,18 +14,22 @@
|
||||
// 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::cli::bridge::FullBridge;
|
||||
use crate::cli::{AccountId, Balance, CliChain, ExplicitOrMaximal, HexBytes, HexLaneId};
|
||||
use crate::select_full_bridge;
|
||||
use crate::{
|
||||
cli::{
|
||||
bridge::FullBridge, AccountId, Balance, CliChain, ExplicitOrMaximal, HexBytes, HexLaneId,
|
||||
},
|
||||
select_full_bridge,
|
||||
};
|
||||
use frame_support::weights::DispatchInfo;
|
||||
use relay_substrate_client::Chain;
|
||||
use structopt::StructOpt;
|
||||
use strum::VariantNames;
|
||||
|
||||
/// Encode source chain runtime call.
|
||||
#[derive(StructOpt, Debug)]
|
||||
pub struct EncodeCall {
|
||||
/// A bridge instance to encode call for.
|
||||
#[structopt(possible_values = &FullBridge::variants(), case_insensitive = true)]
|
||||
#[structopt(possible_values = FullBridge::VARIANTS, case_insensitive = true)]
|
||||
bridge: FullBridge,
|
||||
#[structopt(flatten)]
|
||||
call: Call,
|
||||
@@ -125,31 +129,30 @@ pub(crate) fn preprocess_call<Source: CliEncodeCall + CliChain, Target: CliEncod
|
||||
bridge_instance: u8,
|
||||
) {
|
||||
match *call {
|
||||
Call::Raw { .. } => {}
|
||||
Call::Remark {
|
||||
ref remark_size,
|
||||
ref mut remark_payload,
|
||||
} => {
|
||||
Call::Raw { .. } => {},
|
||||
Call::Remark { ref remark_size, ref mut remark_payload } =>
|
||||
if remark_payload.is_none() {
|
||||
*remark_payload = Some(HexBytes(generate_remark_payload(
|
||||
remark_size,
|
||||
compute_maximal_message_arguments_size(Source::max_extrinsic_size(), Target::max_extrinsic_size()),
|
||||
compute_maximal_message_arguments_size(
|
||||
Source::max_extrinsic_size(),
|
||||
Target::max_extrinsic_size(),
|
||||
),
|
||||
)));
|
||||
}
|
||||
}
|
||||
},
|
||||
Call::Transfer { ref mut recipient, .. } => {
|
||||
recipient.enforce_chain::<Source>();
|
||||
}
|
||||
Call::BridgeSendMessage {
|
||||
ref mut bridge_instance_index,
|
||||
..
|
||||
} => {
|
||||
},
|
||||
Call::BridgeSendMessage { ref mut bridge_instance_index, .. } => {
|
||||
*bridge_instance_index = bridge_instance;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn generate_remark_payload(remark_size: &Option<ExplicitOrMaximal<usize>>, maximal_allowed_size: u32) -> Vec<u8> {
|
||||
fn generate_remark_payload(
|
||||
remark_size: &Option<ExplicitOrMaximal<usize>>,
|
||||
maximal_allowed_size: u32,
|
||||
) -> Vec<u8> {
|
||||
match remark_size {
|
||||
Some(ExplicitOrMaximal::Explicit(remark_size)) => vec![0; *remark_size],
|
||||
Some(ExplicitOrMaximal::Maximal) => vec![0; maximal_allowed_size as _],
|
||||
@@ -171,9 +174,11 @@ pub(crate) fn compute_maximal_message_arguments_size(
|
||||
) -> u32 {
|
||||
// assume that both signed extensions and other arguments fit 1KB
|
||||
let service_tx_bytes_on_source_chain = 1024;
|
||||
let maximal_source_extrinsic_size = maximal_source_extrinsic_size - service_tx_bytes_on_source_chain;
|
||||
let maximal_call_size =
|
||||
bridge_runtime_common::messages::target::maximal_incoming_message_size(maximal_target_extrinsic_size);
|
||||
let maximal_source_extrinsic_size =
|
||||
maximal_source_extrinsic_size - service_tx_bytes_on_source_chain;
|
||||
let maximal_call_size = bridge_runtime_common::messages::target::maximal_incoming_message_size(
|
||||
maximal_target_extrinsic_size,
|
||||
);
|
||||
let maximal_call_size = if maximal_call_size > maximal_source_extrinsic_size {
|
||||
maximal_source_extrinsic_size
|
||||
} else {
|
||||
@@ -188,13 +193,14 @@ pub(crate) fn compute_maximal_message_arguments_size(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::cli::send_message::SendMessage;
|
||||
|
||||
#[test]
|
||||
fn should_encode_transfer_call() {
|
||||
// given
|
||||
let mut encode_call = EncodeCall::from_iter(vec![
|
||||
"encode-call",
|
||||
"RialtoToMillau",
|
||||
"rialto-to-millau",
|
||||
"transfer",
|
||||
"--amount",
|
||||
"12345",
|
||||
@@ -208,20 +214,21 @@ mod tests {
|
||||
// then
|
||||
assert_eq!(
|
||||
format!("{:?}", hex),
|
||||
"0x0c00d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27de5c0"
|
||||
"0x040000d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27de5c0"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_encode_remark_with_default_payload() {
|
||||
// given
|
||||
let mut encode_call = EncodeCall::from_iter(vec!["encode-call", "RialtoToMillau", "remark"]);
|
||||
let mut encode_call =
|
||||
EncodeCall::from_iter(vec!["encode-call", "rialto-to-millau", "remark"]);
|
||||
|
||||
// when
|
||||
let hex = encode_call.encode().unwrap();
|
||||
|
||||
// then
|
||||
assert!(format!("{:?}", hex).starts_with("0x070154556e69782074696d653a"));
|
||||
assert!(format!("{:?}", hex).starts_with("0x000154556e69782074696d653a"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -229,7 +236,7 @@ mod tests {
|
||||
// given
|
||||
let mut encode_call = EncodeCall::from_iter(vec![
|
||||
"encode-call",
|
||||
"RialtoToMillau",
|
||||
"rialto-to-millau",
|
||||
"remark",
|
||||
"--remark-payload",
|
||||
"1234",
|
||||
@@ -239,20 +246,25 @@ mod tests {
|
||||
let hex = encode_call.encode().unwrap();
|
||||
|
||||
// then
|
||||
assert_eq!(format!("{:?}", hex), "0x0701081234");
|
||||
assert_eq!(format!("{:?}", hex), "0x0001081234");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_encode_remark_with_size() {
|
||||
// given
|
||||
let mut encode_call =
|
||||
EncodeCall::from_iter(vec!["encode-call", "RialtoToMillau", "remark", "--remark-size", "12"]);
|
||||
let mut encode_call = EncodeCall::from_iter(vec![
|
||||
"encode-call",
|
||||
"rialto-to-millau",
|
||||
"remark",
|
||||
"--remark-size",
|
||||
"12",
|
||||
]);
|
||||
|
||||
// when
|
||||
let hex = encode_call.encode().unwrap();
|
||||
|
||||
// then
|
||||
assert_eq!(format!("{:?}", hex), "0x070130000000000000000000000000");
|
||||
assert_eq!(format!("{:?}", hex), "0x000130000000000000000000000000");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -260,7 +272,7 @@ mod tests {
|
||||
// when
|
||||
let err = EncodeCall::from_iter_safe(vec![
|
||||
"encode-call",
|
||||
"RialtoToMillau",
|
||||
"rialto-to-millau",
|
||||
"remark",
|
||||
"--remark-payload",
|
||||
"1234",
|
||||
@@ -273,6 +285,68 @@ mod tests {
|
||||
assert_eq!(err.kind, structopt::clap::ErrorKind::ArgumentConflict);
|
||||
|
||||
let info = err.info.unwrap();
|
||||
assert!(info.contains(&"remark-payload".to_string()) | info.contains(&"remark-size".to_string()))
|
||||
assert!(
|
||||
info.contains(&"remark-payload".to_string()) |
|
||||
info.contains(&"remark-size".to_string())
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_encode_raw_call() {
|
||||
// given
|
||||
let mut encode_call = EncodeCall::from_iter(vec![
|
||||
"encode-call",
|
||||
"rialto-to-millau",
|
||||
"raw",
|
||||
"040000d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27de5c0",
|
||||
]);
|
||||
|
||||
// when
|
||||
let hex = encode_call.encode().unwrap();
|
||||
|
||||
// then
|
||||
assert_eq!(
|
||||
format!("{:?}", hex),
|
||||
"0x040000d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27de5c0"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_encode_bridge_send_message_call() {
|
||||
// given
|
||||
let encode_message = SendMessage::from_iter(vec![
|
||||
"send-message",
|
||||
"millau-to-rialto",
|
||||
"--source-port",
|
||||
"10946",
|
||||
"--source-signer",
|
||||
"//Alice",
|
||||
"--target-signer",
|
||||
"//Alice",
|
||||
"--origin",
|
||||
"Target",
|
||||
"remark",
|
||||
])
|
||||
.encode_payload()
|
||||
.unwrap();
|
||||
|
||||
let mut encode_call = EncodeCall::from_iter(vec![
|
||||
"encode-call",
|
||||
"rialto-to-millau",
|
||||
"bridge-send-message",
|
||||
"--fee",
|
||||
"12345",
|
||||
"--payload",
|
||||
format!("{:}", &HexBytes::encode(&encode_message)).as_str(),
|
||||
]);
|
||||
|
||||
// when
|
||||
let call_hex = encode_call.encode().unwrap();
|
||||
|
||||
// then
|
||||
assert!(format!("{:?}", call_hex).starts_with(
|
||||
"0x10030000000001000000381409000000000001d43593c715fdd31c61141abd04a99fd6822c8558854cc\
|
||||
de39a5684e7a56da27d01d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,12 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::cli::{bridge::FullBridge, AccountId, CliChain, HexBytes};
|
||||
use crate::select_full_bridge;
|
||||
use crate::{
|
||||
cli::{bridge::FullBridge, AccountId, CliChain, HexBytes},
|
||||
select_full_bridge,
|
||||
};
|
||||
use structopt::StructOpt;
|
||||
use strum::VariantNames;
|
||||
|
||||
/// Generic message payload.
|
||||
#[derive(StructOpt, Debug, PartialEq, Eq)]
|
||||
@@ -41,7 +44,7 @@ pub enum MessagePayload {
|
||||
#[derive(StructOpt)]
|
||||
pub struct EncodeMessage {
|
||||
/// A bridge instance to initialize.
|
||||
#[structopt(possible_values = &FullBridge::variants(), case_insensitive = true)]
|
||||
#[structopt(possible_values = FullBridge::VARIANTS, case_insensitive = true)]
|
||||
bridge: FullBridge,
|
||||
#[structopt(flatten)]
|
||||
payload: MessagePayload,
|
||||
@@ -51,7 +54,8 @@ impl EncodeMessage {
|
||||
/// Run the command.
|
||||
pub fn encode(self) -> anyhow::Result<HexBytes> {
|
||||
select_full_bridge!(self.bridge, {
|
||||
let payload = Source::encode_message(self.payload).map_err(|e| anyhow::format_err!("{}", e))?;
|
||||
let payload =
|
||||
Source::encode_message(self.payload).map_err(|e| anyhow::format_err!("{}", e))?;
|
||||
Ok(HexBytes::encode(&payload))
|
||||
})
|
||||
}
|
||||
@@ -73,7 +77,8 @@ mod tests {
|
||||
fn should_encode_raw_message() {
|
||||
// given
|
||||
let msg = "01000000e88514000000000002d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d003c040130000000000000000000000000";
|
||||
let encode_message = EncodeMessage::from_iter(vec!["encode-message", "MillauToRialto", "raw", msg]);
|
||||
let encode_message =
|
||||
EncodeMessage::from_iter(vec!["encode-message", "rialto-to-millau", "raw", msg]);
|
||||
|
||||
// when
|
||||
let hex = encode_message.encode().unwrap();
|
||||
@@ -88,7 +93,7 @@ mod tests {
|
||||
let sender = sp_keyring::AccountKeyring::Alice.to_account_id().to_ss58check();
|
||||
let encode_message = EncodeMessage::from_iter(vec![
|
||||
"encode-message",
|
||||
"RialtoToMillau",
|
||||
"rialto-to-millau",
|
||||
"call",
|
||||
"--sender",
|
||||
&sender,
|
||||
@@ -101,6 +106,6 @@ mod tests {
|
||||
let hex = encode_message.encode().unwrap();
|
||||
|
||||
// then
|
||||
assert_eq!(format!("{:?}", hex), "0x01000000b0d60f000000000002d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d003c040130000000000000000000000000");
|
||||
assert_eq!(format!("{:?}", hex), "0x0100000010f108000000000002d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d003c000130000000000000000000000000");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,18 +14,21 @@
|
||||
// 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::cli::bridge::FullBridge;
|
||||
use crate::cli::{Balance, CliChain, HexBytes, HexLaneId, SourceConnectionParams};
|
||||
use crate::select_full_bridge;
|
||||
use crate::{
|
||||
cli::{bridge::FullBridge, Balance, CliChain, HexBytes, HexLaneId, SourceConnectionParams},
|
||||
select_full_bridge,
|
||||
};
|
||||
use bp_runtime::BalanceOf;
|
||||
use codec::{Decode, Encode};
|
||||
use relay_substrate_client::Chain;
|
||||
use structopt::StructOpt;
|
||||
use strum::VariantNames;
|
||||
|
||||
/// Estimate Delivery & Dispatch Fee command.
|
||||
#[derive(StructOpt, Debug, PartialEq, Eq)]
|
||||
pub struct EstimateFee {
|
||||
/// A bridge instance to encode call for.
|
||||
#[structopt(possible_values = &FullBridge::variants(), case_insensitive = true)]
|
||||
#[structopt(possible_values = FullBridge::VARIANTS, case_insensitive = true)]
|
||||
bridge: FullBridge,
|
||||
#[structopt(flatten)]
|
||||
source: SourceConnectionParams,
|
||||
@@ -40,21 +43,21 @@ pub struct EstimateFee {
|
||||
impl EstimateFee {
|
||||
/// Run the command.
|
||||
pub async fn run(self) -> anyhow::Result<()> {
|
||||
let Self {
|
||||
source,
|
||||
bridge,
|
||||
lane,
|
||||
payload,
|
||||
} = self;
|
||||
let Self { source, bridge, lane, payload } = self;
|
||||
|
||||
select_full_bridge!(bridge, {
|
||||
let source_client = source.to_client::<Source>().await?;
|
||||
let lane = lane.into();
|
||||
let payload = Source::encode_message(payload).map_err(|e| anyhow::format_err!("{:?}", e))?;
|
||||
let payload =
|
||||
Source::encode_message(payload).map_err(|e| anyhow::format_err!("{:?}", e))?;
|
||||
|
||||
let fee: <Source as Chain>::Balance =
|
||||
estimate_message_delivery_and_dispatch_fee(&source_client, ESTIMATE_MESSAGE_FEE_METHOD, lane, payload)
|
||||
.await?;
|
||||
let fee: BalanceOf<Source> = estimate_message_delivery_and_dispatch_fee(
|
||||
&source_client,
|
||||
ESTIMATE_MESSAGE_FEE_METHOD,
|
||||
lane,
|
||||
payload,
|
||||
)
|
||||
.await?;
|
||||
|
||||
log::info!(target: "bridge", "Fee: {:?}", Balance(fee as _));
|
||||
println!("{}", fee);
|
||||
@@ -72,10 +75,11 @@ pub(crate) async fn estimate_message_delivery_and_dispatch_fee<Fee: Decode, C: C
|
||||
let encoded_response = client
|
||||
.state_call(estimate_fee_method.into(), (lane, payload).encode().into(), None)
|
||||
.await?;
|
||||
let decoded_response: Option<Fee> =
|
||||
Decode::decode(&mut &encoded_response.0[..]).map_err(relay_substrate_client::Error::ResponseParseFailed)?;
|
||||
let fee = decoded_response
|
||||
.ok_or_else(|| anyhow::format_err!("Unable to decode fee from: {:?}", HexBytes(encoded_response.to_vec())))?;
|
||||
let decoded_response: Option<Fee> = Decode::decode(&mut &encoded_response.0[..])
|
||||
.map_err(relay_substrate_client::Error::ResponseParseFailed)?;
|
||||
let fee = decoded_response.ok_or_else(|| {
|
||||
anyhow::format_err!("Unable to decode fee from: {:?}", HexBytes(encoded_response.to_vec()))
|
||||
})?;
|
||||
Ok(fee)
|
||||
}
|
||||
|
||||
@@ -93,7 +97,7 @@ mod tests {
|
||||
// when
|
||||
let res = EstimateFee::from_iter(vec![
|
||||
"estimate_fee",
|
||||
"RialtoToMillau",
|
||||
"rialto-to-millau",
|
||||
"--source-port",
|
||||
"1234",
|
||||
"call",
|
||||
|
||||
@@ -18,15 +18,16 @@ use crate::cli::{SourceConnectionParams, TargetConnectionParams, TargetSigningPa
|
||||
use bp_header_chain::InitializationData;
|
||||
use bp_runtime::Chain as ChainBase;
|
||||
use codec::Encode;
|
||||
use relay_substrate_client::{Chain, TransactionSignScheme};
|
||||
use relay_substrate_client::{Chain, TransactionSignScheme, UnsignedTransaction};
|
||||
use sp_core::{Bytes, Pair};
|
||||
use structopt::{clap::arg_enum, StructOpt};
|
||||
use structopt::StructOpt;
|
||||
use strum::{EnumString, EnumVariantNames, VariantNames};
|
||||
|
||||
/// Initialize bridge pallet.
|
||||
#[derive(StructOpt)]
|
||||
pub struct InitBridge {
|
||||
/// A bridge instance to initialize.
|
||||
#[structopt(possible_values = &InitBridgeName::variants(), case_insensitive = true)]
|
||||
#[structopt(possible_values = InitBridgeName::VARIANTS, case_insensitive = true)]
|
||||
bridge: InitBridgeName,
|
||||
#[structopt(flatten)]
|
||||
source: SourceConnectionParams,
|
||||
@@ -36,17 +37,17 @@ pub struct InitBridge {
|
||||
target_sign: TargetSigningParams,
|
||||
}
|
||||
|
||||
// TODO [#851] Use kebab-case.
|
||||
arg_enum! {
|
||||
#[derive(Debug)]
|
||||
/// Bridge to initialize.
|
||||
pub enum InitBridgeName {
|
||||
MillauToRialto,
|
||||
RialtoToMillau,
|
||||
WestendToMillau,
|
||||
RococoToWococo,
|
||||
WococoToRococo,
|
||||
}
|
||||
#[derive(Debug, EnumString, EnumVariantNames)]
|
||||
#[strum(serialize_all = "kebab_case")]
|
||||
/// Bridge to initialize.
|
||||
pub enum InitBridgeName {
|
||||
MillauToRialto,
|
||||
RialtoToMillau,
|
||||
WestendToMillau,
|
||||
RococoToWococo,
|
||||
WococoToRococo,
|
||||
KusamaToPolkadot,
|
||||
PolkadotToKusama,
|
||||
}
|
||||
|
||||
macro_rules! select_bridge {
|
||||
@@ -59,14 +60,17 @@ macro_rules! select_bridge {
|
||||
fn encode_init_bridge(
|
||||
init_data: InitializationData<<Source as ChainBase>::Header>,
|
||||
) -> <Target as Chain>::Call {
|
||||
rialto_runtime::SudoCall::sudo(Box::new(
|
||||
rialto_runtime::BridgeGrandpaMillauCall::initialize(init_data).into(),
|
||||
))
|
||||
rialto_runtime::SudoCall::sudo {
|
||||
call: Box::new(
|
||||
rialto_runtime::BridgeGrandpaMillauCall::initialize { init_data }
|
||||
.into(),
|
||||
),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
$generic
|
||||
}
|
||||
},
|
||||
InitBridgeName::RialtoToMillau => {
|
||||
type Source = relay_rialto_client::Rialto;
|
||||
type Target = relay_millau_client::Millau;
|
||||
@@ -74,15 +78,17 @@ macro_rules! select_bridge {
|
||||
fn encode_init_bridge(
|
||||
init_data: InitializationData<<Source as ChainBase>::Header>,
|
||||
) -> <Target as Chain>::Call {
|
||||
let initialize_call = millau_runtime::BridgeGrandpaRialtoCall::<
|
||||
let initialize_call = millau_runtime::BridgeGrandpaCall::<
|
||||
millau_runtime::Runtime,
|
||||
millau_runtime::RialtoGrandpaInstance,
|
||||
>::initialize(init_data);
|
||||
millau_runtime::SudoCall::sudo(Box::new(initialize_call.into())).into()
|
||||
>::initialize {
|
||||
init_data,
|
||||
};
|
||||
millau_runtime::SudoCall::sudo { call: Box::new(initialize_call.into()) }.into()
|
||||
}
|
||||
|
||||
$generic
|
||||
}
|
||||
},
|
||||
InitBridgeName::WestendToMillau => {
|
||||
type Source = relay_westend_client::Westend;
|
||||
type Target = relay_millau_client::Millau;
|
||||
@@ -90,18 +96,21 @@ macro_rules! select_bridge {
|
||||
fn encode_init_bridge(
|
||||
init_data: InitializationData<<Source as ChainBase>::Header>,
|
||||
) -> <Target as Chain>::Call {
|
||||
// at Westend -> Millau initialization we're not using sudo, because otherwise our deployments
|
||||
// may fail, because we need to initialize both Rialto -> Millau and Westend -> Millau bridge.
|
||||
// => since there's single possible sudo account, one of transaction may fail with duplicate nonce error
|
||||
millau_runtime::BridgeGrandpaWestendCall::<
|
||||
// at Westend -> Millau initialization we're not using sudo, because otherwise
|
||||
// our deployments may fail, because we need to initialize both Rialto -> Millau
|
||||
// and Westend -> Millau bridge. => since there's single possible sudo account,
|
||||
// one of transaction may fail with duplicate nonce error
|
||||
millau_runtime::BridgeGrandpaCall::<
|
||||
millau_runtime::Runtime,
|
||||
millau_runtime::WestendGrandpaInstance,
|
||||
>::initialize(init_data)
|
||||
>::initialize {
|
||||
init_data,
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
$generic
|
||||
}
|
||||
},
|
||||
InitBridgeName::RococoToWococo => {
|
||||
type Source = relay_rococo_client::Rococo;
|
||||
type Target = relay_wococo_client::Wococo;
|
||||
@@ -110,12 +119,14 @@ macro_rules! select_bridge {
|
||||
init_data: InitializationData<<Source as ChainBase>::Header>,
|
||||
) -> <Target as Chain>::Call {
|
||||
relay_wococo_client::runtime::Call::BridgeGrandpaRococo(
|
||||
relay_wococo_client::runtime::BridgeGrandpaRococoCall::initialize(init_data),
|
||||
relay_wococo_client::runtime::BridgeGrandpaRococoCall::initialize(
|
||||
init_data,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
$generic
|
||||
}
|
||||
},
|
||||
InitBridgeName::WococoToRococo => {
|
||||
type Source = relay_wococo_client::Wococo;
|
||||
type Target = relay_rococo_client::Rococo;
|
||||
@@ -124,12 +135,46 @@ macro_rules! select_bridge {
|
||||
init_data: InitializationData<<Source as ChainBase>::Header>,
|
||||
) -> <Target as Chain>::Call {
|
||||
relay_rococo_client::runtime::Call::BridgeGrandpaWococo(
|
||||
relay_rococo_client::runtime::BridgeGrandpaWococoCall::initialize(init_data),
|
||||
relay_rococo_client::runtime::BridgeGrandpaWococoCall::initialize(
|
||||
init_data,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
$generic
|
||||
}
|
||||
},
|
||||
InitBridgeName::KusamaToPolkadot => {
|
||||
type Source = relay_kusama_client::Kusama;
|
||||
type Target = relay_polkadot_client::Polkadot;
|
||||
|
||||
fn encode_init_bridge(
|
||||
init_data: InitializationData<<Source as ChainBase>::Header>,
|
||||
) -> <Target as Chain>::Call {
|
||||
relay_polkadot_client::runtime::Call::BridgeKusamaGrandpa(
|
||||
relay_polkadot_client::runtime::BridgeKusamaGrandpaCall::initialize(
|
||||
init_data,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
$generic
|
||||
},
|
||||
InitBridgeName::PolkadotToKusama => {
|
||||
type Source = relay_polkadot_client::Polkadot;
|
||||
type Target = relay_kusama_client::Kusama;
|
||||
|
||||
fn encode_init_bridge(
|
||||
init_data: InitializationData<<Source as ChainBase>::Header>,
|
||||
) -> <Target as Chain>::Call {
|
||||
relay_kusama_client::runtime::Call::BridgePolkadotGrandpa(
|
||||
relay_kusama_client::runtime::BridgePolkadotGrandpaCall::initialize(
|
||||
init_data,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
$generic
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -142,7 +187,7 @@ impl InitBridge {
|
||||
let target_client = self.target.to_client::<Target>().await?;
|
||||
let target_sign = self.target_sign.to_keypair::<Target>()?;
|
||||
|
||||
crate::headers_initialize::initialize(
|
||||
substrate_relay_helper::headers_initialize::initialize(
|
||||
source_client,
|
||||
target_client.clone(),
|
||||
target_sign.public().into(),
|
||||
@@ -151,8 +196,11 @@ impl InitBridge {
|
||||
Target::sign_transaction(
|
||||
*target_client.genesis_hash(),
|
||||
&target_sign,
|
||||
transaction_nonce,
|
||||
encode_init_bridge(initialization_data),
|
||||
relay_substrate_client::TransactionEra::immortal(),
|
||||
UnsignedTransaction::new(
|
||||
encode_init_bridge(initialization_data),
|
||||
transaction_nonce,
|
||||
),
|
||||
)
|
||||
.encode(),
|
||||
)
|
||||
|
||||
@@ -32,9 +32,12 @@ pub(crate) mod send_message;
|
||||
|
||||
mod derive_account;
|
||||
mod init_bridge;
|
||||
mod register_parachain;
|
||||
mod relay_headers;
|
||||
mod relay_headers_and_messages;
|
||||
mod relay_messages;
|
||||
mod resubmit_transactions;
|
||||
mod swap_tokens;
|
||||
|
||||
/// Parse relay CLI args.
|
||||
pub fn parse_args() -> Command {
|
||||
@@ -84,8 +87,15 @@ pub enum Command {
|
||||
EncodeMessage(encode_message::EncodeMessage),
|
||||
/// Estimate Delivery and Dispatch Fee required for message submission to messages pallet.
|
||||
EstimateFee(estimate_fee::EstimateFee),
|
||||
/// Given a source chain `AccountId`, derive the corresponding `AccountId` for the target chain.
|
||||
/// Given a source chain `AccountId`, derive the corresponding `AccountId` for the target
|
||||
/// chain.
|
||||
DeriveAccount(derive_account::DeriveAccount),
|
||||
/// Resubmit transactions with increased tip if they are stalled.
|
||||
ResubmitTransactions(resubmit_transactions::ResubmitTransactions),
|
||||
/// Swap tokens using token-swap bridge.
|
||||
SwapTokens(swap_tokens::SwapTokens),
|
||||
/// Register parachain.
|
||||
RegisterParachain(register_parachain::RegisterParachain),
|
||||
}
|
||||
|
||||
impl Command {
|
||||
@@ -94,12 +104,15 @@ impl Command {
|
||||
use relay_utils::initialize::{initialize_logger, initialize_relay};
|
||||
|
||||
match self {
|
||||
Self::RelayHeaders(_) | Self::RelayMessages(_) | Self::RelayHeadersAndMessages(_) | Self::InitBridge(_) => {
|
||||
Self::RelayHeaders(_) |
|
||||
Self::RelayMessages(_) |
|
||||
Self::RelayHeadersAndMessages(_) |
|
||||
Self::InitBridge(_) => {
|
||||
initialize_relay();
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
initialize_logger(false);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +129,9 @@ impl Command {
|
||||
Self::EncodeMessage(arg) => arg.run().await?,
|
||||
Self::EstimateFee(arg) => arg.run().await?,
|
||||
Self::DeriveAccount(arg) => arg.run().await?,
|
||||
Self::ResubmitTransactions(arg) => arg.run().await?,
|
||||
Self::SwapTokens(arg) => arg.run().await?,
|
||||
Self::RegisterParachain(arg) => arg.run().await?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -187,10 +203,7 @@ const SS58_FORMAT_PROOF: &str = "u16 -> Ss58Format is infallible; qed";
|
||||
impl AccountId {
|
||||
/// Create new SS58-formatted address from raw account id.
|
||||
pub fn from_raw<T: CliChain>(account: sp_runtime::AccountId32) -> Self {
|
||||
Self {
|
||||
account,
|
||||
ss58_format: T::ss58_format().try_into().expect(SS58_FORMAT_PROOF),
|
||||
}
|
||||
Self { account, ss58_format: T::ss58_format().try_into().expect(SS58_FORMAT_PROOF) }
|
||||
}
|
||||
|
||||
/// Enforces formatting account to be for given [`CliChain`] type.
|
||||
@@ -228,7 +241,7 @@ pub trait CliChain: relay_substrate_client::Chain {
|
||||
/// Chain's current version of the runtime.
|
||||
const RUNTIME_VERSION: sp_version::RuntimeVersion;
|
||||
|
||||
/// Crypto keypair type used to send messages.
|
||||
/// Crypto KeyPair type used to send messages.
|
||||
///
|
||||
/// In case of chains supporting multiple cryptos, pick one used by the CLI.
|
||||
type KeyPair: sp_core::crypto::Pair;
|
||||
@@ -242,7 +255,9 @@ pub trait CliChain: relay_substrate_client::Chain {
|
||||
fn ss58_format() -> u16;
|
||||
|
||||
/// Construct message payload to be sent over the bridge.
|
||||
fn encode_message(message: crate::cli::encode_message::MessagePayload) -> Result<Self::MessagePayload, String>;
|
||||
fn encode_message(
|
||||
message: crate::cli::encode_message::MessagePayload,
|
||||
) -> anyhow::Result<Self::MessagePayload>;
|
||||
|
||||
/// Maximal extrinsic weight (from the runtime).
|
||||
fn max_extrinsic_weight() -> Weight;
|
||||
@@ -344,7 +359,7 @@ where
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if s.to_lowercase() == "max" {
|
||||
return Ok(ExplicitOrMaximal::Maximal);
|
||||
return Ok(ExplicitOrMaximal::Maximal)
|
||||
}
|
||||
|
||||
V::from_str(s)
|
||||
@@ -360,7 +375,7 @@ macro_rules! declare_chain_options {
|
||||
($chain:ident, $chain_prefix:ident) => {
|
||||
paste::item! {
|
||||
#[doc = $chain " connection params."]
|
||||
#[derive(StructOpt, Debug, PartialEq, Eq)]
|
||||
#[derive(StructOpt, Debug, PartialEq, Eq, Clone)]
|
||||
pub struct [<$chain ConnectionParams>] {
|
||||
#[doc = "Connect to " $chain " node at given host."]
|
||||
#[structopt(long, default_value = "127.0.0.1")]
|
||||
@@ -374,28 +389,117 @@ macro_rules! declare_chain_options {
|
||||
}
|
||||
|
||||
#[doc = $chain " signing params."]
|
||||
#[derive(StructOpt, Debug, PartialEq, Eq)]
|
||||
#[derive(StructOpt, Debug, PartialEq, Eq, Clone)]
|
||||
pub struct [<$chain SigningParams>] {
|
||||
#[doc = "The SURI of secret key to use when transactions are submitted to the " $chain " node."]
|
||||
#[structopt(long)]
|
||||
pub [<$chain_prefix _signer>]: String,
|
||||
pub [<$chain_prefix _signer>]: Option<String>,
|
||||
#[doc = "The password for the SURI of secret key to use when transactions are submitted to the " $chain " node."]
|
||||
#[structopt(long)]
|
||||
pub [<$chain_prefix _signer_password>]: Option<String>,
|
||||
|
||||
#[doc = "Path to the file, that contains SURI of secret key to use when transactions are submitted to the " $chain " node. Can be overridden with " $chain_prefix "_signer option."]
|
||||
#[structopt(long)]
|
||||
pub [<$chain_prefix _signer_file>]: Option<std::path::PathBuf>,
|
||||
#[doc = "Path to the file, that password for the SURI of secret key to use when transactions are submitted to the " $chain " node. Can be overridden with " $chain_prefix "_signer_password option."]
|
||||
#[structopt(long)]
|
||||
pub [<$chain_prefix _signer_password_file>]: Option<std::path::PathBuf>,
|
||||
|
||||
#[doc = "Transactions mortality period, in blocks. MUST be a power of two in [4; 65536] range. MAY NOT be larger than `BlockHashCount` parameter of the chain system module."]
|
||||
#[structopt(long)]
|
||||
pub [<$chain_prefix _transactions_mortality>]: Option<u32>,
|
||||
}
|
||||
|
||||
#[doc = "Parameters required to sign transaction on behalf of owner of the messages pallet at " $chain "."]
|
||||
#[derive(StructOpt, Debug, PartialEq, Eq)]
|
||||
pub struct [<$chain MessagesPalletOwnerSigningParams>] {
|
||||
#[doc = "The SURI of secret key to use when transactions are submitted to the " $chain " node."]
|
||||
#[structopt(long)]
|
||||
pub [<$chain_prefix _messages_pallet_owner>]: Option<String>,
|
||||
#[doc = "The password for the SURI of secret key to use when transactions are submitted to the " $chain " node."]
|
||||
#[structopt(long)]
|
||||
pub [<$chain_prefix _messages_pallet_owner_password>]: Option<String>,
|
||||
}
|
||||
|
||||
impl [<$chain SigningParams>] {
|
||||
/// Parse signing params into chain-specific `KeyPair`.
|
||||
/// Return transactions mortality.
|
||||
#[allow(dead_code)]
|
||||
pub fn transactions_mortality(&self) -> anyhow::Result<Option<u32>> {
|
||||
self.[<$chain_prefix _transactions_mortality>]
|
||||
.map(|transactions_mortality| {
|
||||
if !(4..=65536).contains(&transactions_mortality)
|
||||
|| !transactions_mortality.is_power_of_two()
|
||||
{
|
||||
Err(anyhow::format_err!(
|
||||
"Transactions mortality {} is not a power of two in a [4; 65536] range",
|
||||
transactions_mortality,
|
||||
))
|
||||
} else {
|
||||
Ok(transactions_mortality)
|
||||
}
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
||||
/// Parse signing params into chain-specific KeyPair.
|
||||
#[allow(dead_code)]
|
||||
pub fn to_keypair<Chain: CliChain>(&self) -> anyhow::Result<Chain::KeyPair> {
|
||||
let suri = match (self.[<$chain_prefix _signer>].as_ref(), self.[<$chain_prefix _signer_file>].as_ref()) {
|
||||
(Some(suri), _) => suri.to_owned(),
|
||||
(None, Some(suri_file)) => std::fs::read_to_string(suri_file)
|
||||
.map_err(|err| anyhow::format_err!(
|
||||
"Failed to read SURI from file {:?}: {}",
|
||||
suri_file,
|
||||
err,
|
||||
))?,
|
||||
(None, None) => return Err(anyhow::format_err!(
|
||||
"One of options must be specified: '{}' or '{}'",
|
||||
stringify!([<$chain_prefix _signer>]),
|
||||
stringify!([<$chain_prefix _signer_file>]),
|
||||
)),
|
||||
};
|
||||
|
||||
let suri_password = match (
|
||||
self.[<$chain_prefix _signer_password>].as_ref(),
|
||||
self.[<$chain_prefix _signer_password_file>].as_ref(),
|
||||
) {
|
||||
(Some(suri_password), _) => Some(suri_password.to_owned()),
|
||||
(None, Some(suri_password_file)) => std::fs::read_to_string(suri_password_file)
|
||||
.map(Some)
|
||||
.map_err(|err| anyhow::format_err!(
|
||||
"Failed to read SURI password from file {:?}: {}",
|
||||
suri_password_file,
|
||||
err,
|
||||
))?,
|
||||
_ => None,
|
||||
};
|
||||
|
||||
use sp_core::crypto::Pair;
|
||||
|
||||
Chain::KeyPair::from_string(
|
||||
&self.[<$chain_prefix _signer>],
|
||||
self.[<$chain_prefix _signer_password>].as_deref()
|
||||
&suri,
|
||||
suri_password.as_deref()
|
||||
).map_err(|e| anyhow::format_err!("{:?}", e))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl [<$chain MessagesPalletOwnerSigningParams>] {
|
||||
/// Parse signing params into chain-specific KeyPair.
|
||||
pub fn to_keypair<Chain: CliChain>(&self) -> anyhow::Result<Option<Chain::KeyPair>> {
|
||||
use sp_core::crypto::Pair;
|
||||
|
||||
let [<$chain_prefix _messages_pallet_owner>] = match self.[<$chain_prefix _messages_pallet_owner>] {
|
||||
Some(ref messages_pallet_owner) => messages_pallet_owner,
|
||||
None => return Ok(None),
|
||||
};
|
||||
Chain::KeyPair::from_string(
|
||||
[<$chain_prefix _messages_pallet_owner>],
|
||||
self.[<$chain_prefix _messages_pallet_owner_password>].as_deref()
|
||||
).map_err(|e| anyhow::format_err!("{:?}", e)).map(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl [<$chain ConnectionParams>] {
|
||||
/// Convert connection params into Substrate client.
|
||||
pub async fn to_client<Chain: CliChain>(
|
||||
@@ -416,9 +520,12 @@ macro_rules! declare_chain_options {
|
||||
|
||||
declare_chain_options!(Source, source);
|
||||
declare_chain_options!(Target, target);
|
||||
declare_chain_options!(Relaychain, relaychain);
|
||||
declare_chain_options!(Parachain, parachain);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use sp_core::Pair;
|
||||
use std::str::FromStr;
|
||||
|
||||
use super::*;
|
||||
@@ -434,10 +541,7 @@ mod tests {
|
||||
let expected = vec![rialto1, rialto2, millau1, millau2];
|
||||
|
||||
// when
|
||||
let parsed = expected
|
||||
.iter()
|
||||
.map(|s| AccountId::from_str(s).unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
let parsed = expected.iter().map(|s| AccountId::from_str(s).unwrap()).collect::<Vec<_>>();
|
||||
|
||||
let actual = parsed.iter().map(|a| format!("{}", a)).collect::<Vec<_>>();
|
||||
|
||||
@@ -456,4 +560,93 @@ mod tests {
|
||||
// then
|
||||
assert_eq!(hex.0, hex2.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reads_suri_from_file() {
|
||||
const ALICE: &str = "//Alice";
|
||||
const BOB: &str = "//Bob";
|
||||
const ALICE_PASSWORD: &str = "alice_password";
|
||||
const BOB_PASSWORD: &str = "bob_password";
|
||||
|
||||
let alice = sp_core::sr25519::Pair::from_string(ALICE, Some(ALICE_PASSWORD)).unwrap();
|
||||
let bob = sp_core::sr25519::Pair::from_string(BOB, Some(BOB_PASSWORD)).unwrap();
|
||||
let bob_with_alice_password =
|
||||
sp_core::sr25519::Pair::from_string(BOB, Some(ALICE_PASSWORD)).unwrap();
|
||||
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let mut suri_file_path = temp_dir.path().to_path_buf();
|
||||
let mut password_file_path = temp_dir.path().to_path_buf();
|
||||
suri_file_path.push("suri");
|
||||
password_file_path.push("password");
|
||||
std::fs::write(&suri_file_path, BOB.as_bytes()).unwrap();
|
||||
std::fs::write(&password_file_path, BOB_PASSWORD.as_bytes()).unwrap();
|
||||
|
||||
// when both seed and password are read from file
|
||||
assert_eq!(
|
||||
TargetSigningParams {
|
||||
target_signer: Some(ALICE.into()),
|
||||
target_signer_password: Some(ALICE_PASSWORD.into()),
|
||||
|
||||
target_signer_file: None,
|
||||
target_signer_password_file: None,
|
||||
|
||||
target_transactions_mortality: None,
|
||||
}
|
||||
.to_keypair::<relay_rialto_client::Rialto>()
|
||||
.map(|p| p.public())
|
||||
.map_err(drop),
|
||||
Ok(alice.public()),
|
||||
);
|
||||
|
||||
// when both seed and password are read from file
|
||||
assert_eq!(
|
||||
TargetSigningParams {
|
||||
target_signer: None,
|
||||
target_signer_password: None,
|
||||
|
||||
target_signer_file: Some(suri_file_path.clone()),
|
||||
target_signer_password_file: Some(password_file_path.clone()),
|
||||
|
||||
target_transactions_mortality: None,
|
||||
}
|
||||
.to_keypair::<relay_rialto_client::Rialto>()
|
||||
.map(|p| p.public())
|
||||
.map_err(drop),
|
||||
Ok(bob.public()),
|
||||
);
|
||||
|
||||
// when password are is overriden by cli option
|
||||
assert_eq!(
|
||||
TargetSigningParams {
|
||||
target_signer: None,
|
||||
target_signer_password: Some(ALICE_PASSWORD.into()),
|
||||
|
||||
target_signer_file: Some(suri_file_path.clone()),
|
||||
target_signer_password_file: Some(password_file_path.clone()),
|
||||
|
||||
target_transactions_mortality: None,
|
||||
}
|
||||
.to_keypair::<relay_rialto_client::Rialto>()
|
||||
.map(|p| p.public())
|
||||
.map_err(drop),
|
||||
Ok(bob_with_alice_password.public()),
|
||||
);
|
||||
|
||||
// when both seed and password are overriden by cli options
|
||||
assert_eq!(
|
||||
TargetSigningParams {
|
||||
target_signer: Some(ALICE.into()),
|
||||
target_signer_password: Some(ALICE_PASSWORD.into()),
|
||||
|
||||
target_signer_file: Some(suri_file_path),
|
||||
target_signer_password_file: Some(password_file_path),
|
||||
|
||||
target_transactions_mortality: None,
|
||||
}
|
||||
.to_keypair::<relay_rialto_client::Rialto>()
|
||||
.map(|p| p.public())
|
||||
.map_err(drop),
|
||||
Ok(alice.public()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,346 @@
|
||||
// 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::cli::{
|
||||
swap_tokens::wait_until_transaction_is_finalized, Balance, ParachainConnectionParams,
|
||||
RelaychainConnectionParams, RelaychainSigningParams,
|
||||
};
|
||||
|
||||
use codec::Encode;
|
||||
use num_traits::Zero;
|
||||
use polkadot_parachain::primitives::{
|
||||
HeadData as ParaHeadData, Id as ParaId, ValidationCode as ParaValidationCode,
|
||||
};
|
||||
use polkadot_runtime_common::{
|
||||
paras_registrar::Call as ParaRegistrarCall, slots::Call as ParaSlotsCall,
|
||||
};
|
||||
use polkadot_runtime_parachains::paras::ParaLifecycle;
|
||||
use relay_substrate_client::{
|
||||
AccountIdOf, CallOf, Chain, Client, TransactionSignScheme, UnsignedTransaction,
|
||||
};
|
||||
use rialto_runtime::SudoCall;
|
||||
use sp_core::{
|
||||
storage::{well_known_keys::CODE, StorageKey},
|
||||
Bytes, Pair,
|
||||
};
|
||||
use structopt::StructOpt;
|
||||
use strum::{EnumString, EnumVariantNames, VariantNames};
|
||||
|
||||
/// Name of the `NextFreeParaId` value in the `polkadot_runtime_common::paras_registrar` pallet.
|
||||
const NEXT_FREE_PARA_ID_STORAGE_NAME: &str = "NextFreeParaId";
|
||||
/// Name of the `ParaLifecycles` map in the `polkadot_runtime_parachains::paras` pallet.
|
||||
const PARAS_LIFECYCLES_STORAGE_NAME: &str = "ParaLifecycles";
|
||||
|
||||
/// Register parachain.
|
||||
#[derive(StructOpt, Debug, PartialEq)]
|
||||
pub struct RegisterParachain {
|
||||
/// A parachain to register.
|
||||
#[structopt(possible_values = Parachain::VARIANTS, case_insensitive = true)]
|
||||
parachain: Parachain,
|
||||
/// Parachain deposit.
|
||||
#[structopt(long, default_value = "0")]
|
||||
deposit: Balance,
|
||||
/// Lease begin.
|
||||
#[structopt(long, default_value = "0")]
|
||||
lease_begin: u32,
|
||||
/// Lease end.
|
||||
#[structopt(long, default_value = "256")]
|
||||
lease_end: u32,
|
||||
#[structopt(flatten)]
|
||||
relay_connection: RelaychainConnectionParams,
|
||||
#[structopt(flatten)]
|
||||
relay_sign: RelaychainSigningParams,
|
||||
#[structopt(flatten)]
|
||||
para_connection: ParachainConnectionParams,
|
||||
}
|
||||
|
||||
/// Parachain to register.
|
||||
#[derive(Debug, EnumString, EnumVariantNames, PartialEq)]
|
||||
#[strum(serialize_all = "kebab_case")]
|
||||
pub enum Parachain {
|
||||
RialtoParachain,
|
||||
}
|
||||
|
||||
macro_rules! select_bridge {
|
||||
($bridge: expr, $generic: tt) => {
|
||||
match $bridge {
|
||||
Parachain::RialtoParachain => {
|
||||
type Relaychain = relay_rialto_client::Rialto;
|
||||
type Parachain = relay_rialto_parachain_client::RialtoParachain;
|
||||
|
||||
use bp_rialto::{PARAS_PALLET_NAME, PARAS_REGISTRAR_PALLET_NAME};
|
||||
|
||||
$generic
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl RegisterParachain {
|
||||
/// Run the command.
|
||||
pub async fn run(self) -> anyhow::Result<()> {
|
||||
select_bridge!(self.parachain, {
|
||||
let relay_client = self.relay_connection.to_client::<Relaychain>().await?;
|
||||
let relay_sign = self.relay_sign.to_keypair::<Relaychain>()?;
|
||||
let para_client = self.para_connection.to_client::<Parachain>().await?;
|
||||
|
||||
// hopefully we're the only actor that is registering parachain right now
|
||||
// => read next parachain id
|
||||
let para_id_key = bp_runtime::storage_value_final_key(
|
||||
PARAS_REGISTRAR_PALLET_NAME.as_bytes(),
|
||||
NEXT_FREE_PARA_ID_STORAGE_NAME.as_bytes(),
|
||||
);
|
||||
let para_id: ParaId = relay_client
|
||||
.storage_value(StorageKey(para_id_key.to_vec()), None)
|
||||
.await?
|
||||
.unwrap_or(polkadot_primitives::v1::LOWEST_PUBLIC_ID)
|
||||
.max(polkadot_primitives::v1::LOWEST_PUBLIC_ID);
|
||||
log::info!(target: "bridge", "Going to reserve parachain id: {:?}", para_id);
|
||||
|
||||
// step 1: reserve a parachain id
|
||||
let relay_genesis_hash = *relay_client.genesis_hash();
|
||||
let relay_sudo_account: AccountIdOf<Relaychain> = relay_sign.public().into();
|
||||
let reserve_parachain_id_call: CallOf<Relaychain> =
|
||||
ParaRegistrarCall::reserve {}.into();
|
||||
let reserve_parachain_signer = relay_sign.clone();
|
||||
wait_until_transaction_is_finalized::<Relaychain>(
|
||||
relay_client
|
||||
.submit_and_watch_signed_extrinsic(
|
||||
relay_sudo_account.clone(),
|
||||
move |_, transaction_nonce| {
|
||||
Bytes(
|
||||
Relaychain::sign_transaction(
|
||||
relay_genesis_hash,
|
||||
&reserve_parachain_signer,
|
||||
relay_substrate_client::TransactionEra::immortal(),
|
||||
UnsignedTransaction::new(
|
||||
reserve_parachain_id_call,
|
||||
transaction_nonce,
|
||||
),
|
||||
)
|
||||
.encode(),
|
||||
)
|
||||
},
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
.await?;
|
||||
log::info!(target: "bridge", "Reserved parachain id: {:?}", para_id);
|
||||
|
||||
// step 2: register parathread
|
||||
let para_genesis_header = para_client.header_by_number(Zero::zero()).await?;
|
||||
let para_code = para_client
|
||||
.raw_storage_value(StorageKey(CODE.to_vec()), Some(para_genesis_header.hash()))
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
anyhow::format_err!("Cannot fetch validation code of {}", Parachain::NAME)
|
||||
})?
|
||||
.0;
|
||||
log::info!(
|
||||
target: "bridge",
|
||||
"Going to register parachain {:?}: genesis len = {} code len = {}",
|
||||
para_id,
|
||||
para_genesis_header.encode().len(),
|
||||
para_code.len(),
|
||||
);
|
||||
let register_parathread_call: CallOf<Relaychain> = ParaRegistrarCall::register {
|
||||
id: para_id,
|
||||
genesis_head: ParaHeadData(para_genesis_header.encode()),
|
||||
validation_code: ParaValidationCode(para_code),
|
||||
}
|
||||
.into();
|
||||
let register_parathread_signer = relay_sign.clone();
|
||||
wait_until_transaction_is_finalized::<Relaychain>(
|
||||
relay_client
|
||||
.submit_and_watch_signed_extrinsic(
|
||||
relay_sudo_account.clone(),
|
||||
move |_, transaction_nonce| {
|
||||
Bytes(
|
||||
Relaychain::sign_transaction(
|
||||
relay_genesis_hash,
|
||||
®ister_parathread_signer,
|
||||
relay_substrate_client::TransactionEra::immortal(),
|
||||
UnsignedTransaction::new(
|
||||
register_parathread_call,
|
||||
transaction_nonce,
|
||||
),
|
||||
)
|
||||
.encode(),
|
||||
)
|
||||
},
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
.await?;
|
||||
log::info!(target: "bridge", "Registered parachain: {:?}. Waiting for onboarding", para_id);
|
||||
|
||||
// wait until parathread is onboarded
|
||||
let para_state_key = bp_runtime::storage_map_final_key_twox64_concat(
|
||||
PARAS_PALLET_NAME,
|
||||
PARAS_LIFECYCLES_STORAGE_NAME,
|
||||
¶_id.encode(),
|
||||
);
|
||||
wait_para_state(
|
||||
&relay_client,
|
||||
¶_state_key.0,
|
||||
&[ParaLifecycle::Onboarding, ParaLifecycle::Parathread],
|
||||
ParaLifecycle::Parathread,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// step 3: force parachain leases
|
||||
let lease_begin = self.lease_begin;
|
||||
let lease_end = self.lease_end;
|
||||
let para_deposit = self.deposit.cast().into();
|
||||
log::info!(
|
||||
target: "bridge",
|
||||
"Going to force leases of parachain {:?}: [{}; {}]",
|
||||
para_id,
|
||||
lease_begin,
|
||||
lease_end,
|
||||
);
|
||||
let force_lease_call: CallOf<Relaychain> = SudoCall::sudo {
|
||||
call: Box::new(
|
||||
ParaSlotsCall::force_lease {
|
||||
para: para_id,
|
||||
leaser: relay_sudo_account.clone(),
|
||||
amount: para_deposit,
|
||||
period_begin: lease_begin,
|
||||
period_count: lease_end.saturating_sub(lease_begin).saturating_add(1),
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
}
|
||||
.into();
|
||||
let force_lease_signer = relay_sign.clone();
|
||||
relay_client
|
||||
.submit_signed_extrinsic(relay_sudo_account.clone(), move |_, transaction_nonce| {
|
||||
Bytes(
|
||||
Relaychain::sign_transaction(
|
||||
relay_genesis_hash,
|
||||
&force_lease_signer,
|
||||
relay_substrate_client::TransactionEra::immortal(),
|
||||
UnsignedTransaction::new(force_lease_call, transaction_nonce),
|
||||
)
|
||||
.encode(),
|
||||
)
|
||||
})
|
||||
.await?;
|
||||
log::info!(target: "bridge", "Registered parachain leases: {:?}. Waiting for onboarding", para_id);
|
||||
|
||||
// wait until parachain is onboarded
|
||||
wait_para_state(
|
||||
&relay_client,
|
||||
¶_state_key.0,
|
||||
&[
|
||||
ParaLifecycle::Onboarding,
|
||||
ParaLifecycle::UpgradingParathread,
|
||||
ParaLifecycle::Parathread,
|
||||
],
|
||||
ParaLifecycle::Parachain,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait until parachain state is changed.
|
||||
async fn wait_para_state<Relaychain: Chain>(
|
||||
relay_client: &Client<Relaychain>,
|
||||
para_state_key: &[u8],
|
||||
from_states: &[ParaLifecycle],
|
||||
to_state: ParaLifecycle,
|
||||
) -> anyhow::Result<()> {
|
||||
loop {
|
||||
let para_state: ParaLifecycle = relay_client
|
||||
.storage_value(StorageKey(para_state_key.to_vec()), None)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
anyhow::format_err!(
|
||||
"Cannot fetch next free parachain lifecycle from the runtime storage of {}",
|
||||
Relaychain::NAME,
|
||||
)
|
||||
})?;
|
||||
if para_state == to_state {
|
||||
log::info!(target: "bridge", "Parachain state is now: {:?}", to_state);
|
||||
return Ok(())
|
||||
}
|
||||
if !from_states.contains(¶_state) {
|
||||
return Err(anyhow::format_err!("Invalid parachain lifecycle: {:?}", para_state))
|
||||
}
|
||||
|
||||
log::info!(target: "bridge", "Parachain state: {:?}. Waiting for {:?}", para_state, to_state);
|
||||
async_std::task::sleep(Relaychain::AVERAGE_BLOCK_INTERVAL).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn register_rialto_parachain() {
|
||||
let register_parachain = RegisterParachain::from_iter(vec![
|
||||
"register-parachain",
|
||||
"rialto-parachain",
|
||||
"--parachain-host",
|
||||
"127.0.0.1",
|
||||
"--parachain-port",
|
||||
"11949",
|
||||
"--relaychain-host",
|
||||
"127.0.0.1",
|
||||
"--relaychain-port",
|
||||
"9944",
|
||||
"--relaychain-signer",
|
||||
"//Alice",
|
||||
"--deposit",
|
||||
"42",
|
||||
"--lease-begin",
|
||||
"100",
|
||||
"--lease-end",
|
||||
"200",
|
||||
]);
|
||||
|
||||
assert_eq!(
|
||||
register_parachain,
|
||||
RegisterParachain {
|
||||
parachain: Parachain::RialtoParachain,
|
||||
deposit: Balance(42),
|
||||
lease_begin: 100,
|
||||
lease_end: 200,
|
||||
relay_connection: RelaychainConnectionParams {
|
||||
relaychain_host: "127.0.0.1".into(),
|
||||
relaychain_port: 9944,
|
||||
relaychain_secure: false,
|
||||
},
|
||||
relay_sign: RelaychainSigningParams {
|
||||
relaychain_signer: Some("//Alice".into()),
|
||||
relaychain_signer_password: None,
|
||||
relaychain_signer_file: None,
|
||||
relaychain_signer_password_file: None,
|
||||
relaychain_transactions_mortality: None,
|
||||
},
|
||||
para_connection: ParachainConnectionParams {
|
||||
parachain_host: "127.0.0.1".into(),
|
||||
parachain_port: 11949,
|
||||
parachain_secure: false,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -14,17 +14,23 @@
|
||||
// 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::cli::{PrometheusParams, SourceConnectionParams, TargetConnectionParams, TargetSigningParams};
|
||||
use crate::finality_pipeline::SubstrateFinalitySyncPipeline;
|
||||
use structopt::{clap::arg_enum, StructOpt};
|
||||
use structopt::StructOpt;
|
||||
use strum::{EnumString, EnumVariantNames, VariantNames};
|
||||
|
||||
use substrate_relay_helper::finality_pipeline::SubstrateFinalitySyncPipeline;
|
||||
|
||||
use crate::cli::{
|
||||
PrometheusParams, SourceConnectionParams, TargetConnectionParams, TargetSigningParams,
|
||||
};
|
||||
|
||||
/// Start headers relayer process.
|
||||
#[derive(StructOpt)]
|
||||
pub struct RelayHeaders {
|
||||
/// A bridge instance to relay headers for.
|
||||
#[structopt(possible_values = &RelayHeadersBridge::variants(), case_insensitive = true)]
|
||||
#[structopt(possible_values = RelayHeadersBridge::VARIANTS, case_insensitive = true)]
|
||||
bridge: RelayHeadersBridge,
|
||||
/// If passed, only mandatory headers (headers that are changing the GRANDPA authorities set) are relayed.
|
||||
/// If passed, only mandatory headers (headers that are changing the GRANDPA authorities set)
|
||||
/// are relayed.
|
||||
#[structopt(long)]
|
||||
only_mandatory_headers: bool,
|
||||
#[structopt(flatten)]
|
||||
@@ -37,17 +43,17 @@ pub struct RelayHeaders {
|
||||
prometheus_params: PrometheusParams,
|
||||
}
|
||||
|
||||
// TODO [#851] Use kebab-case.
|
||||
arg_enum! {
|
||||
#[derive(Debug)]
|
||||
/// Headers relay bridge.
|
||||
pub enum RelayHeadersBridge {
|
||||
MillauToRialto,
|
||||
RialtoToMillau,
|
||||
WestendToMillau,
|
||||
RococoToWococo,
|
||||
WococoToRococo,
|
||||
}
|
||||
#[derive(Debug, EnumString, EnumVariantNames)]
|
||||
#[strum(serialize_all = "kebab_case")]
|
||||
/// Headers relay bridge.
|
||||
pub enum RelayHeadersBridge {
|
||||
MillauToRialto,
|
||||
RialtoToMillau,
|
||||
WestendToMillau,
|
||||
RococoToWococo,
|
||||
WococoToRococo,
|
||||
KusamaToPolkadot,
|
||||
PolkadotToKusama,
|
||||
}
|
||||
|
||||
macro_rules! select_bridge {
|
||||
@@ -59,35 +65,49 @@ macro_rules! select_bridge {
|
||||
type Finality = crate::chains::millau_headers_to_rialto::MillauFinalityToRialto;
|
||||
|
||||
$generic
|
||||
}
|
||||
},
|
||||
RelayHeadersBridge::RialtoToMillau => {
|
||||
type Source = relay_rialto_client::Rialto;
|
||||
type Target = relay_millau_client::Millau;
|
||||
type Finality = crate::chains::rialto_headers_to_millau::RialtoFinalityToMillau;
|
||||
|
||||
$generic
|
||||
}
|
||||
},
|
||||
RelayHeadersBridge::WestendToMillau => {
|
||||
type Source = relay_westend_client::Westend;
|
||||
type Target = relay_millau_client::Millau;
|
||||
type Finality = crate::chains::westend_headers_to_millau::WestendFinalityToMillau;
|
||||
|
||||
$generic
|
||||
}
|
||||
},
|
||||
RelayHeadersBridge::RococoToWococo => {
|
||||
type Source = relay_rococo_client::Rococo;
|
||||
type Target = relay_wococo_client::Wococo;
|
||||
type Finality = crate::chains::rococo_headers_to_wococo::RococoFinalityToWococo;
|
||||
|
||||
$generic
|
||||
}
|
||||
},
|
||||
RelayHeadersBridge::WococoToRococo => {
|
||||
type Source = relay_wococo_client::Wococo;
|
||||
type Target = relay_rococo_client::Rococo;
|
||||
type Finality = crate::chains::wococo_headers_to_rococo::WococoFinalityToRococo;
|
||||
|
||||
$generic
|
||||
}
|
||||
},
|
||||
RelayHeadersBridge::KusamaToPolkadot => {
|
||||
type Source = relay_kusama_client::Kusama;
|
||||
type Target = relay_polkadot_client::Polkadot;
|
||||
type Finality = crate::chains::kusama_headers_to_polkadot::KusamaFinalityToPolkadot;
|
||||
|
||||
$generic
|
||||
},
|
||||
RelayHeadersBridge::PolkadotToKusama => {
|
||||
type Source = relay_polkadot_client::Polkadot;
|
||||
type Target = relay_kusama_client::Kusama;
|
||||
type Finality = crate::chains::polkadot_headers_to_kusama::PolkadotFinalityToKusama;
|
||||
|
||||
$generic
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -98,16 +118,18 @@ impl RelayHeaders {
|
||||
select_bridge!(self.bridge, {
|
||||
let source_client = self.source.to_client::<Source>().await?;
|
||||
let target_client = self.target.to_client::<Target>().await?;
|
||||
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())?;
|
||||
let finality = Finality::new(target_client.clone(), target_sign);
|
||||
finality.start_relay_guards();
|
||||
|
||||
crate::finality_pipeline::run(
|
||||
substrate_relay_helper::finality_pipeline::run(
|
||||
finality,
|
||||
source_client,
|
||||
target_client,
|
||||
self.only_mandatory_headers,
|
||||
target_transactions_mortality,
|
||||
metrics_params,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -22,20 +22,41 @@
|
||||
//! 2) add `declare_bridge_options!(...)` for the bridge;
|
||||
//! 3) add bridge support to the `select_bridge! { ... }` macro.
|
||||
|
||||
use crate::cli::{CliChain, HexLaneId, PrometheusParams};
|
||||
use crate::declare_chain_options;
|
||||
use crate::messages_lane::MessagesRelayParams;
|
||||
use crate::on_demand_headers::OnDemandHeadersRelay;
|
||||
|
||||
use futures::{FutureExt, TryFutureExt};
|
||||
use relay_utils::metrics::MetricsParams;
|
||||
use structopt::StructOpt;
|
||||
use strum::VariantNames;
|
||||
|
||||
use codec::Encode;
|
||||
use messages_relay::relay_strategy::MixStrategy;
|
||||
use relay_substrate_client::{
|
||||
AccountIdOf, Chain, Client, TransactionSignScheme, UnsignedTransaction,
|
||||
};
|
||||
use relay_utils::metrics::MetricsParams;
|
||||
use sp_core::{Bytes, Pair};
|
||||
use substrate_relay_helper::{
|
||||
messages_lane::{MessagesRelayParams, SubstrateMessageLane},
|
||||
on_demand_headers::OnDemandHeadersRelay,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
cli::{relay_messages::RelayerMode, CliChain, HexLaneId, PrometheusParams},
|
||||
declare_chain_options,
|
||||
};
|
||||
|
||||
/// Maximal allowed conversion rate error ratio (abs(real - stored) / stored) that we allow.
|
||||
///
|
||||
/// If it is zero, then transaction will be submitted every time we see difference between
|
||||
/// stored and real conversion rates. If it is large enough (e.g. > than 10 percents, which is 0.1),
|
||||
/// then rational relayers may stop relaying messages because they were submitted using
|
||||
/// lesser conversion rate.
|
||||
const CONVERSION_RATE_ALLOWED_DIFFERENCE_RATIO: f64 = 0.05;
|
||||
|
||||
/// Start headers+messages relayer process.
|
||||
#[derive(StructOpt)]
|
||||
pub enum RelayHeadersAndMessages {
|
||||
MillauRialto(MillauRialtoHeadersAndMessages),
|
||||
RococoWococo(RococoWococoHeadersAndMessages),
|
||||
KusamaPolkadot(KusamaPolkadotHeadersAndMessages),
|
||||
}
|
||||
|
||||
/// Parameters that have the same names across all bridges.
|
||||
@@ -44,13 +65,22 @@ pub struct HeadersAndMessagesSharedParams {
|
||||
/// Hex-encoded lane identifiers that should be served by the complex relay.
|
||||
#[structopt(long, default_value = "00000000")]
|
||||
lane: Vec<HexLaneId>,
|
||||
#[structopt(long, possible_values = RelayerMode::VARIANTS, case_insensitive = true, default_value = "rational")]
|
||||
relayer_mode: RelayerMode,
|
||||
/// Create relayers fund accounts on both chains, if it does not exists yet.
|
||||
#[structopt(long)]
|
||||
create_relayers_fund_accounts: bool,
|
||||
/// If passed, only mandatory headers (headers that are changing the GRANDPA authorities set)
|
||||
/// are relayed.
|
||||
#[structopt(long)]
|
||||
only_mandatory_headers: bool,
|
||||
#[structopt(flatten)]
|
||||
prometheus_params: PrometheusParams,
|
||||
}
|
||||
|
||||
// The reason behind this macro is that 'normal' relays are using source and target chains terminology,
|
||||
// which is unusable for both-way relays (if you're relaying headers from Rialto to Millau and from
|
||||
// Millau to Rialto, then which chain is source?).
|
||||
// The reason behind this macro is that 'normal' relays are using source and target chains
|
||||
// terminology, which is unusable for both-way relays (if you're relaying headers from Rialto to
|
||||
// Millau and from Millau to Rialto, then which chain is source?).
|
||||
macro_rules! declare_bridge_options {
|
||||
($chain1:ident, $chain2:ident) => {
|
||||
paste::item! {
|
||||
@@ -64,12 +94,15 @@ macro_rules! declare_bridge_options {
|
||||
#[structopt(flatten)]
|
||||
left_sign: [<$chain1 SigningParams>],
|
||||
#[structopt(flatten)]
|
||||
left_messages_pallet_owner: [<$chain1 MessagesPalletOwnerSigningParams>],
|
||||
#[structopt(flatten)]
|
||||
right: [<$chain2 ConnectionParams>],
|
||||
#[structopt(flatten)]
|
||||
right_sign: [<$chain2 SigningParams>],
|
||||
#[structopt(flatten)]
|
||||
right_messages_pallet_owner: [<$chain2 MessagesPalletOwnerSigningParams>],
|
||||
}
|
||||
|
||||
#[allow(unreachable_patterns)]
|
||||
impl From<RelayHeadersAndMessages> for [<$chain1 $chain2 HeadersAndMessages>] {
|
||||
fn from(relay_params: RelayHeadersAndMessages) -> [<$chain1 $chain2 HeadersAndMessages>] {
|
||||
match relay_params {
|
||||
@@ -91,40 +124,223 @@ macro_rules! select_bridge {
|
||||
type Left = relay_millau_client::Millau;
|
||||
type Right = relay_rialto_client::Rialto;
|
||||
|
||||
type LeftToRightFinality = crate::chains::millau_headers_to_rialto::MillauFinalityToRialto;
|
||||
type RightToLeftFinality = crate::chains::rialto_headers_to_millau::RialtoFinalityToMillau;
|
||||
type LeftToRightFinality =
|
||||
crate::chains::millau_headers_to_rialto::MillauFinalityToRialto;
|
||||
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 LeftToRightMessages =
|
||||
crate::chains::millau_messages_to_rialto::MillauMessagesToRialto;
|
||||
type RightToLeftMessages =
|
||||
crate::chains::rialto_messages_to_millau::RialtoMessagesToMillau;
|
||||
|
||||
const MAX_MISSING_LEFT_HEADERS_AT_RIGHT: bp_millau::BlockNumber = bp_millau::SESSION_LENGTH;
|
||||
const MAX_MISSING_RIGHT_HEADERS_AT_LEFT: bp_rialto::BlockNumber = bp_rialto::SESSION_LENGTH;
|
||||
type LeftAccountIdConverter = bp_millau::AccountIdConverter;
|
||||
type RightAccountIdConverter = bp_rialto::AccountIdConverter;
|
||||
|
||||
use crate::chains::millau_messages_to_rialto::run as left_to_right_messages;
|
||||
use crate::chains::rialto_messages_to_millau::run as right_to_left_messages;
|
||||
const MAX_MISSING_LEFT_HEADERS_AT_RIGHT: bp_millau::BlockNumber =
|
||||
bp_millau::SESSION_LENGTH;
|
||||
const MAX_MISSING_RIGHT_HEADERS_AT_LEFT: bp_rialto::BlockNumber =
|
||||
bp_rialto::SESSION_LENGTH;
|
||||
|
||||
use crate::chains::{
|
||||
millau_messages_to_rialto::{
|
||||
add_standalone_metrics as add_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,
|
||||
},
|
||||
};
|
||||
|
||||
async fn left_create_account(
|
||||
_left_client: Client<Left>,
|
||||
_left_sign: <Left as TransactionSignScheme>::AccountKeyPair,
|
||||
_account_id: AccountIdOf<Left>,
|
||||
) -> anyhow::Result<()> {
|
||||
Err(anyhow::format_err!("Account creation is not supported by this bridge"))
|
||||
}
|
||||
|
||||
async fn right_create_account(
|
||||
_right_client: Client<Right>,
|
||||
_right_sign: <Right as TransactionSignScheme>::AccountKeyPair,
|
||||
_account_id: AccountIdOf<Right>,
|
||||
) -> anyhow::Result<()> {
|
||||
Err(anyhow::format_err!("Account creation is not supported by this bridge"))
|
||||
}
|
||||
|
||||
$generic
|
||||
}
|
||||
},
|
||||
RelayHeadersAndMessages::RococoWococo(_) => {
|
||||
type Params = RococoWococoHeadersAndMessages;
|
||||
|
||||
type Left = relay_rococo_client::Rococo;
|
||||
type Right = relay_wococo_client::Wococo;
|
||||
|
||||
type LeftToRightFinality = crate::chains::rococo_headers_to_wococo::RococoFinalityToWococo;
|
||||
type RightToLeftFinality = crate::chains::wococo_headers_to_rococo::WococoFinalityToRococo;
|
||||
type LeftToRightFinality =
|
||||
crate::chains::rococo_headers_to_wococo::RococoFinalityToWococo;
|
||||
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 LeftToRightMessages =
|
||||
crate::chains::rococo_messages_to_wococo::RococoMessagesToWococo;
|
||||
type RightToLeftMessages =
|
||||
crate::chains::wococo_messages_to_rococo::WococoMessagesToRococo;
|
||||
|
||||
const MAX_MISSING_LEFT_HEADERS_AT_RIGHT: bp_rococo::BlockNumber = bp_rococo::SESSION_LENGTH;
|
||||
const MAX_MISSING_RIGHT_HEADERS_AT_LEFT: bp_wococo::BlockNumber = bp_wococo::SESSION_LENGTH;
|
||||
type LeftAccountIdConverter = bp_rococo::AccountIdConverter;
|
||||
type RightAccountIdConverter = bp_wococo::AccountIdConverter;
|
||||
|
||||
use crate::chains::rococo_messages_to_wococo::run as left_to_right_messages;
|
||||
use crate::chains::wococo_messages_to_rococo::run as right_to_left_messages;
|
||||
const MAX_MISSING_LEFT_HEADERS_AT_RIGHT: bp_rococo::BlockNumber =
|
||||
bp_rococo::SESSION_LENGTH;
|
||||
const MAX_MISSING_RIGHT_HEADERS_AT_LEFT: bp_wococo::BlockNumber =
|
||||
bp_wococo::SESSION_LENGTH;
|
||||
|
||||
use crate::chains::{
|
||||
rococo_messages_to_wococo::{
|
||||
add_standalone_metrics as add_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,
|
||||
},
|
||||
};
|
||||
|
||||
async fn update_right_to_left_conversion_rate(
|
||||
_client: Client<Left>,
|
||||
_signer: <Left as TransactionSignScheme>::AccountKeyPair,
|
||||
_updated_rate: f64,
|
||||
) -> anyhow::Result<()> {
|
||||
Err(anyhow::format_err!("Conversion rate is not supported by this bridge"))
|
||||
}
|
||||
|
||||
async fn update_left_to_right_conversion_rate(
|
||||
_client: Client<Right>,
|
||||
_signer: <Right as TransactionSignScheme>::AccountKeyPair,
|
||||
_updated_rate: f64,
|
||||
) -> anyhow::Result<()> {
|
||||
Err(anyhow::format_err!("Conversion rate is not supported by this bridge"))
|
||||
}
|
||||
|
||||
async fn left_create_account(
|
||||
_left_client: Client<Left>,
|
||||
_left_sign: <Left as TransactionSignScheme>::AccountKeyPair,
|
||||
_account_id: AccountIdOf<Left>,
|
||||
) -> anyhow::Result<()> {
|
||||
Err(anyhow::format_err!("Account creation is not supported by this bridge"))
|
||||
}
|
||||
|
||||
async fn right_create_account(
|
||||
_right_client: Client<Right>,
|
||||
_right_sign: <Right as TransactionSignScheme>::AccountKeyPair,
|
||||
_account_id: AccountIdOf<Right>,
|
||||
) -> anyhow::Result<()> {
|
||||
Err(anyhow::format_err!("Account creation is not supported by this bridge"))
|
||||
}
|
||||
|
||||
$generic
|
||||
}
|
||||
},
|
||||
RelayHeadersAndMessages::KusamaPolkadot(_) => {
|
||||
type Params = KusamaPolkadotHeadersAndMessages;
|
||||
|
||||
type Left = relay_kusama_client::Kusama;
|
||||
type Right = relay_polkadot_client::Polkadot;
|
||||
|
||||
type LeftToRightFinality =
|
||||
crate::chains::kusama_headers_to_polkadot::KusamaFinalityToPolkadot;
|
||||
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;
|
||||
|
||||
const MAX_MISSING_LEFT_HEADERS_AT_RIGHT: bp_kusama::BlockNumber =
|
||||
bp_kusama::SESSION_LENGTH;
|
||||
const MAX_MISSING_RIGHT_HEADERS_AT_LEFT: bp_polkadot::BlockNumber =
|
||||
bp_polkadot::SESSION_LENGTH;
|
||||
|
||||
use crate::chains::{
|
||||
kusama_messages_to_polkadot::{
|
||||
add_standalone_metrics as add_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,
|
||||
},
|
||||
};
|
||||
|
||||
async fn left_create_account(
|
||||
left_client: Client<Left>,
|
||||
left_sign: <Left as TransactionSignScheme>::AccountKeyPair,
|
||||
account_id: AccountIdOf<Left>,
|
||||
) -> anyhow::Result<()> {
|
||||
let left_genesis_hash = *left_client.genesis_hash();
|
||||
left_client
|
||||
.submit_signed_extrinsic(
|
||||
left_sign.public().into(),
|
||||
move |_, transaction_nonce| {
|
||||
Bytes(
|
||||
Left::sign_transaction(left_genesis_hash, &left_sign, relay_substrate_client::TransactionEra::immortal(),
|
||||
UnsignedTransaction::new(
|
||||
relay_kusama_client::runtime::Call::Balances(
|
||||
relay_kusama_client::runtime::BalancesCall::transfer(
|
||||
bp_kusama::AccountAddress::Id(account_id),
|
||||
bp_kusama::EXISTENTIAL_DEPOSIT.into(),
|
||||
),
|
||||
),
|
||||
transaction_nonce,
|
||||
),
|
||||
).encode()
|
||||
)
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map(drop)
|
||||
.map_err(|e| anyhow::format_err!("{}", e))
|
||||
}
|
||||
|
||||
async fn right_create_account(
|
||||
right_client: Client<Right>,
|
||||
right_sign: <Right as TransactionSignScheme>::AccountKeyPair,
|
||||
account_id: AccountIdOf<Right>,
|
||||
) -> anyhow::Result<()> {
|
||||
let right_genesis_hash = *right_client.genesis_hash();
|
||||
right_client
|
||||
.submit_signed_extrinsic(
|
||||
right_sign.public().into(),
|
||||
move |_, transaction_nonce| {
|
||||
Bytes(
|
||||
Right::sign_transaction(right_genesis_hash, &right_sign, relay_substrate_client::TransactionEra::immortal(),
|
||||
UnsignedTransaction::new(
|
||||
relay_polkadot_client::runtime::Call::Balances(
|
||||
relay_polkadot_client::runtime::BalancesCall::transfer(
|
||||
bp_polkadot::AccountAddress::Id(account_id),
|
||||
bp_polkadot::EXISTENTIAL_DEPOSIT.into(),
|
||||
),
|
||||
),
|
||||
transaction_nonce,
|
||||
),
|
||||
).encode()
|
||||
)
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map(drop)
|
||||
.map_err(|e| anyhow::format_err!("{}", e))
|
||||
}
|
||||
|
||||
$generic
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -134,9 +350,12 @@ declare_chain_options!(Millau, millau);
|
||||
declare_chain_options!(Rialto, rialto);
|
||||
declare_chain_options!(Rococo, rococo);
|
||||
declare_chain_options!(Wococo, wococo);
|
||||
declare_chain_options!(Kusama, kusama);
|
||||
declare_chain_options!(Polkadot, polkadot);
|
||||
// All supported bridges.
|
||||
declare_bridge_options!(Millau, Rialto);
|
||||
declare_bridge_options!(Rococo, Wococo);
|
||||
declare_bridge_options!(Kusama, Polkadot);
|
||||
|
||||
impl RelayHeadersAndMessages {
|
||||
/// Run the command.
|
||||
@@ -145,26 +364,147 @@ impl RelayHeadersAndMessages {
|
||||
let params: Params = self.into();
|
||||
|
||||
let left_client = params.left.to_client::<Left>().await?;
|
||||
let left_transactions_mortality = params.left_sign.transactions_mortality()?;
|
||||
let left_sign = params.left_sign.to_keypair::<Left>()?;
|
||||
let left_messages_pallet_owner =
|
||||
params.left_messages_pallet_owner.to_keypair::<Left>()?;
|
||||
let right_client = params.right.to_client::<Right>().await?;
|
||||
let right_transactions_mortality = params.right_sign.transactions_mortality()?;
|
||||
let right_sign = params.right_sign.to_keypair::<Right>()?;
|
||||
let right_messages_pallet_owner =
|
||||
params.right_messages_pallet_owner.to_keypair::<Right>()?;
|
||||
|
||||
let lanes = params.shared.lane;
|
||||
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";
|
||||
|
||||
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())?;
|
||||
if let Some(left_messages_pallet_owner) = left_messages_pallet_owner {
|
||||
let left_client = left_client.clone();
|
||||
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),
|
||||
left_to_right_metrics
|
||||
.target_to_base_conversion_rate
|
||||
.clone()
|
||||
.expect(METRIC_IS_SOME_PROOF),
|
||||
left_to_right_metrics
|
||||
.source_to_base_conversion_rate
|
||||
.clone()
|
||||
.expect(METRIC_IS_SOME_PROOF),
|
||||
CONVERSION_RATE_ALLOWED_DIFFERENCE_RATIO,
|
||||
move |new_rate| {
|
||||
log::info!(
|
||||
target: "bridge",
|
||||
"Going to update {} -> {} (on {}) conversion rate to {}.",
|
||||
Right::NAME,
|
||||
Left::NAME,
|
||||
Left::NAME,
|
||||
new_rate,
|
||||
);
|
||||
update_right_to_left_conversion_rate(
|
||||
left_client.clone(),
|
||||
left_messages_pallet_owner.clone(),
|
||||
new_rate,
|
||||
)
|
||||
},
|
||||
);
|
||||
}
|
||||
if let Some(right_messages_pallet_owner) = right_messages_pallet_owner {
|
||||
let right_client = right_client.clone();
|
||||
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),
|
||||
left_to_right_metrics
|
||||
.source_to_base_conversion_rate
|
||||
.expect(METRIC_IS_SOME_PROOF),
|
||||
left_to_right_metrics
|
||||
.target_to_base_conversion_rate
|
||||
.expect(METRIC_IS_SOME_PROOF),
|
||||
CONVERSION_RATE_ALLOWED_DIFFERENCE_RATIO,
|
||||
move |new_rate| {
|
||||
log::info!(
|
||||
target: "bridge",
|
||||
"Going to update {} -> {} (on {}) conversion rate to {}.",
|
||||
Left::NAME,
|
||||
Right::NAME,
|
||||
Right::NAME,
|
||||
new_rate,
|
||||
);
|
||||
update_left_to_right_conversion_rate(
|
||||
right_client.clone(),
|
||||
right_messages_pallet_owner.clone(),
|
||||
new_rate,
|
||||
)
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if params.shared.create_relayers_fund_accounts {
|
||||
let relayer_fund_acount_id = pallet_bridge_messages::relayer_fund_account_id::<
|
||||
AccountIdOf<Left>,
|
||||
LeftAccountIdConverter,
|
||||
>();
|
||||
let relayers_fund_account_balance =
|
||||
left_client.free_native_balance(relayer_fund_acount_id.clone()).await;
|
||||
if let Err(relay_substrate_client::Error::AccountDoesNotExist) =
|
||||
relayers_fund_account_balance
|
||||
{
|
||||
log::info!(target: "bridge", "Going to create relayers fund account at {}.", Left::NAME);
|
||||
left_create_account(
|
||||
left_client.clone(),
|
||||
left_sign.clone(),
|
||||
relayer_fund_acount_id,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let relayer_fund_acount_id = pallet_bridge_messages::relayer_fund_account_id::<
|
||||
AccountIdOf<Right>,
|
||||
RightAccountIdConverter,
|
||||
>();
|
||||
let relayers_fund_account_balance =
|
||||
right_client.free_native_balance(relayer_fund_acount_id.clone()).await;
|
||||
if let Err(relay_substrate_client::Error::AccountDoesNotExist) =
|
||||
relayers_fund_account_balance
|
||||
{
|
||||
log::info!(target: "bridge", "Going to create relayers fund account at {}.", Right::NAME);
|
||||
right_create_account(
|
||||
right_client.clone(),
|
||||
right_sign.clone(),
|
||||
relayer_fund_acount_id,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
let left_to_right_on_demand_headers = OnDemandHeadersRelay::new(
|
||||
left_client.clone(),
|
||||
right_client.clone(),
|
||||
right_transactions_mortality,
|
||||
LeftToRightFinality::new(right_client.clone(), right_sign.clone()),
|
||||
MAX_MISSING_LEFT_HEADERS_AT_RIGHT,
|
||||
params.shared.only_mandatory_headers,
|
||||
);
|
||||
let right_to_left_on_demand_headers = OnDemandHeadersRelay::new(
|
||||
right_client.clone(),
|
||||
left_client.clone(),
|
||||
left_transactions_mortality,
|
||||
RightToLeftFinality::new(left_client.clone(), left_sign.clone()),
|
||||
MAX_MISSING_RIGHT_HEADERS_AT_LEFT,
|
||||
params.shared.only_mandatory_headers,
|
||||
);
|
||||
|
||||
// Need 2x capacity since we consider both directions for each lane
|
||||
@@ -174,28 +514,38 @@ impl RelayHeadersAndMessages {
|
||||
let left_to_right_messages = left_to_right_messages(MessagesRelayParams {
|
||||
source_client: left_client.clone(),
|
||||
source_sign: left_sign.clone(),
|
||||
source_transactions_mortality: left_transactions_mortality,
|
||||
target_client: right_client.clone(),
|
||||
target_sign: right_sign.clone(),
|
||||
target_transactions_mortality: right_transactions_mortality,
|
||||
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>(&lane),
|
||||
messages_relay::message_lane_loop::metrics_prefix::<
|
||||
<LeftToRightMessages as SubstrateMessageLane>::MessageLane,
|
||||
>(&lane),
|
||||
),
|
||||
relay_strategy: relay_strategy.clone(),
|
||||
})
|
||||
.map_err(|e| anyhow::format_err!("{}", e))
|
||||
.boxed();
|
||||
let right_to_left_messages = right_to_left_messages(MessagesRelayParams {
|
||||
source_client: right_client.clone(),
|
||||
source_sign: right_sign.clone(),
|
||||
source_transactions_mortality: right_transactions_mortality,
|
||||
target_client: left_client.clone(),
|
||||
target_sign: left_sign.clone(),
|
||||
target_transactions_mortality: left_transactions_mortality,
|
||||
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>(&lane),
|
||||
messages_relay::message_lane_loop::metrics_prefix::<
|
||||
<RightToLeftMessages as SubstrateMessageLane>::MessageLane,
|
||||
>(&lane),
|
||||
),
|
||||
relay_strategy: relay_strategy.clone(),
|
||||
})
|
||||
.map_err(|e| anyhow::format_err!("{}", e))
|
||||
.boxed();
|
||||
|
||||
@@ -14,25 +14,51 @@
|
||||
// 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::cli::bridge::FullBridge;
|
||||
use crate::cli::{
|
||||
HexLaneId, PrometheusParams, SourceConnectionParams, SourceSigningParams, TargetConnectionParams,
|
||||
TargetSigningParams,
|
||||
};
|
||||
use crate::messages_lane::MessagesRelayParams;
|
||||
use crate::select_full_bridge;
|
||||
|
||||
use structopt::StructOpt;
|
||||
use strum::{EnumString, EnumVariantNames, VariantNames};
|
||||
|
||||
use messages_relay::relay_strategy::MixStrategy;
|
||||
use substrate_relay_helper::messages_lane::MessagesRelayParams;
|
||||
|
||||
use crate::{
|
||||
cli::{
|
||||
bridge::FullBridge, HexLaneId, PrometheusParams, SourceConnectionParams,
|
||||
SourceSigningParams, TargetConnectionParams, TargetSigningParams,
|
||||
},
|
||||
select_full_bridge,
|
||||
};
|
||||
|
||||
/// Relayer operating mode.
|
||||
#[derive(Debug, EnumString, EnumVariantNames, Clone, Copy, PartialEq)]
|
||||
#[strum(serialize_all = "kebab_case")]
|
||||
pub enum RelayerMode {
|
||||
/// The relayer doesn't care about rewards.
|
||||
Altruistic,
|
||||
/// The relayer will deliver all messages and confirmations as long as he's not losing any
|
||||
/// funds.
|
||||
Rational,
|
||||
}
|
||||
|
||||
impl From<RelayerMode> for messages_relay::message_lane_loop::RelayerMode {
|
||||
fn from(mode: RelayerMode) -> Self {
|
||||
match mode {
|
||||
RelayerMode::Altruistic => Self::Altruistic,
|
||||
RelayerMode::Rational => Self::Rational,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Start messages relayer process.
|
||||
#[derive(StructOpt)]
|
||||
pub struct RelayMessages {
|
||||
/// A bridge instance to relay messages for.
|
||||
#[structopt(possible_values = &FullBridge::variants(), case_insensitive = true)]
|
||||
#[structopt(possible_values = FullBridge::VARIANTS, case_insensitive = true)]
|
||||
bridge: FullBridge,
|
||||
/// Hex-encoded lane id that should be served by the relay. Defaults to `00000000`.
|
||||
#[structopt(long, default_value = "00000000")]
|
||||
lane: HexLaneId,
|
||||
#[structopt(long, possible_values = RelayerMode::VARIANTS, case_insensitive = true, default_value = "rational")]
|
||||
relayer_mode: RelayerMode,
|
||||
#[structopt(flatten)]
|
||||
source: SourceConnectionParams,
|
||||
#[structopt(flatten)]
|
||||
@@ -51,21 +77,68 @@ impl RelayMessages {
|
||||
select_full_bridge!(self.bridge, {
|
||||
let source_client = self.source.to_client::<Source>().await?;
|
||||
let source_sign = self.source_sign.to_keypair::<Source>()?;
|
||||
let source_transactions_mortality = self.source_sign.transactions_mortality()?;
|
||||
let target_client = self.target.to_client::<Target>().await?;
|
||||
let target_sign = self.target_sign.to_keypair::<Target>()?;
|
||||
let target_transactions_mortality = self.target_sign.transactions_mortality()?;
|
||||
let relayer_mode = self.relayer_mode.into();
|
||||
let relay_strategy = MixStrategy::new(relayer_mode);
|
||||
|
||||
relay_messages(MessagesRelayParams {
|
||||
source_client,
|
||||
source_sign,
|
||||
source_transactions_mortality,
|
||||
target_client,
|
||||
target_sign,
|
||||
target_transactions_mortality,
|
||||
source_to_target_headers_relay: None,
|
||||
target_to_source_headers_relay: None,
|
||||
lane_id: self.lane.into(),
|
||||
metrics_params: self.prometheus_params.into(),
|
||||
relay_strategy,
|
||||
})
|
||||
.await
|
||||
.map_err(|e| anyhow::format_err!("{}", e))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn should_use_rational_relayer_mode_by_default() {
|
||||
assert_eq!(
|
||||
RelayMessages::from_iter(vec![
|
||||
"relay-messages",
|
||||
"rialto-to-millau",
|
||||
"--source-port=0",
|
||||
"--source-signer=//Alice",
|
||||
"--target-port=0",
|
||||
"--target-signer=//Alice",
|
||||
"--lane=00000000",
|
||||
])
|
||||
.relayer_mode,
|
||||
RelayerMode::Rational,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_accept_altruistic_relayer_mode() {
|
||||
assert_eq!(
|
||||
RelayMessages::from_iter(vec![
|
||||
"relay-messages",
|
||||
"rialto-to-millau",
|
||||
"--source-port=0",
|
||||
"--source-signer=//Alice",
|
||||
"--target-port=0",
|
||||
"--target-signer=//Alice",
|
||||
"--lane=00000000",
|
||||
"--relayer-mode=altruistic",
|
||||
])
|
||||
.relayer_mode,
|
||||
RelayerMode::Altruistic,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,559 @@
|
||||
// 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::cli::{Balance, TargetConnectionParams, TargetSigningParams};
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use num_traits::{One, Zero};
|
||||
use relay_substrate_client::{
|
||||
BlockWithJustification, Chain, Client, Error as SubstrateError, HeaderOf, TransactionSignScheme,
|
||||
};
|
||||
use relay_utils::FailedClient;
|
||||
use sp_core::Bytes;
|
||||
use sp_runtime::{
|
||||
traits::{Hash, Header as HeaderT},
|
||||
transaction_validity::TransactionPriority,
|
||||
};
|
||||
use structopt::StructOpt;
|
||||
use strum::{EnumString, EnumVariantNames, VariantNames};
|
||||
|
||||
/// Start resubmit transactions process.
|
||||
#[derive(StructOpt)]
|
||||
pub struct ResubmitTransactions {
|
||||
/// A bridge instance to relay headers for.
|
||||
#[structopt(possible_values = RelayChain::VARIANTS, case_insensitive = true)]
|
||||
chain: RelayChain,
|
||||
#[structopt(flatten)]
|
||||
target: TargetConnectionParams,
|
||||
#[structopt(flatten)]
|
||||
target_sign: TargetSigningParams,
|
||||
/// Number of blocks we see before considering queued transaction as stalled.
|
||||
#[structopt(long, default_value = "5")]
|
||||
stalled_blocks: u32,
|
||||
/// Tip limit. We'll never submit transaction with larger tip.
|
||||
#[structopt(long)]
|
||||
tip_limit: Balance,
|
||||
/// Tip increase step. We'll be checking updated transaction priority by increasing its tip by
|
||||
/// this step.
|
||||
#[structopt(long)]
|
||||
tip_step: Balance,
|
||||
/// Priority selection strategy.
|
||||
#[structopt(subcommand)]
|
||||
strategy: PrioritySelectionStrategy,
|
||||
}
|
||||
|
||||
/// Chain, which transactions we're going to track && resubmit.
|
||||
#[derive(Debug, EnumString, EnumVariantNames)]
|
||||
#[strum(serialize_all = "kebab_case")]
|
||||
pub enum RelayChain {
|
||||
Millau,
|
||||
Kusama,
|
||||
Polkadot,
|
||||
}
|
||||
|
||||
/// Strategy to use for priority selection.
|
||||
#[derive(StructOpt, Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum PrioritySelectionStrategy {
|
||||
/// Strategy selects tip that changes transaction priority to be better than priority of
|
||||
/// the first transaction of previous block.
|
||||
///
|
||||
/// It only makes sense to use this strategy for Millau transactions. Millau has transactions
|
||||
/// that are close to block limits, so if there are any other queued transactions, 'large'
|
||||
/// transaction won't fit the block && will be postponed. To avoid this, we change its priority
|
||||
/// to some large value, making it best transaction => it'll be 'mined' first.
|
||||
MakeItBestTransaction,
|
||||
/// Strategy selects tip that changes transaction priority to be better than priority of
|
||||
/// selected queued transaction.
|
||||
///
|
||||
/// When we first see stalled transaction, we make it better than worst 1/4 of queued
|
||||
/// transactions. If it is still stalled, we'll make it better than 1/3 of queued transactions,
|
||||
/// ...
|
||||
MakeItBetterThanQueuedTransaction,
|
||||
}
|
||||
|
||||
macro_rules! select_bridge {
|
||||
($bridge: expr, $generic: tt) => {
|
||||
match $bridge {
|
||||
RelayChain::Millau => {
|
||||
type Target = relay_millau_client::Millau;
|
||||
type TargetSign = relay_millau_client::Millau;
|
||||
|
||||
$generic
|
||||
},
|
||||
RelayChain::Kusama => {
|
||||
type Target = relay_kusama_client::Kusama;
|
||||
type TargetSign = relay_kusama_client::Kusama;
|
||||
|
||||
$generic
|
||||
},
|
||||
RelayChain::Polkadot => {
|
||||
type Target = relay_polkadot_client::Polkadot;
|
||||
type TargetSign = relay_polkadot_client::Polkadot;
|
||||
|
||||
$generic
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl ResubmitTransactions {
|
||||
/// Run the command.
|
||||
pub async fn run(self) -> anyhow::Result<()> {
|
||||
select_bridge!(self.chain, {
|
||||
let relay_loop_name = format!("ResubmitTransactions{}", Target::NAME);
|
||||
let client = self.target.to_client::<Target>().await?;
|
||||
let key_pair = self.target_sign.to_keypair::<Target>()?;
|
||||
|
||||
relay_utils::relay_loop((), client)
|
||||
.run(relay_loop_name, move |_, client, _| {
|
||||
run_until_connection_lost::<Target, TargetSign>(
|
||||
client,
|
||||
key_pair.clone(),
|
||||
Context {
|
||||
strategy: self.strategy,
|
||||
best_header: HeaderOf::<Target>::new(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
),
|
||||
transaction: None,
|
||||
resubmitted: 0,
|
||||
stalled_for: Zero::zero(),
|
||||
stalled_for_limit: self.stalled_blocks as _,
|
||||
tip_step: self.tip_step.cast() as _,
|
||||
tip_limit: self.tip_limit.cast() as _,
|
||||
},
|
||||
)
|
||||
})
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PrioritySelectionStrategy {
|
||||
/// Select target priority.
|
||||
async fn select_target_priority<C: Chain, S: TransactionSignScheme<Chain = C>>(
|
||||
&self,
|
||||
client: &Client<C>,
|
||||
context: &Context<C>,
|
||||
) -> Result<Option<TransactionPriority>, SubstrateError> {
|
||||
match *self {
|
||||
PrioritySelectionStrategy::MakeItBestTransaction =>
|
||||
read_previous_block_best_priority::<C, S>(client, context).await,
|
||||
PrioritySelectionStrategy::MakeItBetterThanQueuedTransaction =>
|
||||
select_priority_from_queue::<C, S>(client, context).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Context<C: Chain> {
|
||||
/// Priority selection strategy.
|
||||
strategy: PrioritySelectionStrategy,
|
||||
/// Best known block header.
|
||||
best_header: C::Header,
|
||||
/// Hash of the (potentially) stalled transaction.
|
||||
transaction: Option<C::Hash>,
|
||||
/// How many times we have resubmitted this `transaction`?
|
||||
resubmitted: u32,
|
||||
/// This transaction is in pool for `stalled_for` wakeup intervals.
|
||||
stalled_for: C::BlockNumber,
|
||||
/// When `stalled_for` reaching this limit, transaction is considered stalled.
|
||||
stalled_for_limit: C::BlockNumber,
|
||||
/// Tip step interval.
|
||||
tip_step: C::Balance,
|
||||
/// Maximal tip.
|
||||
tip_limit: C::Balance,
|
||||
}
|
||||
|
||||
impl<C: Chain> Context<C> {
|
||||
/// Return true if transaction has stalled.
|
||||
fn is_stalled(&self) -> bool {
|
||||
self.stalled_for >= self.stalled_for_limit
|
||||
}
|
||||
|
||||
/// Notice resubmitted transaction.
|
||||
fn notice_resubmitted_transaction(mut self, transaction: C::Hash) -> Self {
|
||||
self.transaction = Some(transaction);
|
||||
self.stalled_for = Zero::zero();
|
||||
self.resubmitted += 1;
|
||||
self
|
||||
}
|
||||
|
||||
/// Notice transaction from the transaction pool.
|
||||
fn notice_transaction(mut self, transaction: C::Hash) -> Self {
|
||||
if self.transaction == Some(transaction) {
|
||||
self.stalled_for += One::one();
|
||||
} else {
|
||||
self.transaction = Some(transaction);
|
||||
self.stalled_for = One::one();
|
||||
self.resubmitted = 0;
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Run resubmit transactions loop.
|
||||
async fn run_until_connection_lost<C: Chain, S: TransactionSignScheme<Chain = C>>(
|
||||
client: Client<C>,
|
||||
key_pair: S::AccountKeyPair,
|
||||
mut context: Context<C>,
|
||||
) -> Result<(), FailedClient> {
|
||||
loop {
|
||||
async_std::task::sleep(C::AVERAGE_BLOCK_INTERVAL).await;
|
||||
|
||||
let result = run_loop_iteration::<C, S>(client.clone(), key_pair.clone(), context).await;
|
||||
context = match result {
|
||||
Ok(context) => context,
|
||||
Err(error) => {
|
||||
log::error!(
|
||||
target: "bridge",
|
||||
"Resubmit {} transactions loop has failed with error: {:?}",
|
||||
C::NAME,
|
||||
error,
|
||||
);
|
||||
return Err(FailedClient::Target)
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Run single loop iteration.
|
||||
async fn run_loop_iteration<C: Chain, S: TransactionSignScheme<Chain = C>>(
|
||||
client: Client<C>,
|
||||
key_pair: S::AccountKeyPair,
|
||||
mut context: Context<C>,
|
||||
) -> Result<Context<C>, SubstrateError> {
|
||||
// correct best header is required for all other actions
|
||||
context.best_header = client.best_header().await?;
|
||||
|
||||
// check if there's queued transaction, signed by given author
|
||||
let original_transaction = match lookup_signer_transaction::<C, S>(&client, &key_pair).await? {
|
||||
Some(original_transaction) => original_transaction,
|
||||
None => {
|
||||
log::trace!(target: "bridge", "No {} transactions from required signer in the txpool", C::NAME);
|
||||
return Ok(context)
|
||||
},
|
||||
};
|
||||
let original_transaction_hash = C::Hasher::hash(&original_transaction.encode());
|
||||
let context = context.notice_transaction(original_transaction_hash);
|
||||
|
||||
// if transaction hasn't been mined for `stalled_blocks`, we'll need to resubmit it
|
||||
if !context.is_stalled() {
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"{} transaction {:?} is not yet stalled ({:?}/{:?})",
|
||||
C::NAME,
|
||||
context.transaction,
|
||||
context.stalled_for,
|
||||
context.stalled_for_limit,
|
||||
);
|
||||
return Ok(context)
|
||||
}
|
||||
|
||||
// select priority for updated transaction
|
||||
let target_priority =
|
||||
match context.strategy.select_target_priority::<C, S>(&client, &context).await? {
|
||||
Some(target_priority) => target_priority,
|
||||
None => {
|
||||
log::trace!(target: "bridge", "Failed to select target priority");
|
||||
return Ok(context)
|
||||
},
|
||||
};
|
||||
|
||||
// update transaction tip
|
||||
let (is_updated, updated_transaction) = update_transaction_tip::<C, S>(
|
||||
&client,
|
||||
&key_pair,
|
||||
context.best_header.hash(),
|
||||
original_transaction,
|
||||
context.tip_step,
|
||||
context.tip_limit,
|
||||
target_priority,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if !is_updated {
|
||||
log::trace!(target: "bridge", "{} transaction tip can not be updated. Reached limit?", C::NAME);
|
||||
return Ok(context)
|
||||
}
|
||||
|
||||
let updated_transaction = updated_transaction.encode();
|
||||
let updated_transaction_hash = C::Hasher::hash(&updated_transaction);
|
||||
client.submit_unsigned_extrinsic(Bytes(updated_transaction)).await?;
|
||||
|
||||
log::info!(
|
||||
target: "bridge",
|
||||
"Replaced {} transaction {} with {} in txpool",
|
||||
C::NAME,
|
||||
original_transaction_hash,
|
||||
updated_transaction_hash,
|
||||
);
|
||||
|
||||
Ok(context.notice_resubmitted_transaction(updated_transaction_hash))
|
||||
}
|
||||
|
||||
/// Search transaction pool for transaction, signed by given key pair.
|
||||
async fn lookup_signer_transaction<C: Chain, S: TransactionSignScheme<Chain = C>>(
|
||||
client: &Client<C>,
|
||||
key_pair: &S::AccountKeyPair,
|
||||
) -> Result<Option<S::SignedTransaction>, SubstrateError> {
|
||||
let pending_transactions = client.pending_extrinsics().await?;
|
||||
for pending_transaction in pending_transactions {
|
||||
let pending_transaction = S::SignedTransaction::decode(&mut &pending_transaction.0[..])
|
||||
.map_err(SubstrateError::ResponseParseFailed)?;
|
||||
if !S::is_signed_by(key_pair, &pending_transaction) {
|
||||
continue
|
||||
}
|
||||
|
||||
return Ok(Some(pending_transaction))
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Read priority of best signed transaction of previous block.
|
||||
async fn read_previous_block_best_priority<C: Chain, S: TransactionSignScheme<Chain = C>>(
|
||||
client: &Client<C>,
|
||||
context: &Context<C>,
|
||||
) -> Result<Option<TransactionPriority>, SubstrateError> {
|
||||
let best_block = client.get_block(Some(context.best_header.hash())).await?;
|
||||
let best_transaction = best_block
|
||||
.extrinsics()
|
||||
.iter()
|
||||
.filter_map(|xt| S::SignedTransaction::decode(&mut &xt[..]).ok())
|
||||
.find(|xt| S::is_signed(xt));
|
||||
match best_transaction {
|
||||
Some(best_transaction) => Ok(Some(
|
||||
client
|
||||
.validate_transaction(*context.best_header.parent_hash(), best_transaction)
|
||||
.await??
|
||||
.priority,
|
||||
)),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Select priority of some queued transaction.
|
||||
async fn select_priority_from_queue<C: Chain, S: TransactionSignScheme<Chain = C>>(
|
||||
client: &Client<C>,
|
||||
context: &Context<C>,
|
||||
) -> Result<Option<TransactionPriority>, SubstrateError> {
|
||||
// select transaction from the queue
|
||||
let queued_transactions = client.pending_extrinsics().await?;
|
||||
let selected_transaction = match select_transaction_from_queue(queued_transactions, context) {
|
||||
Some(selected_transaction) => selected_transaction,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let selected_transaction = S::SignedTransaction::decode(&mut &selected_transaction[..])
|
||||
.map_err(SubstrateError::ResponseParseFailed)?;
|
||||
let target_priority = client
|
||||
.validate_transaction(context.best_header.hash(), selected_transaction)
|
||||
.await??
|
||||
.priority;
|
||||
Ok(Some(target_priority))
|
||||
}
|
||||
|
||||
/// Select transaction with target priority from the vec of queued transactions.
|
||||
fn select_transaction_from_queue<C: Chain>(
|
||||
mut queued_transactions: Vec<Bytes>,
|
||||
context: &Context<C>,
|
||||
) -> Option<Bytes> {
|
||||
if queued_transactions.is_empty() {
|
||||
return None
|
||||
}
|
||||
|
||||
// the more times we resubmit transaction (`context.resubmitted`), the closer we move
|
||||
// to the front of the transaction queue
|
||||
let total_transactions = queued_transactions.len();
|
||||
let resubmitted_factor = context.resubmitted;
|
||||
let divisor =
|
||||
1usize.saturating_add(1usize.checked_shl(resubmitted_factor).unwrap_or(usize::MAX));
|
||||
let transactions_to_skip = total_transactions / divisor;
|
||||
|
||||
Some(
|
||||
queued_transactions
|
||||
.swap_remove(std::cmp::min(total_transactions - 1, transactions_to_skip)),
|
||||
)
|
||||
}
|
||||
|
||||
/// Try to find appropriate tip for transaction so that its priority is larger than given.
|
||||
async fn update_transaction_tip<C: Chain, S: TransactionSignScheme<Chain = C>>(
|
||||
client: &Client<C>,
|
||||
key_pair: &S::AccountKeyPair,
|
||||
at_block: C::Hash,
|
||||
tx: S::SignedTransaction,
|
||||
tip_step: C::Balance,
|
||||
tip_limit: C::Balance,
|
||||
target_priority: TransactionPriority,
|
||||
) -> Result<(bool, S::SignedTransaction), SubstrateError> {
|
||||
let stx = format!("{:?}", tx);
|
||||
let mut current_priority = client.validate_transaction(at_block, tx.clone()).await??.priority;
|
||||
let mut unsigned_tx = S::parse_transaction(tx).ok_or_else(|| {
|
||||
SubstrateError::Custom(format!("Failed to parse {} transaction {}", C::NAME, stx,))
|
||||
})?;
|
||||
let old_tip = unsigned_tx.tip;
|
||||
|
||||
while current_priority < target_priority {
|
||||
let next_tip = unsigned_tx.tip + tip_step;
|
||||
if next_tip > tip_limit {
|
||||
break
|
||||
}
|
||||
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"{} transaction priority with tip={:?}: {}. Target priority: {}",
|
||||
C::NAME,
|
||||
unsigned_tx.tip,
|
||||
current_priority,
|
||||
target_priority,
|
||||
);
|
||||
|
||||
unsigned_tx.tip = next_tip;
|
||||
current_priority = client
|
||||
.validate_transaction(
|
||||
at_block,
|
||||
S::sign_transaction(
|
||||
*client.genesis_hash(),
|
||||
key_pair,
|
||||
relay_substrate_client::TransactionEra::immortal(),
|
||||
unsigned_tx.clone(),
|
||||
),
|
||||
)
|
||||
.await??
|
||||
.priority;
|
||||
}
|
||||
|
||||
log::debug!(
|
||||
target: "bridge",
|
||||
"{} transaction tip has changed from {:?} to {:?}",
|
||||
C::NAME,
|
||||
old_tip,
|
||||
unsigned_tx.tip,
|
||||
);
|
||||
|
||||
Ok((
|
||||
old_tip != unsigned_tx.tip,
|
||||
S::sign_transaction(
|
||||
*client.genesis_hash(),
|
||||
key_pair,
|
||||
relay_substrate_client::TransactionEra::immortal(),
|
||||
unsigned_tx,
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use bp_rialto::Hash;
|
||||
use relay_rialto_client::Rialto;
|
||||
|
||||
fn context() -> Context<Rialto> {
|
||||
Context {
|
||||
strategy: PrioritySelectionStrategy::MakeItBestTransaction,
|
||||
best_header: HeaderOf::<Rialto>::new(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
),
|
||||
transaction: None,
|
||||
resubmitted: 0,
|
||||
stalled_for: Zero::zero(),
|
||||
stalled_for_limit: 3,
|
||||
tip_step: 100,
|
||||
tip_limit: 1000,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn context_works() {
|
||||
let mut context = context();
|
||||
|
||||
// when transaction is noticed 2/3 times, it isn't stalled
|
||||
context = context.notice_transaction(Default::default());
|
||||
assert!(!context.is_stalled());
|
||||
assert_eq!(context.stalled_for, 1);
|
||||
assert_eq!(context.resubmitted, 0);
|
||||
context = context.notice_transaction(Default::default());
|
||||
assert!(!context.is_stalled());
|
||||
assert_eq!(context.stalled_for, 2);
|
||||
assert_eq!(context.resubmitted, 0);
|
||||
|
||||
// when transaction is noticed for 3rd time in a row, it is considered stalled
|
||||
context = context.notice_transaction(Default::default());
|
||||
assert!(context.is_stalled());
|
||||
assert_eq!(context.stalled_for, 3);
|
||||
assert_eq!(context.resubmitted, 0);
|
||||
|
||||
// and after we resubmit it, we forget previous transaction
|
||||
context = context.notice_resubmitted_transaction(Hash::from([1; 32]));
|
||||
assert_eq!(context.transaction, Some(Hash::from([1; 32])));
|
||||
assert_eq!(context.resubmitted, 1);
|
||||
assert_eq!(context.stalled_for, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_transaction_from_queue_works_with_empty_queue() {
|
||||
assert_eq!(select_transaction_from_queue(vec![], &context()), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_transaction_from_queue_works() {
|
||||
let mut context = context();
|
||||
let queued_transactions = vec![
|
||||
Bytes(vec![1]),
|
||||
Bytes(vec![2]),
|
||||
Bytes(vec![3]),
|
||||
Bytes(vec![4]),
|
||||
Bytes(vec![5]),
|
||||
Bytes(vec![6]),
|
||||
];
|
||||
|
||||
// when we resubmit tx for the first time, 1/2 of queue is skipped
|
||||
assert_eq!(
|
||||
select_transaction_from_queue(queued_transactions.clone(), &context),
|
||||
Some(Bytes(vec![4])),
|
||||
);
|
||||
|
||||
// when we resubmit tx for the second time, 1/3 of queue is skipped
|
||||
context = context.notice_resubmitted_transaction(Hash::from([1; 32]));
|
||||
assert_eq!(
|
||||
select_transaction_from_queue(queued_transactions.clone(), &context),
|
||||
Some(Bytes(vec![3])),
|
||||
);
|
||||
|
||||
// when we resubmit tx for the third time, 1/5 of queue is skipped
|
||||
context = context.notice_resubmitted_transaction(Hash::from([2; 32]));
|
||||
assert_eq!(
|
||||
select_transaction_from_queue(queued_transactions.clone(), &context),
|
||||
Some(Bytes(vec![2])),
|
||||
);
|
||||
|
||||
// when we resubmit tx for the second time, 1/9 of queue is skipped
|
||||
context = context.notice_resubmitted_transaction(Hash::from([3; 32]));
|
||||
assert_eq!(
|
||||
select_transaction_from_queue(queued_transactions, &context),
|
||||
Some(Bytes(vec![1])),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -14,46 +14,71 @@
|
||||
// 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::cli::bridge::FullBridge;
|
||||
use crate::cli::encode_call::{self, CliEncodeCall};
|
||||
use crate::cli::estimate_fee::estimate_message_delivery_and_dispatch_fee;
|
||||
use crate::cli::{
|
||||
Balance, CliChain, ExplicitOrMaximal, HexBytes, HexLaneId, Origins, SourceConnectionParams, SourceSigningParams,
|
||||
TargetSigningParams,
|
||||
bridge::FullBridge,
|
||||
encode_call::{self, CliEncodeCall},
|
||||
estimate_fee::estimate_message_delivery_and_dispatch_fee,
|
||||
Balance, CliChain, ExplicitOrMaximal, HexBytes, HexLaneId, Origins, SourceConnectionParams,
|
||||
SourceSigningParams, TargetSigningParams,
|
||||
};
|
||||
use bp_message_dispatch::{CallOrigin, MessagePayload};
|
||||
use bp_runtime::messages::DispatchFeePayment;
|
||||
use bp_runtime::BalanceOf;
|
||||
use codec::Encode;
|
||||
use frame_support::weights::Weight;
|
||||
use relay_substrate_client::{Chain, TransactionSignScheme};
|
||||
use relay_substrate_client::{Chain, TransactionSignScheme, UnsignedTransaction};
|
||||
use sp_core::{Bytes, Pair};
|
||||
use sp_runtime::{traits::IdentifyAccount, AccountId32, MultiSignature, MultiSigner};
|
||||
use std::fmt::Debug;
|
||||
use structopt::StructOpt;
|
||||
use strum::{EnumString, EnumVariantNames, VariantNames};
|
||||
|
||||
/// Relayer operating mode.
|
||||
#[derive(Debug, EnumString, EnumVariantNames, Clone, Copy, PartialEq, Eq)]
|
||||
#[strum(serialize_all = "kebab_case")]
|
||||
pub enum DispatchFeePayment {
|
||||
/// The dispatch fee is paid at the source chain.
|
||||
AtSourceChain,
|
||||
/// The dispatch fee is paid at the target chain.
|
||||
AtTargetChain,
|
||||
}
|
||||
|
||||
impl From<DispatchFeePayment> for bp_runtime::messages::DispatchFeePayment {
|
||||
fn from(dispatch_fee_payment: DispatchFeePayment) -> Self {
|
||||
match dispatch_fee_payment {
|
||||
DispatchFeePayment::AtSourceChain => Self::AtSourceChain,
|
||||
DispatchFeePayment::AtTargetChain => Self::AtTargetChain,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Send bridge message.
|
||||
#[derive(StructOpt)]
|
||||
pub struct SendMessage {
|
||||
/// A bridge instance to encode call for.
|
||||
#[structopt(possible_values = &FullBridge::variants(), case_insensitive = true)]
|
||||
#[structopt(possible_values = FullBridge::VARIANTS, case_insensitive = true)]
|
||||
bridge: FullBridge,
|
||||
#[structopt(flatten)]
|
||||
source: SourceConnectionParams,
|
||||
#[structopt(flatten)]
|
||||
source_sign: SourceSigningParams,
|
||||
/// The SURI of secret key to use when transactions are submitted to the Target node.
|
||||
#[structopt(long, required_if("origin", "Target"))]
|
||||
target_signer: Option<String>,
|
||||
/// The password for the SURI of secret key to use when transactions are submitted to the Target node.
|
||||
#[structopt(long)]
|
||||
target_signer_password: Option<String>,
|
||||
#[structopt(flatten)]
|
||||
target_sign: TargetSigningParams,
|
||||
/// Hex-encoded lane id. Defaults to `00000000`.
|
||||
#[structopt(long, default_value = "00000000")]
|
||||
lane: HexLaneId,
|
||||
/// Where dispatch fee is paid?
|
||||
#[structopt(
|
||||
long,
|
||||
possible_values = DispatchFeePayment::VARIANTS,
|
||||
case_insensitive = true,
|
||||
default_value = "at-source-chain",
|
||||
)]
|
||||
dispatch_fee_payment: DispatchFeePayment,
|
||||
/// Dispatch weight of the message. If not passed, determined automatically.
|
||||
#[structopt(long)]
|
||||
dispatch_weight: Option<ExplicitOrMaximal<Weight>>,
|
||||
/// Delivery and dispatch fee in source chain base currency units. If not passed, determined automatically.
|
||||
/// Delivery and dispatch fee in source chain base currency units. If not passed, determined
|
||||
/// automatically.
|
||||
#[structopt(long)]
|
||||
fee: Option<Balance>,
|
||||
/// Message type.
|
||||
@@ -72,9 +97,9 @@ impl SendMessage {
|
||||
crate::select_full_bridge!(self.bridge, {
|
||||
let SendMessage {
|
||||
source_sign,
|
||||
target_signer,
|
||||
target_signer_password,
|
||||
target_sign,
|
||||
ref mut message,
|
||||
dispatch_fee_payment,
|
||||
dispatch_weight,
|
||||
origin,
|
||||
bridge,
|
||||
@@ -101,12 +126,6 @@ impl SendMessage {
|
||||
match origin {
|
||||
Origins::Source => CallOrigin::SourceAccount(source_account_id),
|
||||
Origins::Target => {
|
||||
let target_sign = TargetSigningParams {
|
||||
target_signer: target_signer.clone().ok_or_else(|| {
|
||||
anyhow::format_err!("The argument target_signer is not available")
|
||||
})?,
|
||||
target_signer_password: target_signer_password.clone(),
|
||||
};
|
||||
let target_sign = target_sign.to_keypair::<Target>()?;
|
||||
let digest = account_ownership_digest(
|
||||
&target_call,
|
||||
@@ -120,9 +139,10 @@ impl SendMessage {
|
||||
target_origin_public.into(),
|
||||
digest_signature.into(),
|
||||
)
|
||||
}
|
||||
},
|
||||
},
|
||||
&target_call,
|
||||
*dispatch_fee_payment,
|
||||
)
|
||||
};
|
||||
Ok(payload)
|
||||
@@ -141,7 +161,7 @@ impl SendMessage {
|
||||
let fee = match self.fee {
|
||||
Some(fee) => fee,
|
||||
None => Balance(
|
||||
estimate_message_delivery_and_dispatch_fee::<<Source as Chain>::Balance, _, _>(
|
||||
estimate_message_delivery_and_dispatch_fee::<BalanceOf<Source>, _, _>(
|
||||
&source_client,
|
||||
ESTIMATE_MESSAGE_FEE_METHOD,
|
||||
lane,
|
||||
@@ -158,24 +178,46 @@ impl SendMessage {
|
||||
fee,
|
||||
})?;
|
||||
|
||||
source_client
|
||||
.submit_signed_extrinsic(source_sign.public().into(), |transaction_nonce| {
|
||||
let signed_source_call = Source::sign_transaction(
|
||||
*source_client.genesis_hash(),
|
||||
let source_genesis_hash = *source_client.genesis_hash();
|
||||
let estimated_transaction_fee = source_client
|
||||
.estimate_extrinsic_fee(Bytes(
|
||||
Source::sign_transaction(
|
||||
source_genesis_hash,
|
||||
&source_sign,
|
||||
transaction_nonce,
|
||||
send_message_call,
|
||||
relay_substrate_client::TransactionEra::immortal(),
|
||||
UnsignedTransaction::new(send_message_call.clone(), 0),
|
||||
)
|
||||
.encode(),
|
||||
))
|
||||
.await?;
|
||||
source_client
|
||||
.submit_signed_extrinsic(source_sign.public().into(), move |_, transaction_nonce| {
|
||||
let signed_source_call = Source::sign_transaction(
|
||||
source_genesis_hash,
|
||||
&source_sign,
|
||||
relay_substrate_client::TransactionEra::immortal(),
|
||||
UnsignedTransaction::new(send_message_call, transaction_nonce),
|
||||
)
|
||||
.encode();
|
||||
|
||||
log::info!(
|
||||
target: "bridge",
|
||||
"Sending message to {}. Size: {}. Dispatch weight: {}. Fee: {}",
|
||||
"Sending message to {}. Lane: {:?}. Size: {}. Dispatch weight: {}. Fee: {}",
|
||||
Target::NAME,
|
||||
lane,
|
||||
signed_source_call.len(),
|
||||
dispatch_weight,
|
||||
fee,
|
||||
);
|
||||
log::info!(
|
||||
target: "bridge",
|
||||
"The source account ({:?}) balance will be reduced by (at most) {} (message fee) + {} (tx fee ) = {} {} tokens",
|
||||
AccountId32::from(source_sign.public()),
|
||||
fee.0,
|
||||
estimated_transaction_fee.inclusion_fee(),
|
||||
fee.0.saturating_add(estimated_transaction_fee.inclusion_fee() as _),
|
||||
Source::NAME,
|
||||
);
|
||||
log::info!(
|
||||
target: "bridge",
|
||||
"Signed {} Call: {:?}",
|
||||
@@ -197,10 +239,7 @@ fn prepare_call_dispatch_weight(
|
||||
weight_from_pre_dispatch_call: ExplicitOrMaximal<Weight>,
|
||||
maximal_allowed_weight: Weight,
|
||||
) -> Weight {
|
||||
match user_specified_dispatch_weight
|
||||
.clone()
|
||||
.unwrap_or(weight_from_pre_dispatch_call)
|
||||
{
|
||||
match user_specified_dispatch_weight.clone().unwrap_or(weight_from_pre_dispatch_call) {
|
||||
ExplicitOrMaximal::Explicit(weight) => weight,
|
||||
ExplicitOrMaximal::Maximal => maximal_allowed_weight,
|
||||
}
|
||||
@@ -211,6 +250,7 @@ pub(crate) fn message_payload<SAccountId, TPublic, TSignature>(
|
||||
weight: Weight,
|
||||
origin: CallOrigin<SAccountId, TPublic, TSignature>,
|
||||
call: &impl Encode,
|
||||
dispatch_fee_payment: DispatchFeePayment,
|
||||
) -> MessagePayload<SAccountId, TPublic, TSignature, Vec<u8>>
|
||||
where
|
||||
SAccountId: Encode + Debug,
|
||||
@@ -222,7 +262,7 @@ where
|
||||
spec_version,
|
||||
weight,
|
||||
origin,
|
||||
dispatch_fee_payment: DispatchFeePayment::AtSourceChain,
|
||||
dispatch_fee_payment: dispatch_fee_payment.into(),
|
||||
call: HexBytes::encode(call),
|
||||
};
|
||||
|
||||
@@ -230,24 +270,14 @@ where
|
||||
log::info!(target: "bridge", "Encoded Message Payload: {:?}", HexBytes::encode(&payload));
|
||||
|
||||
// re-pack to return `Vec<u8>`
|
||||
let MessagePayload {
|
||||
spec_version,
|
||||
weight,
|
||||
origin,
|
||||
dispatch_fee_payment,
|
||||
call,
|
||||
} = payload;
|
||||
MessagePayload {
|
||||
spec_version,
|
||||
weight,
|
||||
origin,
|
||||
dispatch_fee_payment,
|
||||
call: call.0,
|
||||
}
|
||||
let MessagePayload { spec_version, weight, origin, dispatch_fee_payment, call } = payload;
|
||||
MessagePayload { spec_version, weight, origin, dispatch_fee_payment, call: call.0 }
|
||||
}
|
||||
|
||||
pub(crate) fn compute_maximal_message_dispatch_weight(maximal_extrinsic_weight: Weight) -> Weight {
|
||||
bridge_runtime_common::messages::target::maximal_incoming_message_dispatch_weight(maximal_extrinsic_weight)
|
||||
bridge_runtime_common::messages::target::maximal_incoming_message_dispatch_weight(
|
||||
maximal_extrinsic_weight,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -260,7 +290,7 @@ mod tests {
|
||||
// given
|
||||
let mut send_message = SendMessage::from_iter(vec![
|
||||
"send-message",
|
||||
"RialtoToMillau",
|
||||
"rialto-to-millau",
|
||||
"--source-port",
|
||||
"1234",
|
||||
"--source-signer",
|
||||
@@ -278,10 +308,12 @@ mod tests {
|
||||
payload,
|
||||
MessagePayload {
|
||||
spec_version: relay_millau_client::Millau::RUNTIME_VERSION.spec_version,
|
||||
weight: 1038000,
|
||||
origin: CallOrigin::SourceAccount(sp_keyring::AccountKeyring::Alice.to_account_id()),
|
||||
dispatch_fee_payment: DispatchFeePayment::AtSourceChain,
|
||||
call: hex!("0401081234").to_vec(),
|
||||
weight: 576000,
|
||||
origin: CallOrigin::SourceAccount(
|
||||
sp_keyring::AccountKeyring::Alice.to_account_id()
|
||||
),
|
||||
dispatch_fee_payment: bp_runtime::messages::DispatchFeePayment::AtSourceChain,
|
||||
call: hex!("0001081234").to_vec(),
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -291,7 +323,7 @@ mod tests {
|
||||
// given
|
||||
let mut send_message = SendMessage::from_iter(vec![
|
||||
"send-message",
|
||||
"MillauToRialto",
|
||||
"millau-to-rialto",
|
||||
"--source-port",
|
||||
"1234",
|
||||
"--source-signer",
|
||||
@@ -318,24 +350,24 @@ mod tests {
|
||||
payload,
|
||||
MessagePayload {
|
||||
spec_version: relay_millau_client::Millau::RUNTIME_VERSION.spec_version,
|
||||
weight: 1038000,
|
||||
weight: 576000,
|
||||
origin: CallOrigin::TargetAccount(
|
||||
sp_keyring::AccountKeyring::Alice.to_account_id(),
|
||||
sp_keyring::AccountKeyring::Bob.into(),
|
||||
signature,
|
||||
),
|
||||
dispatch_fee_payment: DispatchFeePayment::AtSourceChain,
|
||||
call: hex!("0701081234").to_vec(),
|
||||
dispatch_fee_payment: bp_runtime::messages::DispatchFeePayment::AtSourceChain,
|
||||
call: hex!("0001081234").to_vec(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn target_signer_must_exist_if_origin_is_target() {
|
||||
fn accepts_send_message_command_without_target_sign_options() {
|
||||
// given
|
||||
let send_message = SendMessage::from_iter_safe(vec![
|
||||
"send-message",
|
||||
"MillauToRialto",
|
||||
"rialto-to-millau",
|
||||
"--source-port",
|
||||
"1234",
|
||||
"--source-signer",
|
||||
@@ -347,6 +379,31 @@ mod tests {
|
||||
"1234",
|
||||
]);
|
||||
|
||||
assert!(send_message.is_err());
|
||||
assert!(send_message.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accepts_non_default_dispatch_fee_payment() {
|
||||
// given
|
||||
let mut send_message = SendMessage::from_iter(vec![
|
||||
"send-message",
|
||||
"rialto-to-millau",
|
||||
"--source-port",
|
||||
"1234",
|
||||
"--source-signer",
|
||||
"//Alice",
|
||||
"--dispatch-fee-payment",
|
||||
"at-target-chain",
|
||||
"remark",
|
||||
]);
|
||||
|
||||
// when
|
||||
let payload = send_message.encode_payload().unwrap();
|
||||
|
||||
// then
|
||||
assert_eq!(
|
||||
payload.dispatch_fee_payment,
|
||||
bp_runtime::messages::DispatchFeePayment::AtTargetChain
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,798 @@
|
||||
// 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/>.
|
||||
|
||||
//! Tokens swap using token-swap bridge pallet.
|
||||
|
||||
// TokenSwapBalances fields are never directly accessed, but the whole struct is printed
|
||||
// to show token swap progress
|
||||
#![allow(dead_code)]
|
||||
|
||||
use codec::Encode;
|
||||
use num_traits::One;
|
||||
use rand::random;
|
||||
use structopt::StructOpt;
|
||||
use strum::{EnumString, EnumVariantNames, VariantNames};
|
||||
|
||||
use frame_support::dispatch::GetDispatchInfo;
|
||||
use relay_substrate_client::{
|
||||
AccountIdOf, AccountPublicOf, BalanceOf, BlockNumberOf, CallOf, Chain, ChainWithBalances,
|
||||
Client, Error as SubstrateError, HashOf, SignatureOf, Subscription, TransactionSignScheme,
|
||||
TransactionStatusOf, UnsignedTransaction,
|
||||
};
|
||||
use sp_core::{blake2_256, storage::StorageKey, Bytes, Pair, H256, U256};
|
||||
use sp_runtime::traits::{Convert, Header as HeaderT};
|
||||
|
||||
use crate::cli::{
|
||||
Balance, CliChain, SourceConnectionParams, SourceSigningParams, TargetConnectionParams,
|
||||
TargetSigningParams,
|
||||
};
|
||||
|
||||
/// Swap tokens.
|
||||
#[derive(StructOpt, Debug, PartialEq)]
|
||||
pub struct SwapTokens {
|
||||
/// A bridge instance to use in token swap.
|
||||
#[structopt(possible_values = SwapTokensBridge::VARIANTS, case_insensitive = true)]
|
||||
bridge: SwapTokensBridge,
|
||||
|
||||
#[structopt(flatten)]
|
||||
source: SourceConnectionParams,
|
||||
#[structopt(flatten)]
|
||||
source_sign: SourceSigningParams,
|
||||
|
||||
#[structopt(flatten)]
|
||||
target: TargetConnectionParams,
|
||||
#[structopt(flatten)]
|
||||
target_sign: TargetSigningParams,
|
||||
|
||||
#[structopt(subcommand)]
|
||||
swap_type: TokenSwapType,
|
||||
/// Source chain balance that source signer wants to swap.
|
||||
#[structopt(long)]
|
||||
source_balance: Balance,
|
||||
/// Target chain balance that target signer wants to swap.
|
||||
#[structopt(long)]
|
||||
target_balance: Balance,
|
||||
}
|
||||
|
||||
/// Token swap type.
|
||||
#[derive(StructOpt, Debug, PartialEq, Eq, Clone)]
|
||||
pub enum TokenSwapType {
|
||||
/// The `target_sign` is temporary and only have funds for single swap.
|
||||
NoLock,
|
||||
/// This swap type prevents `source_signer` from restarting the swap after it has been
|
||||
/// completed.
|
||||
LockUntilBlock {
|
||||
/// Number of blocks before the swap expires.
|
||||
#[structopt(long)]
|
||||
blocks_before_expire: u32,
|
||||
/// Unique swap nonce.
|
||||
#[structopt(long)]
|
||||
swap_nonce: Option<U256>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Swap tokens bridge.
|
||||
#[derive(Debug, EnumString, EnumVariantNames, PartialEq)]
|
||||
#[strum(serialize_all = "kebab_case")]
|
||||
pub enum SwapTokensBridge {
|
||||
/// Use token-swap pallet deployed at Millau to swap tokens with Rialto.
|
||||
MillauToRialto,
|
||||
}
|
||||
|
||||
macro_rules! select_bridge {
|
||||
($bridge: expr, $generic: tt) => {
|
||||
match $bridge {
|
||||
SwapTokensBridge::MillauToRialto => {
|
||||
type Source = relay_millau_client::Millau;
|
||||
type Target = relay_rialto_client::Rialto;
|
||||
|
||||
type FromSwapToThisAccountIdConverter = bp_rialto::AccountIdConverter;
|
||||
|
||||
use bp_millau::{
|
||||
derive_account_from_rialto_id as derive_source_account_from_target_account,
|
||||
TO_MILLAU_ESTIMATE_MESSAGE_FEE_METHOD as ESTIMATE_TARGET_TO_SOURCE_MESSAGE_FEE_METHOD,
|
||||
WITH_RIALTO_TOKEN_SWAP_PALLET_NAME as TOKEN_SWAP_PALLET_NAME,
|
||||
};
|
||||
use bp_rialto::{
|
||||
derive_account_from_millau_id as derive_target_account_from_source_account,
|
||||
TO_RIALTO_ESTIMATE_MESSAGE_FEE_METHOD as ESTIMATE_SOURCE_TO_TARGET_MESSAGE_FEE_METHOD,
|
||||
};
|
||||
|
||||
const SOURCE_CHAIN_ID: bp_runtime::ChainId = bp_runtime::MILLAU_CHAIN_ID;
|
||||
const TARGET_CHAIN_ID: bp_runtime::ChainId = bp_runtime::RIALTO_CHAIN_ID;
|
||||
|
||||
const SOURCE_SPEC_VERSION: u32 = millau_runtime::VERSION.spec_version;
|
||||
const TARGET_SPEC_VERSION: u32 = rialto_runtime::VERSION.spec_version;
|
||||
|
||||
const SOURCE_TO_TARGET_LANE_ID: bp_messages::LaneId = *b"swap";
|
||||
const TARGET_TO_SOURCE_LANE_ID: bp_messages::LaneId = [0, 0, 0, 0];
|
||||
|
||||
$generic
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl SwapTokens {
|
||||
/// Run the command.
|
||||
pub async fn run(self) -> anyhow::Result<()> {
|
||||
select_bridge!(self.bridge, {
|
||||
let source_client = self.source.to_client::<Source>().await?;
|
||||
let source_sign = self.source_sign.to_keypair::<Target>()?;
|
||||
let target_client = self.target.to_client::<Target>().await?;
|
||||
let target_sign = self.target_sign.to_keypair::<Target>()?;
|
||||
|
||||
// names of variables in this function are matching names used by the
|
||||
// `pallet-bridge-token-swap`
|
||||
|
||||
// prepare token swap intention
|
||||
let token_swap = self
|
||||
.prepare_token_swap::<Source, Target>(&source_client, &source_sign, &target_sign)
|
||||
.await?;
|
||||
|
||||
// group all accounts that will be used later
|
||||
let accounts = TokenSwapAccounts {
|
||||
source_account_at_bridged_chain: derive_target_account_from_source_account(
|
||||
bp_runtime::SourceAccount::Account(
|
||||
token_swap.source_account_at_this_chain.clone(),
|
||||
),
|
||||
),
|
||||
target_account_at_this_chain: derive_source_account_from_target_account(
|
||||
bp_runtime::SourceAccount::Account(
|
||||
token_swap.target_account_at_bridged_chain.clone(),
|
||||
),
|
||||
),
|
||||
source_account_at_this_chain: token_swap.source_account_at_this_chain.clone(),
|
||||
target_account_at_bridged_chain: token_swap.target_account_at_bridged_chain.clone(),
|
||||
swap_account: FromSwapToThisAccountIdConverter::convert(
|
||||
token_swap.using_encoded(blake2_256).into(),
|
||||
),
|
||||
};
|
||||
|
||||
// account balances are used to demonstrate what's happening :)
|
||||
let initial_balances =
|
||||
read_account_balances(&accounts, &source_client, &target_client).await?;
|
||||
|
||||
// before calling something that may fail, log what we're trying to do
|
||||
log::info!(target: "bridge", "Starting swap: {:?}", token_swap);
|
||||
log::info!(target: "bridge", "Swap accounts: {:?}", accounts);
|
||||
log::info!(target: "bridge", "Initial account balances: {:?}", initial_balances);
|
||||
|
||||
//
|
||||
// Step 1: swap is created
|
||||
//
|
||||
|
||||
// prepare `Currency::transfer` call that will happen at the target chain
|
||||
let bridged_currency_transfer: CallOf<Target> = pallet_balances::Call::transfer {
|
||||
dest: accounts.source_account_at_bridged_chain.clone().into(),
|
||||
value: token_swap.target_balance_at_bridged_chain,
|
||||
}
|
||||
.into();
|
||||
let bridged_currency_transfer_weight =
|
||||
bridged_currency_transfer.get_dispatch_info().weight;
|
||||
|
||||
// sign message
|
||||
let bridged_chain_spec_version = TARGET_SPEC_VERSION;
|
||||
let signature_payload = pallet_bridge_dispatch::account_ownership_digest(
|
||||
&bridged_currency_transfer,
|
||||
&accounts.swap_account,
|
||||
&bridged_chain_spec_version,
|
||||
SOURCE_CHAIN_ID,
|
||||
TARGET_CHAIN_ID,
|
||||
);
|
||||
let bridged_currency_transfer_signature: SignatureOf<Target> =
|
||||
target_sign.sign(&signature_payload).into();
|
||||
|
||||
// prepare `create_swap` call
|
||||
let target_public_at_bridged_chain: AccountPublicOf<Target> =
|
||||
target_sign.public().into();
|
||||
let swap_delivery_and_dispatch_fee: BalanceOf<Source> =
|
||||
crate::cli::estimate_fee::estimate_message_delivery_and_dispatch_fee(
|
||||
&source_client,
|
||||
ESTIMATE_SOURCE_TO_TARGET_MESSAGE_FEE_METHOD,
|
||||
SOURCE_TO_TARGET_LANE_ID,
|
||||
bp_message_dispatch::MessagePayload {
|
||||
spec_version: TARGET_SPEC_VERSION,
|
||||
weight: bridged_currency_transfer_weight,
|
||||
origin: bp_message_dispatch::CallOrigin::TargetAccount(
|
||||
accounts.swap_account.clone(),
|
||||
target_public_at_bridged_chain.clone(),
|
||||
bridged_currency_transfer_signature.clone(),
|
||||
),
|
||||
dispatch_fee_payment:
|
||||
bp_runtime::messages::DispatchFeePayment::AtTargetChain,
|
||||
call: bridged_currency_transfer.encode(),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
let create_swap_call: CallOf<Source> = pallet_bridge_token_swap::Call::create_swap {
|
||||
swap: token_swap.clone(),
|
||||
swap_creation_params: Box::new(bp_token_swap::TokenSwapCreation {
|
||||
target_public_at_bridged_chain,
|
||||
swap_delivery_and_dispatch_fee,
|
||||
bridged_chain_spec_version,
|
||||
bridged_currency_transfer: bridged_currency_transfer.encode(),
|
||||
bridged_currency_transfer_weight,
|
||||
bridged_currency_transfer_signature,
|
||||
}),
|
||||
}
|
||||
.into();
|
||||
|
||||
// start tokens swap
|
||||
let source_genesis_hash = *source_client.genesis_hash();
|
||||
let create_swap_signer = source_sign.clone();
|
||||
let swap_created_at = wait_until_transaction_is_finalized::<Source>(
|
||||
source_client
|
||||
.submit_and_watch_signed_extrinsic(
|
||||
accounts.source_account_at_this_chain.clone(),
|
||||
move |_, transaction_nonce| {
|
||||
Bytes(
|
||||
Source::sign_transaction(
|
||||
source_genesis_hash,
|
||||
&create_swap_signer,
|
||||
relay_substrate_client::TransactionEra::immortal(),
|
||||
UnsignedTransaction::new(create_swap_call, transaction_nonce),
|
||||
)
|
||||
.encode(),
|
||||
)
|
||||
},
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// read state of swap after it has been created
|
||||
let token_swap_hash: H256 = token_swap.using_encoded(blake2_256).into();
|
||||
let token_swap_storage_key = bp_runtime::storage_map_final_key_identity(
|
||||
TOKEN_SWAP_PALLET_NAME,
|
||||
pallet_bridge_token_swap::PENDING_SWAPS_MAP_NAME,
|
||||
token_swap_hash.as_ref(),
|
||||
);
|
||||
match read_token_swap_state(&source_client, swap_created_at, &token_swap_storage_key)
|
||||
.await?
|
||||
{
|
||||
Some(bp_token_swap::TokenSwapState::Started) => {
|
||||
log::info!(target: "bridge", "Swap has been successfully started");
|
||||
let intermediate_balances =
|
||||
read_account_balances(&accounts, &source_client, &target_client).await?;
|
||||
log::info!(target: "bridge", "Intermediate balances: {:?}", intermediate_balances);
|
||||
},
|
||||
Some(token_swap_state) =>
|
||||
return Err(anyhow::format_err!(
|
||||
"Fresh token swap has unexpected state: {:?}",
|
||||
token_swap_state,
|
||||
)),
|
||||
None => return Err(anyhow::format_err!("Failed to start token swap")),
|
||||
};
|
||||
|
||||
//
|
||||
// Step 2: message is being relayed to the target chain and dispathed there
|
||||
//
|
||||
|
||||
// wait until message is dispatched at the target chain and dispatch result delivered
|
||||
// back to source chain
|
||||
let token_swap_state = wait_until_token_swap_state_is_changed(
|
||||
&source_client,
|
||||
&token_swap_storage_key,
|
||||
bp_token_swap::TokenSwapState::Started,
|
||||
)
|
||||
.await?;
|
||||
let is_transfer_succeeded = match token_swap_state {
|
||||
Some(bp_token_swap::TokenSwapState::Started) => {
|
||||
unreachable!("wait_until_token_swap_state_is_changed only returns if state is not Started; qed",)
|
||||
},
|
||||
None =>
|
||||
return Err(anyhow::format_err!("Fresh token swap has disappeared unexpectedly")),
|
||||
Some(bp_token_swap::TokenSwapState::Confirmed) => {
|
||||
log::info!(
|
||||
target: "bridge",
|
||||
"Transfer has been successfully dispatched at the target chain. Swap can be claimed",
|
||||
);
|
||||
true
|
||||
},
|
||||
Some(bp_token_swap::TokenSwapState::Failed) => {
|
||||
log::info!(
|
||||
target: "bridge",
|
||||
"Transfer has been dispatched with an error at the target chain. Swap can be canceled",
|
||||
);
|
||||
false
|
||||
},
|
||||
};
|
||||
|
||||
// by this time: (1) token swap account has been created and (2) if transfer has been
|
||||
// successfully dispatched, both target chain balances have changed
|
||||
let intermediate_balances =
|
||||
read_account_balances(&accounts, &source_client, &target_client).await?;
|
||||
log::info!(target: "bridge", "Intermediate balances: {:?}", intermediate_balances);
|
||||
|
||||
// transfer has been dispatched, but we may need to wait until block where swap can be
|
||||
// claimed/canceled
|
||||
if let bp_token_swap::TokenSwapType::LockClaimUntilBlock(
|
||||
ref last_available_block_number,
|
||||
_,
|
||||
) = token_swap.swap_type
|
||||
{
|
||||
wait_until_swap_unlocked(
|
||||
&source_client,
|
||||
last_available_block_number + BlockNumberOf::<Source>::one(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
//
|
||||
// Step 3: we may now claim or cancel the swap
|
||||
//
|
||||
|
||||
if is_transfer_succeeded {
|
||||
log::info!(target: "bridge", "Claiming the swap swap");
|
||||
|
||||
// prepare `claim_swap` message that will be sent over the bridge
|
||||
let claim_swap_call: CallOf<Source> =
|
||||
pallet_bridge_token_swap::Call::claim_swap { swap: token_swap }.into();
|
||||
let claim_swap_message = bp_message_dispatch::MessagePayload {
|
||||
spec_version: SOURCE_SPEC_VERSION,
|
||||
weight: claim_swap_call.get_dispatch_info().weight,
|
||||
origin: bp_message_dispatch::CallOrigin::SourceAccount(
|
||||
accounts.target_account_at_bridged_chain.clone(),
|
||||
),
|
||||
dispatch_fee_payment: bp_runtime::messages::DispatchFeePayment::AtSourceChain,
|
||||
call: claim_swap_call.encode(),
|
||||
};
|
||||
let claim_swap_delivery_and_dispatch_fee: BalanceOf<Target> =
|
||||
crate::cli::estimate_fee::estimate_message_delivery_and_dispatch_fee(
|
||||
&target_client,
|
||||
ESTIMATE_TARGET_TO_SOURCE_MESSAGE_FEE_METHOD,
|
||||
TARGET_TO_SOURCE_LANE_ID,
|
||||
claim_swap_message.clone(),
|
||||
)
|
||||
.await?;
|
||||
let send_message_call: CallOf<Target> =
|
||||
pallet_bridge_messages::Call::send_message {
|
||||
lane_id: TARGET_TO_SOURCE_LANE_ID,
|
||||
payload: claim_swap_message,
|
||||
delivery_and_dispatch_fee: claim_swap_delivery_and_dispatch_fee,
|
||||
}
|
||||
.into();
|
||||
|
||||
// send `claim_swap` message
|
||||
let target_genesis_hash = *target_client.genesis_hash();
|
||||
let _ = wait_until_transaction_is_finalized::<Target>(
|
||||
target_client
|
||||
.submit_and_watch_signed_extrinsic(
|
||||
accounts.target_account_at_bridged_chain.clone(),
|
||||
move |_, transaction_nonce| {
|
||||
Bytes(
|
||||
Target::sign_transaction(
|
||||
target_genesis_hash,
|
||||
&target_sign,
|
||||
relay_substrate_client::TransactionEra::immortal(),
|
||||
UnsignedTransaction::new(
|
||||
send_message_call,
|
||||
transaction_nonce,
|
||||
),
|
||||
)
|
||||
.encode(),
|
||||
)
|
||||
},
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// wait until swap state is updated
|
||||
let token_swap_state = wait_until_token_swap_state_is_changed(
|
||||
&source_client,
|
||||
&token_swap_storage_key,
|
||||
bp_token_swap::TokenSwapState::Confirmed,
|
||||
)
|
||||
.await?;
|
||||
if token_swap_state != None {
|
||||
return Err(anyhow::format_err!(
|
||||
"Confirmed token swap state has been changed to {:?} unexpectedly"
|
||||
))
|
||||
}
|
||||
} else {
|
||||
log::info!(target: "bridge", "Cancelling the swap");
|
||||
let cancel_swap_call: CallOf<Source> =
|
||||
pallet_bridge_token_swap::Call::cancel_swap { swap: token_swap.clone() }.into();
|
||||
let _ = wait_until_transaction_is_finalized::<Source>(
|
||||
source_client
|
||||
.submit_and_watch_signed_extrinsic(
|
||||
accounts.source_account_at_this_chain.clone(),
|
||||
move |_, transaction_nonce| {
|
||||
Bytes(
|
||||
Source::sign_transaction(
|
||||
source_genesis_hash,
|
||||
&source_sign,
|
||||
relay_substrate_client::TransactionEra::immortal(),
|
||||
UnsignedTransaction::new(
|
||||
cancel_swap_call,
|
||||
transaction_nonce,
|
||||
),
|
||||
)
|
||||
.encode(),
|
||||
)
|
||||
},
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// print final balances
|
||||
let final_balances =
|
||||
read_account_balances(&accounts, &source_client, &target_client).await?;
|
||||
log::info!(target: "bridge", "Final account balances: {:?}", final_balances);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// Prepare token swap intention.
|
||||
async fn prepare_token_swap<Source: CliChain, Target: CliChain>(
|
||||
&self,
|
||||
source_client: &Client<Source>,
|
||||
source_sign: &Source::KeyPair,
|
||||
target_sign: &Target::KeyPair,
|
||||
) -> anyhow::Result<
|
||||
bp_token_swap::TokenSwap<
|
||||
BlockNumberOf<Source>,
|
||||
BalanceOf<Source>,
|
||||
AccountIdOf<Source>,
|
||||
BalanceOf<Target>,
|
||||
AccountIdOf<Target>,
|
||||
>,
|
||||
>
|
||||
where
|
||||
AccountIdOf<Source>: From<<Source::KeyPair as Pair>::Public>,
|
||||
AccountIdOf<Target>: From<<Target::KeyPair as Pair>::Public>,
|
||||
BalanceOf<Source>: From<u64>,
|
||||
BalanceOf<Target>: From<u64>,
|
||||
{
|
||||
// accounts that are directly controlled by participants
|
||||
let source_account_at_this_chain: AccountIdOf<Source> = source_sign.public().into();
|
||||
let target_account_at_bridged_chain: AccountIdOf<Target> = target_sign.public().into();
|
||||
|
||||
// balances that we're going to swap
|
||||
let source_balance_at_this_chain: BalanceOf<Source> = self.source_balance.cast().into();
|
||||
let target_balance_at_bridged_chain: BalanceOf<Target> = self.target_balance.cast().into();
|
||||
|
||||
// prepare token swap intention
|
||||
Ok(bp_token_swap::TokenSwap {
|
||||
swap_type: self.prepare_token_swap_type(source_client).await?,
|
||||
source_balance_at_this_chain,
|
||||
source_account_at_this_chain: source_account_at_this_chain.clone(),
|
||||
target_balance_at_bridged_chain,
|
||||
target_account_at_bridged_chain: target_account_at_bridged_chain.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Prepare token swap type.
|
||||
async fn prepare_token_swap_type<Source: Chain>(
|
||||
&self,
|
||||
source_client: &Client<Source>,
|
||||
) -> anyhow::Result<bp_token_swap::TokenSwapType<BlockNumberOf<Source>>> {
|
||||
match self.swap_type {
|
||||
TokenSwapType::NoLock =>
|
||||
Ok(bp_token_swap::TokenSwapType::TemporaryTargetAccountAtBridgedChain),
|
||||
TokenSwapType::LockUntilBlock { blocks_before_expire, ref swap_nonce } => {
|
||||
let blocks_before_expire: BlockNumberOf<Source> = blocks_before_expire.into();
|
||||
let current_source_block_number = *source_client.best_header().await?.number();
|
||||
Ok(bp_token_swap::TokenSwapType::LockClaimUntilBlock(
|
||||
current_source_block_number + blocks_before_expire,
|
||||
swap_nonce.unwrap_or_else(|| {
|
||||
U256::from(random::<u128>()).overflowing_mul(U256::from(random::<u128>())).0
|
||||
}),
|
||||
))
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Accounts that are participating in the swap.
|
||||
#[derive(Debug)]
|
||||
struct TokenSwapAccounts<ThisAccountId, BridgedAccountId> {
|
||||
source_account_at_this_chain: ThisAccountId,
|
||||
source_account_at_bridged_chain: BridgedAccountId,
|
||||
target_account_at_bridged_chain: BridgedAccountId,
|
||||
target_account_at_this_chain: ThisAccountId,
|
||||
swap_account: ThisAccountId,
|
||||
}
|
||||
|
||||
/// Swap accounts balances.
|
||||
#[derive(Debug)]
|
||||
struct TokenSwapBalances<ThisBalance, BridgedBalance> {
|
||||
source_account_at_this_chain_balance: Option<ThisBalance>,
|
||||
source_account_at_bridged_chain_balance: Option<BridgedBalance>,
|
||||
target_account_at_bridged_chain_balance: Option<BridgedBalance>,
|
||||
target_account_at_this_chain_balance: Option<ThisBalance>,
|
||||
swap_account_balance: Option<ThisBalance>,
|
||||
}
|
||||
|
||||
/// Read swap accounts balances.
|
||||
async fn read_account_balances<Source: ChainWithBalances, Target: ChainWithBalances>(
|
||||
accounts: &TokenSwapAccounts<AccountIdOf<Source>, AccountIdOf<Target>>,
|
||||
source_client: &Client<Source>,
|
||||
target_client: &Client<Target>,
|
||||
) -> anyhow::Result<TokenSwapBalances<BalanceOf<Source>, BalanceOf<Target>>> {
|
||||
Ok(TokenSwapBalances {
|
||||
source_account_at_this_chain_balance: read_account_balance(
|
||||
source_client,
|
||||
&accounts.source_account_at_this_chain,
|
||||
)
|
||||
.await?,
|
||||
source_account_at_bridged_chain_balance: read_account_balance(
|
||||
target_client,
|
||||
&accounts.source_account_at_bridged_chain,
|
||||
)
|
||||
.await?,
|
||||
target_account_at_bridged_chain_balance: read_account_balance(
|
||||
target_client,
|
||||
&accounts.target_account_at_bridged_chain,
|
||||
)
|
||||
.await?,
|
||||
target_account_at_this_chain_balance: read_account_balance(
|
||||
source_client,
|
||||
&accounts.target_account_at_this_chain,
|
||||
)
|
||||
.await?,
|
||||
swap_account_balance: read_account_balance(source_client, &accounts.swap_account).await?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Read account balance.
|
||||
async fn read_account_balance<C: ChainWithBalances>(
|
||||
client: &Client<C>,
|
||||
account: &AccountIdOf<C>,
|
||||
) -> anyhow::Result<Option<BalanceOf<C>>> {
|
||||
match client.free_native_balance(account.clone()).await {
|
||||
Ok(balance) => Ok(Some(balance)),
|
||||
Err(SubstrateError::AccountDoesNotExist) => Ok(None),
|
||||
Err(error) => Err(anyhow::format_err!(
|
||||
"Failed to read balance of {} account {:?}: {:?}",
|
||||
C::NAME,
|
||||
account,
|
||||
error,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait until transaction is included into finalized block.
|
||||
///
|
||||
/// Returns the hash of the finalized block with transaction.
|
||||
pub(crate) async fn wait_until_transaction_is_finalized<C: Chain>(
|
||||
subscription: Subscription<TransactionStatusOf<C>>,
|
||||
) -> anyhow::Result<HashOf<C>> {
|
||||
loop {
|
||||
let transaction_status = subscription.next().await?;
|
||||
match transaction_status {
|
||||
Some(TransactionStatusOf::<C>::FinalityTimeout(_)) |
|
||||
Some(TransactionStatusOf::<C>::Usurped(_)) |
|
||||
Some(TransactionStatusOf::<C>::Dropped) |
|
||||
Some(TransactionStatusOf::<C>::Invalid) |
|
||||
None =>
|
||||
return Err(anyhow::format_err!(
|
||||
"We've been waiting for finalization of {} transaction, but it now has the {:?} status",
|
||||
C::NAME,
|
||||
transaction_status,
|
||||
)),
|
||||
Some(TransactionStatusOf::<C>::Finalized(block_hash)) => {
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"{} transaction has been finalized at block {}",
|
||||
C::NAME,
|
||||
block_hash,
|
||||
);
|
||||
return Ok(block_hash)
|
||||
},
|
||||
_ => {
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Received intermediate status of {} transaction: {:?}",
|
||||
C::NAME,
|
||||
transaction_status,
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Waits until token swap state is changed from `Started` to something else.
|
||||
async fn wait_until_token_swap_state_is_changed<C: Chain>(
|
||||
client: &Client<C>,
|
||||
swap_state_storage_key: &StorageKey,
|
||||
previous_token_swap_state: bp_token_swap::TokenSwapState,
|
||||
) -> anyhow::Result<Option<bp_token_swap::TokenSwapState>> {
|
||||
log::trace!(target: "bridge", "Waiting for token swap state change");
|
||||
loop {
|
||||
async_std::task::sleep(C::AVERAGE_BLOCK_INTERVAL).await;
|
||||
|
||||
let best_block = client.best_finalized_header_number().await?;
|
||||
let best_block_hash = client.block_hash_by_number(best_block).await?;
|
||||
log::trace!(target: "bridge", "Inspecting {} block {}/{}", C::NAME, best_block, best_block_hash);
|
||||
|
||||
let token_swap_state =
|
||||
read_token_swap_state(client, best_block_hash, swap_state_storage_key).await?;
|
||||
match token_swap_state {
|
||||
Some(new_token_swap_state) if new_token_swap_state == previous_token_swap_state => {},
|
||||
_ => {
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Token swap state has been changed from {:?} to {:?}",
|
||||
previous_token_swap_state,
|
||||
token_swap_state,
|
||||
);
|
||||
return Ok(token_swap_state)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Waits until swap can be claimed or canceled.
|
||||
async fn wait_until_swap_unlocked<C: Chain>(
|
||||
client: &Client<C>,
|
||||
required_block_number: BlockNumberOf<C>,
|
||||
) -> anyhow::Result<()> {
|
||||
log::trace!(target: "bridge", "Waiting for token swap unlock");
|
||||
loop {
|
||||
async_std::task::sleep(C::AVERAGE_BLOCK_INTERVAL).await;
|
||||
|
||||
let best_block = client.best_finalized_header_number().await?;
|
||||
let best_block_hash = client.block_hash_by_number(best_block).await?;
|
||||
if best_block >= required_block_number {
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
log::trace!(target: "bridge", "Skipping {} block {}/{}", C::NAME, best_block, best_block_hash);
|
||||
}
|
||||
}
|
||||
|
||||
/// Read state of the active token swap.
|
||||
async fn read_token_swap_state<C: Chain>(
|
||||
client: &Client<C>,
|
||||
at_block: C::Hash,
|
||||
swap_state_storage_key: &StorageKey,
|
||||
) -> anyhow::Result<Option<bp_token_swap::TokenSwapState>> {
|
||||
Ok(client.storage_value(swap_state_storage_key.clone(), Some(at_block)).await?)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn swap_tokens_millau_to_rialto_no_lock() {
|
||||
let swap_tokens = SwapTokens::from_iter(vec![
|
||||
"swap-tokens",
|
||||
"millau-to-rialto",
|
||||
"--source-host",
|
||||
"127.0.0.1",
|
||||
"--source-port",
|
||||
"9000",
|
||||
"--source-signer",
|
||||
"//Alice",
|
||||
"--source-balance",
|
||||
"8000000000",
|
||||
"--target-host",
|
||||
"127.0.0.1",
|
||||
"--target-port",
|
||||
"9001",
|
||||
"--target-signer",
|
||||
"//Bob",
|
||||
"--target-balance",
|
||||
"9000000000",
|
||||
"no-lock",
|
||||
]);
|
||||
|
||||
assert_eq!(
|
||||
swap_tokens,
|
||||
SwapTokens {
|
||||
bridge: SwapTokensBridge::MillauToRialto,
|
||||
source: SourceConnectionParams {
|
||||
source_host: "127.0.0.1".into(),
|
||||
source_port: 9000,
|
||||
source_secure: false,
|
||||
},
|
||||
source_sign: SourceSigningParams {
|
||||
source_signer: Some("//Alice".into()),
|
||||
source_signer_password: None,
|
||||
source_signer_file: None,
|
||||
source_signer_password_file: None,
|
||||
source_transactions_mortality: None,
|
||||
},
|
||||
target: TargetConnectionParams {
|
||||
target_host: "127.0.0.1".into(),
|
||||
target_port: 9001,
|
||||
target_secure: false,
|
||||
},
|
||||
target_sign: TargetSigningParams {
|
||||
target_signer: Some("//Bob".into()),
|
||||
target_signer_password: None,
|
||||
target_signer_file: None,
|
||||
target_signer_password_file: None,
|
||||
target_transactions_mortality: None,
|
||||
},
|
||||
swap_type: TokenSwapType::NoLock,
|
||||
source_balance: Balance(8000000000),
|
||||
target_balance: Balance(9000000000),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn swap_tokens_millau_to_rialto_lock_until() {
|
||||
let swap_tokens = SwapTokens::from_iter(vec![
|
||||
"swap-tokens",
|
||||
"millau-to-rialto",
|
||||
"--source-host",
|
||||
"127.0.0.1",
|
||||
"--source-port",
|
||||
"9000",
|
||||
"--source-signer",
|
||||
"//Alice",
|
||||
"--source-balance",
|
||||
"8000000000",
|
||||
"--target-host",
|
||||
"127.0.0.1",
|
||||
"--target-port",
|
||||
"9001",
|
||||
"--target-signer",
|
||||
"//Bob",
|
||||
"--target-balance",
|
||||
"9000000000",
|
||||
"lock-until-block",
|
||||
"--blocks-before-expire",
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert_eq!(
|
||||
swap_tokens,
|
||||
SwapTokens {
|
||||
bridge: SwapTokensBridge::MillauToRialto,
|
||||
source: SourceConnectionParams {
|
||||
source_host: "127.0.0.1".into(),
|
||||
source_port: 9000,
|
||||
source_secure: false,
|
||||
},
|
||||
source_sign: SourceSigningParams {
|
||||
source_signer: Some("//Alice".into()),
|
||||
source_signer_password: None,
|
||||
source_signer_file: None,
|
||||
source_signer_password_file: None,
|
||||
source_transactions_mortality: None,
|
||||
},
|
||||
target: TargetConnectionParams {
|
||||
target_host: "127.0.0.1".into(),
|
||||
target_port: 9001,
|
||||
target_secure: false,
|
||||
},
|
||||
target_sign: TargetSigningParams {
|
||||
target_signer: Some("//Bob".into()),
|
||||
target_signer_password: None,
|
||||
target_signer_file: None,
|
||||
target_signer_password_file: None,
|
||||
target_transactions_mortality: None,
|
||||
},
|
||||
swap_type: TokenSwapType::LockUntilBlock {
|
||||
blocks_before_expire: 1,
|
||||
swap_nonce: None,
|
||||
},
|
||||
source_balance: Balance(8000000000),
|
||||
target_balance: Balance(9000000000),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -20,13 +20,6 @@
|
||||
|
||||
mod chains;
|
||||
mod cli;
|
||||
mod finality_pipeline;
|
||||
mod finality_target;
|
||||
mod headers_initialize;
|
||||
mod messages_lane;
|
||||
mod messages_source;
|
||||
mod messages_target;
|
||||
mod on_demand_headers;
|
||||
|
||||
fn main() {
|
||||
let command = cli::parse_args();
|
||||
|
||||
@@ -1,210 +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::messages_source::SubstrateMessagesProof;
|
||||
use crate::messages_target::SubstrateMessagesReceivingProof;
|
||||
use crate::on_demand_headers::OnDemandHeadersRelay;
|
||||
|
||||
use bp_messages::{LaneId, MessageNonce};
|
||||
use frame_support::weights::Weight;
|
||||
use messages_relay::message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf};
|
||||
use relay_substrate_client::{BlockNumberOf, Chain, Client, HashOf};
|
||||
use relay_utils::{metrics::MetricsParams, BlockNumberBase};
|
||||
use sp_core::Bytes;
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
/// Substrate <-> Substrate messages relay parameters.
|
||||
pub struct MessagesRelayParams<SC: Chain, SS, TC: Chain, TS> {
|
||||
/// Messages source client.
|
||||
pub source_client: Client<SC>,
|
||||
/// Sign parameters for messages source chain.
|
||||
pub source_sign: SS,
|
||||
/// Messages target client.
|
||||
pub target_client: Client<TC>,
|
||||
/// Sign parameters for messages target chain.
|
||||
pub target_sign: TS,
|
||||
/// Optional on-demand source to target headers relay.
|
||||
pub source_to_target_headers_relay: Option<OnDemandHeadersRelay<SC>>,
|
||||
/// Optional on-demand target to source headers relay.
|
||||
pub target_to_source_headers_relay: Option<OnDemandHeadersRelay<TC>>,
|
||||
/// Identifier of lane that needs to be served.
|
||||
pub lane_id: LaneId,
|
||||
/// Metrics parameters.
|
||||
pub metrics_params: MetricsParams,
|
||||
}
|
||||
|
||||
/// Message sync pipeline for Substrate <-> Substrate relays.
|
||||
pub trait SubstrateMessageLane: MessageLane {
|
||||
/// Name of the runtime method that returns dispatch weight of outbound messages at the source chain.
|
||||
const OUTBOUND_LANE_MESSAGE_DETAILS_METHOD: &'static str;
|
||||
/// Name of the runtime method that returns latest generated nonce at the source chain.
|
||||
const OUTBOUND_LANE_LATEST_GENERATED_NONCE_METHOD: &'static str;
|
||||
/// Name of the runtime method that returns latest received (confirmed) nonce at the the source chain.
|
||||
const OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str;
|
||||
|
||||
/// Name of the runtime method that returns latest received nonce at the target chain.
|
||||
const INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str;
|
||||
/// Name of the runtime method that returns latest confirmed (reward-paid) nonce at the target chain.
|
||||
const INBOUND_LANE_LATEST_CONFIRMED_NONCE_METHOD: &'static str;
|
||||
/// Number of the runtime method that returns state of "unrewarded relayers" set at the target chain.
|
||||
const INBOUND_LANE_UNREWARDED_RELAYERS_STATE: &'static str;
|
||||
|
||||
/// Name of the runtime method that returns id of best finalized source header at target chain.
|
||||
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str;
|
||||
/// Name of the runtime method that returns id of best finalized target header at source chain.
|
||||
const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str;
|
||||
|
||||
/// Source chain.
|
||||
type SourceChain: Chain;
|
||||
/// Target chain.
|
||||
type TargetChain: Chain;
|
||||
|
||||
/// Returns id of account that we're using to sign transactions at target chain (messages proof).
|
||||
fn target_transactions_author(&self) -> <Self::TargetChain as Chain>::AccountId;
|
||||
|
||||
/// Make messages delivery transaction.
|
||||
fn make_messages_delivery_transaction(
|
||||
&self,
|
||||
transaction_nonce: <Self::TargetChain as Chain>::Index,
|
||||
generated_at_header: SourceHeaderIdOf<Self>,
|
||||
nonces: RangeInclusive<MessageNonce>,
|
||||
proof: Self::MessagesProof,
|
||||
) -> Bytes;
|
||||
|
||||
/// Returns id of account that we're using to sign transactions at source chain (delivery proof).
|
||||
fn source_transactions_author(&self) -> <Self::SourceChain as Chain>::AccountId;
|
||||
|
||||
/// Make messages receiving proof transaction.
|
||||
fn make_messages_receiving_proof_transaction(
|
||||
&self,
|
||||
transaction_nonce: <Self::SourceChain as Chain>::Index,
|
||||
generated_at_header: TargetHeaderIdOf<Self>,
|
||||
proof: Self::MessagesReceivingProof,
|
||||
) -> Bytes;
|
||||
}
|
||||
|
||||
/// Substrate-to-Substrate message lane.
|
||||
#[derive(Debug)]
|
||||
pub struct SubstrateMessageLaneToSubstrate<Source: Chain, SourceSignParams, Target: Chain, TargetSignParams> {
|
||||
/// Client for the source Substrate chain.
|
||||
pub(crate) source_client: Client<Source>,
|
||||
/// Parameters required to sign transactions for source chain.
|
||||
pub(crate) source_sign: SourceSignParams,
|
||||
/// Client for the target Substrate chain.
|
||||
pub(crate) target_client: Client<Target>,
|
||||
/// Parameters required to sign transactions for target chain.
|
||||
pub(crate) target_sign: TargetSignParams,
|
||||
/// Account id of relayer at the source chain.
|
||||
pub(crate) relayer_id_at_source: Source::AccountId,
|
||||
}
|
||||
|
||||
impl<Source: Chain, SourceSignParams: Clone, Target: Chain, TargetSignParams: Clone> Clone
|
||||
for SubstrateMessageLaneToSubstrate<Source, SourceSignParams, Target, TargetSignParams>
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
source_client: self.source_client.clone(),
|
||||
source_sign: self.source_sign.clone(),
|
||||
target_client: self.target_client.clone(),
|
||||
target_sign: self.target_sign.clone(),
|
||||
relayer_id_at_source: self.relayer_id_at_source.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Source: Chain, SourceSignParams, Target: Chain, TargetSignParams> MessageLane
|
||||
for SubstrateMessageLaneToSubstrate<Source, SourceSignParams, Target, TargetSignParams>
|
||||
where
|
||||
SourceSignParams: Clone + Send + Sync + 'static,
|
||||
TargetSignParams: Clone + Send + Sync + 'static,
|
||||
BlockNumberOf<Source>: BlockNumberBase,
|
||||
BlockNumberOf<Target>: BlockNumberBase,
|
||||
{
|
||||
const SOURCE_NAME: &'static str = Source::NAME;
|
||||
const TARGET_NAME: &'static str = Target::NAME;
|
||||
|
||||
type MessagesProof = SubstrateMessagesProof<Source>;
|
||||
type MessagesReceivingProof = SubstrateMessagesReceivingProof<Target>;
|
||||
|
||||
type SourceChainBalance = Source::Balance;
|
||||
type SourceHeaderNumber = BlockNumberOf<Source>;
|
||||
type SourceHeaderHash = HashOf<Source>;
|
||||
|
||||
type TargetHeaderNumber = BlockNumberOf<Target>;
|
||||
type TargetHeaderHash = HashOf<Target>;
|
||||
}
|
||||
|
||||
/// Returns maximal number of messages and their maximal cumulative dispatch weight, based
|
||||
/// on given chain parameters.
|
||||
pub fn select_delivery_transaction_limits<W: pallet_bridge_messages::WeightInfoExt>(
|
||||
max_extrinsic_weight: Weight,
|
||||
max_unconfirmed_messages_at_inbound_lane: MessageNonce,
|
||||
) -> (MessageNonce, Weight) {
|
||||
// We may try to guess accurate value, based on maximal number of messages and per-message
|
||||
// weight overhead, but the relay loop isn't using this info in a super-accurate way anyway.
|
||||
// So just a rough guess: let's say 1/3 of max tx weight is for tx itself and the rest is
|
||||
// for messages dispatch.
|
||||
|
||||
// Another thing to keep in mind is that our runtimes (when this code was written) accept
|
||||
// messages with dispatch weight <= max_extrinsic_weight/2. So we can't reserve less than
|
||||
// that for dispatch.
|
||||
|
||||
let weight_for_delivery_tx = max_extrinsic_weight / 3;
|
||||
let weight_for_messages_dispatch = max_extrinsic_weight - weight_for_delivery_tx;
|
||||
|
||||
let delivery_tx_base_weight =
|
||||
W::receive_messages_proof_overhead() + W::receive_messages_proof_outbound_lane_state_overhead();
|
||||
let delivery_tx_weight_rest = weight_for_delivery_tx - delivery_tx_base_weight;
|
||||
let max_number_of_messages = std::cmp::min(
|
||||
delivery_tx_weight_rest / W::receive_messages_proof_messages_overhead(1),
|
||||
max_unconfirmed_messages_at_inbound_lane,
|
||||
);
|
||||
|
||||
assert!(
|
||||
max_number_of_messages > 0,
|
||||
"Relay should fit at least one message in every delivery transaction",
|
||||
);
|
||||
assert!(
|
||||
weight_for_messages_dispatch >= max_extrinsic_weight / 2,
|
||||
"Relay shall be able to deliver messages with dispatch weight = max_extrinsic_weight / 2",
|
||||
);
|
||||
|
||||
(max_number_of_messages, weight_for_messages_dispatch)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
type RialtoToMillauMessagesWeights = pallet_bridge_messages::weights::RialtoWeight<rialto_runtime::Runtime>;
|
||||
|
||||
#[test]
|
||||
fn select_delivery_transaction_limits_works() {
|
||||
let (max_count, max_weight) = select_delivery_transaction_limits::<RialtoToMillauMessagesWeights>(
|
||||
bp_millau::max_extrinsic_weight(),
|
||||
bp_millau::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
|
||||
);
|
||||
assert_eq!(
|
||||
(max_count, max_weight),
|
||||
// We don't actually care about these values, so feel free to update them whenever test
|
||||
// fails. The only thing to do before that is to ensure that new values looks sane: i.e. weight
|
||||
// reserved for messages dispatch allows dispatch of non-trivial messages.
|
||||
//
|
||||
// Any significant change in this values should attract additional attention.
|
||||
(782, 216_583_333_334),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,238 +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/>.
|
||||
|
||||
//! Substrate client as Substrate messages target. The chain we connect to should have
|
||||
//! runtime that implements `<BridgedChainName>HeaderApi` to allow bridging with
|
||||
//! <BridgedName> chain.
|
||||
|
||||
use crate::messages_lane::SubstrateMessageLane;
|
||||
use crate::messages_source::read_client_state;
|
||||
use crate::on_demand_headers::OnDemandHeadersRelay;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bp_messages::{LaneId, MessageNonce, UnrewardedRelayersState};
|
||||
use bp_runtime::ChainId;
|
||||
use bridge_runtime_common::messages::source::FromBridgedChainMessagesDeliveryProof;
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::{traits::Instance, weights::Weight};
|
||||
use messages_relay::{
|
||||
message_lane::{SourceHeaderIdOf, TargetHeaderIdOf},
|
||||
message_lane_loop::{TargetClient, TargetClientState},
|
||||
};
|
||||
use relay_substrate_client::{Chain, Client, Error as SubstrateError, HashOf};
|
||||
use relay_utils::{relay_loop::Client as RelayClient, BlockNumberBase};
|
||||
use sp_core::Bytes;
|
||||
use sp_runtime::{traits::Header as HeaderT, DeserializeOwned};
|
||||
use std::{marker::PhantomData, ops::RangeInclusive};
|
||||
|
||||
/// Message receiving proof returned by the target Substrate node.
|
||||
pub type SubstrateMessagesReceivingProof<C> = (
|
||||
UnrewardedRelayersState,
|
||||
FromBridgedChainMessagesDeliveryProof<HashOf<C>>,
|
||||
);
|
||||
|
||||
/// Substrate client as Substrate messages target.
|
||||
pub struct SubstrateMessagesTarget<C: Chain, P: SubstrateMessageLane, I> {
|
||||
client: Client<C>,
|
||||
lane: P,
|
||||
lane_id: LaneId,
|
||||
instance: ChainId,
|
||||
source_to_target_headers_relay: Option<OnDemandHeadersRelay<P::SourceChain>>,
|
||||
_phantom: PhantomData<I>,
|
||||
}
|
||||
|
||||
impl<C: Chain, P: SubstrateMessageLane, I> SubstrateMessagesTarget<C, P, I> {
|
||||
/// Create new Substrate headers target.
|
||||
pub fn new(
|
||||
client: Client<C>,
|
||||
lane: P,
|
||||
lane_id: LaneId,
|
||||
instance: ChainId,
|
||||
source_to_target_headers_relay: Option<OnDemandHeadersRelay<P::SourceChain>>,
|
||||
) -> Self {
|
||||
SubstrateMessagesTarget {
|
||||
client,
|
||||
lane,
|
||||
lane_id,
|
||||
instance,
|
||||
source_to_target_headers_relay,
|
||||
_phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Chain, P: SubstrateMessageLane, I> Clone for SubstrateMessagesTarget<C, P, I> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
client: self.client.clone(),
|
||||
lane: self.lane.clone(),
|
||||
lane_id: self.lane_id,
|
||||
instance: self.instance,
|
||||
source_to_target_headers_relay: self.source_to_target_headers_relay.clone(),
|
||||
_phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C, P, I> RelayClient for SubstrateMessagesTarget<C, P, I>
|
||||
where
|
||||
C: Chain,
|
||||
P: SubstrateMessageLane,
|
||||
I: Send + Sync + Instance,
|
||||
{
|
||||
type Error = SubstrateError;
|
||||
|
||||
async fn reconnect(&mut self) -> Result<(), SubstrateError> {
|
||||
self.client.reconnect().await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C, P, I> TargetClient<P> for SubstrateMessagesTarget<C, P, I>
|
||||
where
|
||||
C: Chain,
|
||||
C::Header: DeserializeOwned,
|
||||
C::Index: DeserializeOwned,
|
||||
<C::Header as HeaderT>::Number: BlockNumberBase,
|
||||
P: SubstrateMessageLane<
|
||||
TargetChain = C,
|
||||
MessagesReceivingProof = SubstrateMessagesReceivingProof<C>,
|
||||
TargetHeaderNumber = <C::Header as HeaderT>::Number,
|
||||
TargetHeaderHash = <C::Header as HeaderT>::Hash,
|
||||
>,
|
||||
P::SourceChain: Chain<Hash = P::SourceHeaderHash, BlockNumber = P::SourceHeaderNumber>,
|
||||
P::SourceHeaderNumber: Decode,
|
||||
P::SourceHeaderHash: Decode,
|
||||
I: Send + Sync + Instance,
|
||||
{
|
||||
async fn state(&self) -> Result<TargetClientState<P>, SubstrateError> {
|
||||
// we can't continue to deliver messages if target node is out of sync, because
|
||||
// it may have already received (some of) messages that we're going to deliver
|
||||
self.client.ensure_synced().await?;
|
||||
|
||||
read_client_state::<_, P::SourceHeaderHash, P::SourceHeaderNumber>(
|
||||
&self.client,
|
||||
P::BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn latest_received_nonce(
|
||||
&self,
|
||||
id: TargetHeaderIdOf<P>,
|
||||
) -> Result<(TargetHeaderIdOf<P>, MessageNonce), SubstrateError> {
|
||||
let encoded_response = self
|
||||
.client
|
||||
.state_call(
|
||||
P::INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD.into(),
|
||||
Bytes(self.lane_id.encode()),
|
||||
Some(id.1),
|
||||
)
|
||||
.await?;
|
||||
let latest_received_nonce: MessageNonce =
|
||||
Decode::decode(&mut &encoded_response.0[..]).map_err(SubstrateError::ResponseParseFailed)?;
|
||||
Ok((id, latest_received_nonce))
|
||||
}
|
||||
|
||||
async fn latest_confirmed_received_nonce(
|
||||
&self,
|
||||
id: TargetHeaderIdOf<P>,
|
||||
) -> Result<(TargetHeaderIdOf<P>, MessageNonce), SubstrateError> {
|
||||
let encoded_response = self
|
||||
.client
|
||||
.state_call(
|
||||
P::INBOUND_LANE_LATEST_CONFIRMED_NONCE_METHOD.into(),
|
||||
Bytes(self.lane_id.encode()),
|
||||
Some(id.1),
|
||||
)
|
||||
.await?;
|
||||
let latest_received_nonce: MessageNonce =
|
||||
Decode::decode(&mut &encoded_response.0[..]).map_err(SubstrateError::ResponseParseFailed)?;
|
||||
Ok((id, latest_received_nonce))
|
||||
}
|
||||
|
||||
async fn unrewarded_relayers_state(
|
||||
&self,
|
||||
id: TargetHeaderIdOf<P>,
|
||||
) -> Result<(TargetHeaderIdOf<P>, UnrewardedRelayersState), SubstrateError> {
|
||||
let encoded_response = self
|
||||
.client
|
||||
.state_call(
|
||||
P::INBOUND_LANE_UNREWARDED_RELAYERS_STATE.into(),
|
||||
Bytes(self.lane_id.encode()),
|
||||
Some(id.1),
|
||||
)
|
||||
.await?;
|
||||
let unrewarded_relayers_state: UnrewardedRelayersState =
|
||||
Decode::decode(&mut &encoded_response.0[..]).map_err(SubstrateError::ResponseParseFailed)?;
|
||||
Ok((id, unrewarded_relayers_state))
|
||||
}
|
||||
|
||||
async fn prove_messages_receiving(
|
||||
&self,
|
||||
id: TargetHeaderIdOf<P>,
|
||||
) -> Result<(TargetHeaderIdOf<P>, P::MessagesReceivingProof), SubstrateError> {
|
||||
let (id, relayers_state) = self.unrewarded_relayers_state(id).await?;
|
||||
let inbound_data_key = pallet_bridge_messages::storage_keys::inbound_lane_data_key::<I>(&self.lane_id);
|
||||
let proof = self
|
||||
.client
|
||||
.prove_storage(vec![inbound_data_key], id.1)
|
||||
.await?
|
||||
.iter_nodes()
|
||||
.collect();
|
||||
let proof = FromBridgedChainMessagesDeliveryProof {
|
||||
bridged_header_hash: id.1,
|
||||
storage_proof: proof,
|
||||
lane: self.lane_id,
|
||||
};
|
||||
Ok((id, (relayers_state, proof)))
|
||||
}
|
||||
|
||||
async fn submit_messages_proof(
|
||||
&self,
|
||||
generated_at_header: SourceHeaderIdOf<P>,
|
||||
nonces: RangeInclusive<MessageNonce>,
|
||||
proof: P::MessagesProof,
|
||||
) -> Result<RangeInclusive<MessageNonce>, SubstrateError> {
|
||||
self.client
|
||||
.submit_signed_extrinsic(self.lane.target_transactions_author(), |transaction_nonce| {
|
||||
self.lane.make_messages_delivery_transaction(
|
||||
transaction_nonce,
|
||||
generated_at_header,
|
||||
nonces.clone(),
|
||||
proof,
|
||||
)
|
||||
})
|
||||
.await?;
|
||||
Ok(nonces)
|
||||
}
|
||||
|
||||
async fn require_source_header_on_target(&self, id: SourceHeaderIdOf<P>) {
|
||||
if let Some(ref source_to_target_headers_relay) = self.source_to_target_headers_relay {
|
||||
source_to_target_headers_relay.require_finalized_header(id).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn estimate_delivery_transaction_in_source_tokens(
|
||||
&self,
|
||||
_nonces: RangeInclusive<MessageNonce>,
|
||||
_total_dispatch_weight: Weight,
|
||||
_total_size: u32,
|
||||
) -> P::SourceChainBalance {
|
||||
num_traits::Zero::zero() // TODO: https://github.com/paritytech/parity-bridges-common/issues/997
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,13 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
[dependencies]
|
||||
async-std = "1.6.5"
|
||||
bp-eth-poa = { path = "../../primitives/ethereum-poa" }
|
||||
codec = { package = "parity-scale-codec", version = "2.0.0" }
|
||||
headers-relay = { path = "../headers" }
|
||||
hex-literal = "0.3"
|
||||
jsonrpsee-proc-macros = "=0.2.0-alpha.6"
|
||||
jsonrpsee-ws-client = "=0.2.0-alpha.6"
|
||||
libsecp256k1 = { version = "0.3.4", default-features = false, features = ["hmac"] }
|
||||
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" }
|
||||
web3 = { version = "0.15", git = "https://github.com/tomusdrw/rust-web3", branch ="td-ethabi", default-features = false }
|
||||
tokio = "1.8"
|
||||
web3 = { git = "https://github.com/svyatonik/rust-web3.git", branch = "bump-deps" }
|
||||
thiserror = "1.0.26"
|
||||
|
||||
@@ -14,16 +14,18 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::rpc::Ethereum;
|
||||
use crate::types::{
|
||||
Address, Bytes, CallRequest, Header, HeaderWithTransactions, Receipt, SignedRawTx, SyncState, Transaction,
|
||||
TransactionHash, H256, U256,
|
||||
use crate::{
|
||||
rpc::Ethereum,
|
||||
types::{
|
||||
Address, Bytes, CallRequest, Header, HeaderWithTransactions, Receipt, SignedRawTx,
|
||||
SyncState, Transaction, TransactionHash, H256, U256,
|
||||
},
|
||||
ConnectionParams, Error, Result,
|
||||
};
|
||||
use crate::{ConnectionParams, Error, Result};
|
||||
|
||||
use jsonrpsee_ws_client::{WsClient as RpcClient, WsClientBuilder as RpcClientBuilder};
|
||||
use relay_utils::relay_loop::RECONNECT_DELAY;
|
||||
use std::sync::Arc;
|
||||
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;
|
||||
@@ -31,6 +33,7 @@ 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>,
|
||||
}
|
||||
@@ -56,25 +59,30 @@ impl Client {
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to connect to Ethereum node. Returns Ethereum RPC client if connection has been established
|
||||
/// or error otherwise.
|
||||
/// 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> {
|
||||
Ok(Self {
|
||||
client: Self::build_client(¶ms).await?,
|
||||
params,
|
||||
})
|
||||
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<RpcClient>> {
|
||||
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 = RpcClientBuilder::default().build(&uri).await?;
|
||||
Ok(Arc::new(client))
|
||||
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<()> {
|
||||
self.client = Self::build_client(&self.params).await?;
|
||||
let (tokio, client) = Self::build_client(&self.params).await?;
|
||||
self.tokio = tokio;
|
||||
self.client = client;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -82,113 +90,176 @@ impl Client {
|
||||
impl Client {
|
||||
/// Returns true if client is connected to at least one peer and is in synced state.
|
||||
pub async fn ensure_synced(&self) -> Result<()> {
|
||||
match Ethereum::syncing(&*self.client).await? {
|
||||
SyncState::NotSyncing => Ok(()),
|
||||
SyncState::Syncing(syncing) => {
|
||||
let missing_headers = syncing.highest_block.saturating_sub(syncing.current_block);
|
||||
if missing_headers > MAJOR_SYNC_BLOCKS.into() {
|
||||
return Err(Error::ClientNotSynced(missing_headers));
|
||||
}
|
||||
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(())
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Estimate gas usage for the given call.
|
||||
pub async fn estimate_gas(&self, call_request: CallRequest) -> Result<U256> {
|
||||
Ok(Ethereum::estimate_gas(&*self.client, call_request).await?)
|
||||
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> {
|
||||
Ok(Ethereum::block_number(&*self.client).await?.as_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> {
|
||||
let get_full_tx_objects = false;
|
||||
let header = Ethereum::get_block_by_number(&*self.client, block_number, get_full_tx_objects).await?;
|
||||
match header.number.is_some() && header.hash.is_some() && header.logs_bloom.is_some() {
|
||||
true => Ok(header),
|
||||
false => Err(Error::IncompleteHeader),
|
||||
}
|
||||
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> {
|
||||
let get_full_tx_objects = false;
|
||||
let header = Ethereum::get_block_by_hash(&*self.client, hash, get_full_tx_objects).await?;
|
||||
match header.number.is_some() && header.hash.is_some() && header.logs_bloom.is_some() {
|
||||
true => Ok(header),
|
||||
false => Err(Error::IncompleteHeader),
|
||||
}
|
||||
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> {
|
||||
let get_full_tx_objects = true;
|
||||
let header =
|
||||
Ethereum::get_block_by_number_with_transactions(&*self.client, number, get_full_tx_objects).await?;
|
||||
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_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);
|
||||
}
|
||||
let is_complete_transactions = header.transactions.iter().all(|tx| tx.raw.is_some());
|
||||
if !is_complete_transactions {
|
||||
return Err(Error::IncompleteTransaction)
|
||||
}
|
||||
|
||||
Ok(header)
|
||||
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> {
|
||||
let get_full_tx_objects = true;
|
||||
let header = Ethereum::get_block_by_hash_with_transactions(&*self.client, hash, get_full_tx_objects).await?;
|
||||
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_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);
|
||||
}
|
||||
let is_complete_transactions = header.transactions.iter().all(|tx| tx.raw.is_some());
|
||||
if !is_complete_transactions {
|
||||
return Err(Error::IncompleteTransaction)
|
||||
}
|
||||
|
||||
Ok(header)
|
||||
Ok(header)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Retrieve transaction by its hash from Ethereum node.
|
||||
pub async fn transaction_by_hash(&self, hash: H256) -> Result<Option<Transaction>> {
|
||||
Ok(Ethereum::transaction_by_hash(&*self.client, hash).await?)
|
||||
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> {
|
||||
Ok(Ethereum::get_transaction_receipt(&*self.client, transaction_hash).await?)
|
||||
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> {
|
||||
Ok(Ethereum::get_transaction_count(&*self.client, address).await?)
|
||||
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> {
|
||||
let transaction = Bytes(signed_raw_tx);
|
||||
let tx_hash = Ethereum::submit_transaction(&*self.client, transaction).await?;
|
||||
log::trace!(target: "bridge", "Sent transaction to Ethereum node: {:?}", tx_hash);
|
||||
Ok(tx_hash)
|
||||
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> {
|
||||
Ok(Ethereum::call(&*self.client, call_transaction).await?)
|
||||
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?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,38 +18,52 @@
|
||||
|
||||
use crate::types::U256;
|
||||
|
||||
use jsonrpsee_ws_client::Error as RpcError;
|
||||
use jsonrpsee_ws_client::types::Error as RpcError;
|
||||
use relay_utils::MaybeConnectionError;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Result type used by Ethereum client.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Errors that can occur only when interacting with
|
||||
/// an Ethereum node through RPC.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
/// IO error.
|
||||
#[error("IO error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
/// An error that can occur when making an HTTP request to
|
||||
/// an JSON-RPC client.
|
||||
RpcError(RpcError),
|
||||
#[error("RPC error: {0}")]
|
||||
RpcError(#[from] RpcError),
|
||||
/// Failed to parse response.
|
||||
#[error("Response parse failed: {0}")]
|
||||
ResponseParseFailed(String),
|
||||
/// We have received a header with missing fields.
|
||||
#[error("Incomplete Ethereum Header Received (missing some of required fields - hash, number, logs_bloom).")]
|
||||
IncompleteHeader,
|
||||
/// We have received a transaction missing a `raw` field.
|
||||
#[error("Incomplete Ethereum Transaction (missing required field - raw).")]
|
||||
IncompleteTransaction,
|
||||
/// An invalid Substrate block number was received from
|
||||
/// an Ethereum node.
|
||||
#[error("Received an invalid Substrate block from Ethereum Node.")]
|
||||
InvalidSubstrateBlockNumber,
|
||||
/// An invalid index has been received from an Ethereum node.
|
||||
#[error("Received an invalid incomplete index from Ethereum Node.")]
|
||||
InvalidIncompleteIndex,
|
||||
/// The client we're connected to is not synced, so we can't rely on its state. Contains
|
||||
/// number of unsynced headers.
|
||||
#[error("Ethereum client is not synced: syncing {0} headers.")]
|
||||
ClientNotSynced(U256),
|
||||
/// Custom logic error.
|
||||
#[error("{0}")]
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
impl From<RpcError> for Error {
|
||||
fn from(error: RpcError) -> Self {
|
||||
Error::RpcError(error)
|
||||
impl From<tokio::task::JoinError> for Error {
|
||||
fn from(error: tokio::task::JoinError) -> Self {
|
||||
Error::Custom(format!("Failed to wait tokio task: {}", error))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,30 +71,12 @@ impl MaybeConnectionError for Error {
|
||||
fn is_connection_error(&self) -> bool {
|
||||
matches!(
|
||||
*self,
|
||||
Error::RpcError(RpcError::TransportError(_))
|
||||
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(_),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for Error {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
Self::RpcError(e) => e.to_string(),
|
||||
Self::ResponseParseFailed(e) => e.to_string(),
|
||||
Self::IncompleteHeader => {
|
||||
"Incomplete Ethereum Header Received (missing some of required fields - hash, number, logs_bloom)"
|
||||
.to_string()
|
||||
}
|
||||
Self::IncompleteTransaction => "Incomplete Ethereum Transaction (missing required field - raw)".to_string(),
|
||||
Self::InvalidSubstrateBlockNumber => "Received an invalid Substrate block from Ethereum Node".to_string(),
|
||||
Self::InvalidIncompleteIndex => "Received an invalid incomplete index from Ethereum Node".to_string(),
|
||||
Self::ClientNotSynced(missing_headers) => {
|
||||
format!("Ethereum client is not synced: syncing {} headers", missing_headers)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,16 +23,18 @@ mod error;
|
||||
mod rpc;
|
||||
mod sign;
|
||||
|
||||
pub use crate::client::Client;
|
||||
pub use crate::error::{Error, Result};
|
||||
pub use crate::sign::{sign_and_submit_transaction, SigningParams};
|
||||
pub 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 hostname.
|
||||
/// Websocket server host name.
|
||||
pub host: String,
|
||||
/// Websocket server TCP port.
|
||||
pub port: u16,
|
||||
@@ -40,9 +42,6 @@ pub struct ConnectionParams {
|
||||
|
||||
impl Default for ConnectionParams {
|
||||
fn default() -> Self {
|
||||
ConnectionParams {
|
||||
host: "localhost".into(),
|
||||
port: 8546,
|
||||
}
|
||||
ConnectionParams { host: "localhost".into(), port: 8546 }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
//! Ethereum node RPC interface.
|
||||
|
||||
use crate::types::{
|
||||
Address, Bytes, CallRequest, Header, HeaderWithTransactions, Receipt, SyncState, Transaction, TransactionHash,
|
||||
H256, U256, U64,
|
||||
Address, Bytes, CallRequest, Header, HeaderWithTransactions, Receipt, SyncState, Transaction,
|
||||
TransactionHash, H256, U256, U64,
|
||||
};
|
||||
|
||||
jsonrpsee_proc_macros::rpc_client_api! {
|
||||
|
||||
@@ -14,11 +14,13 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::types::{Address, CallRequest, U256};
|
||||
use crate::{Client, Result};
|
||||
use crate::{
|
||||
types::{Address, CallRequest, U256},
|
||||
Client, Result,
|
||||
};
|
||||
use bp_eth_poa::signatures::{secret_to_address, SignTransaction};
|
||||
use hex_literal::hex;
|
||||
use secp256k1::SecretKey;
|
||||
use libsecp256k1::SecretKey;
|
||||
|
||||
/// Ethereum signing params.
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -47,7 +49,7 @@ impl Default for SigningParams {
|
||||
}
|
||||
}
|
||||
|
||||
/// Sign and submit tranaction using given Ethereum client.
|
||||
/// Sign and submit transaction using given Ethereum client.
|
||||
pub async fn sign_and_submit_transaction(
|
||||
client: &Client,
|
||||
params: &SigningParams,
|
||||
|
||||
@@ -6,20 +6,25 @@ edition = "2018"
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "2.0.0" }
|
||||
headers-relay = { path = "../headers" }
|
||||
codec = { package = "parity-scale-codec", version = "2.2.0" }
|
||||
relay-substrate-client = { path = "../client-substrate" }
|
||||
relay-utils = { path = "../utils" }
|
||||
scale-info = { version = "1.0", features = ["derive"] }
|
||||
|
||||
# Bridge dependencies
|
||||
|
||||
bp-header-chain = { path = "../../primitives/header-chain" }
|
||||
bp-kusama = { path = "../../primitives/chain-kusama" }
|
||||
bp-message-dispatch = { path = "../../primitives/message-dispatch" }
|
||||
bp-messages = { path = "../../primitives/messages" }
|
||||
bp-polkadot = { path = "../../primitives/chain-polkadot" }
|
||||
bp-polkadot-core = { path = "../../primitives/polkadot-core" }
|
||||
bp-runtime = { path = "../../primitives/runtime" }
|
||||
bridge-runtime-common = { path = "../../bin/runtime-common" }
|
||||
pallet-bridge-dispatch = { path = "../../modules/dispatch" }
|
||||
|
||||
# Substrate Dependencies
|
||||
|
||||
frame-system = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
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" }
|
||||
|
||||
@@ -16,9 +16,17 @@
|
||||
|
||||
//! Types used to connect to the Kusama chain.
|
||||
|
||||
use relay_substrate_client::{Chain, ChainBase};
|
||||
use codec::Encode;
|
||||
use relay_substrate_client::{
|
||||
Chain, ChainBase, ChainWithBalances, TransactionEraOf, TransactionSignScheme,
|
||||
UnsignedTransaction,
|
||||
};
|
||||
use sp_core::{storage::StorageKey, Pair};
|
||||
use sp_runtime::{generic::SignedPayload, traits::IdentifyAccount};
|
||||
use std::time::Duration;
|
||||
|
||||
pub mod runtime;
|
||||
|
||||
/// Kusama header id.
|
||||
pub type HeaderId = relay_utils::HeaderId<bp_kusama::Hash, bp_kusama::BlockNumber>;
|
||||
|
||||
@@ -31,18 +39,86 @@ impl ChainBase for Kusama {
|
||||
type Hash = bp_kusama::Hash;
|
||||
type Hasher = bp_kusama::Hasher;
|
||||
type Header = bp_kusama::Header;
|
||||
|
||||
type AccountId = bp_kusama::AccountId;
|
||||
type Balance = bp_kusama::Balance;
|
||||
type Index = bp_kusama::Nonce;
|
||||
type Signature = bp_kusama::Signature;
|
||||
}
|
||||
|
||||
impl Chain for Kusama {
|
||||
const NAME: &'static str = "Kusama";
|
||||
const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_secs(6);
|
||||
const STORAGE_PROOF_OVERHEAD: u32 = bp_kusama::EXTRA_STORAGE_PROOF_SIZE;
|
||||
const MAXIMAL_ENCODED_ACCOUNT_ID_SIZE: u32 = bp_kusama::MAXIMAL_ENCODED_ACCOUNT_ID_SIZE;
|
||||
|
||||
type AccountId = bp_kusama::AccountId;
|
||||
type Index = bp_kusama::Nonce;
|
||||
type SignedBlock = bp_kusama::SignedBlock;
|
||||
type Call = ();
|
||||
type Balance = bp_kusama::Balance;
|
||||
type Call = crate::runtime::Call;
|
||||
type WeightToFee = bp_kusama::WeightToFee;
|
||||
}
|
||||
|
||||
impl ChainWithBalances for Kusama {
|
||||
fn account_info_storage_key(account_id: &Self::AccountId) -> StorageKey {
|
||||
StorageKey(bp_kusama::account_info_storage_key(account_id))
|
||||
}
|
||||
}
|
||||
|
||||
impl TransactionSignScheme for Kusama {
|
||||
type Chain = Kusama;
|
||||
type AccountKeyPair = sp_core::sr25519::Pair;
|
||||
type SignedTransaction = crate::runtime::UncheckedExtrinsic;
|
||||
|
||||
fn sign_transaction(
|
||||
genesis_hash: <Self::Chain as ChainBase>::Hash,
|
||||
signer: &Self::AccountKeyPair,
|
||||
era: TransactionEraOf<Self::Chain>,
|
||||
unsigned: UnsignedTransaction<Self::Chain>,
|
||||
) -> Self::SignedTransaction {
|
||||
let raw_payload = SignedPayload::new(
|
||||
unsigned.call,
|
||||
bp_kusama::SignedExtensions::new(
|
||||
bp_kusama::VERSION,
|
||||
era,
|
||||
genesis_hash,
|
||||
unsigned.nonce,
|
||||
unsigned.tip,
|
||||
),
|
||||
)
|
||||
.expect("SignedExtension never fails.");
|
||||
|
||||
let signature = raw_payload.using_encoded(|payload| signer.sign(payload));
|
||||
let signer: sp_runtime::MultiSigner = signer.public().into();
|
||||
let (call, extra, _) = raw_payload.deconstruct();
|
||||
|
||||
bp_kusama::UncheckedExtrinsic::new_signed(
|
||||
call,
|
||||
sp_runtime::MultiAddress::Id(signer.into_account()),
|
||||
signature.into(),
|
||||
extra,
|
||||
)
|
||||
}
|
||||
|
||||
fn is_signed(tx: &Self::SignedTransaction) -> bool {
|
||||
tx.signature.is_some()
|
||||
}
|
||||
|
||||
fn is_signed_by(signer: &Self::AccountKeyPair, tx: &Self::SignedTransaction) -> bool {
|
||||
tx.signature
|
||||
.as_ref()
|
||||
.map(|(address, _, _)| {
|
||||
*address == bp_kusama::AccountId::from(*signer.public().as_array_ref()).into()
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn parse_transaction(tx: Self::SignedTransaction) -> Option<UnsignedTransaction<Self::Chain>> {
|
||||
let extra = &tx.signature.as_ref()?.2;
|
||||
Some(UnsignedTransaction { call: tx.function, nonce: extra.nonce(), tip: extra.tip() })
|
||||
}
|
||||
}
|
||||
|
||||
/// Kusama header type used in headers sync.
|
||||
pub type SyncHeader = relay_substrate_client::SyncHeader<bp_kusama::Header>;
|
||||
|
||||
/// Kusama signing params.
|
||||
pub type SigningParams = sp_core::sr25519::Pair;
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
// 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 specific to the Kusama runtime.
|
||||
|
||||
use bp_messages::{LaneId, UnrewardedRelayersState};
|
||||
use bp_polkadot_core::{AccountAddress, Balance, PolkadotLike};
|
||||
use bp_runtime::Chain;
|
||||
use codec::{Compact, Decode, Encode};
|
||||
use frame_support::weights::Weight;
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::FixedU128;
|
||||
|
||||
/// Unchecked Kusama extrinsic.
|
||||
pub type UncheckedExtrinsic = bp_polkadot_core::UncheckedExtrinsic<Call>;
|
||||
|
||||
/// Polkadot account ownership digest from Kusama.
|
||||
///
|
||||
/// The byte vector returned by this function should be signed with a Polkadot account private key.
|
||||
/// This way, the owner of `kusama_account_id` on Kusama proves that the Polkadot account private
|
||||
/// key is also under his control.
|
||||
pub fn kusama_to_polkadot_account_ownership_digest<Call, AccountId, SpecVersion>(
|
||||
polkadot_call: &Call,
|
||||
kusama_account_id: AccountId,
|
||||
polkadot_spec_version: SpecVersion,
|
||||
) -> Vec<u8>
|
||||
where
|
||||
Call: codec::Encode,
|
||||
AccountId: codec::Encode,
|
||||
SpecVersion: codec::Encode,
|
||||
{
|
||||
pallet_bridge_dispatch::account_ownership_digest(
|
||||
polkadot_call,
|
||||
kusama_account_id,
|
||||
polkadot_spec_version,
|
||||
bp_runtime::KUSAMA_CHAIN_ID,
|
||||
bp_runtime::POLKADOT_CHAIN_ID,
|
||||
)
|
||||
}
|
||||
|
||||
/// Kusama Runtime `Call` enum.
|
||||
///
|
||||
/// The enum represents a subset of possible `Call`s we can send to Kusama chain.
|
||||
/// Ideally this code would be auto-generated from metadata, because we want to
|
||||
/// avoid depending directly on the ENTIRE runtime just to get the encoding of `Dispatchable`s.
|
||||
///
|
||||
/// All entries here (like pretty much in the entire file) must be kept in sync with Kusama
|
||||
/// `construct_runtime`, so that we maintain SCALE-compatibility.
|
||||
///
|
||||
/// See: [link](https://github.com/paritytech/polkadot/blob/master/runtime/kusama/src/lib.rs)
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
|
||||
pub enum Call {
|
||||
/// System pallet.
|
||||
#[codec(index = 0)]
|
||||
System(SystemCall),
|
||||
/// Balances pallet.
|
||||
#[codec(index = 4)]
|
||||
Balances(BalancesCall),
|
||||
/// Polkadot bridge pallet.
|
||||
#[codec(index = 110)]
|
||||
BridgePolkadotGrandpa(BridgePolkadotGrandpaCall),
|
||||
/// Polkadot messages pallet.
|
||||
#[codec(index = 111)]
|
||||
BridgePolkadotMessages(BridgePolkadotMessagesCall),
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum SystemCall {
|
||||
#[codec(index = 1)]
|
||||
remark(Vec<u8>),
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum BalancesCall {
|
||||
#[codec(index = 0)]
|
||||
transfer(AccountAddress, Compact<Balance>),
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum BridgePolkadotGrandpaCall {
|
||||
#[codec(index = 0)]
|
||||
submit_finality_proof(
|
||||
Box<<PolkadotLike as Chain>::Header>,
|
||||
bp_header_chain::justification::GrandpaJustification<<PolkadotLike as Chain>::Header>,
|
||||
),
|
||||
#[codec(index = 1)]
|
||||
initialize(bp_header_chain::InitializationData<<PolkadotLike as Chain>::Header>),
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum BridgePolkadotMessagesCall {
|
||||
#[codec(index = 2)]
|
||||
update_pallet_parameter(BridgePolkadotMessagesParameter),
|
||||
#[codec(index = 3)]
|
||||
send_message(
|
||||
LaneId,
|
||||
bp_message_dispatch::MessagePayload<
|
||||
bp_kusama::AccountId,
|
||||
bp_polkadot::AccountId,
|
||||
bp_polkadot::AccountPublic,
|
||||
Vec<u8>,
|
||||
>,
|
||||
bp_kusama::Balance,
|
||||
),
|
||||
#[codec(index = 5)]
|
||||
receive_messages_proof(
|
||||
bp_polkadot::AccountId,
|
||||
bridge_runtime_common::messages::target::FromBridgedChainMessagesProof<bp_polkadot::Hash>,
|
||||
u32,
|
||||
Weight,
|
||||
),
|
||||
#[codec(index = 6)]
|
||||
receive_messages_delivery_proof(
|
||||
bridge_runtime_common::messages::source::FromBridgedChainMessagesDeliveryProof<
|
||||
bp_polkadot::Hash,
|
||||
>,
|
||||
UnrewardedRelayersState,
|
||||
),
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
|
||||
pub enum BridgePolkadotMessagesParameter {
|
||||
#[codec(index = 0)]
|
||||
PolkadotToKusamaConversionRate(FixedU128),
|
||||
}
|
||||
|
||||
impl sp_runtime::traits::Dispatchable for Call {
|
||||
type Origin = ();
|
||||
type Config = ();
|
||||
type Info = ();
|
||||
type PostInfo = ();
|
||||
|
||||
fn dispatch(self, _origin: Self::Origin) -> sp_runtime::DispatchResultWithInfo<Self::PostInfo> {
|
||||
unimplemented!("The Call is not expected to be dispatched.")
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,13 @@ edition = "2018"
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "2.0.0" }
|
||||
headers-relay = { path = "../headers" }
|
||||
codec = { package = "parity-scale-codec", version = "2.2.0" }
|
||||
relay-substrate-client = { path = "../client-substrate" }
|
||||
relay-utils = { path = "../utils" }
|
||||
|
||||
# Supported Chains
|
||||
|
||||
bp-millau = { path = "../../primitives/chain-millau" }
|
||||
millau-runtime = { path = "../../bin/millau/runtime" }
|
||||
|
||||
# Substrate Dependencies
|
||||
@@ -21,5 +21,4 @@ frame-support = { git = "https://github.com/paritytech/substrate", branch = "mas
|
||||
frame-system = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
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" }
|
||||
|
||||
@@ -16,8 +16,11 @@
|
||||
|
||||
//! Types used to connect to the Millau-Substrate chain.
|
||||
|
||||
use codec::Encode;
|
||||
use relay_substrate_client::{Chain, ChainBase, ChainWithBalances, TransactionSignScheme};
|
||||
use codec::{Compact, Decode, Encode};
|
||||
use relay_substrate_client::{
|
||||
BalanceOf, Chain, ChainBase, ChainWithBalances, IndexOf, TransactionEraOf,
|
||||
TransactionSignScheme, UnsignedTransaction,
|
||||
};
|
||||
use sp_core::{storage::StorageKey, Pair};
|
||||
use sp_runtime::{generic::SignedPayload, traits::IdentifyAccount};
|
||||
use std::time::Duration;
|
||||
@@ -34,17 +37,22 @@ impl ChainBase for Millau {
|
||||
type Hash = millau_runtime::Hash;
|
||||
type Hasher = millau_runtime::Hashing;
|
||||
type Header = millau_runtime::Header;
|
||||
|
||||
type AccountId = millau_runtime::AccountId;
|
||||
type Balance = millau_runtime::Balance;
|
||||
type Index = millau_runtime::Index;
|
||||
type Signature = millau_runtime::Signature;
|
||||
}
|
||||
|
||||
impl Chain for Millau {
|
||||
const NAME: &'static str = "Millau";
|
||||
const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_secs(5);
|
||||
const STORAGE_PROOF_OVERHEAD: u32 = bp_millau::EXTRA_STORAGE_PROOF_SIZE;
|
||||
const MAXIMAL_ENCODED_ACCOUNT_ID_SIZE: u32 = bp_millau::MAXIMAL_ENCODED_ACCOUNT_ID_SIZE;
|
||||
|
||||
type AccountId = millau_runtime::AccountId;
|
||||
type Index = millau_runtime::Index;
|
||||
type SignedBlock = millau_runtime::SignedBlock;
|
||||
type Call = millau_runtime::Call;
|
||||
type Balance = millau_runtime::Balance;
|
||||
type WeightToFee = bp_millau::WeightToFee;
|
||||
}
|
||||
|
||||
impl ChainWithBalances for Millau {
|
||||
@@ -64,25 +72,25 @@ impl TransactionSignScheme for Millau {
|
||||
fn sign_transaction(
|
||||
genesis_hash: <Self::Chain as ChainBase>::Hash,
|
||||
signer: &Self::AccountKeyPair,
|
||||
signer_nonce: <Self::Chain as Chain>::Index,
|
||||
call: <Self::Chain as Chain>::Call,
|
||||
era: TransactionEraOf<Self::Chain>,
|
||||
unsigned: UnsignedTransaction<Self::Chain>,
|
||||
) -> Self::SignedTransaction {
|
||||
let raw_payload = SignedPayload::from_raw(
|
||||
call,
|
||||
unsigned.call,
|
||||
(
|
||||
frame_system::CheckSpecVersion::<millau_runtime::Runtime>::new(),
|
||||
frame_system::CheckTxVersion::<millau_runtime::Runtime>::new(),
|
||||
frame_system::CheckGenesis::<millau_runtime::Runtime>::new(),
|
||||
frame_system::CheckEra::<millau_runtime::Runtime>::from(sp_runtime::generic::Era::Immortal),
|
||||
frame_system::CheckNonce::<millau_runtime::Runtime>::from(signer_nonce),
|
||||
frame_system::CheckEra::<millau_runtime::Runtime>::from(era.frame_era()),
|
||||
frame_system::CheckNonce::<millau_runtime::Runtime>::from(unsigned.nonce),
|
||||
frame_system::CheckWeight::<millau_runtime::Runtime>::new(),
|
||||
pallet_transaction_payment::ChargeTransactionPayment::<millau_runtime::Runtime>::from(0),
|
||||
pallet_transaction_payment::ChargeTransactionPayment::<millau_runtime::Runtime>::from(unsigned.tip),
|
||||
),
|
||||
(
|
||||
millau_runtime::VERSION.spec_version,
|
||||
millau_runtime::VERSION.transaction_version,
|
||||
genesis_hash,
|
||||
genesis_hash,
|
||||
era.signed_payload(genesis_hash),
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
@@ -92,7 +100,36 @@ impl TransactionSignScheme for Millau {
|
||||
let signer: sp_runtime::MultiSigner = signer.public().into();
|
||||
let (call, extra, _) = raw_payload.deconstruct();
|
||||
|
||||
millau_runtime::UncheckedExtrinsic::new_signed(call, signer.into_account(), signature.into(), extra)
|
||||
millau_runtime::UncheckedExtrinsic::new_signed(
|
||||
call,
|
||||
signer.into_account(),
|
||||
signature.into(),
|
||||
extra,
|
||||
)
|
||||
}
|
||||
|
||||
fn is_signed(tx: &Self::SignedTransaction) -> bool {
|
||||
tx.signature.is_some()
|
||||
}
|
||||
|
||||
fn is_signed_by(signer: &Self::AccountKeyPair, tx: &Self::SignedTransaction) -> bool {
|
||||
tx.signature
|
||||
.as_ref()
|
||||
.map(|(address, _, _)| {
|
||||
*address == millau_runtime::Address::from(*signer.public().as_array_ref())
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn parse_transaction(tx: Self::SignedTransaction) -> Option<UnsignedTransaction<Self::Chain>> {
|
||||
let extra = &tx.signature.as_ref()?.2;
|
||||
Some(UnsignedTransaction {
|
||||
call: tx.function,
|
||||
nonce: Compact::<IndexOf<Self::Chain>>::decode(&mut &extra.4.encode()[..]).ok()?.into(),
|
||||
tip: Compact::<BalanceOf<Self::Chain>>::decode(&mut &extra.6.encode()[..])
|
||||
.ok()?
|
||||
.into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,20 +6,25 @@ edition = "2018"
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "2.0.0" }
|
||||
headers-relay = { path = "../headers" }
|
||||
codec = { package = "parity-scale-codec", version = "2.2.0" }
|
||||
relay-substrate-client = { path = "../client-substrate" }
|
||||
relay-utils = { path = "../utils" }
|
||||
scale-info = { version = "1.0", features = ["derive"] }
|
||||
|
||||
# Bridge dependencies
|
||||
|
||||
bp-header-chain = { path = "../../primitives/header-chain" }
|
||||
bp-kusama = { path = "../../primitives/chain-kusama" }
|
||||
bp-message-dispatch = { path = "../../primitives/message-dispatch" }
|
||||
bp-messages = { path = "../../primitives/messages" }
|
||||
bp-polkadot = { path = "../../primitives/chain-polkadot" }
|
||||
bp-polkadot-core = { path = "../../primitives/polkadot-core" }
|
||||
bp-runtime = { path = "../../primitives/runtime" }
|
||||
bridge-runtime-common = { path = "../../bin/runtime-common" }
|
||||
pallet-bridge-dispatch = { path = "../../modules/dispatch" }
|
||||
|
||||
# Substrate Dependencies
|
||||
|
||||
frame-system = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
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" }
|
||||
|
||||
@@ -16,9 +16,17 @@
|
||||
|
||||
//! Types used to connect to the Polkadot chain.
|
||||
|
||||
use relay_substrate_client::{Chain, ChainBase};
|
||||
use codec::Encode;
|
||||
use relay_substrate_client::{
|
||||
Chain, ChainBase, ChainWithBalances, TransactionEraOf, TransactionSignScheme,
|
||||
UnsignedTransaction,
|
||||
};
|
||||
use sp_core::{storage::StorageKey, Pair};
|
||||
use sp_runtime::{generic::SignedPayload, traits::IdentifyAccount};
|
||||
use std::time::Duration;
|
||||
|
||||
pub mod runtime;
|
||||
|
||||
/// Polkadot header id.
|
||||
pub type HeaderId = relay_utils::HeaderId<bp_polkadot::Hash, bp_polkadot::BlockNumber>;
|
||||
|
||||
@@ -31,18 +39,86 @@ impl ChainBase for Polkadot {
|
||||
type Hash = bp_polkadot::Hash;
|
||||
type Hasher = bp_polkadot::Hasher;
|
||||
type Header = bp_polkadot::Header;
|
||||
|
||||
type AccountId = bp_polkadot::AccountId;
|
||||
type Balance = bp_polkadot::Balance;
|
||||
type Index = bp_polkadot::Nonce;
|
||||
type Signature = bp_polkadot::Signature;
|
||||
}
|
||||
|
||||
impl Chain for Polkadot {
|
||||
const NAME: &'static str = "Polkadot";
|
||||
const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_secs(6);
|
||||
const STORAGE_PROOF_OVERHEAD: u32 = bp_polkadot::EXTRA_STORAGE_PROOF_SIZE;
|
||||
const MAXIMAL_ENCODED_ACCOUNT_ID_SIZE: u32 = bp_polkadot::MAXIMAL_ENCODED_ACCOUNT_ID_SIZE;
|
||||
|
||||
type AccountId = bp_polkadot::AccountId;
|
||||
type Index = bp_polkadot::Nonce;
|
||||
type SignedBlock = bp_polkadot::SignedBlock;
|
||||
type Call = ();
|
||||
type Balance = bp_polkadot::Balance;
|
||||
type Call = crate::runtime::Call;
|
||||
type WeightToFee = bp_polkadot::WeightToFee;
|
||||
}
|
||||
|
||||
impl ChainWithBalances for Polkadot {
|
||||
fn account_info_storage_key(account_id: &Self::AccountId) -> StorageKey {
|
||||
StorageKey(bp_polkadot::account_info_storage_key(account_id))
|
||||
}
|
||||
}
|
||||
|
||||
impl TransactionSignScheme for Polkadot {
|
||||
type Chain = Polkadot;
|
||||
type AccountKeyPair = sp_core::sr25519::Pair;
|
||||
type SignedTransaction = crate::runtime::UncheckedExtrinsic;
|
||||
|
||||
fn sign_transaction(
|
||||
genesis_hash: <Self::Chain as ChainBase>::Hash,
|
||||
signer: &Self::AccountKeyPair,
|
||||
era: TransactionEraOf<Self::Chain>,
|
||||
unsigned: UnsignedTransaction<Self::Chain>,
|
||||
) -> Self::SignedTransaction {
|
||||
let raw_payload = SignedPayload::new(
|
||||
unsigned.call,
|
||||
bp_polkadot::SignedExtensions::new(
|
||||
bp_polkadot::VERSION,
|
||||
era,
|
||||
genesis_hash,
|
||||
unsigned.nonce,
|
||||
unsigned.tip,
|
||||
),
|
||||
)
|
||||
.expect("SignedExtension never fails.");
|
||||
|
||||
let signature = raw_payload.using_encoded(|payload| signer.sign(payload));
|
||||
let signer: sp_runtime::MultiSigner = signer.public().into();
|
||||
let (call, extra, _) = raw_payload.deconstruct();
|
||||
|
||||
bp_polkadot::UncheckedExtrinsic::new_signed(
|
||||
call,
|
||||
sp_runtime::MultiAddress::Id(signer.into_account()),
|
||||
signature.into(),
|
||||
extra,
|
||||
)
|
||||
}
|
||||
|
||||
fn is_signed(tx: &Self::SignedTransaction) -> bool {
|
||||
tx.signature.is_some()
|
||||
}
|
||||
|
||||
fn is_signed_by(signer: &Self::AccountKeyPair, tx: &Self::SignedTransaction) -> bool {
|
||||
tx.signature
|
||||
.as_ref()
|
||||
.map(|(address, _, _)| {
|
||||
*address == bp_polkadot::AccountId::from(*signer.public().as_array_ref()).into()
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn parse_transaction(tx: Self::SignedTransaction) -> Option<UnsignedTransaction<Self::Chain>> {
|
||||
let extra = &tx.signature.as_ref()?.2;
|
||||
Some(UnsignedTransaction { call: tx.function, nonce: extra.nonce(), tip: extra.tip() })
|
||||
}
|
||||
}
|
||||
|
||||
/// Polkadot header type used in headers sync.
|
||||
pub type SyncHeader = relay_substrate_client::SyncHeader<bp_polkadot::Header>;
|
||||
|
||||
/// Polkadot signing params.
|
||||
pub type SigningParams = sp_core::sr25519::Pair;
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
// 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 specific to the Polkadot runtime.
|
||||
|
||||
use bp_messages::{LaneId, UnrewardedRelayersState};
|
||||
use bp_polkadot_core::{AccountAddress, Balance, PolkadotLike};
|
||||
use bp_runtime::Chain;
|
||||
use codec::{Compact, Decode, Encode};
|
||||
use frame_support::weights::Weight;
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::FixedU128;
|
||||
|
||||
/// Unchecked Polkadot extrinsic.
|
||||
pub type UncheckedExtrinsic = bp_polkadot_core::UncheckedExtrinsic<Call>;
|
||||
|
||||
/// Kusama account ownership digest from Polkadot.
|
||||
///
|
||||
/// The byte vector returned by this function should be signed with a Kusama account private key.
|
||||
/// This way, the owner of `kusam_account_id` on Polkadot proves that the Kusama account private key
|
||||
/// is also under his control.
|
||||
pub fn polkadot_to_kusama_account_ownership_digest<Call, AccountId, SpecVersion>(
|
||||
kusama_call: &Call,
|
||||
kusam_account_id: AccountId,
|
||||
kusama_spec_version: SpecVersion,
|
||||
) -> Vec<u8>
|
||||
where
|
||||
Call: codec::Encode,
|
||||
AccountId: codec::Encode,
|
||||
SpecVersion: codec::Encode,
|
||||
{
|
||||
pallet_bridge_dispatch::account_ownership_digest(
|
||||
kusama_call,
|
||||
kusam_account_id,
|
||||
kusama_spec_version,
|
||||
bp_runtime::POLKADOT_CHAIN_ID,
|
||||
bp_runtime::KUSAMA_CHAIN_ID,
|
||||
)
|
||||
}
|
||||
|
||||
/// Polkadot Runtime `Call` enum.
|
||||
///
|
||||
/// The enum represents a subset of possible `Call`s we can send to Polkadot chain.
|
||||
/// Ideally this code would be auto-generated from metadata, because we want to
|
||||
/// avoid depending directly on the ENTIRE runtime just to get the encoding of `Dispatchable`s.
|
||||
///
|
||||
/// All entries here (like pretty much in the entire file) must be kept in sync with Polkadot
|
||||
/// `construct_runtime`, so that we maintain SCALE-compatibility.
|
||||
///
|
||||
/// See: [link](https://github.com/paritytech/kusama/blob/master/runtime/kusam/src/lib.rs)
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
|
||||
pub enum Call {
|
||||
/// System pallet.
|
||||
#[codec(index = 0)]
|
||||
System(SystemCall),
|
||||
/// Balances pallet.
|
||||
#[codec(index = 5)]
|
||||
Balances(BalancesCall),
|
||||
/// Kusama bridge pallet.
|
||||
#[codec(index = 110)]
|
||||
BridgeKusamaGrandpa(BridgeKusamaGrandpaCall),
|
||||
/// Kusama messages pallet.
|
||||
#[codec(index = 111)]
|
||||
BridgeKusamaMessages(BridgeKusamaMessagesCall),
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum SystemCall {
|
||||
#[codec(index = 1)]
|
||||
remark(Vec<u8>),
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum BalancesCall {
|
||||
#[codec(index = 0)]
|
||||
transfer(AccountAddress, Compact<Balance>),
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum BridgeKusamaGrandpaCall {
|
||||
#[codec(index = 0)]
|
||||
submit_finality_proof(
|
||||
Box<<PolkadotLike as Chain>::Header>,
|
||||
bp_header_chain::justification::GrandpaJustification<<PolkadotLike as Chain>::Header>,
|
||||
),
|
||||
#[codec(index = 1)]
|
||||
initialize(bp_header_chain::InitializationData<<PolkadotLike as Chain>::Header>),
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum BridgeKusamaMessagesCall {
|
||||
#[codec(index = 2)]
|
||||
update_pallet_parameter(BridgeKusamaMessagesParameter),
|
||||
#[codec(index = 3)]
|
||||
send_message(
|
||||
LaneId,
|
||||
bp_message_dispatch::MessagePayload<
|
||||
bp_polkadot::AccountId,
|
||||
bp_kusama::AccountId,
|
||||
bp_kusama::AccountPublic,
|
||||
Vec<u8>,
|
||||
>,
|
||||
bp_polkadot::Balance,
|
||||
),
|
||||
#[codec(index = 5)]
|
||||
receive_messages_proof(
|
||||
bp_kusama::AccountId,
|
||||
bridge_runtime_common::messages::target::FromBridgedChainMessagesProof<bp_kusama::Hash>,
|
||||
u32,
|
||||
Weight,
|
||||
),
|
||||
#[codec(index = 6)]
|
||||
receive_messages_delivery_proof(
|
||||
bridge_runtime_common::messages::source::FromBridgedChainMessagesDeliveryProof<
|
||||
bp_kusama::Hash,
|
||||
>,
|
||||
UnrewardedRelayersState,
|
||||
),
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
|
||||
pub enum BridgeKusamaMessagesParameter {
|
||||
#[codec(index = 0)]
|
||||
KusamaToPolkadotConversionRate(FixedU128),
|
||||
}
|
||||
|
||||
impl sp_runtime::traits::Dispatchable for Call {
|
||||
type Origin = ();
|
||||
type Config = ();
|
||||
type Info = ();
|
||||
type PostInfo = ();
|
||||
|
||||
fn dispatch(self, _origin: Self::Origin) -> sp_runtime::DispatchResultWithInfo<Self::PostInfo> {
|
||||
unimplemented!("The Call is not expected to be dispatched.")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "relay-rialto-parachain-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]
|
||||
relay-substrate-client = { path = "../client-substrate" }
|
||||
relay-utils = { path = "../utils" }
|
||||
|
||||
# Bridge dependencies
|
||||
|
||||
bp-rialto = { path = "../../primitives/chain-rialto" }
|
||||
rialto-parachain-runtime = { path = "../../bin/rialto-parachain/runtime" }
|
||||
|
||||
# Substrate Dependencies
|
||||
|
||||
frame-system = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
@@ -0,0 +1,51 @@
|
||||
// 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 used to connect to the Rialto-Substrate chain.
|
||||
|
||||
use relay_substrate_client::{Chain, ChainBase};
|
||||
use std::time::Duration;
|
||||
|
||||
/// Rialto header id.
|
||||
pub type HeaderId =
|
||||
relay_utils::HeaderId<rialto_parachain_runtime::Hash, rialto_parachain_runtime::BlockNumber>;
|
||||
|
||||
/// Rialto parachain definition
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct RialtoParachain;
|
||||
|
||||
impl ChainBase for RialtoParachain {
|
||||
type BlockNumber = rialto_parachain_runtime::BlockNumber;
|
||||
type Hash = rialto_parachain_runtime::Hash;
|
||||
type Hasher = rialto_parachain_runtime::Hashing;
|
||||
type Header = rialto_parachain_runtime::Header;
|
||||
|
||||
type AccountId = rialto_parachain_runtime::AccountId;
|
||||
type Balance = rialto_parachain_runtime::Balance;
|
||||
type Index = rialto_parachain_runtime::Index;
|
||||
type Signature = rialto_parachain_runtime::Signature;
|
||||
}
|
||||
|
||||
impl Chain for RialtoParachain {
|
||||
const NAME: &'static str = "RialtoParachain";
|
||||
const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_secs(5);
|
||||
const STORAGE_PROOF_OVERHEAD: u32 = bp_rialto::EXTRA_STORAGE_PROOF_SIZE;
|
||||
const MAXIMAL_ENCODED_ACCOUNT_ID_SIZE: u32 = bp_rialto::MAXIMAL_ENCODED_ACCOUNT_ID_SIZE;
|
||||
|
||||
type SignedBlock = rialto_parachain_runtime::SignedBlock;
|
||||
type Call = rialto_parachain_runtime::Call;
|
||||
type WeightToFee = bp_rialto::WeightToFee;
|
||||
}
|
||||
@@ -6,13 +6,13 @@ edition = "2018"
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "2.0.0" }
|
||||
headers-relay = { path = "../headers" }
|
||||
codec = { package = "parity-scale-codec", version = "2.2.0" }
|
||||
relay-substrate-client = { path = "../client-substrate" }
|
||||
relay-utils = { path = "../utils" }
|
||||
|
||||
# Bridge dependencies
|
||||
|
||||
bp-rialto = { path = "../../primitives/chain-rialto" }
|
||||
rialto-runtime = { path = "../../bin/rialto/runtime" }
|
||||
|
||||
# Substrate Dependencies
|
||||
@@ -21,5 +21,4 @@ frame-system = { git = "https://github.com/paritytech/substrate", branch = "mast
|
||||
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
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" }
|
||||
|
||||
@@ -16,8 +16,11 @@
|
||||
|
||||
//! Types used to connect to the Rialto-Substrate chain.
|
||||
|
||||
use codec::Encode;
|
||||
use relay_substrate_client::{Chain, ChainBase, ChainWithBalances, TransactionSignScheme};
|
||||
use codec::{Compact, Decode, Encode};
|
||||
use relay_substrate_client::{
|
||||
BalanceOf, Chain, ChainBase, ChainWithBalances, IndexOf, TransactionEraOf,
|
||||
TransactionSignScheme, UnsignedTransaction,
|
||||
};
|
||||
use sp_core::{storage::StorageKey, Pair};
|
||||
use sp_runtime::{generic::SignedPayload, traits::IdentifyAccount};
|
||||
use std::time::Duration;
|
||||
@@ -34,17 +37,22 @@ impl ChainBase for Rialto {
|
||||
type Hash = rialto_runtime::Hash;
|
||||
type Hasher = rialto_runtime::Hashing;
|
||||
type Header = rialto_runtime::Header;
|
||||
|
||||
type AccountId = rialto_runtime::AccountId;
|
||||
type Balance = rialto_runtime::Balance;
|
||||
type Index = rialto_runtime::Index;
|
||||
type Signature = rialto_runtime::Signature;
|
||||
}
|
||||
|
||||
impl Chain for Rialto {
|
||||
const NAME: &'static str = "Rialto";
|
||||
const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_secs(5);
|
||||
const STORAGE_PROOF_OVERHEAD: u32 = bp_rialto::EXTRA_STORAGE_PROOF_SIZE;
|
||||
const MAXIMAL_ENCODED_ACCOUNT_ID_SIZE: u32 = bp_rialto::MAXIMAL_ENCODED_ACCOUNT_ID_SIZE;
|
||||
|
||||
type AccountId = rialto_runtime::AccountId;
|
||||
type Index = rialto_runtime::Index;
|
||||
type SignedBlock = rialto_runtime::SignedBlock;
|
||||
type Call = rialto_runtime::Call;
|
||||
type Balance = rialto_runtime::Balance;
|
||||
type WeightToFee = bp_rialto::WeightToFee;
|
||||
}
|
||||
|
||||
impl ChainWithBalances for Rialto {
|
||||
@@ -64,25 +72,25 @@ impl TransactionSignScheme for Rialto {
|
||||
fn sign_transaction(
|
||||
genesis_hash: <Self::Chain as ChainBase>::Hash,
|
||||
signer: &Self::AccountKeyPair,
|
||||
signer_nonce: <Self::Chain as Chain>::Index,
|
||||
call: <Self::Chain as Chain>::Call,
|
||||
era: TransactionEraOf<Self::Chain>,
|
||||
unsigned: UnsignedTransaction<Self::Chain>,
|
||||
) -> Self::SignedTransaction {
|
||||
let raw_payload = SignedPayload::from_raw(
|
||||
call,
|
||||
unsigned.call,
|
||||
(
|
||||
frame_system::CheckSpecVersion::<rialto_runtime::Runtime>::new(),
|
||||
frame_system::CheckTxVersion::<rialto_runtime::Runtime>::new(),
|
||||
frame_system::CheckGenesis::<rialto_runtime::Runtime>::new(),
|
||||
frame_system::CheckEra::<rialto_runtime::Runtime>::from(sp_runtime::generic::Era::Immortal),
|
||||
frame_system::CheckNonce::<rialto_runtime::Runtime>::from(signer_nonce),
|
||||
frame_system::CheckEra::<rialto_runtime::Runtime>::from(era.frame_era()),
|
||||
frame_system::CheckNonce::<rialto_runtime::Runtime>::from(unsigned.nonce),
|
||||
frame_system::CheckWeight::<rialto_runtime::Runtime>::new(),
|
||||
pallet_transaction_payment::ChargeTransactionPayment::<rialto_runtime::Runtime>::from(0),
|
||||
pallet_transaction_payment::ChargeTransactionPayment::<rialto_runtime::Runtime>::from(unsigned.tip),
|
||||
),
|
||||
(
|
||||
rialto_runtime::VERSION.spec_version,
|
||||
rialto_runtime::VERSION.transaction_version,
|
||||
genesis_hash,
|
||||
genesis_hash,
|
||||
era.signed_payload(genesis_hash),
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
@@ -92,7 +100,34 @@ impl TransactionSignScheme for Rialto {
|
||||
let signer: sp_runtime::MultiSigner = signer.public().into();
|
||||
let (call, extra, _) = raw_payload.deconstruct();
|
||||
|
||||
rialto_runtime::UncheckedExtrinsic::new_signed(call, signer.into_account(), signature.into(), extra)
|
||||
rialto_runtime::UncheckedExtrinsic::new_signed(
|
||||
call,
|
||||
signer.into_account().into(),
|
||||
signature.into(),
|
||||
extra,
|
||||
)
|
||||
}
|
||||
|
||||
fn is_signed(tx: &Self::SignedTransaction) -> bool {
|
||||
tx.signature.is_some()
|
||||
}
|
||||
|
||||
fn is_signed_by(signer: &Self::AccountKeyPair, tx: &Self::SignedTransaction) -> bool {
|
||||
tx.signature
|
||||
.as_ref()
|
||||
.map(|(address, _, _)| *address == rialto_runtime::Address::Id(signer.public().into()))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn parse_transaction(tx: Self::SignedTransaction) -> Option<UnsignedTransaction<Self::Chain>> {
|
||||
let extra = &tx.signature.as_ref()?.2;
|
||||
Some(UnsignedTransaction {
|
||||
call: tx.function,
|
||||
nonce: Compact::<IndexOf<Self::Chain>>::decode(&mut &extra.4.encode()[..]).ok()?.into(),
|
||||
tip: Compact::<BalanceOf<Self::Chain>>::decode(&mut &extra.6.encode()[..])
|
||||
.ok()?
|
||||
.into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,12 +6,13 @@ edition = "2018"
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "2.0.0" }
|
||||
headers-relay = { path = "../headers" }
|
||||
codec = { package = "parity-scale-codec", version = "2.2.0" }
|
||||
relay-substrate-client = { path = "../client-substrate" }
|
||||
relay-utils = { path = "../utils" }
|
||||
scale-info = { version = "1.0", features = ["derive"] }
|
||||
|
||||
# Bridge dependencies
|
||||
|
||||
bridge-runtime-common = { path = "../../bin/runtime-common" }
|
||||
bp-header-chain = { path = "../../primitives/header-chain" }
|
||||
bp-message-dispatch = { path = "../../primitives/message-dispatch" }
|
||||
@@ -24,9 +25,7 @@ pallet-bridge-dispatch = { path = "../../modules/dispatch" }
|
||||
pallet-bridge-messages = { path = "../../modules/messages" }
|
||||
|
||||
# Substrate Dependencies
|
||||
frame-system = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
|
||||
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
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" }
|
||||
|
||||
@@ -17,7 +17,10 @@
|
||||
//! Types used to connect to the Rococo-Substrate chain.
|
||||
|
||||
use codec::Encode;
|
||||
use relay_substrate_client::{Chain, ChainBase, ChainWithBalances, TransactionSignScheme};
|
||||
use relay_substrate_client::{
|
||||
Chain, ChainBase, ChainWithBalances, TransactionEraOf, TransactionSignScheme,
|
||||
UnsignedTransaction,
|
||||
};
|
||||
use sp_core::{storage::StorageKey, Pair};
|
||||
use sp_runtime::{generic::SignedPayload, traits::IdentifyAccount};
|
||||
use std::time::Duration;
|
||||
@@ -39,17 +42,22 @@ impl ChainBase for Rococo {
|
||||
type Hash = bp_rococo::Hash;
|
||||
type Hasher = bp_rococo::Hashing;
|
||||
type Header = bp_rococo::Header;
|
||||
|
||||
type AccountId = bp_rococo::AccountId;
|
||||
type Balance = bp_rococo::Balance;
|
||||
type Index = bp_rococo::Nonce;
|
||||
type Signature = bp_rococo::Signature;
|
||||
}
|
||||
|
||||
impl Chain for Rococo {
|
||||
const NAME: &'static str = "Rococo";
|
||||
const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_secs(6);
|
||||
const STORAGE_PROOF_OVERHEAD: u32 = bp_rococo::EXTRA_STORAGE_PROOF_SIZE;
|
||||
const MAXIMAL_ENCODED_ACCOUNT_ID_SIZE: u32 = bp_rococo::MAXIMAL_ENCODED_ACCOUNT_ID_SIZE;
|
||||
|
||||
type AccountId = bp_rococo::AccountId;
|
||||
type Index = bp_rococo::Index;
|
||||
type SignedBlock = bp_rococo::SignedBlock;
|
||||
type Call = crate::runtime::Call;
|
||||
type Balance = bp_rococo::Balance;
|
||||
type WeightToFee = bp_rococo::WeightToFee;
|
||||
}
|
||||
|
||||
impl ChainWithBalances for Rococo {
|
||||
@@ -66,17 +74,17 @@ impl TransactionSignScheme for Rococo {
|
||||
fn sign_transaction(
|
||||
genesis_hash: <Self::Chain as ChainBase>::Hash,
|
||||
signer: &Self::AccountKeyPair,
|
||||
signer_nonce: <Self::Chain as Chain>::Index,
|
||||
call: <Self::Chain as Chain>::Call,
|
||||
era: TransactionEraOf<Self::Chain>,
|
||||
unsigned: UnsignedTransaction<Self::Chain>,
|
||||
) -> Self::SignedTransaction {
|
||||
let raw_payload = SignedPayload::new(
|
||||
call,
|
||||
unsigned.call,
|
||||
bp_rococo::SignedExtensions::new(
|
||||
bp_rococo::VERSION,
|
||||
sp_runtime::generic::Era::Immortal,
|
||||
era,
|
||||
genesis_hash,
|
||||
signer_nonce,
|
||||
0,
|
||||
unsigned.nonce,
|
||||
unsigned.tip,
|
||||
),
|
||||
)
|
||||
.expect("SignedExtension never fails.");
|
||||
@@ -92,6 +100,24 @@ impl TransactionSignScheme for Rococo {
|
||||
extra,
|
||||
)
|
||||
}
|
||||
|
||||
fn is_signed(tx: &Self::SignedTransaction) -> bool {
|
||||
tx.signature.is_some()
|
||||
}
|
||||
|
||||
fn is_signed_by(signer: &Self::AccountKeyPair, tx: &Self::SignedTransaction) -> bool {
|
||||
tx.signature
|
||||
.as_ref()
|
||||
.map(|(address, _, _)| {
|
||||
*address == bp_rococo::AccountId::from(*signer.public().as_array_ref()).into()
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn parse_transaction(tx: Self::SignedTransaction) -> Option<UnsignedTransaction<Self::Chain>> {
|
||||
let extra = &tx.signature.as_ref()?.2;
|
||||
Some(UnsignedTransaction { call: tx.function, nonce: extra.nonce(), tip: extra.tip() })
|
||||
}
|
||||
}
|
||||
|
||||
/// Rococo signing params.
|
||||
|
||||
@@ -21,9 +21,7 @@ use bp_polkadot_core::PolkadotLike;
|
||||
use bp_runtime::Chain;
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::weights::Weight;
|
||||
|
||||
/// Instance of messages pallet that is used to bridge with Wococo chain.
|
||||
pub type WithWococoMessagesInstance = pallet_bridge_messages::Instance1;
|
||||
use scale_info::TypeInfo;
|
||||
|
||||
/// Unchecked Rococo extrinsic.
|
||||
pub type UncheckedExtrinsic = bp_polkadot_core::UncheckedExtrinsic<Call>;
|
||||
@@ -55,15 +53,15 @@ where
|
||||
/// Rococo Runtime `Call` enum.
|
||||
///
|
||||
/// The enum represents a subset of possible `Call`s we can send to Rococo chain.
|
||||
/// Ideally this code would be auto-generated from Metadata, because we want to
|
||||
/// Ideally this code would be auto-generated from metadata, because we want to
|
||||
/// avoid depending directly on the ENTIRE runtime just to get the encoding of `Dispatchable`s.
|
||||
///
|
||||
/// All entries here (like pretty much in the entire file) must be kept in sync with Rococo
|
||||
/// `construct_runtime`, so that we maintain SCALE-compatibility.
|
||||
///
|
||||
/// See: https://github.com/paritytech/polkadot/blob/master/runtime/rococo/src/lib.rs
|
||||
/// See: [link](https://github.com/paritytech/polkadot/blob/master/runtime/rococo/src/lib.rs)
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
|
||||
pub enum Call {
|
||||
/// System pallet.
|
||||
#[codec(index = 0)]
|
||||
@@ -76,26 +74,26 @@ pub enum Call {
|
||||
BridgeMessagesWococo(BridgeMessagesWococoCall),
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum SystemCall {
|
||||
#[codec(index = 1)]
|
||||
remark(Vec<u8>),
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum BridgeGrandpaWococoCall {
|
||||
#[codec(index = 0)]
|
||||
submit_finality_proof(
|
||||
<PolkadotLike as Chain>::Header,
|
||||
Box<<PolkadotLike as Chain>::Header>,
|
||||
bp_header_chain::justification::GrandpaJustification<<PolkadotLike as Chain>::Header>,
|
||||
),
|
||||
#[codec(index = 1)]
|
||||
initialize(bp_header_chain::InitializationData<<PolkadotLike as Chain>::Header>),
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum BridgeMessagesWococoCall {
|
||||
#[codec(index = 3)]
|
||||
@@ -118,7 +116,9 @@ pub enum BridgeMessagesWococoCall {
|
||||
),
|
||||
#[codec(index = 6)]
|
||||
receive_messages_delivery_proof(
|
||||
bridge_runtime_common::messages::source::FromBridgedChainMessagesDeliveryProof<bp_wococo::Hash>,
|
||||
bridge_runtime_common::messages::source::FromBridgedChainMessagesDeliveryProof<
|
||||
bp_wococo::Hash,
|
||||
>,
|
||||
UnrewardedRelayersState,
|
||||
),
|
||||
}
|
||||
|
||||
@@ -8,17 +8,18 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
[dependencies]
|
||||
async-std = { version = "1.6.5", features = ["attributes"] }
|
||||
async-trait = "0.1.40"
|
||||
codec = { package = "parity-scale-codec", version = "2.0.0" }
|
||||
jsonrpsee-proc-macros = "=0.2.0-alpha.6"
|
||||
jsonrpsee-ws-client = "=0.2.0-alpha.6"
|
||||
codec = { package = "parity-scale-codec", version = "2.2.0" }
|
||||
jsonrpsee-proc-macros = "0.3.1"
|
||||
jsonrpsee-ws-client = "0.3.1"
|
||||
log = "0.4.11"
|
||||
num-traits = "0.2"
|
||||
rand = "0.7"
|
||||
tokio = "1.8"
|
||||
thiserror = "1.0.26"
|
||||
|
||||
# Bridge dependencies
|
||||
|
||||
bp-header-chain = { path = "../../primitives/header-chain" }
|
||||
bp-messages = { path = "../../primitives/messages" }
|
||||
bp-runtime = { path = "../../primitives/runtime" }
|
||||
finality-relay = { path = "../finality" }
|
||||
headers-relay = { path = "../headers" }
|
||||
@@ -29,12 +30,15 @@ relay-utils = { path = "../utils" }
|
||||
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
frame-system = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sc-rpc-api = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sc-transaction-pool-api = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sp-finality-grandpa = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sp-rpc = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sp-storage = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sp-std = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sp-trie = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sp-version = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
|
||||
|
||||
@@ -14,16 +14,16 @@
|
||||
// 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 bp_runtime::Chain as ChainBase;
|
||||
use frame_support::Parameter;
|
||||
use jsonrpsee_ws_client::{DeserializeOwned, Serialize};
|
||||
use num_traits::{CheckedSub, SaturatingAdd, Zero};
|
||||
use bp_runtime::{Chain as ChainBase, HashOf, TransactionEraOf};
|
||||
use codec::{Codec, Encode};
|
||||
use frame_support::weights::WeightToFeePolynomial;
|
||||
use jsonrpsee_ws_client::types::{DeserializeOwned, Serialize};
|
||||
use num_traits::Zero;
|
||||
use sc_transaction_pool_api::TransactionStatus;
|
||||
use sp_core::{storage::StorageKey, Pair};
|
||||
use sp_runtime::{
|
||||
generic::SignedBlock,
|
||||
traits::{
|
||||
AtLeast32Bit, Block as BlockT, Dispatchable, MaybeDisplay, MaybeSerialize, MaybeSerializeDeserialize, Member,
|
||||
},
|
||||
traits::{Block as BlockT, Dispatchable, Member},
|
||||
EncodedJustification,
|
||||
};
|
||||
use std::{fmt::Debug, time::Duration};
|
||||
@@ -37,30 +37,27 @@ pub trait Chain: ChainBase + Clone {
|
||||
/// How often blocks are produced on that chain. It's suggested to set this value
|
||||
/// to match the block time of the chain.
|
||||
const AVERAGE_BLOCK_INTERVAL: Duration;
|
||||
/// Maximal expected storage proof overhead (in bytes).
|
||||
const STORAGE_PROOF_OVERHEAD: u32;
|
||||
/// Maximal size (in bytes) of SCALE-encoded account id on this chain.
|
||||
const MAXIMAL_ENCODED_ACCOUNT_ID_SIZE: u32;
|
||||
|
||||
/// The user account identifier type for the runtime.
|
||||
type AccountId: Parameter + Member + MaybeSerializeDeserialize + Debug + MaybeDisplay + Ord + Default;
|
||||
/// Index of a transaction used by the chain.
|
||||
type Index: Parameter
|
||||
+ Member
|
||||
+ MaybeSerialize
|
||||
+ Debug
|
||||
+ Default
|
||||
+ MaybeDisplay
|
||||
+ DeserializeOwned
|
||||
+ AtLeast32Bit
|
||||
+ Copy;
|
||||
/// Block type.
|
||||
type SignedBlock: Member + Serialize + DeserializeOwned + BlockWithJustification<Self::Header>;
|
||||
/// The aggregated `Call` type.
|
||||
type Call: Dispatchable + Debug;
|
||||
/// Balance of an account in native tokens.
|
||||
///
|
||||
/// The chain may support multiple tokens, but this particular type is for token that is used
|
||||
/// to pay for transaction dispatch, to reward different relayers (headers, messages), etc.
|
||||
type Balance: Parameter + Member + DeserializeOwned + Clone + Copy + CheckedSub + PartialOrd + SaturatingAdd + Zero;
|
||||
type Call: Clone + Dispatchable + Debug;
|
||||
|
||||
/// Type that is used by the chain, to convert from weight to fee.
|
||||
type WeightToFee: WeightToFeePolynomial<Balance = Self::Balance>;
|
||||
}
|
||||
|
||||
/// Call type used by the chain.
|
||||
pub type CallOf<C> = <C as Chain>::Call;
|
||||
/// Weight-to-Fee type used by the chain.
|
||||
pub type WeightToFeeOf<C> = <C as Chain>::WeightToFee;
|
||||
/// Transaction status of the chain.
|
||||
pub type TransactionStatusOf<C> = TransactionStatus<HashOf<C>, HashOf<C>>;
|
||||
|
||||
/// Substrate-based chain with `frame_system::Config::AccountData` set to
|
||||
/// the `pallet_balances::AccountData<Balance>`.
|
||||
pub trait ChainWithBalances: Chain {
|
||||
@@ -68,14 +65,43 @@ pub trait ChainWithBalances: Chain {
|
||||
fn account_info_storage_key(account_id: &Self::AccountId) -> StorageKey;
|
||||
}
|
||||
|
||||
/// SCALE-encoded extrinsic.
|
||||
pub type EncodedExtrinsic = Vec<u8>;
|
||||
|
||||
/// Block with justification.
|
||||
pub trait BlockWithJustification<Header> {
|
||||
/// Return block header.
|
||||
fn header(&self) -> Header;
|
||||
/// Return encoded block extrinsics.
|
||||
fn extrinsics(&self) -> Vec<EncodedExtrinsic>;
|
||||
/// Return block justification, if known.
|
||||
fn justification(&self) -> Option<&EncodedJustification>;
|
||||
}
|
||||
|
||||
/// Transaction before it is signed.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct UnsignedTransaction<C: Chain> {
|
||||
/// Runtime call of this transaction.
|
||||
pub call: C::Call,
|
||||
/// Transaction nonce.
|
||||
pub nonce: C::Index,
|
||||
/// Tip included into transaction.
|
||||
pub tip: C::Balance,
|
||||
}
|
||||
|
||||
impl<C: Chain> UnsignedTransaction<C> {
|
||||
/// Create new unsigned transaction with given call, nonce and zero tip.
|
||||
pub fn new(call: C::Call, nonce: C::Index) -> Self {
|
||||
Self { call, nonce, tip: Zero::zero() }
|
||||
}
|
||||
|
||||
/// Set transaction tip.
|
||||
pub fn tip(mut self, tip: C::Balance) -> Self {
|
||||
self.tip = tip;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Substrate-based chain transactions signing scheme.
|
||||
pub trait TransactionSignScheme {
|
||||
/// Chain that this scheme is to be used.
|
||||
@@ -83,15 +109,26 @@ pub trait TransactionSignScheme {
|
||||
/// Type of key pairs used to sign transactions.
|
||||
type AccountKeyPair: Pair;
|
||||
/// Signed transaction.
|
||||
type SignedTransaction;
|
||||
type SignedTransaction: Clone + Debug + Codec + Send + 'static;
|
||||
|
||||
/// Create transaction for given runtime call, signed by given account.
|
||||
fn sign_transaction(
|
||||
genesis_hash: <Self::Chain as ChainBase>::Hash,
|
||||
signer: &Self::AccountKeyPair,
|
||||
signer_nonce: <Self::Chain as Chain>::Index,
|
||||
call: <Self::Chain as Chain>::Call,
|
||||
era: TransactionEraOf<Self::Chain>,
|
||||
unsigned: UnsignedTransaction<Self::Chain>,
|
||||
) -> Self::SignedTransaction;
|
||||
|
||||
/// Returns true if transaction is signed.
|
||||
fn is_signed(tx: &Self::SignedTransaction) -> bool;
|
||||
|
||||
/// Returns true if transaction is signed by given signer.
|
||||
fn is_signed_by(signer: &Self::AccountKeyPair, tx: &Self::SignedTransaction) -> bool;
|
||||
|
||||
/// Parse signed transaction into its unsigned part.
|
||||
///
|
||||
/// Returns `None` if signed transaction has unsupported format.
|
||||
fn parse_transaction(tx: Self::SignedTransaction) -> Option<UnsignedTransaction<Self::Chain>>;
|
||||
}
|
||||
|
||||
impl<Block: BlockT> BlockWithJustification<Block::Header> for SignedBlock<Block> {
|
||||
@@ -99,6 +136,10 @@ impl<Block: BlockT> BlockWithJustification<Block::Header> for SignedBlock<Block>
|
||||
self.block.header().clone()
|
||||
}
|
||||
|
||||
fn extrinsics(&self) -> Vec<EncodedExtrinsic> {
|
||||
self.block.extrinsics().iter().map(Encode::encode).collect()
|
||||
}
|
||||
|
||||
fn justification(&self) -> Option<&EncodedJustification> {
|
||||
self.justifications
|
||||
.as_ref()
|
||||
|
||||
@@ -16,27 +16,46 @@
|
||||
|
||||
//! Substrate node client.
|
||||
|
||||
use crate::chain::{Chain, ChainWithBalances};
|
||||
use crate::rpc::Substrate;
|
||||
use crate::{ConnectionParams, Error, Result};
|
||||
use crate::{
|
||||
chain::{Chain, ChainWithBalances, TransactionStatusOf},
|
||||
rpc::Substrate,
|
||||
ConnectionParams, Error, HashOf, HeaderIdOf, Result,
|
||||
};
|
||||
|
||||
use async_std::sync::{Arc, Mutex};
|
||||
use codec::Decode;
|
||||
use async_trait::async_trait;
|
||||
use codec::{Decode, Encode};
|
||||
use frame_system::AccountInfo;
|
||||
use jsonrpsee_ws_client::{traits::SubscriptionClient, v2::params::JsonRpcParams, DeserializeOwned};
|
||||
use jsonrpsee_ws_client::{Subscription, WsClient as RpcClient, WsClientBuilder as RpcClientBuilder};
|
||||
use num_traits::Zero;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use jsonrpsee_ws_client::{
|
||||
types::{
|
||||
self as jsonrpsee_types, traits::SubscriptionClient, v2::params::JsonRpcParams,
|
||||
DeserializeOwned,
|
||||
},
|
||||
WsClient as RpcClient, WsClientBuilder as RpcClientBuilder,
|
||||
};
|
||||
use num_traits::{Bounded, Zero};
|
||||
use pallet_balances::AccountData;
|
||||
use relay_utils::relay_loop::RECONNECT_DELAY;
|
||||
use sp_core::{storage::StorageKey, Bytes};
|
||||
use pallet_transaction_payment::InclusionFee;
|
||||
use relay_utils::{relay_loop::RECONNECT_DELAY, HeaderId};
|
||||
use sp_core::{
|
||||
storage::{StorageData, StorageKey},
|
||||
Bytes, Hasher,
|
||||
};
|
||||
use sp_runtime::{
|
||||
traits::Header as HeaderT,
|
||||
transaction_validity::{TransactionSource, TransactionValidity},
|
||||
};
|
||||
use sp_trie::StorageProof;
|
||||
use sp_version::RuntimeVersion;
|
||||
use std::{convert::TryFrom, future::Future};
|
||||
|
||||
const SUB_API_GRANDPA_AUTHORITIES: &str = "GrandpaApi_grandpa_authorities";
|
||||
const SUB_API_TXPOOL_VALIDATE_TRANSACTION: &str = "TaggedTransactionQueue_validate_transaction";
|
||||
const MAX_SUBSCRIPTION_CAPACITY: usize = 4096;
|
||||
|
||||
/// Opaque justifications subscription type.
|
||||
pub type JustificationsSubscription = Subscription<Bytes>;
|
||||
pub struct Subscription<T>(Mutex<futures::channel::mpsc::Receiver<Option<T>>>);
|
||||
|
||||
/// Opaque GRANDPA authorities set.
|
||||
pub type OpaqueGrandpaAuthoritiesSet = Vec<u8>;
|
||||
@@ -45,21 +64,37 @@ pub type OpaqueGrandpaAuthoritiesSet = Vec<u8>;
|
||||
///
|
||||
/// Cloning `Client` is a cheap operation.
|
||||
pub struct Client<C: Chain> {
|
||||
/// Tokio runtime handle.
|
||||
tokio: Arc<tokio::runtime::Runtime>,
|
||||
/// Client connection params.
|
||||
params: ConnectionParams,
|
||||
/// Substrate RPC client.
|
||||
client: Arc<RpcClient>,
|
||||
/// Genesis block hash.
|
||||
genesis_hash: C::Hash,
|
||||
/// If several tasks are submitting their transactions simultaneously using `submit_signed_extrinsic`
|
||||
/// method, they may get the same transaction nonce. So one of transactions will be rejected
|
||||
/// from the pool. This lock is here to prevent situations like that.
|
||||
genesis_hash: HashOf<C>,
|
||||
/// If several tasks are submitting their transactions simultaneously using
|
||||
/// `submit_signed_extrinsic` method, they may get the same transaction nonce. So one of
|
||||
/// transactions will be rejected from the pool. This lock is here to prevent situations like
|
||||
/// that.
|
||||
submit_signed_extrinsic_lock: Arc<Mutex<()>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C: Chain> relay_utils::relay_loop::Client for Client<C> {
|
||||
type Error = Error;
|
||||
|
||||
async fn reconnect(&mut self) -> Result<()> {
|
||||
let (tokio, client) = Self::build_client(self.params.clone()).await?;
|
||||
self.tokio = tokio;
|
||||
self.client = client;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Chain> Clone for Client<C> {
|
||||
fn clone(&self) -> Self {
|
||||
Client {
|
||||
tokio: self.tokio.clone(),
|
||||
params: self.params.clone(),
|
||||
client: self.client.clone(),
|
||||
genesis_hash: self.genesis_hash,
|
||||
@@ -70,9 +105,7 @@ impl<C: Chain> Clone for Client<C> {
|
||||
|
||||
impl<C: Chain> std::fmt::Debug for Client<C> {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
fmt.debug_struct("Client")
|
||||
.field("genesis_hash", &self.genesis_hash)
|
||||
.finish()
|
||||
fmt.debug_struct("Client").field("genesis_hash", &self.genesis_hash).finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,12 +134,18 @@ impl<C: Chain> Client<C> {
|
||||
/// Try to connect to Substrate node over websocket. Returns Substrate RPC client if connection
|
||||
/// has been established or error otherwise.
|
||||
pub async fn try_connect(params: ConnectionParams) -> Result<Self> {
|
||||
let client = Self::build_client(params.clone()).await?;
|
||||
let (tokio, client) = Self::build_client(params.clone()).await?;
|
||||
|
||||
let number: C::BlockNumber = Zero::zero();
|
||||
let genesis_hash = Substrate::<C>::chain_get_block_hash(&*client, number).await?;
|
||||
let genesis_hash_client = client.clone();
|
||||
let genesis_hash = tokio
|
||||
.spawn(async move {
|
||||
Substrate::<C>::chain_get_block_hash(&*genesis_hash_client, number).await
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(Self {
|
||||
tokio,
|
||||
params,
|
||||
client,
|
||||
genesis_hash,
|
||||
@@ -114,39 +153,43 @@ impl<C: Chain> Client<C> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Reopen client connection.
|
||||
pub async fn reconnect(&mut self) -> Result<()> {
|
||||
self.client = Self::build_client(self.params.clone()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Build client to use in connection.
|
||||
async fn build_client(params: ConnectionParams) -> Result<Arc<RpcClient>> {
|
||||
async fn build_client(
|
||||
params: ConnectionParams,
|
||||
) -> Result<(Arc<tokio::runtime::Runtime>, Arc<RpcClient>)> {
|
||||
let tokio = tokio::runtime::Runtime::new()?;
|
||||
let uri = format!(
|
||||
"{}://{}:{}",
|
||||
if params.secure { "wss" } else { "ws" },
|
||||
params.host,
|
||||
params.port,
|
||||
);
|
||||
let client = RpcClientBuilder::default()
|
||||
.max_notifs_per_subscription(MAX_SUBSCRIPTION_CAPACITY)
|
||||
.build(&uri)
|
||||
.await?;
|
||||
let client = tokio
|
||||
.spawn(async move {
|
||||
RpcClientBuilder::default()
|
||||
.max_notifs_per_subscription(MAX_SUBSCRIPTION_CAPACITY)
|
||||
.build(&uri)
|
||||
.await
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(Arc::new(client))
|
||||
Ok((Arc::new(tokio), Arc::new(client)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Chain> Client<C> {
|
||||
/// Returns true if client is connected to at least one peer and is in synced state.
|
||||
pub async fn ensure_synced(&self) -> Result<()> {
|
||||
let health = Substrate::<C>::system_health(&*self.client).await?;
|
||||
let is_synced = !health.is_syncing && (!health.should_have_peers || health.peers > 0);
|
||||
if is_synced {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::ClientNotSynced(health))
|
||||
}
|
||||
self.jsonrpsee_execute(|client| async move {
|
||||
let health = Substrate::<C>::system_health(&*client).await?;
|
||||
let is_synced = !health.is_syncing && (!health.should_have_peers || health.peers > 0);
|
||||
if is_synced {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::ClientNotSynced(health))
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Return hash of the genesis block.
|
||||
@@ -156,7 +199,15 @@ impl<C: Chain> Client<C> {
|
||||
|
||||
/// Return hash of the best finalized block.
|
||||
pub async fn best_finalized_header_hash(&self) -> Result<C::Hash> {
|
||||
Ok(Substrate::<C>::chain_get_finalized_head(&*self.client).await?)
|
||||
self.jsonrpsee_execute(|client| async move {
|
||||
Ok(Substrate::<C>::chain_get_finalized_head(&*client).await?)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Return number of the best finalized block.
|
||||
pub async fn best_finalized_header_number(&self) -> Result<C::BlockNumber> {
|
||||
Ok(*self.header_by_hash(self.best_finalized_header_hash().await?).await?.number())
|
||||
}
|
||||
|
||||
/// Returns the best Substrate header.
|
||||
@@ -164,12 +215,18 @@ impl<C: Chain> Client<C> {
|
||||
where
|
||||
C::Header: DeserializeOwned,
|
||||
{
|
||||
Ok(Substrate::<C>::chain_get_header(&*self.client, None).await?)
|
||||
self.jsonrpsee_execute(|client| async move {
|
||||
Ok(Substrate::<C>::chain_get_header(&*client, None).await?)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get a Substrate block from its hash.
|
||||
pub async fn get_block(&self, block_hash: Option<C::Hash>) -> Result<C::SignedBlock> {
|
||||
Ok(Substrate::<C>::chain_get_block(&*self.client, block_hash).await?)
|
||||
self.jsonrpsee_execute(move |client| async move {
|
||||
Ok(Substrate::<C>::chain_get_block(&*client, block_hash).await?)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get a Substrate header by its hash.
|
||||
@@ -177,12 +234,18 @@ impl<C: Chain> Client<C> {
|
||||
where
|
||||
C::Header: DeserializeOwned,
|
||||
{
|
||||
Ok(Substrate::<C>::chain_get_header(&*self.client, block_hash).await?)
|
||||
self.jsonrpsee_execute(move |client| async move {
|
||||
Ok(Substrate::<C>::chain_get_header(&*client, block_hash).await?)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get a Substrate block hash by its number.
|
||||
pub async fn block_hash_by_number(&self, number: C::BlockNumber) -> Result<C::Hash> {
|
||||
Ok(Substrate::<C>::chain_get_block_hash(&*self.client, number).await?)
|
||||
self.jsonrpsee_execute(move |client| async move {
|
||||
Ok(Substrate::<C>::chain_get_block_hash(&*client, number).await?)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get a Substrate header by its number.
|
||||
@@ -191,51 +254,84 @@ impl<C: Chain> Client<C> {
|
||||
C::Header: DeserializeOwned,
|
||||
{
|
||||
let block_hash = Self::block_hash_by_number(self, block_number).await?;
|
||||
Ok(Self::header_by_hash(self, block_hash).await?)
|
||||
let header_by_hash = Self::header_by_hash(self, block_hash).await?;
|
||||
Ok(header_by_hash)
|
||||
}
|
||||
|
||||
/// Return runtime version.
|
||||
pub async fn runtime_version(&self) -> Result<RuntimeVersion> {
|
||||
Ok(Substrate::<C>::state_runtime_version(&*self.client).await?)
|
||||
self.jsonrpsee_execute(move |client| async move {
|
||||
Ok(Substrate::<C>::state_runtime_version(&*client).await?)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Read value from runtime storage.
|
||||
pub async fn storage_value<T: Decode>(&self, storage_key: StorageKey) -> Result<Option<T>> {
|
||||
Substrate::<C>::state_get_storage(&*self.client, storage_key)
|
||||
pub async fn storage_value<T: Send + Decode + 'static>(
|
||||
&self,
|
||||
storage_key: StorageKey,
|
||||
block_hash: Option<C::Hash>,
|
||||
) -> Result<Option<T>> {
|
||||
self.raw_storage_value(storage_key, block_hash)
|
||||
.await?
|
||||
.map(|encoded_value| T::decode(&mut &encoded_value.0[..]).map_err(Error::ResponseParseFailed))
|
||||
.map(|encoded_value| {
|
||||
T::decode(&mut &encoded_value.0[..]).map_err(Error::ResponseParseFailed)
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
||||
/// Read raw value from runtime storage.
|
||||
pub async fn raw_storage_value(
|
||||
&self,
|
||||
storage_key: StorageKey,
|
||||
block_hash: Option<C::Hash>,
|
||||
) -> Result<Option<StorageData>> {
|
||||
self.jsonrpsee_execute(move |client| async move {
|
||||
Ok(Substrate::<C>::state_get_storage(&*client, storage_key, block_hash).await?)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Return native tokens balance of the account.
|
||||
pub async fn free_native_balance(&self, account: C::AccountId) -> Result<C::Balance>
|
||||
where
|
||||
C: ChainWithBalances,
|
||||
{
|
||||
let storage_key = C::account_info_storage_key(&account);
|
||||
let encoded_account_data = Substrate::<C>::state_get_storage(&*self.client, storage_key)
|
||||
.await?
|
||||
.ok_or(Error::AccountDoesNotExist)?;
|
||||
let decoded_account_data =
|
||||
AccountInfo::<C::Index, AccountData<C::Balance>>::decode(&mut &encoded_account_data.0[..])
|
||||
.map_err(Error::ResponseParseFailed)?;
|
||||
Ok(decoded_account_data.data.free)
|
||||
self.jsonrpsee_execute(move |client| async move {
|
||||
let storage_key = C::account_info_storage_key(&account);
|
||||
let encoded_account_data =
|
||||
Substrate::<C>::state_get_storage(&*client, storage_key, None)
|
||||
.await?
|
||||
.ok_or(Error::AccountDoesNotExist)?;
|
||||
let decoded_account_data = AccountInfo::<C::Index, AccountData<C::Balance>>::decode(
|
||||
&mut &encoded_account_data.0[..],
|
||||
)
|
||||
.map_err(Error::ResponseParseFailed)?;
|
||||
Ok(decoded_account_data.data.free)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get the nonce of the given Substrate account.
|
||||
///
|
||||
/// Note: It's the caller's responsibility to make sure `account` is a valid SS58 address.
|
||||
pub async fn next_account_index(&self, account: C::AccountId) -> Result<C::Index> {
|
||||
Ok(Substrate::<C>::system_account_next_index(&*self.client, account).await?)
|
||||
self.jsonrpsee_execute(move |client| async move {
|
||||
Ok(Substrate::<C>::system_account_next_index(&*client, account).await?)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Submit unsigned extrinsic for inclusion in a block.
|
||||
///
|
||||
/// Note: The given transaction needs to be SCALE encoded beforehand.
|
||||
pub async fn submit_unsigned_extrinsic(&self, transaction: Bytes) -> Result<C::Hash> {
|
||||
let tx_hash = Substrate::<C>::author_submit_extrinsic(&*self.client, transaction).await?;
|
||||
log::trace!(target: "bridge", "Sent transaction to Substrate node: {:?}", tx_hash);
|
||||
Ok(tx_hash)
|
||||
self.jsonrpsee_execute(move |client| async move {
|
||||
let tx_hash = Substrate::<C>::author_submit_extrinsic(&*client, transaction).await?;
|
||||
log::trace!(target: "bridge", "Sent transaction to Substrate node: {:?}", tx_hash);
|
||||
Ok(tx_hash)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Submit an extrinsic signed by given account.
|
||||
@@ -248,51 +344,242 @@ impl<C: Chain> Client<C> {
|
||||
pub async fn submit_signed_extrinsic(
|
||||
&self,
|
||||
extrinsic_signer: C::AccountId,
|
||||
prepare_extrinsic: impl FnOnce(C::Index) -> Bytes,
|
||||
prepare_extrinsic: impl FnOnce(HeaderIdOf<C>, C::Index) -> Bytes + Send + 'static,
|
||||
) -> Result<C::Hash> {
|
||||
let _guard = self.submit_signed_extrinsic_lock.lock().await;
|
||||
let transaction_nonce = self.next_account_index(extrinsic_signer).await?;
|
||||
let extrinsic = prepare_extrinsic(transaction_nonce);
|
||||
let tx_hash = Substrate::<C>::author_submit_extrinsic(&*self.client, extrinsic).await?;
|
||||
log::trace!(target: "bridge", "Sent transaction to {} node: {:?}", C::NAME, tx_hash);
|
||||
Ok(tx_hash)
|
||||
let best_header = self.best_header().await?;
|
||||
let best_header_id = HeaderId(*best_header.number(), best_header.hash());
|
||||
self.jsonrpsee_execute(move |client| async move {
|
||||
let extrinsic = prepare_extrinsic(best_header_id, transaction_nonce);
|
||||
let tx_hash = Substrate::<C>::author_submit_extrinsic(&*client, extrinsic).await?;
|
||||
log::trace!(target: "bridge", "Sent transaction to {} node: {:?}", C::NAME, tx_hash);
|
||||
Ok(tx_hash)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Does exactly the same as `submit_signed_extrinsic`, but keeps watching for extrinsic status
|
||||
/// after submission.
|
||||
pub async fn submit_and_watch_signed_extrinsic(
|
||||
&self,
|
||||
extrinsic_signer: C::AccountId,
|
||||
prepare_extrinsic: impl FnOnce(HeaderIdOf<C>, C::Index) -> Bytes + Send + 'static,
|
||||
) -> Result<Subscription<TransactionStatusOf<C>>> {
|
||||
let _guard = self.submit_signed_extrinsic_lock.lock().await;
|
||||
let transaction_nonce = self.next_account_index(extrinsic_signer).await?;
|
||||
let best_header = self.best_header().await?;
|
||||
let best_header_id = HeaderId(*best_header.number(), best_header.hash());
|
||||
let subscription = self
|
||||
.jsonrpsee_execute(move |client| async move {
|
||||
let extrinsic = prepare_extrinsic(best_header_id, transaction_nonce);
|
||||
let tx_hash = C::Hasher::hash(&extrinsic.0);
|
||||
let subscription = client
|
||||
.subscribe(
|
||||
"author_submitAndWatchExtrinsic",
|
||||
JsonRpcParams::Array(vec![jsonrpsee_types::to_json_value(extrinsic)
|
||||
.map_err(|e| Error::RpcError(e.into()))?]),
|
||||
"author_unwatchExtrinsic",
|
||||
)
|
||||
.await?;
|
||||
log::trace!(target: "bridge", "Sent transaction to {} node: {:?}", C::NAME, tx_hash);
|
||||
Ok(subscription)
|
||||
})
|
||||
.await?;
|
||||
let (sender, receiver) = futures::channel::mpsc::channel(MAX_SUBSCRIPTION_CAPACITY);
|
||||
self.tokio.spawn(Subscription::background_worker(
|
||||
C::NAME.into(),
|
||||
"extrinsic".into(),
|
||||
subscription,
|
||||
sender,
|
||||
));
|
||||
Ok(Subscription(Mutex::new(receiver)))
|
||||
}
|
||||
|
||||
/// Returns pending extrinsics from transaction pool.
|
||||
pub async fn pending_extrinsics(&self) -> Result<Vec<Bytes>> {
|
||||
self.jsonrpsee_execute(move |client| async move {
|
||||
Ok(Substrate::<C>::author_pending_extrinsics(&*client).await?)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Validate transaction at given block state.
|
||||
pub async fn validate_transaction<SignedTransaction: Encode + Send + 'static>(
|
||||
&self,
|
||||
at_block: C::Hash,
|
||||
transaction: SignedTransaction,
|
||||
) -> Result<TransactionValidity> {
|
||||
self.jsonrpsee_execute(move |client| async move {
|
||||
let call = SUB_API_TXPOOL_VALIDATE_TRANSACTION.to_string();
|
||||
let data = Bytes((TransactionSource::External, transaction, at_block).encode());
|
||||
|
||||
let encoded_response =
|
||||
Substrate::<C>::state_call(&*client, call, data, Some(at_block)).await?;
|
||||
let validity = TransactionValidity::decode(&mut &encoded_response.0[..])
|
||||
.map_err(Error::ResponseParseFailed)?;
|
||||
|
||||
Ok(validity)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Estimate fee that will be spent on given extrinsic.
|
||||
pub async fn estimate_extrinsic_fee(
|
||||
&self,
|
||||
transaction: Bytes,
|
||||
) -> Result<InclusionFee<C::Balance>> {
|
||||
self.jsonrpsee_execute(move |client| async move {
|
||||
let fee_details =
|
||||
Substrate::<C>::payment_query_fee_details(&*client, transaction, None).await?;
|
||||
let inclusion_fee = fee_details
|
||||
.inclusion_fee
|
||||
.map(|inclusion_fee| InclusionFee {
|
||||
base_fee: C::Balance::try_from(inclusion_fee.base_fee.into_u256())
|
||||
.unwrap_or_else(|_| C::Balance::max_value()),
|
||||
len_fee: C::Balance::try_from(inclusion_fee.len_fee.into_u256())
|
||||
.unwrap_or_else(|_| C::Balance::max_value()),
|
||||
adjusted_weight_fee: C::Balance::try_from(
|
||||
inclusion_fee.adjusted_weight_fee.into_u256(),
|
||||
)
|
||||
.unwrap_or_else(|_| C::Balance::max_value()),
|
||||
})
|
||||
.unwrap_or_else(|| InclusionFee {
|
||||
base_fee: Zero::zero(),
|
||||
len_fee: Zero::zero(),
|
||||
adjusted_weight_fee: Zero::zero(),
|
||||
});
|
||||
Ok(inclusion_fee)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get the GRANDPA authority set at given block.
|
||||
pub async fn grandpa_authorities_set(&self, block: C::Hash) -> Result<OpaqueGrandpaAuthoritiesSet> {
|
||||
let call = SUB_API_GRANDPA_AUTHORITIES.to_string();
|
||||
let data = Bytes(Vec::new());
|
||||
pub async fn grandpa_authorities_set(
|
||||
&self,
|
||||
block: C::Hash,
|
||||
) -> Result<OpaqueGrandpaAuthoritiesSet> {
|
||||
self.jsonrpsee_execute(move |client| async move {
|
||||
let call = SUB_API_GRANDPA_AUTHORITIES.to_string();
|
||||
let data = Bytes(Vec::new());
|
||||
|
||||
let encoded_response = Substrate::<C>::state_call(&*self.client, call, data, Some(block)).await?;
|
||||
let authority_list = encoded_response.0;
|
||||
let encoded_response =
|
||||
Substrate::<C>::state_call(&*client, call, data, Some(block)).await?;
|
||||
let authority_list = encoded_response.0;
|
||||
|
||||
Ok(authority_list)
|
||||
Ok(authority_list)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Execute runtime call at given block.
|
||||
pub async fn state_call(&self, method: String, data: Bytes, at_block: Option<C::Hash>) -> Result<Bytes> {
|
||||
Substrate::<C>::state_call(&*self.client, method, data, at_block)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
pub async fn state_call(
|
||||
&self,
|
||||
method: String,
|
||||
data: Bytes,
|
||||
at_block: Option<C::Hash>,
|
||||
) -> Result<Bytes> {
|
||||
self.jsonrpsee_execute(move |client| async move {
|
||||
Substrate::<C>::state_call(&*client, method, data, at_block)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Returns storage proof of given storage keys.
|
||||
pub async fn prove_storage(&self, keys: Vec<StorageKey>, at_block: C::Hash) -> Result<StorageProof> {
|
||||
Substrate::<C>::state_prove_storage(&*self.client, keys, Some(at_block))
|
||||
.await
|
||||
.map(|proof| StorageProof::new(proof.proof.into_iter().map(|b| b.0).collect()))
|
||||
.map_err(Into::into)
|
||||
pub async fn prove_storage(
|
||||
&self,
|
||||
keys: Vec<StorageKey>,
|
||||
at_block: C::Hash,
|
||||
) -> Result<StorageProof> {
|
||||
self.jsonrpsee_execute(move |client| async move {
|
||||
Substrate::<C>::state_prove_storage(&*client, keys, Some(at_block))
|
||||
.await
|
||||
.map(|proof| StorageProof::new(proof.proof.into_iter().map(|b| b.0).collect()))
|
||||
.map_err(Into::into)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Return new justifications stream.
|
||||
pub async fn subscribe_justifications(&self) -> Result<JustificationsSubscription> {
|
||||
Ok(self
|
||||
.client
|
||||
.subscribe(
|
||||
"grandpa_subscribeJustifications",
|
||||
JsonRpcParams::NoParams,
|
||||
"grandpa_unsubscribeJustifications",
|
||||
)
|
||||
.await?)
|
||||
pub async fn subscribe_justifications(&self) -> Result<Subscription<Bytes>> {
|
||||
let subscription = self
|
||||
.jsonrpsee_execute(move |client| async move {
|
||||
Ok(client
|
||||
.subscribe(
|
||||
"grandpa_subscribeJustifications",
|
||||
JsonRpcParams::NoParams,
|
||||
"grandpa_unsubscribeJustifications",
|
||||
)
|
||||
.await?)
|
||||
})
|
||||
.await?;
|
||||
let (sender, receiver) = futures::channel::mpsc::channel(MAX_SUBSCRIPTION_CAPACITY);
|
||||
self.tokio.spawn(Subscription::background_worker(
|
||||
C::NAME.into(),
|
||||
"justification".into(),
|
||||
subscription,
|
||||
sender,
|
||||
));
|
||||
Ok(Subscription(Mutex::new(receiver)))
|
||||
}
|
||||
|
||||
/// 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?
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: DeserializeOwned> Subscription<T> {
|
||||
/// Return next item from the subscription.
|
||||
pub async fn next(&self) -> Result<Option<T>> {
|
||||
let mut receiver = self.0.lock().await;
|
||||
let item = receiver.next().await;
|
||||
Ok(item.unwrap_or(None))
|
||||
}
|
||||
|
||||
/// Background worker that is executed in tokio context as `jsonrpsee` requires.
|
||||
async fn background_worker(
|
||||
chain_name: String,
|
||||
item_type: String,
|
||||
mut subscription: jsonrpsee_types::Subscription<T>,
|
||||
mut sender: futures::channel::mpsc::Sender<Option<T>>,
|
||||
) {
|
||||
loop {
|
||||
match subscription.next().await {
|
||||
Ok(Some(item)) =>
|
||||
if sender.send(Some(item)).await.is_err() {
|
||||
break
|
||||
},
|
||||
Ok(None) => {
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"{} {} subscription stream has returned None. Stream needs to be restarted.",
|
||||
chain_name,
|
||||
item_type,
|
||||
);
|
||||
let _ = sender.send(None).await;
|
||||
break
|
||||
},
|
||||
Err(e) => {
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"{} {} subscription stream has returned '{:?}'. Stream needs to be restarted.",
|
||||
chain_name,
|
||||
item_type,
|
||||
e,
|
||||
);
|
||||
let _ = sender.send(None).await;
|
||||
break
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,54 +16,55 @@
|
||||
|
||||
//! Substrate node RPC errors.
|
||||
|
||||
use jsonrpsee_ws_client::Error as RpcError;
|
||||
use jsonrpsee_ws_client::types::Error as RpcError;
|
||||
use relay_utils::MaybeConnectionError;
|
||||
use sc_rpc_api::system::Health;
|
||||
use sp_runtime::transaction_validity::TransactionValidityError;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Result type used by Substrate client.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Errors that can occur only when interacting with
|
||||
/// a Substrate node through RPC.
|
||||
#[derive(Debug)]
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
/// IO error.
|
||||
#[error("IO error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
/// An error that can occur when making a request to
|
||||
/// an JSON-RPC server.
|
||||
RpcError(RpcError),
|
||||
#[error("RPC error: {0}")]
|
||||
RpcError(#[from] RpcError),
|
||||
/// The response from the server could not be SCALE decoded.
|
||||
ResponseParseFailed(codec::Error),
|
||||
#[error("Response parse failed: {0}")]
|
||||
ResponseParseFailed(#[from] codec::Error),
|
||||
/// The Substrate bridge pallet has not yet been initialized.
|
||||
#[error("The Substrate bridge pallet has not been initialized yet.")]
|
||||
UninitializedBridgePallet,
|
||||
/// Account does not exist on the chain.
|
||||
#[error("Account does not exist on the chain.")]
|
||||
AccountDoesNotExist,
|
||||
/// Runtime storage is missing mandatory ":code:" entry.
|
||||
#[error("Mandatory :code: entry is missing from runtime storage.")]
|
||||
MissingMandatoryCodeEntry,
|
||||
/// The client we're connected to is not synced, so we can't rely on its state.
|
||||
#[error("Substrate client is not synced {0}.")]
|
||||
ClientNotSynced(Health),
|
||||
/// An error has happened when we have tried to parse storage proof.
|
||||
#[error("Error when parsing storage proof: {0:?}.")]
|
||||
StorageProofError(bp_runtime::StorageProofError),
|
||||
/// The Substrate transaction is invalid.
|
||||
#[error("Substrate transaction is invalid: {0:?}")]
|
||||
TransactionInvalid(#[from] TransactionValidityError),
|
||||
/// Custom logic error.
|
||||
#[error("{0}")]
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
Self::RpcError(ref e) => Some(e),
|
||||
Self::ResponseParseFailed(ref e) => Some(e),
|
||||
Self::UninitializedBridgePallet => None,
|
||||
Self::AccountDoesNotExist => None,
|
||||
Self::MissingMandatoryCodeEntry => None,
|
||||
Self::ClientNotSynced(_) => None,
|
||||
Self::StorageProofError(_) => None,
|
||||
Self::Custom(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RpcError> for Error {
|
||||
fn from(error: RpcError) -> Self {
|
||||
Error::RpcError(error)
|
||||
impl From<tokio::task::JoinError> for Error {
|
||||
fn from(error: tokio::task::JoinError) -> Self {
|
||||
Error::Custom(format!("Failed to wait tokio task: {}", error))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +72,7 @@ impl MaybeConnectionError for Error {
|
||||
fn is_connection_error(&self) -> bool {
|
||||
matches!(
|
||||
*self,
|
||||
Error::RpcError(RpcError::TransportError(_))
|
||||
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(_))
|
||||
@@ -80,26 +81,3 @@ impl MaybeConnectionError for Error {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
let s = match self {
|
||||
Self::RpcError(e) => e.to_string(),
|
||||
Self::ResponseParseFailed(e) => e.to_string(),
|
||||
Self::UninitializedBridgePallet => "The Substrate bridge pallet has not been initialized yet.".into(),
|
||||
Self::AccountDoesNotExist => "Account does not exist on the chain".into(),
|
||||
Self::MissingMandatoryCodeEntry => "Mandatory :code: entry is missing from runtime storage".into(),
|
||||
Self::StorageProofError(e) => format!("Error when parsing storage proof: {:?}", e),
|
||||
Self::ClientNotSynced(health) => format!("Substrate client is not synced: {}", health),
|
||||
Self::Custom(e) => e.clone(),
|
||||
};
|
||||
|
||||
write!(f, "{}", s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for String {
|
||||
fn from(error: Error) -> String {
|
||||
error.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,10 +16,12 @@
|
||||
|
||||
//! Default generic implementation of finality source for basic Substrate client.
|
||||
|
||||
use crate::chain::{BlockWithJustification, Chain};
|
||||
use crate::client::Client;
|
||||
use crate::error::Error;
|
||||
use crate::sync_header::SyncHeader;
|
||||
use crate::{
|
||||
chain::{BlockWithJustification, Chain},
|
||||
client::Client,
|
||||
error::Error,
|
||||
sync_header::SyncHeader,
|
||||
};
|
||||
|
||||
use async_std::sync::{Arc, Mutex};
|
||||
use async_trait::async_trait;
|
||||
@@ -43,12 +45,11 @@ pub struct FinalitySource<C: Chain, P> {
|
||||
|
||||
impl<C: Chain, P> FinalitySource<C, P> {
|
||||
/// Create new headers source using given client.
|
||||
pub fn new(client: Client<C>, maximal_header_number: Option<RequiredHeaderNumberRef<C>>) -> Self {
|
||||
FinalitySource {
|
||||
client,
|
||||
maximal_header_number,
|
||||
_phantom: Default::default(),
|
||||
}
|
||||
pub fn new(
|
||||
client: Client<C>,
|
||||
maximal_header_number: Option<RequiredHeaderNumberRef<C>>,
|
||||
) -> Self {
|
||||
FinalitySource { client, maximal_header_number, _phantom: Default::default() }
|
||||
}
|
||||
|
||||
/// Returns reference to the underlying RPC client.
|
||||
@@ -122,7 +123,9 @@ where
|
||||
|
||||
let justification = signed_block
|
||||
.justification()
|
||||
.map(|raw_justification| GrandpaJustification::<C::Header>::decode(&mut raw_justification.as_slice()))
|
||||
.map(|raw_justification| {
|
||||
GrandpaJustification::<C::Header>::decode(&mut raw_justification.as_slice())
|
||||
})
|
||||
.transpose()
|
||||
.map_err(Error::ResponseParseFailed)?;
|
||||
|
||||
@@ -132,27 +135,35 @@ where
|
||||
async fn finality_proofs(&self) -> Result<Self::FinalityProofsStream, Error> {
|
||||
Ok(unfold(
|
||||
self.client.clone().subscribe_justifications().await?,
|
||||
move |mut subscription| async move {
|
||||
move |subscription| async move {
|
||||
loop {
|
||||
let next_justification = subscription.next().await?;
|
||||
let log_error = |err| {
|
||||
log::error!(
|
||||
target: "bridge",
|
||||
"Failed to read justification target from the {} justifications stream: {:?}",
|
||||
P::SOURCE_NAME,
|
||||
err,
|
||||
);
|
||||
};
|
||||
|
||||
let next_justification = subscription
|
||||
.next()
|
||||
.await
|
||||
.map_err(|err| log_error(err.to_string()))
|
||||
.ok()??;
|
||||
|
||||
let decoded_justification =
|
||||
GrandpaJustification::<C::Header>::decode(&mut &next_justification.0[..]);
|
||||
GrandpaJustification::<C::Header>::decode(&mut &next_justification[..]);
|
||||
|
||||
let justification = match decoded_justification {
|
||||
Ok(j) => j,
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
target: "bridge",
|
||||
"Failed to decode justification target from the {} justifications stream: {:?}",
|
||||
P::SOURCE_NAME,
|
||||
err,
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
log_error(format!("decode failed with error {:?}", err));
|
||||
continue
|
||||
},
|
||||
};
|
||||
|
||||
return Some((justification, subscription));
|
||||
return Some((justification, subscription))
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -17,32 +17,41 @@
|
||||
//! Pallet provides a set of guard functions that are running in background threads
|
||||
//! and are aborting process if some condition fails.
|
||||
|
||||
use crate::{Chain, ChainWithBalances, Client};
|
||||
use crate::{error::Error, Chain, ChainWithBalances, Client};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use num_traits::CheckedSub;
|
||||
use sp_version::RuntimeVersion;
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
fmt::Display,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
/// Guards environment.
|
||||
#[async_trait]
|
||||
pub trait Environment<C: ChainWithBalances>: Send + Sync + 'static {
|
||||
/// Error type.
|
||||
type Error: Display + Send + Sync + 'static;
|
||||
|
||||
/// Return current runtime version.
|
||||
async fn runtime_version(&mut self) -> Result<RuntimeVersion, String>;
|
||||
async fn runtime_version(&mut self) -> Result<RuntimeVersion, Self::Error>;
|
||||
/// Return free native balance of the account on the chain.
|
||||
async fn free_native_balance(&mut self, account: C::AccountId) -> Result<C::Balance, String>;
|
||||
async fn free_native_balance(
|
||||
&mut self,
|
||||
account: C::AccountId,
|
||||
) -> Result<C::Balance, Self::Error>;
|
||||
|
||||
/// Return current time.
|
||||
fn now(&self) -> Instant {
|
||||
Instant::now()
|
||||
}
|
||||
|
||||
/// Sleep given amount of time.
|
||||
async fn sleep(&mut self, duration: Duration) {
|
||||
async_std::task::sleep(duration).await
|
||||
}
|
||||
|
||||
/// Abort current process. Called when guard condition check fails.
|
||||
async fn abort(&mut self) {
|
||||
std::process::abort();
|
||||
@@ -50,7 +59,10 @@ pub trait Environment<C: ChainWithBalances>: Send + Sync + 'static {
|
||||
}
|
||||
|
||||
/// Abort when runtime spec version is different from specified.
|
||||
pub fn abort_on_spec_version_change<C: ChainWithBalances>(mut env: impl Environment<C>, expected_spec_version: u32) {
|
||||
pub fn abort_on_spec_version_change<C: ChainWithBalances>(
|
||||
mut env: impl Environment<C>,
|
||||
expected_spec_version: u32,
|
||||
) {
|
||||
async_std::task::spawn(async move {
|
||||
loop {
|
||||
let actual_spec_version = env.runtime_version().await;
|
||||
@@ -66,10 +78,10 @@ pub fn abort_on_spec_version_change<C: ChainWithBalances>(mut env: impl Environm
|
||||
);
|
||||
|
||||
env.abort().await;
|
||||
}
|
||||
},
|
||||
Err(error) => log::warn!(
|
||||
target: "bridge-guard",
|
||||
"Failed to read {} runtime version: {:?}. Relay may need to be stopped manually",
|
||||
"Failed to read {} runtime version: {}. Relay may need to be stopped manually",
|
||||
C::NAME,
|
||||
error,
|
||||
),
|
||||
@@ -80,8 +92,9 @@ pub fn abort_on_spec_version_change<C: ChainWithBalances>(mut env: impl Environm
|
||||
});
|
||||
}
|
||||
|
||||
/// Abort if, during a 24 hours, free balance of given account is decreased at least by given value.
|
||||
/// Other components may increase (or decrease) balance of account and it WILL affect logic of the guard.
|
||||
/// Abort if, during 24 hours, free balance of given account is decreased at least by given value.
|
||||
/// Other components may increase (or decrease) balance of account and it WILL affect logic of the
|
||||
/// guard.
|
||||
pub fn abort_when_account_balance_decreased<C: ChainWithBalances>(
|
||||
mut env: impl Environment<C>,
|
||||
account_id: C::AccountId,
|
||||
@@ -127,16 +140,16 @@ pub fn abort_when_account_balance_decreased<C: ChainWithBalances>(
|
||||
|
||||
env.abort().await;
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
log::warn!(
|
||||
target: "bridge-guard",
|
||||
"Failed to read {} account {:?} balance: {:?}. Relay may need to be stopped manually",
|
||||
"Failed to read {} account {:?} balance: {}. Relay may need to be stopped manually",
|
||||
C::NAME,
|
||||
account_id,
|
||||
error,
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
env.sleep(conditions_check_delay::<C>()).await;
|
||||
@@ -151,20 +164,24 @@ fn conditions_check_delay<C: Chain>() -> Duration {
|
||||
|
||||
#[async_trait]
|
||||
impl<C: ChainWithBalances> Environment<C> for Client<C> {
|
||||
async fn runtime_version(&mut self) -> Result<RuntimeVersion, String> {
|
||||
Client::<C>::runtime_version(self).await.map_err(|e| e.to_string())
|
||||
type Error = Error;
|
||||
|
||||
async fn runtime_version(&mut self) -> Result<RuntimeVersion, Self::Error> {
|
||||
Client::<C>::runtime_version(self).await
|
||||
}
|
||||
|
||||
async fn free_native_balance(&mut self, account: C::AccountId) -> Result<C::Balance, String> {
|
||||
Client::<C>::free_native_balance(self, account)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
async fn free_native_balance(
|
||||
&mut self,
|
||||
account: C::AccountId,
|
||||
) -> Result<C::Balance, Self::Error> {
|
||||
Client::<C>::free_native_balance(self, account).await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use frame_support::weights::IdentityFee;
|
||||
use futures::{
|
||||
channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender},
|
||||
future::FutureExt,
|
||||
@@ -180,18 +197,24 @@ mod tests {
|
||||
type Hash = sp_core::H256;
|
||||
type Hasher = sp_runtime::traits::BlakeTwo256;
|
||||
type Header = sp_runtime::generic::Header<u32, sp_runtime::traits::BlakeTwo256>;
|
||||
|
||||
type AccountId = u32;
|
||||
type Balance = u32;
|
||||
type Index = u32;
|
||||
type Signature = sp_runtime::testing::TestSignature;
|
||||
}
|
||||
|
||||
impl Chain for TestChain {
|
||||
const NAME: &'static str = "Test";
|
||||
const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_millis(1);
|
||||
const STORAGE_PROOF_OVERHEAD: u32 = 0;
|
||||
const MAXIMAL_ENCODED_ACCOUNT_ID_SIZE: u32 = 0;
|
||||
|
||||
type AccountId = u32;
|
||||
type Index = u32;
|
||||
type SignedBlock =
|
||||
sp_runtime::generic::SignedBlock<sp_runtime::generic::Block<Self::Header, sp_runtime::OpaqueExtrinsic>>;
|
||||
type SignedBlock = sp_runtime::generic::SignedBlock<
|
||||
sp_runtime::generic::Block<Self::Header, sp_runtime::OpaqueExtrinsic>,
|
||||
>;
|
||||
type Call = ();
|
||||
type Balance = u32;
|
||||
type WeightToFee = IdentityFee<u32>;
|
||||
}
|
||||
|
||||
impl ChainWithBalances for TestChain {
|
||||
@@ -209,11 +232,13 @@ mod tests {
|
||||
|
||||
#[async_trait]
|
||||
impl Environment<TestChain> for TestEnvironment {
|
||||
async fn runtime_version(&mut self) -> Result<RuntimeVersion, String> {
|
||||
type Error = Error;
|
||||
|
||||
async fn runtime_version(&mut self) -> Result<RuntimeVersion, Self::Error> {
|
||||
Ok(self.runtime_version_rx.next().await.unwrap_or_default())
|
||||
}
|
||||
|
||||
async fn free_native_balance(&mut self, _account: u32) -> Result<u32, String> {
|
||||
async fn free_native_balance(&mut self, _account: u32) -> Result<u32, Self::Error> {
|
||||
Ok(self.free_native_balance_rx.next().await.unwrap_or_default())
|
||||
}
|
||||
|
||||
@@ -249,10 +274,7 @@ mod tests {
|
||||
|
||||
// client responds with wrong version
|
||||
runtime_version_tx
|
||||
.send(RuntimeVersion {
|
||||
spec_version: 42,
|
||||
..Default::default()
|
||||
})
|
||||
.send(RuntimeVersion { spec_version: 42, ..Default::default() })
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -284,10 +306,7 @@ mod tests {
|
||||
|
||||
// client responds with the same version
|
||||
runtime_version_tx
|
||||
.send(RuntimeVersion {
|
||||
spec_version: 42,
|
||||
..Default::default()
|
||||
})
|
||||
.send(RuntimeVersion { spec_version: 42, ..Default::default() })
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -16,9 +16,11 @@
|
||||
|
||||
//! Default generic implementation of headers source for basic Substrate client.
|
||||
|
||||
use crate::chain::{BlockWithJustification, Chain};
|
||||
use crate::client::Client;
|
||||
use crate::error::Error;
|
||||
use crate::{
|
||||
chain::{BlockWithJustification, Chain},
|
||||
client::Client,
|
||||
error::Error,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use headers_relay::{
|
||||
@@ -38,19 +40,13 @@ pub struct HeadersSource<C: Chain, 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(),
|
||||
}
|
||||
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(),
|
||||
}
|
||||
HeadersSource { client: self.client.clone(), _phantom: Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +65,12 @@ 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: 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> {
|
||||
@@ -79,22 +80,17 @@ where
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
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> {
|
||||
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();
|
||||
@@ -102,7 +98,11 @@ where
|
||||
Ok((id, grandpa_justification))
|
||||
}
|
||||
|
||||
async fn header_extra(&self, id: HeaderIdOf<P>, _header: QueuedHeader<P>) -> Result<(HeaderIdOf<P>, ()), Error> {
|
||||
async fn header_extra(
|
||||
&self,
|
||||
id: HeaderIdOf<P>,
|
||||
_header: QueuedHeader<P>,
|
||||
) -> Result<(HeaderIdOf<P>, ()), Error> {
|
||||
Ok((id, ()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,11 +29,21 @@ pub mod guard;
|
||||
pub mod headers_source;
|
||||
pub mod metrics;
|
||||
|
||||
pub use crate::chain::{BlockWithJustification, Chain, ChainWithBalances, TransactionSignScheme};
|
||||
pub use crate::client::{Client, JustificationsSubscription, OpaqueGrandpaAuthoritiesSet};
|
||||
pub use crate::error::{Error, Result};
|
||||
pub use crate::sync_header::SyncHeader;
|
||||
pub use bp_runtime::{BlockNumberOf, Chain as ChainBase, HashOf, HeaderOf};
|
||||
use std::time::Duration;
|
||||
|
||||
pub use crate::{
|
||||
chain::{
|
||||
BlockWithJustification, CallOf, Chain, ChainWithBalances, TransactionSignScheme,
|
||||
TransactionStatusOf, UnsignedTransaction, WeightToFeeOf,
|
||||
},
|
||||
client::{Client, OpaqueGrandpaAuthoritiesSet, Subscription},
|
||||
error::{Error, Result},
|
||||
sync_header::SyncHeader,
|
||||
};
|
||||
pub use bp_runtime::{
|
||||
AccountIdOf, AccountPublicOf, BalanceOf, BlockNumberOf, Chain as ChainBase, HashOf, HeaderOf,
|
||||
IndexOf, SignatureOf, TransactionEra, TransactionEraOf,
|
||||
};
|
||||
|
||||
/// Header id used by the chain.
|
||||
pub type HeaderIdOf<C> = relay_utils::HeaderId<HashOf<C>, BlockNumberOf<C>>;
|
||||
@@ -41,7 +51,7 @@ pub type HeaderIdOf<C> = relay_utils::HeaderId<HashOf<C>, BlockNumberOf<C>>;
|
||||
/// Substrate-over-websocket connection params.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConnectionParams {
|
||||
/// Websocket server hostname.
|
||||
/// Websocket server host name.
|
||||
pub host: String,
|
||||
/// Websocket server TCP port.
|
||||
pub port: u16,
|
||||
@@ -51,10 +61,48 @@ pub struct ConnectionParams {
|
||||
|
||||
impl Default for ConnectionParams {
|
||||
fn default() -> Self {
|
||||
ConnectionParams {
|
||||
host: "localhost".into(),
|
||||
port: 9944,
|
||||
secure: false,
|
||||
}
|
||||
ConnectionParams { host: "localhost".into(), port: 9944, secure: false }
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns stall timeout for relay loop.
|
||||
///
|
||||
/// Relay considers himself stalled if he has submitted transaction to the node, but it has not
|
||||
/// been mined for this period.
|
||||
pub fn transaction_stall_timeout(
|
||||
mortality_period: Option<u32>,
|
||||
average_block_interval: Duration,
|
||||
default_stall_timeout: Duration,
|
||||
) -> Duration {
|
||||
// 1 extra block for transaction to reach the pool && 1 for relayer to awake after it is mined
|
||||
mortality_period
|
||||
.map(|mortality_period| average_block_interval.saturating_mul(mortality_period + 1 + 1))
|
||||
.unwrap_or(default_stall_timeout)
|
||||
}
|
||||
|
||||
/// Returns stall timeout for relay loop that submit transactions to two chains.
|
||||
///
|
||||
/// Bidirectional relay may have two active transactions. Even if one of them has been spoiled, we
|
||||
/// can't just restart the loop - the other transaction may still be alive and we'll be submitting
|
||||
/// duplicate transaction, which may result in funds loss. So we'll be selecting maximal mortality
|
||||
/// for choosing loop stall timeout.
|
||||
pub fn bidirectional_transaction_stall_timeout(
|
||||
left_mortality_period: Option<u32>,
|
||||
right_mortality_period: Option<u32>,
|
||||
left_average_block_interval: Duration,
|
||||
right_average_block_interval: Duration,
|
||||
default_stall_timeout: Duration,
|
||||
) -> Duration {
|
||||
std::cmp::max(
|
||||
transaction_stall_timeout(
|
||||
left_mortality_period,
|
||||
left_average_block_interval,
|
||||
default_stall_timeout,
|
||||
),
|
||||
transaction_stall_timeout(
|
||||
right_mortality_period,
|
||||
right_average_block_interval,
|
||||
default_stall_timeout,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -14,12 +14,14 @@
|
||||
// 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::chain::Chain;
|
||||
use crate::client::Client;
|
||||
use crate::{chain::Chain, client::Client};
|
||||
|
||||
use async_std::sync::{Arc, RwLock};
|
||||
use async_trait::async_trait;
|
||||
use codec::Decode;
|
||||
use relay_utils::metrics::{metric_name, register, Gauge, PrometheusError, Registry, StandaloneMetrics, F64};
|
||||
use relay_utils::metrics::{
|
||||
metric_name, register, F64SharedRef, Gauge, PrometheusError, Registry, StandaloneMetrics, F64,
|
||||
};
|
||||
use sp_core::storage::StorageKey;
|
||||
use sp_runtime::{traits::UniqueSaturatedInto, FixedPointNumber};
|
||||
use std::time::Duration;
|
||||
@@ -34,6 +36,7 @@ pub struct FloatStorageValueMetric<C: Chain, T: Clone> {
|
||||
storage_key: StorageKey,
|
||||
maybe_default_value: Option<T>,
|
||||
metric: Gauge<F64>,
|
||||
shared_value_ref: F64SharedRef,
|
||||
}
|
||||
|
||||
impl<C: Chain, T: Decode + FixedPointNumber> FloatStorageValueMetric<C, T> {
|
||||
@@ -47,13 +50,20 @@ impl<C: Chain, T: Decode + FixedPointNumber> FloatStorageValueMetric<C, T> {
|
||||
name: String,
|
||||
help: String,
|
||||
) -> Result<Self, PrometheusError> {
|
||||
let shared_value_ref = Arc::new(RwLock::new(None));
|
||||
Ok(FloatStorageValueMetric {
|
||||
client,
|
||||
storage_key,
|
||||
maybe_default_value,
|
||||
metric: register(Gauge::new(metric_name(prefix, &name), help)?, registry)?,
|
||||
shared_value_ref,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get shared reference to metric value.
|
||||
pub fn shared_value_ref(&self) -> F64SharedRef {
|
||||
self.shared_value_ref.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -66,17 +76,18 @@ where
|
||||
}
|
||||
|
||||
async fn update(&self) {
|
||||
relay_utils::metrics::set_gauge_value(
|
||||
&self.metric,
|
||||
self.client
|
||||
.storage_value::<T>(self.storage_key.clone())
|
||||
.await
|
||||
.map(|maybe_storage_value| {
|
||||
maybe_storage_value.or(self.maybe_default_value).map(|storage_value| {
|
||||
storage_value.into_inner().unique_saturated_into() as f64
|
||||
/ T::DIV.unique_saturated_into() as f64
|
||||
})
|
||||
}),
|
||||
);
|
||||
let value = self
|
||||
.client
|
||||
.storage_value::<T>(self.storage_key.clone(), None)
|
||||
.await
|
||||
.map(|maybe_storage_value| {
|
||||
maybe_storage_value.or(self.maybe_default_value).map(|storage_value| {
|
||||
storage_value.into_inner().unique_saturated_into() as f64 /
|
||||
T::DIV.unique_saturated_into() as f64
|
||||
})
|
||||
})
|
||||
.map_err(drop);
|
||||
relay_utils::metrics::set_gauge_value(&self.metric, value);
|
||||
*self.shared_value_ref.write().await = value.ok().and_then(|x| x);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,12 +14,12 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::chain::Chain;
|
||||
use crate::client::Client;
|
||||
use crate::error::Error;
|
||||
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};
|
||||
use relay_utils::metrics::{
|
||||
metric_name, register, Gauge, PrometheusError, Registry, StandaloneMetrics, U64,
|
||||
};
|
||||
use sp_core::storage::StorageKey;
|
||||
use sp_runtime::traits::Header as HeaderT;
|
||||
use sp_storage::well_known_keys::CODE;
|
||||
@@ -40,10 +40,7 @@ pub struct StorageProofOverheadMetric<C: Chain> {
|
||||
|
||||
impl<C: Chain> Clone for StorageProofOverheadMetric<C> {
|
||||
fn clone(&self) -> Self {
|
||||
StorageProofOverheadMetric {
|
||||
client: self.client.clone(),
|
||||
metric: self.metric.clone(),
|
||||
}
|
||||
StorageProofOverheadMetric { client: self.client.clone(), metric: self.metric.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,15 +70,15 @@ impl<C: Chain> StorageProofOverheadMetric<C> {
|
||||
.await?;
|
||||
let storage_proof_size: usize = storage_proof.clone().iter_nodes().map(|n| n.len()).sum();
|
||||
|
||||
let storage_value_reader =
|
||||
bp_runtime::StorageProofChecker::<C::Hasher>::new(*best_header.state_root(), storage_proof)
|
||||
.map_err(Error::StorageProofError)?;
|
||||
let maybe_encoded_storage_value = storage_value_reader
|
||||
.read_value(CODE)
|
||||
.map_err(Error::StorageProofError)?;
|
||||
let encoded_storage_value_size = maybe_encoded_storage_value
|
||||
.ok_or(Error::MissingMandatoryCodeEntry)?
|
||||
.len();
|
||||
let storage_value_reader = bp_runtime::StorageProofChecker::<C::Hasher>::new(
|
||||
*best_header.state_root(),
|
||||
storage_proof,
|
||||
)
|
||||
.map_err(Error::StorageProofError)?;
|
||||
let maybe_encoded_storage_value =
|
||||
storage_value_reader.read_value(CODE).map_err(Error::StorageProofError)?;
|
||||
let encoded_storage_value_size =
|
||||
maybe_encoded_storage_value.ok_or(Error::MissingMandatoryCodeEntry)?.len();
|
||||
|
||||
Ok(storage_proof_size - encoded_storage_value_size)
|
||||
}
|
||||
|
||||
@@ -18,11 +18,13 @@
|
||||
|
||||
use crate::chain::Chain;
|
||||
|
||||
use pallet_transaction_payment_rpc_runtime_api::FeeDetails;
|
||||
use sc_rpc_api::{state::ReadProof, system::Health};
|
||||
use sp_core::{
|
||||
storage::{StorageData, StorageKey},
|
||||
Bytes,
|
||||
};
|
||||
use sp_rpc::number::NumberOrHex;
|
||||
use sp_version::RuntimeVersion;
|
||||
|
||||
jsonrpsee_proc_macros::rpc_client_api! {
|
||||
@@ -41,13 +43,17 @@ jsonrpsee_proc_macros::rpc_client_api! {
|
||||
fn system_account_next_index(account_id: C::AccountId) -> C::Index;
|
||||
#[rpc(method = "author_submitExtrinsic", positional_params)]
|
||||
fn author_submit_extrinsic(extrinsic: Bytes) -> C::Hash;
|
||||
#[rpc(method = "author_pendingExtrinsics", positional_params)]
|
||||
fn author_pending_extrinsics() -> Vec<Bytes>;
|
||||
#[rpc(method = "state_call", positional_params)]
|
||||
fn state_call(method: String, data: Bytes, at_block: Option<C::Hash>) -> Bytes;
|
||||
#[rpc(method = "state_getStorage", positional_params)]
|
||||
fn state_get_storage(key: StorageKey) -> Option<StorageData>;
|
||||
fn state_get_storage(key: StorageKey, at_block: Option<C::Hash>) -> Option<StorageData>;
|
||||
#[rpc(method = "state_getReadProof", positional_params)]
|
||||
fn state_prove_storage(keys: Vec<StorageKey>, hash: Option<C::Hash>) -> ReadProof<C::Hash>;
|
||||
#[rpc(method = "state_getRuntimeVersion", positional_params)]
|
||||
fn state_runtime_version() -> RuntimeVersion;
|
||||
#[rpc(method = "payment_queryFeeDetails", positional_params)]
|
||||
fn payment_query_fee_details(extrinsic: Bytes, at_block: Option<C::Hash>) -> FeeDetails<NumberOrHex>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,7 @@ edition = "2018"
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "2.0.0" }
|
||||
headers-relay = { path = "../headers" }
|
||||
codec = { package = "parity-scale-codec", version = "2.2.0" }
|
||||
relay-substrate-client = { path = "../client-substrate" }
|
||||
relay-utils = { path = "../utils" }
|
||||
|
||||
@@ -17,9 +16,5 @@ bp-westend = { path = "../../primitives/chain-westend" }
|
||||
|
||||
# Substrate Dependencies
|
||||
|
||||
frame-system = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
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" }
|
||||
|
||||
@@ -16,10 +16,8 @@
|
||||
|
||||
//! Types used to connect to the Westend chain.
|
||||
|
||||
use codec::Encode;
|
||||
use relay_substrate_client::{Chain, ChainBase, ChainWithBalances, TransactionSignScheme};
|
||||
use sp_core::{storage::StorageKey, Pair};
|
||||
use sp_runtime::{generic::SignedPayload, traits::IdentifyAccount};
|
||||
use relay_substrate_client::{Chain, ChainBase, ChainWithBalances};
|
||||
use sp_core::storage::StorageKey;
|
||||
use std::time::Duration;
|
||||
|
||||
/// Westend header id.
|
||||
@@ -37,17 +35,22 @@ impl ChainBase for Westend {
|
||||
type Hash = bp_westend::Hash;
|
||||
type Hasher = bp_westend::Hasher;
|
||||
type Header = bp_westend::Header;
|
||||
|
||||
type AccountId = bp_westend::AccountId;
|
||||
type Balance = bp_westend::Balance;
|
||||
type Index = bp_westend::Nonce;
|
||||
type Signature = bp_westend::Signature;
|
||||
}
|
||||
|
||||
impl Chain for Westend {
|
||||
const NAME: &'static str = "Westend";
|
||||
const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_secs(6);
|
||||
const STORAGE_PROOF_OVERHEAD: u32 = bp_westend::EXTRA_STORAGE_PROOF_SIZE;
|
||||
const MAXIMAL_ENCODED_ACCOUNT_ID_SIZE: u32 = bp_westend::MAXIMAL_ENCODED_ACCOUNT_ID_SIZE;
|
||||
|
||||
type AccountId = bp_westend::AccountId;
|
||||
type Index = bp_westend::Nonce;
|
||||
type SignedBlock = bp_westend::SignedBlock;
|
||||
type Call = bp_westend::Call;
|
||||
type Balance = bp_westend::Balance;
|
||||
type WeightToFee = bp_westend::WeightToFee;
|
||||
}
|
||||
|
||||
impl ChainWithBalances for Westend {
|
||||
@@ -55,42 +58,3 @@ impl ChainWithBalances for Westend {
|
||||
StorageKey(bp_westend::account_info_storage_key(account_id))
|
||||
}
|
||||
}
|
||||
|
||||
impl TransactionSignScheme for Westend {
|
||||
type Chain = Westend;
|
||||
type AccountKeyPair = sp_core::sr25519::Pair;
|
||||
type SignedTransaction = bp_westend::UncheckedExtrinsic;
|
||||
|
||||
fn sign_transaction(
|
||||
genesis_hash: <Self::Chain as ChainBase>::Hash,
|
||||
signer: &Self::AccountKeyPair,
|
||||
signer_nonce: <Self::Chain as Chain>::Index,
|
||||
call: <Self::Chain as Chain>::Call,
|
||||
) -> Self::SignedTransaction {
|
||||
let raw_payload = SignedPayload::new(
|
||||
call,
|
||||
bp_westend::SignedExtensions::new(
|
||||
bp_westend::VERSION,
|
||||
sp_runtime::generic::Era::Immortal,
|
||||
genesis_hash,
|
||||
signer_nonce,
|
||||
0,
|
||||
),
|
||||
)
|
||||
.expect("SignedExtension never fails.");
|
||||
|
||||
let signature = raw_payload.using_encoded(|payload| signer.sign(payload));
|
||||
let signer: sp_runtime::MultiSigner = signer.public().into();
|
||||
let (call, extra, _) = raw_payload.deconstruct();
|
||||
|
||||
bp_westend::UncheckedExtrinsic::new_signed(
|
||||
call,
|
||||
sp_runtime::MultiAddress::Id(signer.into_account()),
|
||||
signature.into(),
|
||||
extra,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Westend signing params.
|
||||
pub type SigningParams = sp_core::sr25519::Pair;
|
||||
|
||||
@@ -6,10 +6,10 @@ edition = "2018"
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "2.0.0" }
|
||||
headers-relay = { path = "../headers" }
|
||||
codec = { package = "parity-scale-codec", version = "2.2.0" }
|
||||
relay-substrate-client = { path = "../client-substrate" }
|
||||
relay-utils = { path = "../utils" }
|
||||
scale-info = { version = "1.0", default-features = false, features = ["derive"] }
|
||||
|
||||
# Bridge dependencies
|
||||
bridge-runtime-common = { path = "../../bin/runtime-common" }
|
||||
@@ -24,9 +24,6 @@ pallet-bridge-dispatch = { path = "../../modules/dispatch" }
|
||||
pallet-bridge-messages = { path = "../../modules/messages" }
|
||||
|
||||
# Substrate Dependencies
|
||||
frame-system = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
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" }
|
||||
|
||||
@@ -17,7 +17,10 @@
|
||||
//! Types used to connect to the Wococo-Substrate chain.
|
||||
|
||||
use codec::Encode;
|
||||
use relay_substrate_client::{Chain, ChainBase, ChainWithBalances, TransactionSignScheme};
|
||||
use relay_substrate_client::{
|
||||
Chain, ChainBase, ChainWithBalances, TransactionEraOf, TransactionSignScheme,
|
||||
UnsignedTransaction,
|
||||
};
|
||||
use sp_core::{storage::StorageKey, Pair};
|
||||
use sp_runtime::{generic::SignedPayload, traits::IdentifyAccount};
|
||||
use std::time::Duration;
|
||||
@@ -39,17 +42,22 @@ impl ChainBase for Wococo {
|
||||
type Hash = bp_wococo::Hash;
|
||||
type Hasher = bp_wococo::Hashing;
|
||||
type Header = bp_wococo::Header;
|
||||
|
||||
type AccountId = bp_wococo::AccountId;
|
||||
type Balance = bp_wococo::Balance;
|
||||
type Index = bp_wococo::Nonce;
|
||||
type Signature = bp_wococo::Signature;
|
||||
}
|
||||
|
||||
impl Chain for Wococo {
|
||||
const NAME: &'static str = "Wococo";
|
||||
const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_secs(6);
|
||||
const STORAGE_PROOF_OVERHEAD: u32 = bp_wococo::EXTRA_STORAGE_PROOF_SIZE;
|
||||
const MAXIMAL_ENCODED_ACCOUNT_ID_SIZE: u32 = bp_wococo::MAXIMAL_ENCODED_ACCOUNT_ID_SIZE;
|
||||
|
||||
type AccountId = bp_wococo::AccountId;
|
||||
type Index = bp_wococo::Index;
|
||||
type SignedBlock = bp_wococo::SignedBlock;
|
||||
type Call = crate::runtime::Call;
|
||||
type Balance = bp_wococo::Balance;
|
||||
type WeightToFee = bp_wococo::WeightToFee;
|
||||
}
|
||||
|
||||
impl ChainWithBalances for Wococo {
|
||||
@@ -66,17 +74,17 @@ impl TransactionSignScheme for Wococo {
|
||||
fn sign_transaction(
|
||||
genesis_hash: <Self::Chain as ChainBase>::Hash,
|
||||
signer: &Self::AccountKeyPair,
|
||||
signer_nonce: <Self::Chain as Chain>::Index,
|
||||
call: <Self::Chain as Chain>::Call,
|
||||
era: TransactionEraOf<Self::Chain>,
|
||||
unsigned: UnsignedTransaction<Self::Chain>,
|
||||
) -> Self::SignedTransaction {
|
||||
let raw_payload = SignedPayload::new(
|
||||
call,
|
||||
unsigned.call,
|
||||
bp_wococo::SignedExtensions::new(
|
||||
bp_wococo::VERSION,
|
||||
sp_runtime::generic::Era::Immortal,
|
||||
era,
|
||||
genesis_hash,
|
||||
signer_nonce,
|
||||
0,
|
||||
unsigned.nonce,
|
||||
unsigned.tip,
|
||||
),
|
||||
)
|
||||
.expect("SignedExtension never fails.");
|
||||
@@ -92,6 +100,24 @@ impl TransactionSignScheme for Wococo {
|
||||
extra,
|
||||
)
|
||||
}
|
||||
|
||||
fn is_signed(tx: &Self::SignedTransaction) -> bool {
|
||||
tx.signature.is_some()
|
||||
}
|
||||
|
||||
fn is_signed_by(signer: &Self::AccountKeyPair, tx: &Self::SignedTransaction) -> bool {
|
||||
tx.signature
|
||||
.as_ref()
|
||||
.map(|(address, _, _)| {
|
||||
*address == bp_wococo::AccountId::from(*signer.public().as_array_ref()).into()
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn parse_transaction(tx: Self::SignedTransaction) -> Option<UnsignedTransaction<Self::Chain>> {
|
||||
let extra = &tx.signature.as_ref()?.2;
|
||||
Some(UnsignedTransaction { call: tx.function, nonce: extra.nonce(), tip: extra.tip() })
|
||||
}
|
||||
}
|
||||
|
||||
/// Wococo signing params.
|
||||
|
||||
@@ -21,9 +21,7 @@ use bp_polkadot_core::PolkadotLike;
|
||||
use bp_runtime::Chain;
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::weights::Weight;
|
||||
|
||||
/// Instance of messages pallet that is used to bridge with Rococo chain.
|
||||
pub type WithRococoMessagesInstance = pallet_bridge_messages::DefaultInstance;
|
||||
use scale_info::TypeInfo;
|
||||
|
||||
/// Unchecked Wococo extrinsic.
|
||||
pub type UncheckedExtrinsic = bp_polkadot_core::UncheckedExtrinsic<Call>;
|
||||
@@ -55,15 +53,15 @@ where
|
||||
/// Wococo Runtime `Call` enum.
|
||||
///
|
||||
/// The enum represents a subset of possible `Call`s we can send to Rococo chain.
|
||||
/// Ideally this code would be auto-generated from Metadata, because we want to
|
||||
/// Ideally this code would be auto-generated from metadata, because we want to
|
||||
/// avoid depending directly on the ENTIRE runtime just to get the encoding of `Dispatchable`s.
|
||||
///
|
||||
/// All entries here (like pretty much in the entire file) must be kept in sync with Rococo
|
||||
/// `construct_runtime`, so that we maintain SCALE-compatibility.
|
||||
///
|
||||
/// See: https://github.com/paritytech/polkadot/blob/master/runtime/rococo/src/lib.rs
|
||||
/// See: [link](https://github.com/paritytech/polkadot/blob/master/runtime/rococo/src/lib.rs)
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
|
||||
pub enum Call {
|
||||
/// System pallet.
|
||||
#[codec(index = 0)]
|
||||
@@ -76,26 +74,26 @@ pub enum Call {
|
||||
BridgeMessagesRococo(BridgeMessagesRococoCall),
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum SystemCall {
|
||||
#[codec(index = 1)]
|
||||
remark(Vec<u8>),
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum BridgeGrandpaRococoCall {
|
||||
#[codec(index = 0)]
|
||||
submit_finality_proof(
|
||||
<PolkadotLike as Chain>::Header,
|
||||
Box<<PolkadotLike as Chain>::Header>,
|
||||
bp_header_chain::justification::GrandpaJustification<<PolkadotLike as Chain>::Header>,
|
||||
),
|
||||
#[codec(index = 1)]
|
||||
initialize(bp_header_chain::InitializationData<<PolkadotLike as Chain>::Header>),
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum BridgeMessagesRococoCall {
|
||||
#[codec(index = 3)]
|
||||
@@ -118,7 +116,9 @@ pub enum BridgeMessagesRococoCall {
|
||||
),
|
||||
#[codec(index = 6)]
|
||||
receive_messages_delivery_proof(
|
||||
bridge_runtime_common::messages::source::FromBridgedChainMessagesDeliveryProof<bp_rococo::Hash>,
|
||||
bridge_runtime_common::messages::source::FromBridgedChainMessagesDeliveryProof<
|
||||
bp_rococo::Hash,
|
||||
>,
|
||||
UnrewardedRelayersState,
|
||||
),
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ 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"
|
||||
@@ -14,3 +15,4 @@ log = "0.4.11"
|
||||
num-traits = "0.2"
|
||||
parking_lot = "0.11.0"
|
||||
relay-utils = { path = "../utils" }
|
||||
thiserror = "1.0.26"
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Exchange-relay errors.
|
||||
|
||||
use crate::exchange::{BlockHashOf, BlockNumberOf, TransactionHashOf};
|
||||
|
||||
use relay_utils::MaybeConnectionError;
|
||||
use std::fmt::{Debug, Display};
|
||||
use thiserror::Error;
|
||||
|
||||
/// Error type given pipeline.
|
||||
pub type ErrorOf<P> = Error<BlockHashOf<P>, BlockNumberOf<P>, TransactionHashOf<P>>;
|
||||
|
||||
/// Exchange-relay error type.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error<Hash: Display, HeaderNumber: Display, SourceTxHash: Display> {
|
||||
/// Failed to check finality of the requested header on the target node.
|
||||
#[error("Failed to check finality of header {0}/{1} on {2} node: {3:?}")]
|
||||
Finality(HeaderNumber, Hash, &'static str, anyhow::Error),
|
||||
/// Error retrieving block from the source node.
|
||||
#[error("Error retrieving block {0} from {1} node: {2:?}")]
|
||||
RetrievingBlock(Hash, &'static str, anyhow::Error),
|
||||
/// Error retrieving transaction from the source node.
|
||||
#[error("Error retrieving transaction {0} from {1} node: {2:?}")]
|
||||
RetrievingTransaction(SourceTxHash, &'static str, anyhow::Error),
|
||||
/// Failed to check existence of header from the target node.
|
||||
#[error("Failed to check existence of header {0}/{1} on {2} node: {3:?}")]
|
||||
CheckHeaderExistence(HeaderNumber, Hash, &'static str, anyhow::Error),
|
||||
/// Failed to prepare proof for the transaction from the source node.
|
||||
#[error("Error building transaction {0} proof on {1} node: {2:?}")]
|
||||
BuildTransactionProof(String, &'static str, anyhow::Error, bool),
|
||||
/// Failed to submit the transaction proof to the target node.
|
||||
#[error("Error submitting transaction {0} proof to {1} node: {2:?}")]
|
||||
SubmitTransactionProof(String, &'static str, anyhow::Error, bool),
|
||||
/// Transaction filtering failed.
|
||||
#[error("Transaction filtering has failed with {0:?}")]
|
||||
TransactionFiltering(anyhow::Error, bool),
|
||||
/// Utilities/metrics error.
|
||||
#[error("{0}")]
|
||||
Utils(#[from] relay_utils::Error),
|
||||
}
|
||||
|
||||
impl<T: Display, U: Display, V: Display> MaybeConnectionError for Error<T, U, V> {
|
||||
fn is_connection_error(&self) -> bool {
|
||||
match *self {
|
||||
Self::BuildTransactionProof(_, _, _, b) => b,
|
||||
Self::SubmitTransactionProof(_, _, _, b) => b,
|
||||
Self::TransactionFiltering(_, b) => b,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,10 +16,11 @@
|
||||
|
||||
//! Relaying proofs of exchange transaction.
|
||||
|
||||
use crate::error::{Error, ErrorOf};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use async_trait::async_trait;
|
||||
use relay_utils::{
|
||||
relay_loop::Client as RelayClient, FailedClient, MaybeConnectionError, StringifiedMaybeConnectionError,
|
||||
};
|
||||
use relay_utils::{relay_loop::Client as RelayClient, FailedClient, MaybeConnectionError};
|
||||
use std::{
|
||||
fmt::{Debug, Display},
|
||||
string::ToString,
|
||||
@@ -66,7 +67,7 @@ pub trait SourceBlock: 'static + Send + Sync {
|
||||
/// Transaction that is participating in exchange.
|
||||
pub trait SourceTransaction: 'static + Send {
|
||||
/// Transaction hash type.
|
||||
type Hash: Debug + Display;
|
||||
type Hash: Debug + Display + Clone;
|
||||
|
||||
/// Return transaction hash.
|
||||
fn hash(&self) -> Self::Hash;
|
||||
@@ -96,12 +97,18 @@ pub trait SourceClient<P: TransactionProofPipeline>: RelayClient {
|
||||
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>;
|
||||
/// 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>;
|
||||
async fn transaction_proof(
|
||||
&self,
|
||||
block: &P::Block,
|
||||
tx_index: usize,
|
||||
) -> Result<P::TransactionProof, Self::Error>;
|
||||
}
|
||||
|
||||
/// Target client API.
|
||||
@@ -110,15 +117,19 @@ pub trait TargetClient<P: TransactionProofPipeline>: RelayClient {
|
||||
/// Sleep until exchange-related data is (probably) updated.
|
||||
async fn tick(&self);
|
||||
/// Returns `Ok(true)` if header is known to the target node.
|
||||
async fn is_header_known(&self, id: &HeaderId<P>) -> Result<bool, Self::Error>;
|
||||
async fn is_header_known(&self, id: &HeaderId<P>) -> std::result::Result<bool, Self::Error>;
|
||||
/// Returns `Ok(true)` if header is finalized by the target node.
|
||||
async fn is_header_finalized(&self, id: &HeaderId<P>) -> Result<bool, Self::Error>;
|
||||
/// Returns best finalized header id.
|
||||
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>;
|
||||
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>;
|
||||
async fn submit_transaction_proof(&self, proof: P::TransactionProof)
|
||||
-> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
/// Block transaction statistics.
|
||||
@@ -154,27 +165,28 @@ pub async fn relay_block_transactions<P: TransactionProofPipeline>(
|
||||
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 source_tx_proof = prepare_transaction_proof(
|
||||
source_client,
|
||||
&source_tx_id,
|
||||
source_block,
|
||||
source_tx_index,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| (FailedClient::Source, e))?;
|
||||
|
||||
let needs_to_be_relayed =
|
||||
target_client
|
||||
.filter_transaction_proof(&source_tx_proof)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
(
|
||||
FailedClient::Target,
|
||||
StringifiedMaybeConnectionError::new(
|
||||
err.is_connection_error(),
|
||||
format!("Transaction filtering has failed with {:?}", err),
|
||||
),
|
||||
)
|
||||
})?;
|
||||
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);
|
||||
return Ok(false)
|
||||
}
|
||||
|
||||
relay_ready_transaction_proof(target_client, &source_tx_id, source_tx_proof)
|
||||
@@ -191,13 +203,14 @@ pub async fn relay_block_transactions<P: TransactionProofPipeline>(
|
||||
// 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.
|
||||
// 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",
|
||||
@@ -209,7 +222,7 @@ pub async fn relay_block_transactions<P: TransactionProofPipeline>(
|
||||
|
||||
relayed_transactions.processed += 1;
|
||||
relayed_transactions.relayed += 1;
|
||||
}
|
||||
},
|
||||
Err((failed_client, err)) => {
|
||||
log::error!(
|
||||
target: "bridge",
|
||||
@@ -226,12 +239,12 @@ pub async fn relay_block_transactions<P: TransactionProofPipeline>(
|
||||
);
|
||||
|
||||
if err.is_connection_error() {
|
||||
return Err((failed_client, relayed_transactions));
|
||||
return Err((failed_client, relayed_transactions))
|
||||
}
|
||||
|
||||
relayed_transactions.processed += 1;
|
||||
relayed_transactions.failed += 1;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,19 +256,14 @@ pub async fn relay_single_transaction_proof<P: TransactionProofPipeline>(
|
||||
source_client: &impl SourceClient<P>,
|
||||
target_client: &impl TargetClient<P>,
|
||||
source_tx_hash: TransactionHashOf<P>,
|
||||
) -> Result<(), String> {
|
||||
) -> Result<(), ErrorOf<P>> {
|
||||
// wait for transaction and header on source node
|
||||
let (source_header_id, source_tx_index) = wait_transaction_mined(source_client, &source_tx_hash).await?;
|
||||
let (source_header_id, source_tx_index) =
|
||||
wait_transaction_mined(source_client, &source_tx_hash).await?;
|
||||
let source_block = source_client.block_by_hash(source_header_id.1.clone()).await;
|
||||
let source_block = source_block.map_err(|err| {
|
||||
format!(
|
||||
"Error retrieving block {} from {} node: {:?}",
|
||||
source_header_id.1,
|
||||
P::SOURCE_NAME,
|
||||
err,
|
||||
)
|
||||
Error::RetrievingBlock(source_header_id.1.clone(), P::SOURCE_NAME, anyhow!("{:?}", err))
|
||||
})?;
|
||||
|
||||
// wait for transaction and header on target node
|
||||
wait_header_imported(target_client, &source_header_id).await?;
|
||||
wait_header_finalized(target_client, &source_header_id).await?;
|
||||
@@ -266,11 +274,10 @@ pub async fn relay_single_transaction_proof<P: TransactionProofPipeline>(
|
||||
target_client,
|
||||
&source_tx_id,
|
||||
prepare_transaction_proof(source_client, &source_tx_id, &source_block, source_tx_index)
|
||||
.await
|
||||
.map_err(|err| err.to_string())?,
|
||||
.await?,
|
||||
)
|
||||
.await
|
||||
.map_err(|err| err.to_string())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Prepare transaction proof.
|
||||
@@ -279,19 +286,16 @@ async fn prepare_transaction_proof<P: TransactionProofPipeline>(
|
||||
source_tx_id: &str,
|
||||
source_block: &P::Block,
|
||||
source_tx_index: usize,
|
||||
) -> Result<P::TransactionProof, StringifiedMaybeConnectionError> {
|
||||
) -> Result<P::TransactionProof, ErrorOf<P>> {
|
||||
source_client
|
||||
.transaction_proof(source_block, source_tx_index)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
StringifiedMaybeConnectionError::new(
|
||||
Error::BuildTransactionProof(
|
||||
source_tx_id.to_owned(),
|
||||
P::SOURCE_NAME,
|
||||
anyhow!("{:?}", err),
|
||||
err.is_connection_error(),
|
||||
format!(
|
||||
"Error building transaction {} proof on {} node: {:?}",
|
||||
source_tx_id,
|
||||
P::SOURCE_NAME,
|
||||
err,
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -301,37 +305,31 @@ async fn relay_ready_transaction_proof<P: TransactionProofPipeline>(
|
||||
target_client: &impl TargetClient<P>,
|
||||
source_tx_id: &str,
|
||||
source_tx_proof: P::TransactionProof,
|
||||
) -> Result<(), StringifiedMaybeConnectionError> {
|
||||
target_client
|
||||
.submit_transaction_proof(source_tx_proof)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
StringifiedMaybeConnectionError::new(
|
||||
err.is_connection_error(),
|
||||
format!(
|
||||
"Error submitting transaction {} proof to {} node: {:?}",
|
||||
source_tx_id,
|
||||
P::TARGET_NAME,
|
||||
err,
|
||||
),
|
||||
)
|
||||
})
|
||||
) -> 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), String> {
|
||||
) -> Result<(HeaderId<P>, usize), ErrorOf<P>> {
|
||||
loop {
|
||||
let source_header_and_tx = source_client.transaction_block(source_tx_hash).await.map_err(|err| {
|
||||
format!(
|
||||
"Error retrieving transaction {} from {} node: {:?}",
|
||||
source_tx_hash,
|
||||
P::SOURCE_NAME,
|
||||
err,
|
||||
)
|
||||
})?;
|
||||
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!(
|
||||
@@ -341,8 +339,8 @@ async fn wait_transaction_mined<P: TransactionProofPipeline>(
|
||||
P::SOURCE_NAME,
|
||||
);
|
||||
|
||||
return Ok((source_header_id, source_tx));
|
||||
}
|
||||
return Ok((source_header_id, source_tx))
|
||||
},
|
||||
None => {
|
||||
log::info!(
|
||||
target: "bridge",
|
||||
@@ -352,7 +350,7 @@ async fn wait_transaction_mined<P: TransactionProofPipeline>(
|
||||
);
|
||||
|
||||
source_client.tick().await;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -361,17 +359,17 @@ async fn wait_transaction_mined<P: TransactionProofPipeline>(
|
||||
async fn wait_header_imported<P: TransactionProofPipeline>(
|
||||
target_client: &impl TargetClient<P>,
|
||||
source_header_id: &HeaderId<P>,
|
||||
) -> Result<(), String> {
|
||||
) -> Result<(), ErrorOf<P>> {
|
||||
loop {
|
||||
let is_header_known = target_client.is_header_known(source_header_id).await.map_err(|err| {
|
||||
format!(
|
||||
"Failed to check existence of header {}/{} on {} node: {:?}",
|
||||
source_header_id.0,
|
||||
source_header_id.1,
|
||||
P::TARGET_NAME,
|
||||
err,
|
||||
)
|
||||
})?;
|
||||
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!(
|
||||
@@ -382,8 +380,8 @@ async fn wait_header_imported<P: TransactionProofPipeline>(
|
||||
P::TARGET_NAME,
|
||||
);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
return Ok(())
|
||||
},
|
||||
false => {
|
||||
log::info!(
|
||||
target: "bridge",
|
||||
@@ -394,7 +392,7 @@ async fn wait_header_imported<P: TransactionProofPipeline>(
|
||||
);
|
||||
|
||||
target_client.tick().await;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -403,18 +401,15 @@ async fn wait_header_imported<P: TransactionProofPipeline>(
|
||||
async fn wait_header_finalized<P: TransactionProofPipeline>(
|
||||
target_client: &impl TargetClient<P>,
|
||||
source_header_id: &HeaderId<P>,
|
||||
) -> Result<(), String> {
|
||||
) -> Result<(), ErrorOf<P>> {
|
||||
loop {
|
||||
let is_header_finalized = target_client
|
||||
.is_header_finalized(source_header_id)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
format!(
|
||||
"Failed to check finality of header {}/{} on {} node: {:?}",
|
||||
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,
|
||||
source_header_id.1.clone(),
|
||||
P::TARGET_NAME,
|
||||
err,
|
||||
anyhow!("{:?}", err),
|
||||
)
|
||||
})?;
|
||||
match is_header_finalized {
|
||||
@@ -427,8 +422,8 @@ async fn wait_header_finalized<P: TransactionProofPipeline>(
|
||||
P::TARGET_NAME,
|
||||
);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
return Ok(())
|
||||
},
|
||||
false => {
|
||||
log::info!(
|
||||
target: "bridge",
|
||||
@@ -439,7 +434,7 @@ async fn wait_header_finalized<P: TransactionProofPipeline>(
|
||||
);
|
||||
|
||||
target_client.tick().await;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -582,15 +577,22 @@ pub(crate) mod tests {
|
||||
self.data.lock().block.clone()
|
||||
}
|
||||
|
||||
async fn transaction_block(&self, _: &TestTransactionHash) -> Result<Option<(TestHeaderId, usize)>, TestError> {
|
||||
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> {
|
||||
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);
|
||||
return Err(err)
|
||||
}
|
||||
|
||||
Ok(TestTransactionProof(tx_hash))
|
||||
@@ -653,21 +655,32 @@ pub(crate) mod tests {
|
||||
self.data.lock().best_finalized_header_id.clone()
|
||||
}
|
||||
|
||||
async fn filter_transaction_proof(&self, proof: &TestTransactionProof) -> Result<bool, TestError> {
|
||||
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> {
|
||||
async fn submit_transaction_proof(
|
||||
&self,
|
||||
proof: TestTransactionProof,
|
||||
) -> Result<(), TestError> {
|
||||
self.data.lock().submitted_proofs.push(proof);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_relay_single_success(source: &TestTransactionsSource, target: &TestTransactionsTarget) {
|
||||
assert_eq!(
|
||||
async_std::task::block_on(relay_single_transaction_proof(source, target, test_transaction_hash(0),)),
|
||||
Ok(()),
|
||||
);
|
||||
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))],
|
||||
@@ -782,11 +795,7 @@ pub(crate) mod tests {
|
||||
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));
|
||||
target.data.lock().transactions_to_accept.remove(&test_transaction_hash(0));
|
||||
|
||||
ensure_relay_single_success(&source, &target)
|
||||
}
|
||||
@@ -814,25 +823,14 @@ pub(crate) mod tests {
|
||||
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));
|
||||
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());
|
||||
let relayed_transactions =
|
||||
test_relay_block_transactions(&source, &target, Default::default());
|
||||
assert_eq!(
|
||||
relayed_transactions,
|
||||
Ok(RelayedBlockTransactions {
|
||||
processed: 3,
|
||||
relayed: 1,
|
||||
failed: 0,
|
||||
}),
|
||||
Ok(RelayedBlockTransactions { processed: 3, relayed: 1, failed: 0 }),
|
||||
);
|
||||
assert_eq!(
|
||||
target.data.lock().submitted_proofs,
|
||||
@@ -852,14 +850,11 @@ pub(crate) mod tests {
|
||||
.proofs_to_fail
|
||||
.insert(test_transaction_hash(0), TestError(false));
|
||||
|
||||
let relayed_transactions = test_relay_block_transactions(&source, &target, Default::default());
|
||||
let relayed_transactions =
|
||||
test_relay_block_transactions(&source, &target, Default::default());
|
||||
assert_eq!(
|
||||
relayed_transactions,
|
||||
Ok(RelayedBlockTransactions {
|
||||
processed: 3,
|
||||
relayed: 0,
|
||||
failed: 1,
|
||||
}),
|
||||
Ok(RelayedBlockTransactions { processed: 3, relayed: 0, failed: 1 }),
|
||||
);
|
||||
assert_eq!(target.data.lock().submitted_proofs, vec![]);
|
||||
}
|
||||
@@ -876,14 +871,11 @@ pub(crate) mod tests {
|
||||
.proofs_to_fail
|
||||
.insert(test_transaction_hash(1), TestError(true));
|
||||
|
||||
let relayed_transactions = test_relay_block_transactions(&source, &target, Default::default());
|
||||
let relayed_transactions =
|
||||
test_relay_block_transactions(&source, &target, Default::default());
|
||||
assert_eq!(
|
||||
relayed_transactions,
|
||||
Err(RelayedBlockTransactions {
|
||||
processed: 1,
|
||||
relayed: 1,
|
||||
failed: 0,
|
||||
}),
|
||||
Err(RelayedBlockTransactions { processed: 1, relayed: 1, failed: 0 }),
|
||||
);
|
||||
assert_eq!(
|
||||
target.data.lock().submitted_proofs,
|
||||
@@ -893,20 +885,13 @@ pub(crate) mod tests {
|
||||
// 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));
|
||||
target.data.lock().transactions_to_accept.insert(test_transaction_hash(2));
|
||||
|
||||
let relayed_transactions = test_relay_block_transactions(&source, &target, relayed_transactions.unwrap_err());
|
||||
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,
|
||||
}),
|
||||
Ok(RelayedBlockTransactions { processed: 3, relayed: 2, failed: 0 }),
|
||||
);
|
||||
assert_eq!(
|
||||
target.data.lock().submitted_proofs,
|
||||
|
||||
@@ -16,12 +16,16 @@
|
||||
|
||||
//! Relaying proofs of exchange transactions.
|
||||
|
||||
use crate::exchange::{
|
||||
relay_block_transactions, BlockNumberOf, RelayedBlockTransactions, SourceClient, TargetClient,
|
||||
TransactionProofPipeline,
|
||||
use crate::{
|
||||
error::Error,
|
||||
exchange::{
|
||||
relay_block_transactions, BlockNumberOf, RelayedBlockTransactions, SourceClient,
|
||||
TargetClient, TransactionProofPipeline,
|
||||
},
|
||||
exchange_loop_metrics::ExchangeLoopMetrics,
|
||||
};
|
||||
use crate::exchange_loop_metrics::ExchangeLoopMetrics;
|
||||
|
||||
use crate::error::ErrorOf;
|
||||
use backoff::backoff::Backoff;
|
||||
use futures::{future::FutureExt, select};
|
||||
use num_traits::One;
|
||||
@@ -58,13 +62,13 @@ pub struct InMemoryStorage<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,
|
||||
}
|
||||
InMemoryStorage { best_processed_header_number }
|
||||
}
|
||||
}
|
||||
|
||||
impl<BlockNumber: 'static + Clone + Copy + Send + Sync> TransactionProofsRelayStorage for InMemoryStorage<BlockNumber> {
|
||||
impl<BlockNumber: 'static + Clone + Copy + Send + Sync> TransactionProofsRelayStorage
|
||||
for InMemoryStorage<BlockNumber>
|
||||
{
|
||||
type BlockNumber = BlockNumber;
|
||||
|
||||
fn state(&self) -> TransactionProofsRelayState<BlockNumber> {
|
||||
@@ -90,13 +94,13 @@ pub async fn run<P: TransactionProofPipeline>(
|
||||
target_client: impl TargetClient<P>,
|
||||
metrics_params: MetricsParams,
|
||||
exit_signal: impl Future<Output = ()> + 'static + Send,
|
||||
) -> Result<(), String> {
|
||||
) -> 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(|registry, prefix| ExchangeLoopMetrics::new(registry, prefix))?
|
||||
.standalone_metric(|registry, prefix| GlobalMetrics::new(registry, prefix))?
|
||||
.loop_metric(ExchangeLoopMetrics::new)?
|
||||
.standalone_metric(GlobalMetrics::new)?
|
||||
.expose()
|
||||
.await?
|
||||
.run(metrics_prefix::<P>(), move |source_client, target_client, metrics| {
|
||||
@@ -109,6 +113,7 @@ pub async fn run<P: TransactionProofPipeline>(
|
||||
)
|
||||
})
|
||||
.await
|
||||
.map_err(Error::Utils)
|
||||
}
|
||||
|
||||
/// Run proofs synchronization.
|
||||
@@ -140,12 +145,11 @@ async fn run_until_connection_lost<P: TransactionProofPipeline>(
|
||||
|
||||
if let Err((is_connection_error, failed_client)) = iteration_result {
|
||||
if is_connection_error {
|
||||
return Err(failed_client);
|
||||
return Err(failed_client)
|
||||
}
|
||||
|
||||
let retry_timeout = retry_backoff
|
||||
.next_backoff()
|
||||
.unwrap_or(relay_utils::relay_loop::RECONNECT_DELAY);
|
||||
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(()),
|
||||
@@ -181,7 +185,7 @@ async fn run_loop_iteration<P: TransactionProofPipeline>(
|
||||
);
|
||||
|
||||
best_finalized_header_id
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
target: "bridge",
|
||||
@@ -191,14 +195,20 @@ async fn run_loop_iteration<P: TransactionProofPipeline>(
|
||||
err,
|
||||
);
|
||||
|
||||
return Err((err.is_connection_error(), FailedClient::Target));
|
||||
}
|
||||
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;
|
||||
let result = relay_block_transactions(
|
||||
source_client,
|
||||
target_client,
|
||||
&block,
|
||||
relayed_transactions,
|
||||
)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(relayed_transactions) => {
|
||||
@@ -212,7 +222,8 @@ async fn run_loop_iteration<P: TransactionProofPipeline>(
|
||||
relayed_transactions.failed,
|
||||
);
|
||||
|
||||
state.best_processed_header_number = state.best_processed_header_number + One::one();
|
||||
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 {
|
||||
@@ -224,11 +235,11 @@ async fn run_loop_iteration<P: TransactionProofPipeline>(
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
return Err((true, failed_client))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,8 +253,8 @@ async fn run_loop_iteration<P: TransactionProofPipeline>(
|
||||
*current_finalized_block = Some((block, RelayedBlockTransactions::default()));
|
||||
|
||||
// we have received new finalized block => go back to relay its transactions
|
||||
continue;
|
||||
}
|
||||
continue
|
||||
},
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
target: "bridge",
|
||||
@@ -253,13 +264,13 @@ async fn run_loop_iteration<P: TransactionProofPipeline>(
|
||||
err,
|
||||
);
|
||||
|
||||
return Err((err.is_connection_error(), FailedClient::Source));
|
||||
}
|
||||
return Err((err.is_connection_error(), FailedClient::Source))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// there are no any transactions we need to relay => wait for new data
|
||||
return Ok(());
|
||||
return Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,17 +278,16 @@ async fn run_loop_iteration<P: TransactionProofPipeline>(
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::exchange::tests::{
|
||||
test_next_block, test_next_block_id, test_transaction_hash, TestTransactionProof, TestTransactionsSource,
|
||||
TestTransactionsTarget,
|
||||
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 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();
|
||||
|
||||
@@ -295,11 +305,8 @@ mod tests {
|
||||
(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));
|
||||
}
|
||||
target_data.lock().transactions_to_accept.insert(test_transaction_hash(1));
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
pub mod error;
|
||||
pub mod exchange;
|
||||
pub mod exchange_loop;
|
||||
pub mod exchange_loop_metrics;
|
||||
|
||||
@@ -43,18 +43,19 @@ pub struct FinalitySyncParams {
|
||||
/// `min(source_block_time, target_block_time)`.
|
||||
///
|
||||
/// This parameter may be used to limit transactions rate. Increase the value && you'll get
|
||||
/// infrequent updates => sparse headers => potential slow down of bridge applications, but pallet storage
|
||||
/// won't be super large. Decrease the value to near `source_block_time` and you'll get
|
||||
/// transaction for (almost) every block of the source chain => all source headers will be known
|
||||
/// to the target chain => bridge applications will run faster, but pallet storage may explode
|
||||
/// (but if pruning is there, then it's fine).
|
||||
/// infrequent updates => sparse headers => potential slow down of bridge applications, but
|
||||
/// pallet storage won't be super large. Decrease the value to near `source_block_time` and
|
||||
/// you'll get transaction for (almost) every block of the source chain => all source headers
|
||||
/// will be known to the target chain => bridge applications will run faster, but pallet
|
||||
/// storage may explode (but if pruning is there, then it's fine).
|
||||
pub tick: Duration,
|
||||
/// Number of finality proofs to keep in internal buffer between loop wakeups.
|
||||
/// Number of finality proofs to keep in internal buffer between loop iterations.
|
||||
///
|
||||
/// While in "major syncing" state, we still read finality proofs from the stream. They're stored
|
||||
/// in the internal buffer between loop wakeups. When we're close to the tip of the chain, we may
|
||||
/// meet finality delays if headers are not finalized frequently. So instead of waiting for next
|
||||
/// finality proof to appear in the stream, we may use existing proof from that buffer.
|
||||
/// While in "major syncing" state, we still read finality proofs from the stream. They're
|
||||
/// stored in the internal buffer between loop iterations. When we're close to the tip of the
|
||||
/// chain, we may meet finality delays if headers are not finalized frequently. So instead of
|
||||
/// waiting for next finality proof to appear in the stream, we may use existing proof from
|
||||
/// that buffer.
|
||||
pub recent_finality_proofs_limit: usize,
|
||||
/// Timeout before we treat our transactions as lost and restart the whole sync process.
|
||||
pub stall_timeout: Duration,
|
||||
@@ -89,10 +90,15 @@ pub trait TargetClient<P: FinalitySyncPipeline>: RelayClient {
|
||||
async fn best_finalized_source_block_number(&self) -> Result<P::Number, Self::Error>;
|
||||
|
||||
/// Submit header finality proof.
|
||||
async fn submit_finality_proof(&self, header: P::Header, proof: P::FinalityProof) -> Result<(), Self::Error>;
|
||||
async fn submit_finality_proof(
|
||||
&self,
|
||||
header: P::Header,
|
||||
proof: P::FinalityProof,
|
||||
) -> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
/// Return prefix that will be used by default to expose Prometheus metrics of the finality proofs sync loop.
|
||||
/// Return prefix that will be used by default to expose Prometheus metrics of the finality proofs
|
||||
/// sync loop.
|
||||
pub fn metrics_prefix<P: FinalitySyncPipeline>() -> String {
|
||||
format!("{}_to_{}_Sync", P::SOURCE_NAME, P::TARGET_NAME)
|
||||
}
|
||||
@@ -104,12 +110,12 @@ pub async fn run<P: FinalitySyncPipeline>(
|
||||
sync_params: FinalitySyncParams,
|
||||
metrics_params: MetricsParams,
|
||||
exit_signal: impl Future<Output = ()> + 'static + Send,
|
||||
) -> Result<(), String> {
|
||||
) -> 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(|registry, prefix| SyncLoopMetrics::new(registry, prefix))?
|
||||
.standalone_metric(|registry, prefix| GlobalMetrics::new(registry, prefix))?
|
||||
.loop_metric(SyncLoopMetrics::new)?
|
||||
.standalone_metric(GlobalMetrics::new)?
|
||||
.expose()
|
||||
.await?
|
||||
.run(metrics_prefix::<P>(), move |source_client, target_client, metrics| {
|
||||
@@ -127,15 +133,11 @@ pub async fn run<P: FinalitySyncPipeline>(
|
||||
/// Unjustified headers container. Ordered by header number.
|
||||
pub(crate) type UnjustifiedHeaders<H> = Vec<H>;
|
||||
/// Finality proofs container. Ordered by target header number.
|
||||
pub(crate) type FinalityProofs<P> = Vec<(
|
||||
<P as FinalitySyncPipeline>::Number,
|
||||
<P as FinalitySyncPipeline>::FinalityProof,
|
||||
)>;
|
||||
pub(crate) type FinalityProofs<P> =
|
||||
Vec<(<P as FinalitySyncPipeline>::Number, <P as FinalitySyncPipeline>::FinalityProof)>;
|
||||
/// Reference to finality proofs container.
|
||||
pub(crate) type FinalityProofsRef<'a, P> = &'a [(
|
||||
<P as FinalitySyncPipeline>::Number,
|
||||
<P as FinalitySyncPipeline>::FinalityProof,
|
||||
)];
|
||||
pub(crate) type FinalityProofsRef<'a, P> =
|
||||
&'a [(<P as FinalitySyncPipeline>::Number, <P as FinalitySyncPipeline>::FinalityProof)];
|
||||
|
||||
/// Error that may happen inside finality synchronization loop.
|
||||
#[derive(Debug)]
|
||||
@@ -186,10 +188,7 @@ pub(crate) struct RestartableFinalityProofsStream<S> {
|
||||
#[cfg(test)]
|
||||
impl<S> From<S> for RestartableFinalityProofsStream<S> {
|
||||
fn from(stream: S) -> Self {
|
||||
RestartableFinalityProofsStream {
|
||||
needs_restart: false,
|
||||
stream: Box::pin(stream),
|
||||
}
|
||||
RestartableFinalityProofsStream { needs_restart: false, stream: Box::pin(stream) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,14 +259,12 @@ async fn run_until_connection_lost<P: FinalitySyncPipeline>(
|
||||
last_transaction = updated_last_transaction;
|
||||
retry_backoff.reset();
|
||||
sync_params.tick
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
log::error!(target: "bridge", "Finality sync loop iteration has failed with error: {:?}", error);
|
||||
error.fail_if_connection_error()?;
|
||||
retry_backoff
|
||||
.next_backoff()
|
||||
.unwrap_or(relay_utils::relay_loop::RECONNECT_DELAY)
|
||||
}
|
||||
retry_backoff.next_backoff().unwrap_or(relay_utils::relay_loop::RECONNECT_DELAY)
|
||||
},
|
||||
};
|
||||
if finality_proofs_stream.needs_restart {
|
||||
log::warn!(target: "bridge", "{} finality proofs stream is being restarted", P::SOURCE_NAME);
|
||||
@@ -297,10 +294,8 @@ where
|
||||
TC: TargetClient<P>,
|
||||
{
|
||||
// read best source headers ids from source and target nodes
|
||||
let best_number_at_source = source_client
|
||||
.best_finalized_block_number()
|
||||
.await
|
||||
.map_err(Error::Source)?;
|
||||
let best_number_at_source =
|
||||
source_client.best_finalized_block_number().await.map_err(Error::Source)?;
|
||||
let best_number_at_target = target_client
|
||||
.best_finalized_source_block_number()
|
||||
.await
|
||||
@@ -309,7 +304,8 @@ where
|
||||
metrics_sync.update_best_block_at_source(best_number_at_source);
|
||||
metrics_sync.update_best_block_at_target(best_number_at_target);
|
||||
}
|
||||
*state.progress = print_sync_progress::<P>(*state.progress, best_number_at_source, best_number_at_target);
|
||||
*state.progress =
|
||||
print_sync_progress::<P>(*state.progress, best_number_at_source, best_number_at_target);
|
||||
|
||||
// if we have already submitted header, then we just need to wait for it
|
||||
// if we're waiting too much, then we believe our transaction has been lost and restart sync
|
||||
@@ -324,9 +320,9 @@ where
|
||||
P::TARGET_NAME,
|
||||
);
|
||||
|
||||
return Err(Error::Stalled);
|
||||
return Err(Error::Stalled)
|
||||
} else {
|
||||
return Ok(Some(last_transaction));
|
||||
return Ok(Some(last_transaction))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -343,10 +339,8 @@ where
|
||||
.await?
|
||||
{
|
||||
Some((header, justification)) => {
|
||||
let new_transaction = Transaction {
|
||||
time: Instant::now(),
|
||||
submitted_header_number: header.number(),
|
||||
};
|
||||
let new_transaction =
|
||||
Transaction { time: Instant::now(), submitted_header_number: header.number() };
|
||||
|
||||
log::debug!(
|
||||
target: "bridge",
|
||||
@@ -361,7 +355,7 @@ where
|
||||
.await
|
||||
.map_err(Error::Target)?;
|
||||
Ok(Some(new_transaction))
|
||||
}
|
||||
},
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
@@ -398,15 +392,15 @@ where
|
||||
)
|
||||
.await?;
|
||||
let (mut unjustified_headers, mut selected_finality_proof) = match selected_finality_proof {
|
||||
SelectedFinalityProof::Mandatory(header, finality_proof) => return Ok(Some((header, finality_proof))),
|
||||
SelectedFinalityProof::Mandatory(header, finality_proof) =>
|
||||
return Ok(Some((header, finality_proof))),
|
||||
_ if sync_params.only_mandatory_headers => {
|
||||
// we are not reading finality proofs from the stream, so eventually it'll break
|
||||
// but we don't care about transient proofs at all, so it is acceptable
|
||||
return Ok(None);
|
||||
}
|
||||
SelectedFinalityProof::Regular(unjustified_headers, header, finality_proof) => {
|
||||
(unjustified_headers, Some((header, finality_proof)))
|
||||
}
|
||||
return Ok(None)
|
||||
},
|
||||
SelectedFinalityProof::Regular(unjustified_headers, header, finality_proof) =>
|
||||
(unjustified_headers, Some((header, finality_proof))),
|
||||
SelectedFinalityProof::None(unjustified_headers) => (unjustified_headers, None),
|
||||
};
|
||||
|
||||
@@ -451,7 +445,11 @@ pub(crate) enum SelectedFinalityProof<Header, FinalityProof> {
|
||||
/// Otherwise, `SelectedFinalityProof::None` is returned.
|
||||
///
|
||||
/// Unless we have found mandatory header, all missing headers are collected and returned.
|
||||
pub(crate) async fn read_missing_headers<P: FinalitySyncPipeline, SC: SourceClient<P>, TC: TargetClient<P>>(
|
||||
pub(crate) async fn read_missing_headers<
|
||||
P: FinalitySyncPipeline,
|
||||
SC: SourceClient<P>,
|
||||
TC: TargetClient<P>,
|
||||
>(
|
||||
source_client: &SC,
|
||||
_target_client: &TC,
|
||||
best_number_at_source: P::Number,
|
||||
@@ -470,22 +468,30 @@ pub(crate) async fn read_missing_headers<P: FinalitySyncPipeline, SC: SourceClie
|
||||
match (is_mandatory, finality_proof) {
|
||||
(true, Some(finality_proof)) => {
|
||||
log::trace!(target: "bridge", "Header {:?} is mandatory", header_number);
|
||||
return Ok(SelectedFinalityProof::Mandatory(header, finality_proof));
|
||||
}
|
||||
return Ok(SelectedFinalityProof::Mandatory(header, finality_proof))
|
||||
},
|
||||
(true, None) => return Err(Error::MissingMandatoryFinalityProof(header.number())),
|
||||
(false, Some(finality_proof)) => {
|
||||
log::trace!(target: "bridge", "Header {:?} has persistent finality proof", header_number);
|
||||
unjustified_headers.clear();
|
||||
selected_finality_proof = Some((header, finality_proof));
|
||||
}
|
||||
},
|
||||
(false, None) => {
|
||||
unjustified_headers.push(header);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
header_number = header_number + One::one();
|
||||
}
|
||||
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Read {} {} headers. Selected finality proof for header: {:?}",
|
||||
best_number_at_source.saturating_sub(best_number_at_target),
|
||||
P::SOURCE_NAME,
|
||||
selected_finality_proof.as_ref().map(|(header, _)| header),
|
||||
);
|
||||
|
||||
Ok(match selected_finality_proof {
|
||||
Some((header, proof)) => SelectedFinalityProof::Regular(unjustified_headers, header, proof),
|
||||
None => SelectedFinalityProof::None(unjustified_headers),
|
||||
@@ -493,22 +499,46 @@ pub(crate) async fn read_missing_headers<P: FinalitySyncPipeline, SC: SourceClie
|
||||
}
|
||||
|
||||
/// Read finality proofs from the stream.
|
||||
pub(crate) fn read_finality_proofs_from_stream<P: FinalitySyncPipeline, FPS: Stream<Item = P::FinalityProof>>(
|
||||
pub(crate) fn read_finality_proofs_from_stream<
|
||||
P: FinalitySyncPipeline,
|
||||
FPS: Stream<Item = P::FinalityProof>,
|
||||
>(
|
||||
finality_proofs_stream: &mut RestartableFinalityProofsStream<FPS>,
|
||||
recent_finality_proofs: &mut FinalityProofs<P>,
|
||||
) {
|
||||
let mut proofs_count = 0;
|
||||
let mut first_header_number = None;
|
||||
let mut last_header_number = None;
|
||||
loop {
|
||||
let next_proof = finality_proofs_stream.stream.next();
|
||||
let finality_proof = match next_proof.now_or_never() {
|
||||
Some(Some(finality_proof)) => finality_proof,
|
||||
Some(None) => {
|
||||
finality_proofs_stream.needs_restart = true;
|
||||
break;
|
||||
}
|
||||
break
|
||||
},
|
||||
None => break,
|
||||
};
|
||||
|
||||
recent_finality_proofs.push((finality_proof.target_header_number(), finality_proof));
|
||||
let target_header_number = finality_proof.target_header_number();
|
||||
if first_header_number.is_none() {
|
||||
first_header_number = Some(target_header_number);
|
||||
}
|
||||
last_header_number = Some(target_header_number);
|
||||
proofs_count += 1;
|
||||
|
||||
recent_finality_proofs.push((target_header_number, finality_proof));
|
||||
}
|
||||
|
||||
if proofs_count != 0 {
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Read {} finality proofs from {} finality stream for headers in range [{:?}; {:?}]",
|
||||
proofs_count,
|
||||
P::SOURCE_NAME,
|
||||
first_header_number,
|
||||
last_header_number,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -520,7 +550,13 @@ pub(crate) fn select_better_recent_finality_proof<P: FinalitySyncPipeline>(
|
||||
selected_finality_proof: Option<(P::Header, P::FinalityProof)>,
|
||||
) -> Option<(P::Header, P::FinalityProof)> {
|
||||
if unjustified_headers.is_empty() || recent_finality_proofs.is_empty() {
|
||||
return selected_finality_proof;
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Can not improve selected {} finality proof {:?}. No unjustified headers and recent proofs",
|
||||
P::SOURCE_NAME,
|
||||
selected_finality_proof.as_ref().map(|(h, _)| h.number()),
|
||||
);
|
||||
return selected_finality_proof
|
||||
}
|
||||
|
||||
const NOT_EMPTY_PROOF: &str = "we have checked that the vec is not empty; qed";
|
||||
@@ -542,9 +578,24 @@ pub(crate) fn select_better_recent_finality_proof<P: FinalitySyncPipeline>(
|
||||
let selected_finality_proof_index = recent_finality_proofs
|
||||
.binary_search_by_key(intersection.end(), |(number, _)| *number)
|
||||
.unwrap_or_else(|index| index.saturating_sub(1));
|
||||
let (selected_header_number, finality_proof) = &recent_finality_proofs[selected_finality_proof_index];
|
||||
if !intersection.contains(selected_header_number) {
|
||||
return selected_finality_proof;
|
||||
let (selected_header_number, finality_proof) =
|
||||
&recent_finality_proofs[selected_finality_proof_index];
|
||||
let has_selected_finality_proof = intersection.contains(selected_header_number);
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Trying to improve selected {} finality proof {:?}. Headers range: [{:?}; {:?}]. Proofs range: [{:?}; {:?}].\
|
||||
Trying to improve to: {:?}. Result: {}",
|
||||
P::SOURCE_NAME,
|
||||
selected_finality_proof.as_ref().map(|(h, _)| h.number()),
|
||||
unjustified_range_begin,
|
||||
unjustified_range_end,
|
||||
buffered_range_begin,
|
||||
buffered_range_end,
|
||||
selected_header_number,
|
||||
if has_selected_finality_proof { "improved" } else { "not improved" },
|
||||
);
|
||||
if !has_selected_finality_proof {
|
||||
return selected_finality_proof
|
||||
}
|
||||
|
||||
// now remove all obsolete headers and extract selected header
|
||||
@@ -560,20 +611,15 @@ pub(crate) fn prune_recent_finality_proofs<P: FinalitySyncPipeline>(
|
||||
recent_finality_proofs: &mut FinalityProofs<P>,
|
||||
recent_finality_proofs_limit: usize,
|
||||
) {
|
||||
let position =
|
||||
recent_finality_proofs.binary_search_by_key(&justified_header_number, |(header_number, _)| *header_number);
|
||||
let position = recent_finality_proofs
|
||||
.binary_search_by_key(&justified_header_number, |(header_number, _)| *header_number);
|
||||
|
||||
// remove all obsolete elements
|
||||
*recent_finality_proofs = recent_finality_proofs.split_off(
|
||||
position
|
||||
.map(|position| position + 1)
|
||||
.unwrap_or_else(|position| position),
|
||||
);
|
||||
*recent_finality_proofs = recent_finality_proofs
|
||||
.split_off(position.map(|position| position + 1).unwrap_or_else(|position| position));
|
||||
|
||||
// now - limit vec by size
|
||||
let split_index = recent_finality_proofs
|
||||
.len()
|
||||
.saturating_sub(recent_finality_proofs_limit);
|
||||
let split_index = recent_finality_proofs.len().saturating_sub(recent_finality_proofs_limit);
|
||||
*recent_finality_proofs = recent_finality_proofs.split_off(split_index);
|
||||
}
|
||||
|
||||
@@ -585,15 +631,15 @@ fn print_sync_progress<P: FinalitySyncPipeline>(
|
||||
let (prev_time, prev_best_number_at_target) = progress_context;
|
||||
let now = Instant::now();
|
||||
|
||||
let need_update = now - prev_time > Duration::from_secs(10)
|
||||
|| prev_best_number_at_target
|
||||
let need_update = now - prev_time > Duration::from_secs(10) ||
|
||||
prev_best_number_at_target
|
||||
.map(|prev_best_number_at_target| {
|
||||
best_number_at_target.saturating_sub(prev_best_number_at_target) > 10.into()
|
||||
})
|
||||
.unwrap_or(true);
|
||||
|
||||
if !need_update {
|
||||
return (prev_time, prev_best_number_at_target);
|
||||
return (prev_time, prev_best_number_at_target)
|
||||
}
|
||||
|
||||
log::info!(
|
||||
|
||||
@@ -18,17 +18,21 @@
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use crate::finality_loop::{
|
||||
prune_recent_finality_proofs, read_finality_proofs_from_stream, run, select_better_recent_finality_proof,
|
||||
select_header_to_submit, FinalityProofs, FinalitySyncParams, RestartableFinalityProofsStream, SourceClient,
|
||||
TargetClient,
|
||||
use crate::{
|
||||
finality_loop::{
|
||||
prune_recent_finality_proofs, read_finality_proofs_from_stream, run,
|
||||
select_better_recent_finality_proof, select_header_to_submit, FinalityProofs,
|
||||
FinalitySyncParams, RestartableFinalityProofsStream, SourceClient, TargetClient,
|
||||
},
|
||||
FinalityProof, FinalitySyncPipeline, SourceHeader,
|
||||
};
|
||||
use crate::{FinalityProof, FinalitySyncPipeline, SourceHeader};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use futures::{FutureExt, Stream, StreamExt};
|
||||
use parking_lot::Mutex;
|
||||
use relay_utils::{metrics::MetricsParams, relay_loop::Client as RelayClient, MaybeConnectionError};
|
||||
use relay_utils::{
|
||||
metrics::MetricsParams, relay_loop::Client as RelayClient, MaybeConnectionError,
|
||||
};
|
||||
use std::{collections::HashMap, pin::Pin, sync::Arc, time::Duration};
|
||||
|
||||
type IsMandatory = bool;
|
||||
@@ -121,10 +125,7 @@ impl SourceClient<TestFinalitySyncPipeline> for TestSourceClient {
|
||||
) -> Result<(TestSourceHeader, Option<TestFinalityProof>), TestError> {
|
||||
let mut data = self.data.lock();
|
||||
(self.on_method_call)(&mut *data);
|
||||
data.source_headers
|
||||
.get(&number)
|
||||
.cloned()
|
||||
.ok_or(TestError::NonConnection)
|
||||
data.source_headers.get(&number).cloned().ok_or(TestError::NonConnection)
|
||||
}
|
||||
|
||||
async fn finality_proofs(&self) -> Result<Self::FinalityProofsStream, TestError> {
|
||||
@@ -157,7 +158,11 @@ impl TargetClient<TestFinalitySyncPipeline> for TestTargetClient {
|
||||
Ok(data.target_best_block_number)
|
||||
}
|
||||
|
||||
async fn submit_finality_proof(&self, header: TestSourceHeader, proof: TestFinalityProof) -> Result<(), TestError> {
|
||||
async fn submit_finality_proof(
|
||||
&self,
|
||||
header: TestSourceHeader,
|
||||
proof: TestFinalityProof,
|
||||
) -> Result<(), TestError> {
|
||||
let mut data = self.data.lock();
|
||||
(self.on_method_call)(&mut *data);
|
||||
data.target_best_block_number = header.number();
|
||||
@@ -171,11 +176,12 @@ fn prepare_test_clients(
|
||||
state_function: impl Fn(&mut ClientsData) -> bool + Send + Sync + 'static,
|
||||
source_headers: HashMap<TestNumber, (TestSourceHeader, Option<TestFinalityProof>)>,
|
||||
) -> (TestSourceClient, TestTargetClient) {
|
||||
let internal_state_function: Arc<dyn Fn(&mut ClientsData) + Send + Sync> = Arc::new(move |data| {
|
||||
if state_function(data) {
|
||||
exit_sender.unbounded_send(()).unwrap();
|
||||
}
|
||||
});
|
||||
let internal_state_function: Arc<dyn Fn(&mut ClientsData) + Send + Sync> =
|
||||
Arc::new(move |data| {
|
||||
if state_function(data) {
|
||||
exit_sender.unbounded_send(()).unwrap();
|
||||
}
|
||||
});
|
||||
let clients_data = Arc::new(Mutex::new(ClientsData {
|
||||
source_best_block_number: 10,
|
||||
source_headers,
|
||||
@@ -189,14 +195,13 @@ fn prepare_test_clients(
|
||||
on_method_call: internal_state_function.clone(),
|
||||
data: clients_data.clone(),
|
||||
},
|
||||
TestTargetClient {
|
||||
on_method_call: internal_state_function,
|
||||
data: clients_data,
|
||||
},
|
||||
TestTargetClient { on_method_call: internal_state_function, data: clients_data },
|
||||
)
|
||||
}
|
||||
|
||||
fn run_sync_loop(state_function: impl Fn(&mut ClientsData) -> bool + Send + Sync + 'static) -> ClientsData {
|
||||
fn run_sync_loop(
|
||||
state_function: impl Fn(&mut ClientsData) -> bool + Send + Sync + 'static,
|
||||
) -> ClientsData {
|
||||
let (exit_sender, exit_receiver) = futures::channel::mpsc::unbounded();
|
||||
let (source_client, target_client) = prepare_test_clients(
|
||||
exit_sender,
|
||||
@@ -234,12 +239,13 @@ fn run_sync_loop(state_function: impl Fn(&mut ClientsData) -> bool + Send + Sync
|
||||
#[test]
|
||||
fn finality_sync_loop_works() {
|
||||
let client_data = run_sync_loop(|data| {
|
||||
// header#7 has persistent finality proof, but it isn't mandatory => it isn't submitted, because
|
||||
// header#8 has persistent finality proof && it is mandatory => it is submitted
|
||||
// header#9 has persistent finality proof, but it isn't mandatory => it is submitted, because
|
||||
// there are no more persistent finality proofs
|
||||
// header#7 has persistent finality proof, but it isn't mandatory => it isn't submitted,
|
||||
// because header#8 has persistent finality proof && it is mandatory => it is submitted
|
||||
// header#9 has persistent finality proof, but it isn't mandatory => it is submitted,
|
||||
// because there are no more persistent finality proofs
|
||||
//
|
||||
// once this ^^^ is done, we generate more blocks && read proof for blocks 12 and 14 from the stream
|
||||
// once this ^^^ is done, we generate more blocks && read proof for blocks 12 and 14 from
|
||||
// the stream
|
||||
if data.target_best_block_number == 9 {
|
||||
data.source_best_block_number = 14;
|
||||
data.source_headers.insert(11, (TestSourceHeader(false, 11), None));
|
||||
@@ -287,10 +293,7 @@ fn run_only_mandatory_headers_mode_test(
|
||||
vec![
|
||||
(6, (TestSourceHeader(false, 6), Some(TestFinalityProof(6)))),
|
||||
(7, (TestSourceHeader(false, 7), Some(TestFinalityProof(7)))),
|
||||
(
|
||||
8,
|
||||
(TestSourceHeader(has_mandatory_headers, 8), Some(TestFinalityProof(8))),
|
||||
),
|
||||
(8, (TestSourceHeader(has_mandatory_headers, 8), Some(TestFinalityProof(8)))),
|
||||
(9, (TestSourceHeader(false, 9), Some(TestFinalityProof(9)))),
|
||||
(10, (TestSourceHeader(false, 10), Some(TestFinalityProof(10)))),
|
||||
]
|
||||
@@ -357,7 +360,8 @@ fn select_better_recent_finality_proof_works() {
|
||||
Some((TestSourceHeader(false, 2), TestFinalityProof(2))),
|
||||
);
|
||||
|
||||
// if there's no intersection between recent finality proofs and unjustified headers, nothing is changed
|
||||
// if there's no intersection between recent finality proofs and unjustified headers, nothing is
|
||||
// changed
|
||||
let mut unjustified_headers = vec![TestSourceHeader(false, 9), TestSourceHeader(false, 10)];
|
||||
assert_eq!(
|
||||
select_better_recent_finality_proof::<TestFinalitySyncPipeline>(
|
||||
@@ -368,13 +372,10 @@ fn select_better_recent_finality_proof_works() {
|
||||
Some((TestSourceHeader(false, 2), TestFinalityProof(2))),
|
||||
);
|
||||
|
||||
// if there's intersection between recent finality proofs and unjustified headers, but there are no
|
||||
// proofs in this intersection, nothing is changed
|
||||
let mut unjustified_headers = vec![
|
||||
TestSourceHeader(false, 8),
|
||||
TestSourceHeader(false, 9),
|
||||
TestSourceHeader(false, 10),
|
||||
];
|
||||
// if there's intersection between recent finality proofs and unjustified headers, but there are
|
||||
// no proofs in this intersection, nothing is changed
|
||||
let mut unjustified_headers =
|
||||
vec![TestSourceHeader(false, 8), TestSourceHeader(false, 9), TestSourceHeader(false, 10)];
|
||||
assert_eq!(
|
||||
select_better_recent_finality_proof::<TestFinalitySyncPipeline>(
|
||||
&[(7, TestFinalityProof(7)), (11, TestFinalityProof(11))],
|
||||
@@ -385,22 +386,15 @@ fn select_better_recent_finality_proof_works() {
|
||||
);
|
||||
assert_eq!(
|
||||
unjustified_headers,
|
||||
vec![
|
||||
TestSourceHeader(false, 8),
|
||||
TestSourceHeader(false, 9),
|
||||
TestSourceHeader(false, 10)
|
||||
]
|
||||
vec![TestSourceHeader(false, 8), TestSourceHeader(false, 9), TestSourceHeader(false, 10)]
|
||||
);
|
||||
|
||||
// if there's intersection between recent finality proofs and unjustified headers and there's
|
||||
// a proof in this intersection:
|
||||
// - this better (last from intersection) proof is selected;
|
||||
// - 'obsolete' unjustified headers are pruned.
|
||||
let mut unjustified_headers = vec![
|
||||
TestSourceHeader(false, 8),
|
||||
TestSourceHeader(false, 9),
|
||||
TestSourceHeader(false, 10),
|
||||
];
|
||||
let mut unjustified_headers =
|
||||
vec![TestSourceHeader(false, 8), TestSourceHeader(false, 9), TestSourceHeader(false, 10)];
|
||||
assert_eq!(
|
||||
select_better_recent_finality_proof::<TestFinalitySyncPipeline>(
|
||||
&[(7, TestFinalityProof(7)), (9, TestFinalityProof(9))],
|
||||
@@ -416,7 +410,10 @@ fn read_finality_proofs_from_stream_works() {
|
||||
// when stream is currently empty, nothing is changed
|
||||
let mut recent_finality_proofs = vec![(1, TestFinalityProof(1))];
|
||||
let mut stream = futures::stream::pending().into();
|
||||
read_finality_proofs_from_stream::<TestFinalitySyncPipeline, _>(&mut stream, &mut recent_finality_proofs);
|
||||
read_finality_proofs_from_stream::<TestFinalitySyncPipeline, _>(
|
||||
&mut stream,
|
||||
&mut recent_finality_proofs,
|
||||
);
|
||||
assert_eq!(recent_finality_proofs, vec![(1, TestFinalityProof(1))]);
|
||||
assert!(!stream.needs_restart);
|
||||
|
||||
@@ -424,20 +421,20 @@ fn read_finality_proofs_from_stream_works() {
|
||||
let mut stream = futures::stream::iter(vec![TestFinalityProof(4)])
|
||||
.chain(futures::stream::pending())
|
||||
.into();
|
||||
read_finality_proofs_from_stream::<TestFinalitySyncPipeline, _>(&mut stream, &mut recent_finality_proofs);
|
||||
assert_eq!(
|
||||
recent_finality_proofs,
|
||||
vec![(1, TestFinalityProof(1)), (4, TestFinalityProof(4))]
|
||||
read_finality_proofs_from_stream::<TestFinalitySyncPipeline, _>(
|
||||
&mut stream,
|
||||
&mut recent_finality_proofs,
|
||||
);
|
||||
assert_eq!(recent_finality_proofs, vec![(1, TestFinalityProof(1)), (4, TestFinalityProof(4))]);
|
||||
assert!(!stream.needs_restart);
|
||||
|
||||
// when stream has ended, we'll need to restart it
|
||||
let mut stream = futures::stream::empty().into();
|
||||
read_finality_proofs_from_stream::<TestFinalitySyncPipeline, _>(&mut stream, &mut recent_finality_proofs);
|
||||
assert_eq!(
|
||||
recent_finality_proofs,
|
||||
vec![(1, TestFinalityProof(1)), (4, TestFinalityProof(4))]
|
||||
read_finality_proofs_from_stream::<TestFinalitySyncPipeline, _>(
|
||||
&mut stream,
|
||||
&mut recent_finality_proofs,
|
||||
);
|
||||
assert_eq!(recent_finality_proofs, vec![(1, TestFinalityProof(1)), (4, TestFinalityProof(4))]);
|
||||
assert!(stream.needs_restart);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,9 @@
|
||||
//! are still submitted to the target node, but are treated as auxiliary data as we are not trying
|
||||
//! to submit all source headers to the target node.
|
||||
|
||||
pub use crate::finality_loop::{metrics_prefix, run, FinalitySyncParams, SourceClient, TargetClient};
|
||||
pub use crate::finality_loop::{
|
||||
metrics_prefix, run, FinalitySyncParams, SourceClient, TargetClient,
|
||||
};
|
||||
|
||||
use bp_header_chain::FinalityProof;
|
||||
use std::fmt::Debug;
|
||||
|
||||
@@ -20,22 +20,33 @@
|
||||
//! may stay until source/target chain state isn't updated. When a header reaches the
|
||||
//! `ready` sub-queue, it may be submitted to the target chain.
|
||||
|
||||
use crate::sync_types::{HeaderIdOf, HeaderStatus, HeadersSyncPipeline, QueuedHeader, SourceHeader};
|
||||
use crate::sync_types::{
|
||||
HeaderIdOf, HeaderStatus, HeadersSyncPipeline, QueuedHeader, SourceHeader,
|
||||
};
|
||||
|
||||
use linked_hash_map::LinkedHashMap;
|
||||
use num_traits::{One, Zero};
|
||||
use relay_utils::HeaderId;
|
||||
use std::{
|
||||
collections::{btree_map::Entry as BTreeMapEntry, hash_map::Entry as HashMapEntry, BTreeMap, HashMap, HashSet},
|
||||
collections::{
|
||||
btree_map::Entry as BTreeMapEntry, hash_map::Entry as HashMapEntry, BTreeMap, HashMap,
|
||||
HashSet,
|
||||
},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
type HeadersQueue<P> =
|
||||
BTreeMap<<P as HeadersSyncPipeline>::Number, HashMap<<P as HeadersSyncPipeline>::Hash, QueuedHeader<P>>>;
|
||||
type SyncedChildren<P> =
|
||||
BTreeMap<<P as HeadersSyncPipeline>::Number, HashMap<<P as HeadersSyncPipeline>::Hash, HashSet<HeaderIdOf<P>>>>;
|
||||
type KnownHeaders<P> =
|
||||
BTreeMap<<P as HeadersSyncPipeline>::Number, HashMap<<P as HeadersSyncPipeline>::Hash, HeaderStatus>>;
|
||||
type HeadersQueue<P> = BTreeMap<
|
||||
<P as HeadersSyncPipeline>::Number,
|
||||
HashMap<<P as HeadersSyncPipeline>::Hash, QueuedHeader<P>>,
|
||||
>;
|
||||
type SyncedChildren<P> = BTreeMap<
|
||||
<P as HeadersSyncPipeline>::Number,
|
||||
HashMap<<P as HeadersSyncPipeline>::Hash, HashSet<HeaderIdOf<P>>>,
|
||||
>;
|
||||
type KnownHeaders<P> = BTreeMap<
|
||||
<P as HeadersSyncPipeline>::Number,
|
||||
HashMap<<P as HeadersSyncPipeline>::Hash, HeaderStatus>,
|
||||
>;
|
||||
|
||||
/// We're trying to fetch completion data for single header at this interval.
|
||||
const RETRY_FETCH_COMPLETION_INTERVAL: Duration = Duration::from_secs(20);
|
||||
@@ -65,7 +76,7 @@ pub struct QueuedHeaders<P: HeadersSyncPipeline> {
|
||||
/// Headers that are (we believe) currently submitted to target node by our,
|
||||
/// not-yet mined transactions.
|
||||
submitted: HeadersQueue<P>,
|
||||
/// Synced headers childrens. We need it to support case when header is synced, but some of
|
||||
/// Synced headers children. We need it to support case when header is synced, but some of
|
||||
/// its parents are incomplete.
|
||||
synced_children: SyncedChildren<P>,
|
||||
/// Pointers to all headers that we ever seen and we believe we can touch in the future.
|
||||
@@ -82,15 +93,6 @@ pub struct QueuedHeaders<P: HeadersSyncPipeline> {
|
||||
prune_border: P::Number,
|
||||
}
|
||||
|
||||
/// Header completion data.
|
||||
#[derive(Debug)]
|
||||
struct HeaderCompletion<Completion> {
|
||||
/// Last time when we tried to upload completion data to target node, if ever.
|
||||
pub last_upload_time: Option<Instant>,
|
||||
/// Completion data.
|
||||
pub completion: Completion,
|
||||
}
|
||||
|
||||
impl<P: HeadersSyncPipeline> Default for QueuedHeaders<P> {
|
||||
fn default() -> Self {
|
||||
QueuedHeaders {
|
||||
@@ -122,35 +124,31 @@ impl<P: HeadersSyncPipeline> QueuedHeaders<P> {
|
||||
pub fn headers_in_status(&self, status: HeaderStatus) -> usize {
|
||||
match status {
|
||||
HeaderStatus::Unknown | HeaderStatus::Synced => 0,
|
||||
HeaderStatus::MaybeOrphan => self
|
||||
.maybe_orphan
|
||||
.values()
|
||||
.fold(0, |total, headers| total + headers.len()),
|
||||
HeaderStatus::Orphan => self.orphan.values().fold(0, |total, headers| total + headers.len()),
|
||||
HeaderStatus::MaybeExtra => self
|
||||
.maybe_extra
|
||||
.values()
|
||||
.fold(0, |total, headers| total + headers.len()),
|
||||
HeaderStatus::Extra => self.extra.values().fold(0, |total, headers| total + headers.len()),
|
||||
HeaderStatus::Ready => self.ready.values().fold(0, |total, headers| total + headers.len()),
|
||||
HeaderStatus::Incomplete => self.incomplete.values().fold(0, |total, headers| total + headers.len()),
|
||||
HeaderStatus::Submitted => self.submitted.values().fold(0, |total, headers| total + headers.len()),
|
||||
HeaderStatus::MaybeOrphan =>
|
||||
self.maybe_orphan.values().fold(0, |total, headers| total + headers.len()),
|
||||
HeaderStatus::Orphan =>
|
||||
self.orphan.values().fold(0, |total, headers| total + headers.len()),
|
||||
HeaderStatus::MaybeExtra =>
|
||||
self.maybe_extra.values().fold(0, |total, headers| total + headers.len()),
|
||||
HeaderStatus::Extra =>
|
||||
self.extra.values().fold(0, |total, headers| total + headers.len()),
|
||||
HeaderStatus::Ready =>
|
||||
self.ready.values().fold(0, |total, headers| total + headers.len()),
|
||||
HeaderStatus::Incomplete =>
|
||||
self.incomplete.values().fold(0, |total, headers| total + headers.len()),
|
||||
HeaderStatus::Submitted =>
|
||||
self.submitted.values().fold(0, |total, headers| total + headers.len()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns number of headers that are currently in the queue.
|
||||
pub fn total_headers(&self) -> usize {
|
||||
self.maybe_orphan
|
||||
.values()
|
||||
.fold(0, |total, headers| total + headers.len())
|
||||
+ self.orphan.values().fold(0, |total, headers| total + headers.len())
|
||||
+ self
|
||||
.maybe_extra
|
||||
.values()
|
||||
.fold(0, |total, headers| total + headers.len())
|
||||
+ self.extra.values().fold(0, |total, headers| total + headers.len())
|
||||
+ self.ready.values().fold(0, |total, headers| total + headers.len())
|
||||
+ self.incomplete.values().fold(0, |total, headers| total + headers.len())
|
||||
self.maybe_orphan.values().fold(0, |total, headers| total + headers.len()) +
|
||||
self.orphan.values().fold(0, |total, headers| total + headers.len()) +
|
||||
self.maybe_extra.values().fold(0, |total, headers| total + headers.len()) +
|
||||
self.extra.values().fold(0, |total, headers| total + headers.len()) +
|
||||
self.ready.values().fold(0, |total, headers| total + headers.len()) +
|
||||
self.incomplete.values().fold(0, |total, headers| total + headers.len())
|
||||
}
|
||||
|
||||
/// Returns number of best block in the queue.
|
||||
@@ -166,8 +164,16 @@ impl<P: HeadersSyncPipeline> QueuedHeaders<P> {
|
||||
std::cmp::max(
|
||||
self.ready.keys().next_back().cloned().unwrap_or_else(Zero::zero),
|
||||
std::cmp::max(
|
||||
self.incomplete.keys().next_back().cloned().unwrap_or_else(Zero::zero),
|
||||
self.submitted.keys().next_back().cloned().unwrap_or_else(Zero::zero),
|
||||
self.incomplete
|
||||
.keys()
|
||||
.next_back()
|
||||
.cloned()
|
||||
.unwrap_or_else(Zero::zero),
|
||||
self.submitted
|
||||
.keys()
|
||||
.next_back()
|
||||
.cloned()
|
||||
.unwrap_or_else(Zero::zero),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -191,7 +197,7 @@ impl<P: HeadersSyncPipeline> QueuedHeaders<P> {
|
||||
.unwrap_or(HeaderStatus::Unknown)
|
||||
}
|
||||
|
||||
/// Get oldest header from given queue.
|
||||
/// Get the oldest header from given queue.
|
||||
pub fn header(&self, status: HeaderStatus) -> Option<&QueuedHeader<P>> {
|
||||
match status {
|
||||
HeaderStatus::Unknown | HeaderStatus::Synced => None,
|
||||
@@ -205,7 +211,7 @@ impl<P: HeadersSyncPipeline> QueuedHeaders<P> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get oldest headers from given queue until functor will return false.
|
||||
/// Get the oldest headers from given queue until functor will return false.
|
||||
pub fn headers(
|
||||
&self,
|
||||
status: HeaderStatus,
|
||||
@@ -235,7 +241,7 @@ impl<P: HeadersSyncPipeline> QueuedHeaders<P> {
|
||||
id,
|
||||
status,
|
||||
);
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
if id.0 < self.prune_border {
|
||||
@@ -245,7 +251,7 @@ impl<P: HeadersSyncPipeline> QueuedHeaders<P> {
|
||||
P::SOURCE_NAME,
|
||||
id,
|
||||
);
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
let parent_id = header.parent_id();
|
||||
@@ -256,20 +262,20 @@ impl<P: HeadersSyncPipeline> QueuedHeaders<P> {
|
||||
HeaderStatus::Unknown | HeaderStatus::MaybeOrphan => {
|
||||
insert_header(&mut self.maybe_orphan, id, header);
|
||||
HeaderStatus::MaybeOrphan
|
||||
}
|
||||
},
|
||||
HeaderStatus::Orphan => {
|
||||
insert_header(&mut self.orphan, id, header);
|
||||
HeaderStatus::Orphan
|
||||
}
|
||||
HeaderStatus::MaybeExtra
|
||||
| HeaderStatus::Extra
|
||||
| HeaderStatus::Ready
|
||||
| HeaderStatus::Incomplete
|
||||
| HeaderStatus::Submitted
|
||||
| HeaderStatus::Synced => {
|
||||
},
|
||||
HeaderStatus::MaybeExtra |
|
||||
HeaderStatus::Extra |
|
||||
HeaderStatus::Ready |
|
||||
HeaderStatus::Incomplete |
|
||||
HeaderStatus::Submitted |
|
||||
HeaderStatus::Synced => {
|
||||
insert_header(&mut self.maybe_extra, id, header);
|
||||
HeaderStatus::MaybeExtra
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
self.known_headers.entry(id.0).or_default().insert(id.1, status);
|
||||
@@ -282,7 +288,7 @@ impl<P: HeadersSyncPipeline> QueuedHeaders<P> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Receive best header from the target node.
|
||||
/// Receive the best header from the target node.
|
||||
pub fn target_best_header_response(&mut self, id: &HeaderIdOf<P>) {
|
||||
self.header_synced(id)
|
||||
}
|
||||
@@ -297,7 +303,7 @@ impl<P: HeadersSyncPipeline> QueuedHeaders<P> {
|
||||
HeaderStatus::Orphan,
|
||||
id,
|
||||
);
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
move_header_descendants::<P>(
|
||||
@@ -360,8 +366,8 @@ impl<P: HeadersSyncPipeline> QueuedHeaders<P> {
|
||||
id,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
return
|
||||
},
|
||||
};
|
||||
|
||||
// do not remove from `incomplete_headers` here, because otherwise we'll miss
|
||||
@@ -423,14 +429,20 @@ impl<P: HeadersSyncPipeline> QueuedHeaders<P> {
|
||||
}
|
||||
|
||||
/// Marks given headers incomplete.
|
||||
pub fn add_incomplete_headers(&mut self, make_header_incomplete: bool, new_incomplete_headers: Vec<HeaderIdOf<P>>) {
|
||||
pub fn add_incomplete_headers(
|
||||
&mut self,
|
||||
make_header_incomplete: bool,
|
||||
new_incomplete_headers: Vec<HeaderIdOf<P>>,
|
||||
) {
|
||||
for new_incomplete_header in new_incomplete_headers {
|
||||
if make_header_incomplete {
|
||||
self.header_synced(&new_incomplete_header);
|
||||
}
|
||||
|
||||
let move_origins = select_synced_children::<P>(&self.synced_children, &new_incomplete_header);
|
||||
let move_origins = move_origins.into_iter().chain(std::iter::once(new_incomplete_header));
|
||||
let move_origins =
|
||||
select_synced_children::<P>(&self.synced_children, &new_incomplete_header);
|
||||
let move_origins =
|
||||
move_origins.into_iter().chain(std::iter::once(new_incomplete_header));
|
||||
for move_origin in move_origins {
|
||||
move_header_descendants::<P>(
|
||||
&mut [&mut self.ready, &mut self.submitted],
|
||||
@@ -453,13 +465,15 @@ impl<P: HeadersSyncPipeline> QueuedHeaders<P> {
|
||||
}
|
||||
}
|
||||
|
||||
/// When incomplete headers ids are receved from target node.
|
||||
/// When incomplete headers ids are received from target node.
|
||||
pub fn incomplete_headers_response(&mut self, ids: HashSet<HeaderIdOf<P>>) {
|
||||
// all new incomplete headers are marked Synced and all their descendants
|
||||
// are moved from Ready/Submitted to Incomplete queue
|
||||
let new_incomplete_headers = ids
|
||||
.iter()
|
||||
.filter(|id| !self.incomplete_headers.contains_key(id) && !self.completion_data.contains_key(id))
|
||||
.filter(|id| {
|
||||
!self.incomplete_headers.contains_key(id) && !self.completion_data.contains_key(id)
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
self.add_incomplete_headers(true, new_incomplete_headers);
|
||||
@@ -477,8 +491,10 @@ impl<P: HeadersSyncPipeline> QueuedHeaders<P> {
|
||||
// sub2eth rejects H if H.Parent is incomplete
|
||||
// sub2sub allows 'syncing' headers like that
|
||||
// => let's check if there are some synced children of just completed header
|
||||
let move_origins = select_synced_children::<P>(&self.synced_children, &just_completed_header);
|
||||
let move_origins = move_origins.into_iter().chain(std::iter::once(just_completed_header));
|
||||
let move_origins =
|
||||
select_synced_children::<P>(&self.synced_children, &just_completed_header);
|
||||
let move_origins =
|
||||
move_origins.into_iter().chain(std::iter::once(just_completed_header));
|
||||
for move_origin in move_origins {
|
||||
move_header_descendants::<P>(
|
||||
&mut [&mut self.incomplete],
|
||||
@@ -509,7 +525,8 @@ impl<P: HeadersSyncPipeline> QueuedHeaders<P> {
|
||||
pub fn incomplete_header(&mut self) -> Option<HeaderIdOf<P>> {
|
||||
queued_incomplete_header(&mut self.incomplete_headers, |last_fetch_time| {
|
||||
let retry = match *last_fetch_time {
|
||||
Some(last_fetch_time) => last_fetch_time.elapsed() > RETRY_FETCH_COMPLETION_INTERVAL,
|
||||
Some(last_fetch_time) =>
|
||||
last_fetch_time.elapsed() > RETRY_FETCH_COMPLETION_INTERVAL,
|
||||
None => true,
|
||||
};
|
||||
|
||||
@@ -530,7 +547,7 @@ impl<P: HeadersSyncPipeline> QueuedHeaders<P> {
|
||||
/// Prune and never accept headers before this block.
|
||||
pub fn prune(&mut self, prune_border: P::Number) {
|
||||
if prune_border <= self.prune_border {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
prune_queue(&mut self.maybe_orphan, prune_border);
|
||||
@@ -579,10 +596,10 @@ impl<P: HeadersSyncPipeline> QueuedHeaders<P> {
|
||||
match header {
|
||||
Some(header) => {
|
||||
let parent_id = header.header().parent_id();
|
||||
self.incomplete_headers.contains_key(&parent_id)
|
||||
|| self.completion_data.contains_key(&parent_id)
|
||||
|| self.status(&parent_id) == HeaderStatus::Incomplete
|
||||
}
|
||||
self.incomplete_headers.contains_key(&parent_id) ||
|
||||
self.completion_data.contains_key(&parent_id) ||
|
||||
self.status(&parent_id) == HeaderStatus::Incomplete
|
||||
},
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
@@ -612,12 +629,8 @@ impl<P: HeadersSyncPipeline> QueuedHeaders<P> {
|
||||
.expect("header has a given status; given queue has the header; qed");
|
||||
|
||||
// remember ids of all the children of the current header
|
||||
let synced_children_entry = self
|
||||
.synced_children
|
||||
.entry(current.0)
|
||||
.or_default()
|
||||
.entry(current.1)
|
||||
.or_default();
|
||||
let synced_children_entry =
|
||||
self.synced_children.entry(current.0).or_default().entry(current.1).or_default();
|
||||
let all_queues = [
|
||||
&self.maybe_orphan,
|
||||
&self.orphan,
|
||||
@@ -633,7 +646,9 @@ impl<P: HeadersSyncPipeline> QueuedHeaders<P> {
|
||||
.map(|potential_children| {
|
||||
potential_children
|
||||
.values()
|
||||
.filter(|potential_child| potential_child.header().parent_id() == current)
|
||||
.filter(|potential_child| {
|
||||
potential_child.header().parent_id() == current
|
||||
})
|
||||
.map(|child| child.id())
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
@@ -670,12 +685,19 @@ impl<P: HeadersSyncPipeline> QueuedHeaders<P> {
|
||||
}
|
||||
|
||||
/// Insert header to the queue.
|
||||
fn insert_header<P: HeadersSyncPipeline>(queue: &mut HeadersQueue<P>, id: HeaderIdOf<P>, header: QueuedHeader<P>) {
|
||||
fn insert_header<P: HeadersSyncPipeline>(
|
||||
queue: &mut HeadersQueue<P>,
|
||||
id: HeaderIdOf<P>,
|
||||
header: QueuedHeader<P>,
|
||||
) {
|
||||
queue.entry(id.0).or_default().insert(id.1, header);
|
||||
}
|
||||
|
||||
/// Remove header from the queue.
|
||||
fn remove_header<P: HeadersSyncPipeline>(queue: &mut HeadersQueue<P>, id: &HeaderIdOf<P>) -> Option<QueuedHeader<P>> {
|
||||
fn remove_header<P: HeadersSyncPipeline>(
|
||||
queue: &mut HeadersQueue<P>,
|
||||
id: &HeaderIdOf<P>,
|
||||
) -> Option<QueuedHeader<P>> {
|
||||
let mut headers_at = match queue.entry(id.0) {
|
||||
BTreeMapEntry::Occupied(headers_at) => headers_at,
|
||||
BTreeMapEntry::Vacant(_) => return None,
|
||||
@@ -689,7 +711,10 @@ fn remove_header<P: HeadersSyncPipeline>(queue: &mut HeadersQueue<P>, id: &Heade
|
||||
}
|
||||
|
||||
/// Get header from the queue.
|
||||
fn header<'a, P: HeadersSyncPipeline>(queue: &'a HeadersQueue<P>, id: &HeaderIdOf<P>) -> Option<&'a QueuedHeader<P>> {
|
||||
fn header<'a, P: HeadersSyncPipeline>(
|
||||
queue: &'a HeadersQueue<P>,
|
||||
id: &HeaderIdOf<P>,
|
||||
) -> Option<&'a QueuedHeader<P>> {
|
||||
queue.get(&id.0).and_then(|by_hash| by_hash.get(&id.1))
|
||||
}
|
||||
|
||||
@@ -808,11 +833,7 @@ fn oldest_headers<P: HeadersSyncPipeline>(
|
||||
queue: &HeadersQueue<P>,
|
||||
mut f: impl FnMut(&QueuedHeader<P>) -> bool,
|
||||
) -> Option<Vec<&QueuedHeader<P>>> {
|
||||
let result = queue
|
||||
.values()
|
||||
.flat_map(|h| h.values())
|
||||
.take_while(|h| f(h))
|
||||
.collect::<Vec<_>>();
|
||||
let result = queue.values().flat_map(|h| h.values()).take_while(|h| f(h)).collect::<Vec<_>>();
|
||||
if result.is_empty() {
|
||||
None
|
||||
} else {
|
||||
@@ -826,7 +847,10 @@ fn prune_queue<P: HeadersSyncPipeline>(queue: &mut HeadersQueue<P>, prune_border
|
||||
}
|
||||
|
||||
/// Forget all known headers with number less than given.
|
||||
fn prune_known_headers<P: HeadersSyncPipeline>(known_headers: &mut KnownHeaders<P>, prune_border: P::Number) {
|
||||
fn prune_known_headers<P: HeadersSyncPipeline>(
|
||||
known_headers: &mut KnownHeaders<P>,
|
||||
prune_border: P::Number,
|
||||
) {
|
||||
let new_known_headers = known_headers.split_off(&prune_border);
|
||||
for (pruned_number, pruned_headers) in &*known_headers {
|
||||
for pruned_hash in pruned_headers.keys() {
|
||||
@@ -857,8 +881,8 @@ fn queued_incomplete_header<Id: Clone + Eq + std::hash::Hash, T>(
|
||||
map: &mut LinkedHashMap<Id, T>,
|
||||
filter: impl FnMut(&mut T) -> bool,
|
||||
) -> Option<(Id, &T)> {
|
||||
// TODO (#84): headers that have been just appended to the end of the queue would have to wait until
|
||||
// all previous headers will be retried
|
||||
// TODO (#84): headers that have been just appended to the end of the queue would have to wait
|
||||
// until all previous headers will be retried
|
||||
|
||||
let retry_old_header = map
|
||||
.front()
|
||||
@@ -866,9 +890,10 @@ fn queued_incomplete_header<Id: Clone + Eq + std::hash::Hash, T>(
|
||||
.and_then(|key| map.get_mut(&key).map(filter))
|
||||
.unwrap_or(false);
|
||||
if retry_old_header {
|
||||
let (header_key, header) = map.pop_front().expect("we have checked that front() exists; qed");
|
||||
let (header_key, header) =
|
||||
map.pop_front().expect("we have checked that front() exists; qed");
|
||||
map.insert(header_key, header);
|
||||
return map.back().map(|(id, data)| (id.clone(), data));
|
||||
return map.back().map(|(id, data)| (id.clone(), data))
|
||||
}
|
||||
|
||||
None
|
||||
@@ -877,15 +902,15 @@ fn queued_incomplete_header<Id: Clone + Eq + std::hash::Hash, T>(
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::sync_loop_tests::{TestHash, TestHeader, TestHeaderId, TestHeadersSyncPipeline, TestNumber};
|
||||
use crate::sync_types::QueuedHeader;
|
||||
use crate::{
|
||||
sync_loop_tests::{
|
||||
TestHash, TestHeader, TestHeaderId, TestHeadersSyncPipeline, TestNumber,
|
||||
},
|
||||
sync_types::QueuedHeader,
|
||||
};
|
||||
|
||||
pub(crate) fn header(number: TestNumber) -> QueuedHeader<TestHeadersSyncPipeline> {
|
||||
QueuedHeader::new(TestHeader {
|
||||
number,
|
||||
hash: hash(number),
|
||||
parent_hash: hash(number - 1),
|
||||
})
|
||||
QueuedHeader::new(TestHeader { number, hash: hash(number), parent_hash: hash(number - 1) })
|
||||
}
|
||||
|
||||
pub(crate) fn hash(number: TestNumber) -> TestHash {
|
||||
@@ -900,34 +925,41 @@ pub(crate) mod tests {
|
||||
fn total_headers_works() {
|
||||
// total headers just sums up number of headers in every queue
|
||||
let mut queue = QueuedHeaders::<TestHeadersSyncPipeline>::default();
|
||||
queue.maybe_orphan.entry(1).or_default().insert(
|
||||
hash(1),
|
||||
QueuedHeader::<TestHeadersSyncPipeline>::new(Default::default()),
|
||||
);
|
||||
queue.maybe_orphan.entry(1).or_default().insert(
|
||||
hash(2),
|
||||
QueuedHeader::<TestHeadersSyncPipeline>::new(Default::default()),
|
||||
);
|
||||
queue.maybe_orphan.entry(2).or_default().insert(
|
||||
hash(3),
|
||||
QueuedHeader::<TestHeadersSyncPipeline>::new(Default::default()),
|
||||
);
|
||||
queue.orphan.entry(3).or_default().insert(
|
||||
hash(4),
|
||||
QueuedHeader::<TestHeadersSyncPipeline>::new(Default::default()),
|
||||
);
|
||||
queue.maybe_extra.entry(4).or_default().insert(
|
||||
hash(5),
|
||||
QueuedHeader::<TestHeadersSyncPipeline>::new(Default::default()),
|
||||
);
|
||||
queue.ready.entry(5).or_default().insert(
|
||||
hash(6),
|
||||
QueuedHeader::<TestHeadersSyncPipeline>::new(Default::default()),
|
||||
);
|
||||
queue.incomplete.entry(6).or_default().insert(
|
||||
hash(7),
|
||||
QueuedHeader::<TestHeadersSyncPipeline>::new(Default::default()),
|
||||
);
|
||||
queue
|
||||
.maybe_orphan
|
||||
.entry(1)
|
||||
.or_default()
|
||||
.insert(hash(1), QueuedHeader::<TestHeadersSyncPipeline>::new(Default::default()));
|
||||
queue
|
||||
.maybe_orphan
|
||||
.entry(1)
|
||||
.or_default()
|
||||
.insert(hash(2), QueuedHeader::<TestHeadersSyncPipeline>::new(Default::default()));
|
||||
queue
|
||||
.maybe_orphan
|
||||
.entry(2)
|
||||
.or_default()
|
||||
.insert(hash(3), QueuedHeader::<TestHeadersSyncPipeline>::new(Default::default()));
|
||||
queue
|
||||
.orphan
|
||||
.entry(3)
|
||||
.or_default()
|
||||
.insert(hash(4), QueuedHeader::<TestHeadersSyncPipeline>::new(Default::default()));
|
||||
queue
|
||||
.maybe_extra
|
||||
.entry(4)
|
||||
.or_default()
|
||||
.insert(hash(5), QueuedHeader::<TestHeadersSyncPipeline>::new(Default::default()));
|
||||
queue
|
||||
.ready
|
||||
.entry(5)
|
||||
.or_default()
|
||||
.insert(hash(6), QueuedHeader::<TestHeadersSyncPipeline>::new(Default::default()));
|
||||
queue
|
||||
.incomplete
|
||||
.entry(6)
|
||||
.or_default()
|
||||
.insert(hash(7), QueuedHeader::<TestHeadersSyncPipeline>::new(Default::default()));
|
||||
assert_eq!(queue.total_headers(), 7);
|
||||
}
|
||||
|
||||
@@ -935,48 +967,56 @@ pub(crate) mod tests {
|
||||
fn best_queued_number_works() {
|
||||
// initially there are headers in MaybeOrphan queue only
|
||||
let mut queue = QueuedHeaders::<TestHeadersSyncPipeline>::default();
|
||||
queue.maybe_orphan.entry(1).or_default().insert(
|
||||
hash(1),
|
||||
QueuedHeader::<TestHeadersSyncPipeline>::new(Default::default()),
|
||||
);
|
||||
queue.maybe_orphan.entry(1).or_default().insert(
|
||||
hash(2),
|
||||
QueuedHeader::<TestHeadersSyncPipeline>::new(Default::default()),
|
||||
);
|
||||
queue.maybe_orphan.entry(3).or_default().insert(
|
||||
hash(3),
|
||||
QueuedHeader::<TestHeadersSyncPipeline>::new(Default::default()),
|
||||
);
|
||||
queue
|
||||
.maybe_orphan
|
||||
.entry(1)
|
||||
.or_default()
|
||||
.insert(hash(1), QueuedHeader::<TestHeadersSyncPipeline>::new(Default::default()));
|
||||
queue
|
||||
.maybe_orphan
|
||||
.entry(1)
|
||||
.or_default()
|
||||
.insert(hash(2), QueuedHeader::<TestHeadersSyncPipeline>::new(Default::default()));
|
||||
queue
|
||||
.maybe_orphan
|
||||
.entry(3)
|
||||
.or_default()
|
||||
.insert(hash(3), QueuedHeader::<TestHeadersSyncPipeline>::new(Default::default()));
|
||||
assert_eq!(queue.best_queued_number(), 3);
|
||||
// and then there's better header in Orphan
|
||||
queue.orphan.entry(10).or_default().insert(
|
||||
hash(10),
|
||||
QueuedHeader::<TestHeadersSyncPipeline>::new(Default::default()),
|
||||
);
|
||||
queue
|
||||
.orphan
|
||||
.entry(10)
|
||||
.or_default()
|
||||
.insert(hash(10), QueuedHeader::<TestHeadersSyncPipeline>::new(Default::default()));
|
||||
assert_eq!(queue.best_queued_number(), 10);
|
||||
// and then there's better header in MaybeExtra
|
||||
queue.maybe_extra.entry(20).or_default().insert(
|
||||
hash(20),
|
||||
QueuedHeader::<TestHeadersSyncPipeline>::new(Default::default()),
|
||||
);
|
||||
queue
|
||||
.maybe_extra
|
||||
.entry(20)
|
||||
.or_default()
|
||||
.insert(hash(20), QueuedHeader::<TestHeadersSyncPipeline>::new(Default::default()));
|
||||
assert_eq!(queue.best_queued_number(), 20);
|
||||
// and then there's better header in Ready
|
||||
queue.ready.entry(30).or_default().insert(
|
||||
hash(30),
|
||||
QueuedHeader::<TestHeadersSyncPipeline>::new(Default::default()),
|
||||
);
|
||||
queue
|
||||
.ready
|
||||
.entry(30)
|
||||
.or_default()
|
||||
.insert(hash(30), QueuedHeader::<TestHeadersSyncPipeline>::new(Default::default()));
|
||||
assert_eq!(queue.best_queued_number(), 30);
|
||||
// and then there's better header in MaybeOrphan again
|
||||
queue.maybe_orphan.entry(40).or_default().insert(
|
||||
hash(40),
|
||||
QueuedHeader::<TestHeadersSyncPipeline>::new(Default::default()),
|
||||
);
|
||||
queue
|
||||
.maybe_orphan
|
||||
.entry(40)
|
||||
.or_default()
|
||||
.insert(hash(40), QueuedHeader::<TestHeadersSyncPipeline>::new(Default::default()));
|
||||
assert_eq!(queue.best_queued_number(), 40);
|
||||
// and then there's some header in Incomplete
|
||||
queue.incomplete.entry(50).or_default().insert(
|
||||
hash(50),
|
||||
QueuedHeader::<TestHeadersSyncPipeline>::new(Default::default()),
|
||||
);
|
||||
queue
|
||||
.incomplete
|
||||
.entry(50)
|
||||
.or_default()
|
||||
.insert(hash(50), QueuedHeader::<TestHeadersSyncPipeline>::new(Default::default()));
|
||||
assert_eq!(queue.best_queued_number(), 50);
|
||||
}
|
||||
|
||||
@@ -986,11 +1026,7 @@ pub(crate) mod tests {
|
||||
let mut queue = QueuedHeaders::<TestHeadersSyncPipeline>::default();
|
||||
assert_eq!(queue.status(&id(10)), HeaderStatus::Unknown);
|
||||
// and status is read from the KnownHeaders
|
||||
queue
|
||||
.known_headers
|
||||
.entry(10)
|
||||
.or_default()
|
||||
.insert(hash(10), HeaderStatus::Ready);
|
||||
queue.known_headers.entry(10).or_default().insert(hash(10), HeaderStatus::Ready);
|
||||
assert_eq!(queue.status(&id(10)), HeaderStatus::Ready);
|
||||
}
|
||||
|
||||
@@ -999,22 +1035,13 @@ pub(crate) mod tests {
|
||||
// initially we have oldest header #10
|
||||
let mut queue = QueuedHeaders::<TestHeadersSyncPipeline>::default();
|
||||
queue.maybe_orphan.entry(10).or_default().insert(hash(1), header(100));
|
||||
assert_eq!(
|
||||
queue.header(HeaderStatus::MaybeOrphan).unwrap().header().hash,
|
||||
hash(100)
|
||||
);
|
||||
assert_eq!(queue.header(HeaderStatus::MaybeOrphan).unwrap().header().hash, hash(100));
|
||||
// inserting #20 changes nothing
|
||||
queue.maybe_orphan.entry(20).or_default().insert(hash(1), header(101));
|
||||
assert_eq!(
|
||||
queue.header(HeaderStatus::MaybeOrphan).unwrap().header().hash,
|
||||
hash(100)
|
||||
);
|
||||
assert_eq!(queue.header(HeaderStatus::MaybeOrphan).unwrap().header().hash, hash(100));
|
||||
// inserting #5 makes it oldest
|
||||
queue.maybe_orphan.entry(5).or_default().insert(hash(1), header(102));
|
||||
assert_eq!(
|
||||
queue.header(HeaderStatus::MaybeOrphan).unwrap().header().hash,
|
||||
hash(102)
|
||||
);
|
||||
assert_eq!(queue.header(HeaderStatus::MaybeOrphan).unwrap().header().hash, hash(102));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1100,11 +1127,7 @@ pub(crate) mod tests {
|
||||
.entry(100)
|
||||
.or_default()
|
||||
.insert(hash(100), HeaderStatus::MaybeOrphan);
|
||||
queue
|
||||
.maybe_orphan
|
||||
.entry(100)
|
||||
.or_default()
|
||||
.insert(hash(100), header(100));
|
||||
queue.maybe_orphan.entry(100).or_default().insert(hash(100), header(100));
|
||||
queue
|
||||
.known_headers
|
||||
.entry(99)
|
||||
@@ -1117,17 +1140,9 @@ pub(crate) mod tests {
|
||||
.or_default()
|
||||
.insert(hash(98), HeaderStatus::MaybeExtra);
|
||||
queue.maybe_extra.entry(98).or_default().insert(hash(98), header(98));
|
||||
queue
|
||||
.known_headers
|
||||
.entry(97)
|
||||
.or_default()
|
||||
.insert(hash(97), HeaderStatus::Extra);
|
||||
queue.known_headers.entry(97).or_default().insert(hash(97), HeaderStatus::Extra);
|
||||
queue.extra.entry(97).or_default().insert(hash(97), header(97));
|
||||
queue
|
||||
.known_headers
|
||||
.entry(96)
|
||||
.or_default()
|
||||
.insert(hash(96), HeaderStatus::Ready);
|
||||
queue.known_headers.entry(96).or_default().insert(hash(96), HeaderStatus::Ready);
|
||||
queue.ready.entry(96).or_default().insert(hash(96), header(96));
|
||||
queue.target_best_header_response(&id(100));
|
||||
|
||||
@@ -1146,31 +1161,19 @@ pub(crate) mod tests {
|
||||
// children of synced headers are stored
|
||||
assert_eq!(
|
||||
vec![id(97)],
|
||||
queue.synced_children[&96][&hash(96)]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
queue.synced_children[&96][&hash(96)].iter().cloned().collect::<Vec<_>>()
|
||||
);
|
||||
assert_eq!(
|
||||
vec![id(98)],
|
||||
queue.synced_children[&97][&hash(97)]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
queue.synced_children[&97][&hash(97)].iter().cloned().collect::<Vec<_>>()
|
||||
);
|
||||
assert_eq!(
|
||||
vec![id(99)],
|
||||
queue.synced_children[&98][&hash(98)]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
queue.synced_children[&98][&hash(98)].iter().cloned().collect::<Vec<_>>()
|
||||
);
|
||||
assert_eq!(
|
||||
vec![id(100)],
|
||||
queue.synced_children[&99][&hash(99)]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
queue.synced_children[&99][&hash(99)].iter().cloned().collect::<Vec<_>>()
|
||||
);
|
||||
assert_eq!(0, queue.synced_children[&100][&hash(100)].len());
|
||||
}
|
||||
@@ -1194,11 +1197,7 @@ pub(crate) mod tests {
|
||||
.entry(102)
|
||||
.or_default()
|
||||
.insert(hash(102), HeaderStatus::MaybeOrphan);
|
||||
queue
|
||||
.maybe_orphan
|
||||
.entry(102)
|
||||
.or_default()
|
||||
.insert(hash(102), header(102));
|
||||
queue.maybe_orphan.entry(102).or_default().insert(hash(102), header(102));
|
||||
queue
|
||||
.known_headers
|
||||
.entry(103)
|
||||
@@ -1230,11 +1229,7 @@ pub(crate) mod tests {
|
||||
.entry(100)
|
||||
.or_default()
|
||||
.insert(hash(100), HeaderStatus::MaybeOrphan);
|
||||
queue
|
||||
.maybe_orphan
|
||||
.entry(100)
|
||||
.or_default()
|
||||
.insert(hash(100), header(100));
|
||||
queue.maybe_orphan.entry(100).or_default().insert(hash(100), header(100));
|
||||
queue
|
||||
.known_headers
|
||||
.entry(101)
|
||||
@@ -1246,11 +1241,7 @@ pub(crate) mod tests {
|
||||
.entry(102)
|
||||
.or_default()
|
||||
.insert(hash(102), HeaderStatus::MaybeOrphan);
|
||||
queue
|
||||
.maybe_orphan
|
||||
.entry(102)
|
||||
.or_default()
|
||||
.insert(hash(102), header(102));
|
||||
queue.maybe_orphan.entry(102).or_default().insert(hash(102), header(102));
|
||||
queue.maybe_orphan_response(&id(99), true);
|
||||
|
||||
// then all headers (#100..#103) are moved to the MaybeExtra queue
|
||||
@@ -1275,21 +1266,13 @@ pub(crate) mod tests {
|
||||
.entry(100)
|
||||
.or_default()
|
||||
.insert(hash(100), HeaderStatus::MaybeOrphan);
|
||||
queue
|
||||
.maybe_orphan
|
||||
.entry(100)
|
||||
.or_default()
|
||||
.insert(hash(100), header(100));
|
||||
queue.maybe_orphan.entry(100).or_default().insert(hash(100), header(100));
|
||||
queue
|
||||
.known_headers
|
||||
.entry(101)
|
||||
.or_default()
|
||||
.insert(hash(101), HeaderStatus::MaybeOrphan);
|
||||
queue
|
||||
.maybe_orphan
|
||||
.entry(101)
|
||||
.or_default()
|
||||
.insert(hash(101), header(101));
|
||||
queue.maybe_orphan.entry(101).or_default().insert(hash(101), header(101));
|
||||
queue.maybe_orphan_response(&id(99), false);
|
||||
|
||||
// then all headers (#100..#101) are moved to the Orphan queue
|
||||
@@ -1404,7 +1387,9 @@ pub(crate) mod tests {
|
||||
queue.incomplete_headers.clear();
|
||||
queue.incomplete_headers.insert(
|
||||
id(100),
|
||||
Some(Instant::now() - RETRY_FETCH_COMPLETION_INTERVAL - RETRY_FETCH_COMPLETION_INTERVAL),
|
||||
Some(
|
||||
Instant::now() - RETRY_FETCH_COMPLETION_INTERVAL - RETRY_FETCH_COMPLETION_INTERVAL,
|
||||
),
|
||||
);
|
||||
assert_eq!(queue.incomplete_header(), Some(id(100)));
|
||||
}
|
||||
@@ -1560,11 +1545,7 @@ pub(crate) mod tests {
|
||||
.entry(104)
|
||||
.or_default()
|
||||
.insert(hash(104), HeaderStatus::MaybeOrphan);
|
||||
queue
|
||||
.maybe_orphan
|
||||
.entry(104)
|
||||
.or_default()
|
||||
.insert(hash(104), header(104));
|
||||
queue.maybe_orphan.entry(104).or_default().insert(hash(104), header(104));
|
||||
queue
|
||||
.known_headers
|
||||
.entry(103)
|
||||
@@ -1633,7 +1614,8 @@ pub(crate) mod tests {
|
||||
fn incomplete_headers_are_still_incomplete_after_advance() {
|
||||
let mut queue = QueuedHeaders::<TestHeadersSyncPipeline>::default();
|
||||
|
||||
// relay#1 knows that header#100 is incomplete && it has headers 101..104 in incomplete queue
|
||||
// relay#1 knows that header#100 is incomplete && it has headers 101..104 in incomplete
|
||||
// queue
|
||||
queue.incomplete_headers.insert(id(100), None);
|
||||
queue.incomplete.entry(101).or_default().insert(hash(101), header(101));
|
||||
queue.incomplete.entry(102).or_default().insert(hash(102), header(102));
|
||||
@@ -1665,8 +1647,8 @@ pub(crate) mod tests {
|
||||
.or_default()
|
||||
.insert(hash(104), HeaderStatus::Incomplete);
|
||||
|
||||
// let's say relay#2 completes header#100 and then submits header#101+header#102 and it turns
|
||||
// out that header#102 is also incomplete
|
||||
// let's say relay#2 completes header#100 and then submits header#101+header#102 and it
|
||||
// turns out that header#102 is also incomplete
|
||||
queue.incomplete_headers_response(vec![id(102)].into_iter().collect());
|
||||
|
||||
// then the header#103 and the header#104 must have Incomplete status
|
||||
|
||||
@@ -19,8 +19,10 @@
|
||||
//! to submit to the target chain? The context makes decisions basing on parameters
|
||||
//! passed using `HeadersSyncParams` structure.
|
||||
|
||||
use crate::headers::QueuedHeaders;
|
||||
use crate::sync_types::{HeaderIdOf, HeaderStatus, HeadersSyncPipeline, QueuedHeader};
|
||||
use crate::{
|
||||
headers::QueuedHeaders,
|
||||
sync_types::{HeaderIdOf, HeaderStatus, HeadersSyncPipeline, QueuedHeader},
|
||||
};
|
||||
use num_traits::{One, Saturating, Zero};
|
||||
|
||||
/// Common sync params.
|
||||
@@ -35,7 +37,7 @@ pub struct HeadersSyncParams {
|
||||
/// 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.
|
||||
/// number >= than "best_substrate_header.number" - "prune_depth".
|
||||
pub prune_depth: u32,
|
||||
/// Target transactions mode.
|
||||
pub target_tx_mode: TargetTransactionMode,
|
||||
@@ -58,9 +60,9 @@ pub enum TargetTransactionMode {
|
||||
pub struct HeadersSync<P: HeadersSyncPipeline> {
|
||||
/// Synchronization parameters.
|
||||
params: HeadersSyncParams,
|
||||
/// Best header number known to source node.
|
||||
/// The best header number known to source node.
|
||||
source_best_number: Option<P::Number>,
|
||||
/// Best header known to target node.
|
||||
/// The best header known to target node.
|
||||
target_best_header: Option<HeaderIdOf<P>>,
|
||||
/// Headers queue.
|
||||
headers: QueuedHeaders<P>,
|
||||
@@ -85,7 +87,7 @@ impl<P: HeadersSyncPipeline> HeadersSync<P> {
|
||||
self.source_best_number
|
||||
}
|
||||
|
||||
/// Best header known to target node.
|
||||
/// The best header known to target node.
|
||||
pub fn target_best_header(&self) -> Option<HeaderIdOf<P>> {
|
||||
self.target_best_header
|
||||
}
|
||||
@@ -121,36 +123,37 @@ impl<P: HeadersSyncPipeline> HeadersSync<P> {
|
||||
// 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
|
||||
// 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;
|
||||
return None
|
||||
}
|
||||
|
||||
// if queue is empty and best header on target is > than best header on source,
|
||||
// then we shoud reorg
|
||||
// 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);
|
||||
return Some(source_best_number)
|
||||
}
|
||||
|
||||
// we assume that there were no reorgs if we have already downloaded best header
|
||||
// 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;
|
||||
return None
|
||||
}
|
||||
|
||||
// download new header
|
||||
Some(best_downloaded_number + One::one())
|
||||
}
|
||||
|
||||
/// Selech orphan header to downoload.
|
||||
/// Select orphan header to download.
|
||||
pub fn select_orphan_header_to_download(&self) -> Option<&QueuedHeader<P>> {
|
||||
let orphan_header = self.headers.header(HeaderStatus::Orphan)?;
|
||||
|
||||
@@ -159,7 +162,7 @@ impl<P: HeadersSyncPipeline> HeadersSync<P> {
|
||||
// => let's avoid fetching duplicate headers
|
||||
let parent_id = orphan_header.parent_id();
|
||||
if self.headers.status(&parent_id) != HeaderStatus::Unknown {
|
||||
return None;
|
||||
return None
|
||||
}
|
||||
|
||||
Some(orphan_header)
|
||||
@@ -169,12 +172,12 @@ impl<P: HeadersSyncPipeline> HeadersSync<P> {
|
||||
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;
|
||||
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;
|
||||
return None
|
||||
}
|
||||
|
||||
let headers_in_submit_status = self.headers.headers_in_status(HeaderStatus::Submitted);
|
||||
@@ -187,15 +190,17 @@ impl<P: HeadersSyncPipeline> HeadersSync<P> {
|
||||
let mut total_headers = 0;
|
||||
self.headers.headers(HeaderStatus::Ready, |header| {
|
||||
if total_headers == headers_to_submit_count {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
if total_headers == self.params.max_headers_in_single_submit {
|
||||
return false;
|
||||
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;
|
||||
if total_headers != 0 &&
|
||||
total_size + encoded_size > self.params.max_headers_size_in_single_submit
|
||||
{
|
||||
return false
|
||||
}
|
||||
|
||||
total_size += encoded_size;
|
||||
@@ -228,15 +233,14 @@ impl<P: HeadersSyncPipeline> HeadersSync<P> {
|
||||
|
||||
// early return if it is still the same
|
||||
if self.target_best_header == Some(best_header) {
|
||||
return false;
|
||||
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()));
|
||||
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);
|
||||
@@ -281,9 +285,11 @@ impl<P: HeadersSyncPipeline> HeadersSync<P> {
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use crate::headers::tests::{header, id};
|
||||
use crate::sync_loop_tests::{TestHash, TestHeadersSyncPipeline, TestNumber};
|
||||
use crate::sync_types::HeaderStatus;
|
||||
use crate::{
|
||||
headers::tests::{header, id},
|
||||
sync_loop_tests::{TestHash, TestHeadersSyncPipeline, TestNumber},
|
||||
sync_types::HeaderStatus,
|
||||
};
|
||||
use relay_utils::HeaderId;
|
||||
|
||||
fn side_hash(number: TestNumber) -> TestHash {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user