mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 14:01:02 +00:00
Squashed 'bridges/' changes from b2099c5..23dda62 (#3369)
23dda62 Rococo <> Wococo messages relay (#1030) bcde21d Update the wasm builder to substrate master (#1029) a8318ce Make target signer optional when sending message. (#1018) f8602e1 Fix insufficient balance when send message. (#1020) d95c0a7 greedy relayer don't need message dispatch to be prepaid if dispatch is supposed to be paid at the target chain (#1016) ad5876f Update types. (#1027) 116cbbc CI: fix starting the pipeline (#1022) 7e0fadd Add temporary `canary` job (#1019) 6787091 Update types to contain dispatch_fee_payment (#1017) 03f79ad Allow Root to assume SourceAccount. (#1011) 372d019 Return dispatch_fee_payment from message details RPC (#1014) 604eb1c Relay basic single-bit message dispatch results back to the source chain (#935) bf52fff Use plain source_queue view when selecting nonces for delivery (#1010) fc5cf7d pay dispatch fee at target chain (#911) 1e35477 Bump Substrate to `286d7ce` (#1006) 7ad07b3 Add --only-mandatory-headers mode (#1004) 5351dc9 Messages relayer operating mode (#995) 9bc29a7 Rococo <> Wococo relayer balance guard (#998) bc17341 rename messages_dispatch_weight -> message_details (#996) 95be244 Bump Rococo and Wococo spec versions (#999) c35567b Move ChainWithBalances::NativeBalance -> Chain::Balance (#990) 1bfece1 Fix some nits (#988) 334ea0f Increase pause before starting relays again (#989) 7fb8248 Fix clippy in test code (#993) d60ae50 fix clippy issues (#991) 75ca813 Make sure GRANDPA shares state with RPC. (#987) da2a38a Bump Substrate (#986) 5a9862f Update submit finality proof weight formula (#981) 69df513 Flag for rejecting all outbound messages (#982) 14d0506 Add script to setup bench machine. (#984) e74e8ab Move CI from GitHub Actions to GitLab (#814) c5ca5dd Custom justification verification (#979) 643f10d Always run on-demand headers relay in complex relay (#975) a35b0ef Add JSON type definitions for Rococo<>Wococo bridge (#977) 0eb83f2 Update cargo.deny (#980) e1d1f4c Bump Rococo/Wococo spec_version (#976) deac90d increase pause before starting relays (#974) 68d6d79 Revert to use InspectCmd, bump substrate `6bef4f4` (#966) 66e1508 Avoid hashing headers twice in verify_justification (#973) a31844f Bump `environmental` dependency (#972) 2a4c29a in auto-relays keep trying to connect to nodes until connection is established (#971) 0e767b3 removed stray file (#969) b9545dc Serve multiple lanes with single complex relay instance (#964) 73419f4 Correct type error (#968) bac256f Start finality relay spec-version guards for Rococo <> Wococo finality relays (#965) bfd7037 pass source and target chain ids to account_ownership_proof (#963) 8436073 Upstream changes from Polkadot repo (#961) e58d851 Increase account endowment amount (#960) git-subtree-dir: bridges git-subtree-split: 23dda6248236b27f20d76cbedc30e189cc6f736c
This commit is contained in:
committed by
GitHub
parent
022e8bc11c
commit
feefc34567
@@ -60,8 +60,8 @@ pub async fn run(params: EthereumDeployContractParams) {
|
||||
} = params;
|
||||
|
||||
let result = async move {
|
||||
let eth_client = EthereumClient::new(eth_params).await.map_err(RpcError::Ethereum)?;
|
||||
let sub_client = SubstrateClient::<Rialto>::new(sub_params).await.map_err(RpcError::Substrate)?;
|
||||
let eth_client = EthereumClient::try_connect(eth_params).await.map_err(RpcError::Ethereum)?;
|
||||
let sub_client = SubstrateClient::<Rialto>::try_connect(sub_params).await.map_err(RpcError::Substrate)?;
|
||||
|
||||
let (initial_header_id, initial_header) = prepare_initial_header(&sub_client, sub_initial_header).await?;
|
||||
let initial_set_id = sub_initial_authorities_set_id.unwrap_or(0);
|
||||
|
||||
@@ -335,8 +335,10 @@ async fn run_single_transaction_relay(params: EthereumExchangeParams, eth_tx_has
|
||||
..
|
||||
} = params;
|
||||
|
||||
let eth_client = EthereumClient::new(eth_params).await.map_err(RpcError::Ethereum)?;
|
||||
let sub_client = SubstrateClient::<Rialto>::new(sub_params)
|
||||
let eth_client = EthereumClient::try_connect(eth_params)
|
||||
.await
|
||||
.map_err(RpcError::Ethereum)?;
|
||||
let sub_client = SubstrateClient::<Rialto>::try_connect(sub_params)
|
||||
.await
|
||||
.map_err(RpcError::Substrate)?;
|
||||
|
||||
@@ -363,12 +365,8 @@ async fn run_auto_transactions_relay_loop(
|
||||
..
|
||||
} = params;
|
||||
|
||||
let eth_client = EthereumClient::new(eth_params)
|
||||
.await
|
||||
.map_err(|err| format!("Error starting Ethereum client: {:?}", err))?;
|
||||
let sub_client = SubstrateClient::<Rialto>::new(sub_params)
|
||||
.await
|
||||
.map_err(|err| format!("Error starting Substrate client: {:?}", err))?;
|
||||
let eth_client = EthereumClient::new(eth_params).await;
|
||||
let sub_client = SubstrateClient::<Rialto>::new(sub_params).await;
|
||||
|
||||
let eth_start_with_block_number = match eth_start_with_block_number {
|
||||
Some(eth_start_with_block_number) => eth_start_with_block_number,
|
||||
|
||||
@@ -52,7 +52,7 @@ pub async fn run(params: EthereumExchangeSubmitParams) {
|
||||
} = params;
|
||||
|
||||
let result: Result<_, String> = async move {
|
||||
let eth_client = EthereumClient::new(eth_params)
|
||||
let eth_client = EthereumClient::try_connect(eth_params)
|
||||
.await
|
||||
.map_err(|err| format!("error connecting to Ethereum node: {:?}", err))?;
|
||||
|
||||
|
||||
@@ -270,8 +270,8 @@ pub async fn run(params: EthereumSyncParams) -> Result<(), RpcError> {
|
||||
instance,
|
||||
} = params;
|
||||
|
||||
let eth_client = EthereumClient::new(eth_params).await?;
|
||||
let sub_client = SubstrateClient::<Rialto>::new(sub_params).await?;
|
||||
let eth_client = EthereumClient::new(eth_params).await;
|
||||
let sub_client = SubstrateClient::<Rialto>::new(sub_params).await;
|
||||
|
||||
let sign_sub_transactions = match sync_params.target_tx_mode {
|
||||
TargetTransactionMode::Signed | TargetTransactionMode::Backup => true,
|
||||
|
||||
@@ -53,7 +53,7 @@ impl BridgeInstance for RialtoPoA {
|
||||
.into_iter()
|
||||
.map(|header| {
|
||||
(
|
||||
into_substrate_ethereum_header(&header.header()),
|
||||
into_substrate_ethereum_header(header.header()),
|
||||
into_substrate_ethereum_receipts(header.extra()),
|
||||
)
|
||||
})
|
||||
@@ -65,7 +65,7 @@ impl BridgeInstance for RialtoPoA {
|
||||
|
||||
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_header(header.header()),
|
||||
into_substrate_ethereum_receipts(header.extra()),
|
||||
);
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ async fn run_command(matches: &clap::ArgMatches<'_>) {
|
||||
match matches.subcommand() {
|
||||
("eth-to-sub", Some(eth_to_sub_matches)) => {
|
||||
log::info!(target: "bridge", "Starting ETH ➡ SUB relay.");
|
||||
if ethereum_sync_loop::run(match ethereum_sync_params(ð_to_sub_matches) {
|
||||
if ethereum_sync_loop::run(match ethereum_sync_params(eth_to_sub_matches) {
|
||||
Ok(ethereum_sync_params) => ethereum_sync_params,
|
||||
Err(err) => {
|
||||
log::error!(target: "bridge", "Error parsing parameters: {}", err);
|
||||
@@ -75,7 +75,7 @@ async fn run_command(matches: &clap::ArgMatches<'_>) {
|
||||
}
|
||||
("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) {
|
||||
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);
|
||||
@@ -90,7 +90,7 @@ async fn run_command(matches: &clap::ArgMatches<'_>) {
|
||||
}
|
||||
("eth-deploy-contract", Some(eth_deploy_matches)) => {
|
||||
log::info!(target: "bridge", "Deploying ETH contracts.");
|
||||
ethereum_deploy_contract::run(match ethereum_deploy_contract_params(ð_deploy_matches) {
|
||||
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);
|
||||
@@ -101,7 +101,7 @@ async fn run_command(matches: &clap::ArgMatches<'_>) {
|
||||
}
|
||||
("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(ð_exchange_submit_matches) {
|
||||
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);
|
||||
@@ -112,7 +112,7 @@ async fn run_command(matches: &clap::ArgMatches<'_>) {
|
||||
}
|
||||
("eth-exchange-sub", Some(eth_exchange_matches)) => {
|
||||
log::info!(target: "bridge", "Starting ETH ➡ SUB exchange transactions relay.");
|
||||
ethereum_exchange::run(match ethereum_exchange_params(ð_exchange_matches) {
|
||||
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);
|
||||
@@ -285,7 +285,7 @@ fn ethereum_exchange_submit_params(matches: &clap::ArgMatches) -> Result<Ethereu
|
||||
let eth_nonce = matches
|
||||
.value_of("eth-nonce")
|
||||
.map(|eth_nonce| {
|
||||
relay_ethereum_client::types::U256::from_dec_str(ð_nonce)
|
||||
relay_ethereum_client::types::U256::from_dec_str(eth_nonce)
|
||||
.map_err(|e| format!("Failed to parse eth-nonce: {}", e))
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
@@ -177,8 +177,8 @@ pub async fn run(params: SubstrateSyncParams) -> Result<(), RpcError> {
|
||||
metrics_params,
|
||||
} = params;
|
||||
|
||||
let eth_client = EthereumClient::new(eth_params).await?;
|
||||
let sub_client = SubstrateClient::<Rialto>::new(sub_params).await?;
|
||||
let eth_client = EthereumClient::new(eth_params).await;
|
||||
let sub_client = SubstrateClient::<Rialto>::new(sub_params).await;
|
||||
|
||||
let target = EthereumHeadersTarget::new(eth_client, eth_contract_address, eth_sign);
|
||||
let source = SubstrateHeadersSource::new(sub_client);
|
||||
|
||||
@@ -59,5 +59,6 @@ sp-trie = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sp-version = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
|
||||
[dev-dependencies]
|
||||
sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
hex-literal = "0.3"
|
||||
pallet-bridge-grandpa = { path = "../../modules/grandpa" }
|
||||
sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
|
||||
@@ -23,7 +23,7 @@ use crate::cli::{
|
||||
};
|
||||
use bp_message_dispatch::{CallOrigin, MessagePayload};
|
||||
use codec::Decode;
|
||||
use frame_support::weights::{GetDispatchInfo, Weight};
|
||||
use frame_support::weights::{DispatchInfo, GetDispatchInfo, Weight};
|
||||
use relay_millau_client::Millau;
|
||||
use sp_version::RuntimeVersion;
|
||||
|
||||
@@ -62,6 +62,10 @@ impl CliEncodeCall for Millau {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn get_dispatch_info(call: &millau_runtime::Call) -> anyhow::Result<DispatchInfo> {
|
||||
Ok(call.get_dispatch_info())
|
||||
}
|
||||
}
|
||||
|
||||
impl CliChain for Millau {
|
||||
|
||||
@@ -23,7 +23,7 @@ use crate::messages_source::SubstrateMessagesSource;
|
||||
use crate::messages_target::SubstrateMessagesTarget;
|
||||
|
||||
use bp_messages::MessageNonce;
|
||||
use bp_runtime::{MILLAU_BRIDGE_INSTANCE, RIALTO_BRIDGE_INSTANCE};
|
||||
use bp_runtime::{MILLAU_CHAIN_ID, RIALTO_CHAIN_ID};
|
||||
use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
|
||||
use codec::Encode;
|
||||
use frame_support::dispatch::GetDispatchInfo;
|
||||
@@ -42,8 +42,7 @@ pub type MillauMessagesToRialto =
|
||||
SubstrateMessageLaneToSubstrate<Millau, MillauSigningParams, Rialto, RialtoSigningParams>;
|
||||
|
||||
impl SubstrateMessageLane for MillauMessagesToRialto {
|
||||
const OUTBOUND_LANE_MESSAGES_DISPATCH_WEIGHT_METHOD: &'static str =
|
||||
bp_rialto::TO_RIALTO_MESSAGES_DISPATCH_WEIGHT_METHOD;
|
||||
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;
|
||||
@@ -59,7 +58,7 @@ impl SubstrateMessageLane for MillauMessagesToRialto {
|
||||
type SourceChain = Millau;
|
||||
type TargetChain = Rialto;
|
||||
|
||||
fn source_transactions_author(&self) -> bp_rialto::AccountId {
|
||||
fn source_transactions_author(&self) -> bp_millau::AccountId {
|
||||
(*self.source_sign.public().as_array_ref()).into()
|
||||
}
|
||||
|
||||
@@ -127,20 +126,12 @@ impl SubstrateMessageLane for MillauMessagesToRialto {
|
||||
}
|
||||
|
||||
/// Millau node as messages source.
|
||||
type MillauSourceClient = SubstrateMessagesSource<
|
||||
Millau,
|
||||
MillauMessagesToRialto,
|
||||
millau_runtime::Runtime,
|
||||
millau_runtime::WithRialtoMessagesInstance,
|
||||
>;
|
||||
type MillauSourceClient =
|
||||
SubstrateMessagesSource<Millau, MillauMessagesToRialto, millau_runtime::WithRialtoMessagesInstance>;
|
||||
|
||||
/// Rialto node as messages target.
|
||||
type RialtoTargetClient = SubstrateMessagesTarget<
|
||||
Rialto,
|
||||
MillauMessagesToRialto,
|
||||
rialto_runtime::Runtime,
|
||||
rialto_runtime::WithMillauMessagesInstance,
|
||||
>;
|
||||
type RialtoTargetClient =
|
||||
SubstrateMessagesTarget<Rialto, MillauMessagesToRialto, rialto_runtime::WithMillauMessagesInstance>;
|
||||
|
||||
/// Run Millau-to-Rialto messages sync.
|
||||
pub async fn run(
|
||||
@@ -160,7 +151,7 @@ pub async fn run(
|
||||
};
|
||||
|
||||
// 2/3 is reserved for proofs and tx overhead
|
||||
let max_messages_size_in_single_batch = bp_rialto::max_extrinsic_size() as usize / 3;
|
||||
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>>(
|
||||
@@ -194,20 +185,21 @@ pub async fn run(
|
||||
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,
|
||||
},
|
||||
},
|
||||
MillauSourceClient::new(
|
||||
source_client.clone(),
|
||||
lane.clone(),
|
||||
lane_id,
|
||||
RIALTO_BRIDGE_INSTANCE,
|
||||
RIALTO_CHAIN_ID,
|
||||
params.target_to_source_headers_relay,
|
||||
),
|
||||
RialtoTargetClient::new(
|
||||
params.target_client,
|
||||
lane,
|
||||
lane_id,
|
||||
MILLAU_BRIDGE_INSTANCE,
|
||||
MILLAU_CHAIN_ID,
|
||||
params.source_to_target_headers_relay,
|
||||
),
|
||||
relay_utils::relay_metrics(
|
||||
|
||||
@@ -21,8 +21,10 @@ pub mod millau_messages_to_rialto;
|
||||
pub mod rialto_headers_to_millau;
|
||||
pub mod rialto_messages_to_millau;
|
||||
pub mod rococo_headers_to_wococo;
|
||||
pub mod rococo_messages_to_wococo;
|
||||
pub mod westend_headers_to_millau;
|
||||
pub mod wococo_headers_to_rococo;
|
||||
pub mod wococo_messages_to_rococo;
|
||||
|
||||
mod millau;
|
||||
mod rialto;
|
||||
@@ -86,7 +88,7 @@ mod tests {
|
||||
let millau_public: bp_millau::AccountSigner = millau_sign.public().into();
|
||||
let millau_account_id: bp_millau::AccountId = millau_public.into_account();
|
||||
|
||||
let digest = millau_runtime::rialto_account_ownership_digest(
|
||||
let digest = millau_runtime::millau_to_rialto_account_ownership_digest(
|
||||
&call,
|
||||
millau_account_id,
|
||||
rialto_runtime::VERSION.spec_version,
|
||||
@@ -107,7 +109,7 @@ mod tests {
|
||||
let rialto_public: bp_rialto::AccountSigner = rialto_sign.public().into();
|
||||
let rialto_account_id: bp_rialto::AccountId = rialto_public.into_account();
|
||||
|
||||
let digest = rialto_runtime::millau_account_ownership_digest(
|
||||
let digest = rialto_runtime::rialto_to_millau_account_ownership_digest(
|
||||
&call,
|
||||
rialto_account_id,
|
||||
millau_runtime::VERSION.spec_version,
|
||||
@@ -271,7 +273,10 @@ mod rococo_tests {
|
||||
votes_ancestries: vec![],
|
||||
};
|
||||
|
||||
let actual = bp_rococo::BridgeGrandpaWococoCall::submit_finality_proof(header.clone(), justification.clone());
|
||||
let actual = relay_rococo_client::runtime::BridgeGrandpaWococoCall::submit_finality_proof(
|
||||
header.clone(),
|
||||
justification.clone(),
|
||||
);
|
||||
let expected = millau_runtime::BridgeGrandpaRialtoCall::<millau_runtime::Runtime>::submit_finality_proof(
|
||||
header,
|
||||
justification,
|
||||
|
||||
@@ -23,7 +23,7 @@ use crate::cli::{
|
||||
};
|
||||
use bp_message_dispatch::{CallOrigin, MessagePayload};
|
||||
use codec::Decode;
|
||||
use frame_support::weights::{GetDispatchInfo, Weight};
|
||||
use frame_support::weights::{DispatchInfo, GetDispatchInfo, Weight};
|
||||
use relay_rialto_client::Rialto;
|
||||
use sp_version::RuntimeVersion;
|
||||
|
||||
@@ -60,6 +60,10 @@ impl CliEncodeCall for Rialto {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn get_dispatch_info(call: &rialto_runtime::Call) -> anyhow::Result<DispatchInfo> {
|
||||
Ok(call.get_dispatch_info())
|
||||
}
|
||||
}
|
||||
|
||||
impl CliChain for Rialto {
|
||||
|
||||
@@ -23,7 +23,7 @@ use crate::messages_source::SubstrateMessagesSource;
|
||||
use crate::messages_target::SubstrateMessagesTarget;
|
||||
|
||||
use bp_messages::MessageNonce;
|
||||
use bp_runtime::{MILLAU_BRIDGE_INSTANCE, RIALTO_BRIDGE_INSTANCE};
|
||||
use bp_runtime::{MILLAU_CHAIN_ID, RIALTO_CHAIN_ID};
|
||||
use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
|
||||
use codec::Encode;
|
||||
use frame_support::dispatch::GetDispatchInfo;
|
||||
@@ -42,8 +42,7 @@ pub type RialtoMessagesToMillau =
|
||||
SubstrateMessageLaneToSubstrate<Rialto, RialtoSigningParams, Millau, MillauSigningParams>;
|
||||
|
||||
impl SubstrateMessageLane for RialtoMessagesToMillau {
|
||||
const OUTBOUND_LANE_MESSAGES_DISPATCH_WEIGHT_METHOD: &'static str =
|
||||
bp_millau::TO_MILLAU_MESSAGES_DISPATCH_WEIGHT_METHOD;
|
||||
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;
|
||||
@@ -86,7 +85,7 @@ impl SubstrateMessageLane for RialtoMessagesToMillau {
|
||||
Bytes(transaction.encode())
|
||||
}
|
||||
|
||||
fn target_transactions_author(&self) -> bp_rialto::AccountId {
|
||||
fn target_transactions_author(&self) -> bp_millau::AccountId {
|
||||
(*self.target_sign.public().as_array_ref()).into()
|
||||
}
|
||||
|
||||
@@ -127,20 +126,12 @@ impl SubstrateMessageLane for RialtoMessagesToMillau {
|
||||
}
|
||||
|
||||
/// Rialto node as messages source.
|
||||
type RialtoSourceClient = SubstrateMessagesSource<
|
||||
Rialto,
|
||||
RialtoMessagesToMillau,
|
||||
rialto_runtime::Runtime,
|
||||
rialto_runtime::WithMillauMessagesInstance,
|
||||
>;
|
||||
type RialtoSourceClient =
|
||||
SubstrateMessagesSource<Rialto, RialtoMessagesToMillau, rialto_runtime::WithMillauMessagesInstance>;
|
||||
|
||||
/// Millau node as messages target.
|
||||
type MillauTargetClient = SubstrateMessagesTarget<
|
||||
Millau,
|
||||
RialtoMessagesToMillau,
|
||||
millau_runtime::Runtime,
|
||||
millau_runtime::WithRialtoMessagesInstance,
|
||||
>;
|
||||
type MillauTargetClient =
|
||||
SubstrateMessagesTarget<Millau, RialtoMessagesToMillau, millau_runtime::WithRialtoMessagesInstance>;
|
||||
|
||||
/// Run Rialto-to-Millau messages sync.
|
||||
pub async fn run(
|
||||
@@ -160,7 +151,7 @@ pub async fn run(
|
||||
};
|
||||
|
||||
// 2/3 is reserved for proofs and tx overhead
|
||||
let max_messages_size_in_single_batch = bp_millau::max_extrinsic_size() as usize / 3;
|
||||
let max_messages_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>>(
|
||||
bp_millau::max_extrinsic_weight(),
|
||||
@@ -193,20 +184,21 @@ pub async fn run(
|
||||
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,
|
||||
},
|
||||
},
|
||||
RialtoSourceClient::new(
|
||||
source_client.clone(),
|
||||
lane.clone(),
|
||||
lane_id,
|
||||
MILLAU_BRIDGE_INSTANCE,
|
||||
MILLAU_CHAIN_ID,
|
||||
params.target_to_source_headers_relay,
|
||||
),
|
||||
MillauTargetClient::new(
|
||||
params.target_client,
|
||||
lane,
|
||||
lane_id,
|
||||
RIALTO_BRIDGE_INSTANCE,
|
||||
RIALTO_CHAIN_ID,
|
||||
params.source_to_target_headers_relay,
|
||||
),
|
||||
relay_utils::relay_metrics(
|
||||
|
||||
@@ -14,11 +14,70 @@
|
||||
// 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::{encode_message, CliChain};
|
||||
use frame_support::weights::Weight;
|
||||
use codec::Decode;
|
||||
use frame_support::weights::{DispatchClass, DispatchInfo, Pays, Weight};
|
||||
use relay_rococo_client::Rococo;
|
||||
use sp_version::RuntimeVersion;
|
||||
|
||||
use crate::cli::{
|
||||
bridge,
|
||||
encode_call::{Call, CliEncodeCall},
|
||||
encode_message, CliChain,
|
||||
};
|
||||
|
||||
/// Weight of the `system::remark` call at Rococo.
|
||||
///
|
||||
/// This weight is larger (x2) than actual weight at current Rooco 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;
|
||||
|
||||
impl CliEncodeCall for Rococo {
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
bp_rococo::max_extrinsic_size()
|
||||
}
|
||||
|
||||
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(
|
||||
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
|
||||
),
|
||||
},
|
||||
_ => anyhow::bail!("The call is not supported"),
|
||||
})
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
_ => anyhow::bail!("Unsupported Rococo call: {:?}", call),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CliChain for Rococo {
|
||||
const RUNTIME_VERSION: RuntimeVersion = bp_rococo::VERSION;
|
||||
|
||||
@@ -30,7 +89,7 @@ impl CliChain for Rococo {
|
||||
}
|
||||
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
0
|
||||
bp_wococo::max_extrinsic_weight()
|
||||
}
|
||||
|
||||
fn encode_message(_message: encode_message::MessagePayload) -> Result<Self::MessagePayload, String> {
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
//! 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;
|
||||
@@ -38,6 +39,18 @@ impl SubstrateFinalitySyncPipeline for RococoFinalityToWococo {
|
||||
crate::chains::add_polkadot_kusama_price_metrics::<Self>(params)
|
||||
}
|
||||
|
||||
fn start_relay_guards(&self) {
|
||||
relay_substrate_client::guard::abort_on_spec_version_change(
|
||||
self.target_client.clone(),
|
||||
bp_wococo::VERSION.spec_version,
|
||||
);
|
||||
relay_substrate_client::guard::abort_when_account_balance_decreased(
|
||||
self.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()
|
||||
}
|
||||
@@ -48,10 +61,9 @@ impl SubstrateFinalitySyncPipeline for RococoFinalityToWococo {
|
||||
header: RococoSyncHeader,
|
||||
proof: GrandpaJustification<bp_rococo::Header>,
|
||||
) -> Bytes {
|
||||
let call = bp_wococo::Call::BridgeGrandpaRococo(bp_wococo::BridgeGrandpaRococoCall::submit_finality_proof(
|
||||
header.into_inner(),
|
||||
proof,
|
||||
));
|
||||
let call = relay_wococo_client::runtime::Call::BridgeGrandpaRococo(
|
||||
relay_wococo_client::runtime::BridgeGrandpaRococoCall::submit_finality_proof(header.into_inner(), proof),
|
||||
);
|
||||
let genesis_hash = *self.target_client.genesis_hash();
|
||||
let transaction = Wococo::sign_transaction(genesis_hash, &self.target_sign, transaction_nonce, call);
|
||||
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
// 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/>.
|
||||
|
||||
//! 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 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};
|
||||
|
||||
/// Rococo-to-Wococo message lane.
|
||||
pub type RococoMessagesToWococo =
|
||||
SubstrateMessageLaneToSubstrate<Rococo, RococoSigningParams, Wococo, WococoSigningParams>;
|
||||
|
||||
impl SubstrateMessageLane for RococoMessagesToWococo {
|
||||
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 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 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;
|
||||
|
||||
type SourceChain = Rococo;
|
||||
type TargetChain = Wococo;
|
||||
|
||||
fn source_transactions_author(&self) -> bp_rococo::AccountId {
|
||||
(*self.source_sign.public().as_array_ref()).into()
|
||||
}
|
||||
|
||||
fn make_messages_receiving_proof_transaction(
|
||||
&self,
|
||||
transaction_nonce: <Rococo as Chain>::Index,
|
||||
_generated_at_block: WococoHeaderId,
|
||||
proof: <Self as MessageLane>::MessagesReceivingProof,
|
||||
) -> Bytes {
|
||||
let (relayers_state, proof) = proof;
|
||||
let call = relay_rococo_client::runtime::Call::BridgeMessagesWococo(
|
||||
relay_rococo_client::runtime::BridgeMessagesWococoCall::receive_messages_delivery_proof(
|
||||
proof,
|
||||
relayers_state,
|
||||
),
|
||||
);
|
||||
let genesis_hash = *self.source_client.genesis_hash();
|
||||
let transaction = Rococo::sign_transaction(genesis_hash, &self.source_sign, transaction_nonce, call);
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Prepared Wococo -> Rococo confirmation transaction. Weight: <unknown>/{}, size: {}/{}",
|
||||
bp_rococo::max_extrinsic_weight(),
|
||||
transaction.encode().len(),
|
||||
bp_rococo::max_extrinsic_size(),
|
||||
);
|
||||
Bytes(transaction.encode())
|
||||
}
|
||||
|
||||
fn target_transactions_author(&self) -> bp_wococo::AccountId {
|
||||
(*self.target_sign.public().as_array_ref()).into()
|
||||
}
|
||||
|
||||
fn make_messages_delivery_transaction(
|
||||
&self,
|
||||
transaction_nonce: <Wococo as Chain>::Index,
|
||||
_generated_at_header: RococoHeaderId,
|
||||
_nonces: RangeInclusive<MessageNonce>,
|
||||
proof: <Self 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_wococo_client::runtime::Call::BridgeMessagesRococo(
|
||||
relay_wococo_client::runtime::BridgeMessagesRococoCall::receive_messages_proof(
|
||||
self.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);
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Prepared Rococo -> Wococo delivery transaction. Weight: <unknown>/{}, size: {}/{}",
|
||||
bp_wococo::max_extrinsic_weight(),
|
||||
transaction.encode().len(),
|
||||
bp_wococo::max_extrinsic_size(),
|
||||
);
|
||||
Bytes(transaction.encode())
|
||||
}
|
||||
}
|
||||
|
||||
/// Rococo node as messages source.
|
||||
type RococoSourceClient =
|
||||
SubstrateMessagesSource<Rococo, RococoMessagesToWococo, relay_rococo_client::runtime::WithWococoMessagesInstance>;
|
||||
|
||||
/// Wococo node as messages target.
|
||||
type WococoTargetClient =
|
||||
SubstrateMessagesTarget<Wococo, RococoMessagesToWococo, relay_wococo_client::runtime::WithRococoMessagesInstance>;
|
||||
|
||||
/// 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);
|
||||
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,
|
||||
};
|
||||
|
||||
// 2/3 is reserved for proofs and tx overhead
|
||||
let max_messages_size_in_single_batch = bp_wococo::max_extrinsic_size() / 3;
|
||||
// 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>>(
|
||||
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,
|
||||
);
|
||||
|
||||
log::info!(
|
||||
target: "bridge",
|
||||
"Starting Rococo -> Wococo messages relay.\n\t\
|
||||
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_in_single_batch,
|
||||
max_messages_size_in_single_batch,
|
||||
max_messages_weight_in_single_batch,
|
||||
);
|
||||
|
||||
messages_relay::message_lane_loop::run(
|
||||
messages_relay::message_lane_loop::Params {
|
||||
lane: lane_id,
|
||||
source_tick: Rococo::AVERAGE_BLOCK_INTERVAL,
|
||||
target_tick: Wococo::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_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,
|
||||
},
|
||||
},
|
||||
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,
|
||||
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(),
|
||||
futures::future::pending(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -1,60 +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/>.
|
||||
|
||||
//! Westend-to-Rococo headers sync entrypoint.
|
||||
|
||||
use crate::finality_pipeline::{SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate};
|
||||
|
||||
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_utils::metrics::MetricsParams;
|
||||
use relay_westend_client::{SyncHeader as WestendSyncHeader, Westend};
|
||||
use sp_core::{Bytes, Pair};
|
||||
|
||||
/// Westend-to-Rococo finality sync pipeline.
|
||||
pub(crate) type WestendFinalityToRococo = SubstrateFinalityToSubstrate<Westend, Rococo, RococoSigningParams>;
|
||||
|
||||
impl SubstrateFinalitySyncPipeline for WestendFinalityToRococo {
|
||||
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_westend::BEST_FINALIZED_WESTEND_HEADER_METHOD;
|
||||
|
||||
type TargetChain = Rococo;
|
||||
|
||||
fn customize_metrics(params: MetricsParams) -> anyhow::Result<MetricsParams> {
|
||||
crate::chains::add_polkadot_kusama_price_metrics::<Self>(params)
|
||||
}
|
||||
|
||||
fn transactions_author(&self) -> bp_rococo::AccountId {
|
||||
(*self.target_sign.public().as_array_ref()).into()
|
||||
}
|
||||
|
||||
fn make_submit_finality_proof_transaction(
|
||||
&self,
|
||||
transaction_nonce: <Rococo as Chain>::Index,
|
||||
header: WestendSyncHeader,
|
||||
proof: GrandpaJustification<bp_westend::Header>,
|
||||
) -> Bytes {
|
||||
let call = bp_rococo::Call::BridgeGrandpaWestend(bp_rococo::BridgeGrandpaCall::submit_finality_proof(
|
||||
header.into_inner(),
|
||||
proof,
|
||||
));
|
||||
let genesis_hash = *self.target_client.genesis_hash();
|
||||
let transaction = Rococo::sign_transaction(genesis_hash, &self.target_sign, transaction_nonce, call);
|
||||
|
||||
Bytes(transaction.encode())
|
||||
}
|
||||
}
|
||||
@@ -14,11 +14,64 @@
|
||||
// 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::{encode_message, CliChain};
|
||||
use frame_support::weights::Weight;
|
||||
use codec::Decode;
|
||||
use frame_support::weights::{DispatchClass, DispatchInfo, Pays, Weight};
|
||||
use relay_wococo_client::Wococo;
|
||||
use sp_version::RuntimeVersion;
|
||||
|
||||
use crate::cli::{
|
||||
bridge,
|
||||
encode_call::{Call, CliEncodeCall},
|
||||
encode_message, CliChain,
|
||||
};
|
||||
|
||||
impl CliEncodeCall for Wococo {
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
bp_wococo::max_extrinsic_size()
|
||||
}
|
||||
|
||||
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(
|
||||
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
|
||||
),
|
||||
},
|
||||
_ => anyhow::bail!("The call is not supported"),
|
||||
})
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
_ => anyhow::bail!("Unsupported Rococo call: {:?}", call),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CliChain for Wococo {
|
||||
const RUNTIME_VERSION: RuntimeVersion = bp_wococo::VERSION;
|
||||
|
||||
@@ -30,7 +83,7 @@ impl CliChain for Wococo {
|
||||
}
|
||||
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
0
|
||||
bp_wococo::max_extrinsic_weight()
|
||||
}
|
||||
|
||||
fn encode_message(_message: encode_message::MessagePayload) -> Result<Self::MessagePayload, String> {
|
||||
|
||||
@@ -26,6 +26,13 @@ use relay_utils::metrics::MetricsParams;
|
||||
use relay_wococo_client::{SyncHeader as WococoSyncHeader, Wococo};
|
||||
use sp_core::{Bytes, Pair};
|
||||
|
||||
/// Maximal saturating difference between `balance(now)` and `balance(now-24h)` to treat
|
||||
/// relay as gone wild.
|
||||
///
|
||||
/// See `maximal_balance_decrease_per_day_is_sane` test for details.
|
||||
/// Note that this is in plancks, so this corresponds to `1500 UNITS`.
|
||||
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>;
|
||||
|
||||
@@ -38,6 +45,18 @@ impl SubstrateFinalitySyncPipeline for WococoFinalityToRococo {
|
||||
crate::chains::add_polkadot_kusama_price_metrics::<Self>(params)
|
||||
}
|
||||
|
||||
fn start_relay_guards(&self) {
|
||||
relay_substrate_client::guard::abort_on_spec_version_change(
|
||||
self.target_client.clone(),
|
||||
bp_rococo::VERSION.spec_version,
|
||||
);
|
||||
relay_substrate_client::guard::abort_when_account_balance_decreased(
|
||||
self.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()
|
||||
}
|
||||
@@ -48,13 +67,50 @@ impl SubstrateFinalitySyncPipeline for WococoFinalityToRococo {
|
||||
header: WococoSyncHeader,
|
||||
proof: GrandpaJustification<bp_wococo::Header>,
|
||||
) -> Bytes {
|
||||
let call = bp_rococo::Call::BridgeGrandpaWococo(bp_rococo::BridgeGrandpaWococoCall::submit_finality_proof(
|
||||
header.into_inner(),
|
||||
proof,
|
||||
));
|
||||
let call = relay_rococo_client::runtime::Call::BridgeGrandpaWococo(
|
||||
relay_rococo_client::runtime::BridgeGrandpaWococoCall::submit_finality_proof(header.into_inner(), proof),
|
||||
);
|
||||
let genesis_hash = *self.target_client.genesis_hash();
|
||||
let transaction = Rococo::sign_transaction(genesis_hash, &self.target_sign, transaction_nonce, call);
|
||||
|
||||
Bytes(transaction.encode())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use frame_support::weights::WeightToFeePolynomial;
|
||||
use pallet_bridge_grandpa::weights::WeightInfo;
|
||||
|
||||
#[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;
|
||||
assert!(
|
||||
MAXIMAL_BALANCE_DECREASE_PER_DAY >= maximal_expected_decrease,
|
||||
"Maximal expected loss per day {} is larger than hardcoded {}",
|
||||
maximal_expected_decrease,
|
||||
MAXIMAL_BALANCE_DECREASE_PER_DAY,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
// 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/>.
|
||||
|
||||
//! 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 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};
|
||||
|
||||
/// Wococo-to-Rococo message lane.
|
||||
pub type WococoMessagesToRococo =
|
||||
SubstrateMessageLaneToSubstrate<Wococo, WococoSigningParams, Rococo, RococoSigningParams>;
|
||||
|
||||
impl SubstrateMessageLane for WococoMessagesToRococo {
|
||||
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 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 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;
|
||||
|
||||
type SourceChain = Wococo;
|
||||
type TargetChain = Rococo;
|
||||
|
||||
fn source_transactions_author(&self) -> bp_wococo::AccountId {
|
||||
(*self.source_sign.public().as_array_ref()).into()
|
||||
}
|
||||
|
||||
fn make_messages_receiving_proof_transaction(
|
||||
&self,
|
||||
transaction_nonce: <Wococo as Chain>::Index,
|
||||
_generated_at_block: RococoHeaderId,
|
||||
proof: <Self as MessageLane>::MessagesReceivingProof,
|
||||
) -> Bytes {
|
||||
let (relayers_state, proof) = proof;
|
||||
let call = relay_wococo_client::runtime::Call::BridgeMessagesRococo(
|
||||
relay_wococo_client::runtime::BridgeMessagesRococoCall::receive_messages_delivery_proof(
|
||||
proof,
|
||||
relayers_state,
|
||||
),
|
||||
);
|
||||
let genesis_hash = *self.source_client.genesis_hash();
|
||||
let transaction = Wococo::sign_transaction(genesis_hash, &self.source_sign, transaction_nonce, call);
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Prepared Rococo -> Wococo confirmation transaction. Weight: <unknown>/{}, size: {}/{}",
|
||||
bp_wococo::max_extrinsic_weight(),
|
||||
transaction.encode().len(),
|
||||
bp_wococo::max_extrinsic_size(),
|
||||
);
|
||||
Bytes(transaction.encode())
|
||||
}
|
||||
|
||||
fn target_transactions_author(&self) -> bp_rococo::AccountId {
|
||||
(*self.target_sign.public().as_array_ref()).into()
|
||||
}
|
||||
|
||||
fn make_messages_delivery_transaction(
|
||||
&self,
|
||||
transaction_nonce: <Rococo as Chain>::Index,
|
||||
_generated_at_header: WococoHeaderId,
|
||||
_nonces: RangeInclusive<MessageNonce>,
|
||||
proof: <Self 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_rococo_client::runtime::Call::BridgeMessagesWococo(
|
||||
relay_rococo_client::runtime::BridgeMessagesWococoCall::receive_messages_proof(
|
||||
self.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);
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Prepared Wococo -> Rococo delivery transaction. Weight: <unknown>/{}, size: {}/{}",
|
||||
bp_rococo::max_extrinsic_weight(),
|
||||
transaction.encode().len(),
|
||||
bp_rococo::max_extrinsic_size(),
|
||||
);
|
||||
Bytes(transaction.encode())
|
||||
}
|
||||
}
|
||||
|
||||
/// Wococo node as messages source.
|
||||
type WococoSourceClient =
|
||||
SubstrateMessagesSource<Wococo, WococoMessagesToRococo, relay_wococo_client::runtime::WithRococoMessagesInstance>;
|
||||
|
||||
/// Rococo node as messages target.
|
||||
type RococoTargetClient =
|
||||
SubstrateMessagesTarget<Rococo, WococoMessagesToRococo, relay_rococo_client::runtime::WithWococoMessagesInstance>;
|
||||
|
||||
/// 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);
|
||||
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,
|
||||
};
|
||||
|
||||
// 2/3 is reserved for proofs and tx overhead
|
||||
let max_messages_size_in_single_batch = bp_rococo::max_extrinsic_size() / 3;
|
||||
// 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>>(
|
||||
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,
|
||||
);
|
||||
|
||||
log::info!(
|
||||
target: "bridge",
|
||||
"Starting Wococo -> Rococo messages relay.\n\t\
|
||||
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_in_single_batch,
|
||||
max_messages_size_in_single_batch,
|
||||
max_messages_weight_in_single_batch,
|
||||
);
|
||||
|
||||
messages_relay::message_lane_loop::run(
|
||||
messages_relay::message_lane_loop::Params {
|
||||
lane: lane_id,
|
||||
source_tick: Wococo::AVERAGE_BLOCK_INTERVAL,
|
||||
target_tick: Rococo::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_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,
|
||||
},
|
||||
},
|
||||
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,
|
||||
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(),
|
||||
futures::future::pending(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -22,6 +22,8 @@ arg_enum! {
|
||||
pub enum FullBridge {
|
||||
MillauToRialto,
|
||||
RialtoToMillau,
|
||||
RococoToWococo,
|
||||
WococoToRococo,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,12 +33,16 @@ impl FullBridge {
|
||||
match self {
|
||||
Self::MillauToRialto => MILLAU_TO_RIALTO_INDEX,
|
||||
Self::RialtoToMillau => RIALTO_TO_MILLAU_INDEX,
|
||||
Self::RococoToWococo => ROCOCO_TO_WOCOCO_INDEX,
|
||||
Self::WococoToRococo => WOCOCO_TO_ROCOCO_INDEX,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
/// The macro allows executing bridge-specific code without going fully generic.
|
||||
///
|
||||
@@ -64,7 +70,7 @@ macro_rules! select_full_bridge {
|
||||
use bp_rialto::TO_RIALTO_ESTIMATE_MESSAGE_FEE_METHOD as ESTIMATE_MESSAGE_FEE_METHOD;
|
||||
// Send-message
|
||||
#[allow(unused_imports)]
|
||||
use millau_runtime::rialto_account_ownership_digest as account_ownership_digest;
|
||||
use millau_runtime::millau_to_rialto_account_ownership_digest as account_ownership_digest;
|
||||
|
||||
$generic
|
||||
}
|
||||
@@ -87,7 +93,51 @@ macro_rules! select_full_bridge {
|
||||
|
||||
// Send-message
|
||||
#[allow(unused_imports)]
|
||||
use rialto_runtime::millau_account_ownership_digest as account_ownership_digest;
|
||||
use rialto_runtime::rialto_to_millau_account_ownership_digest as account_ownership_digest;
|
||||
|
||||
$generic
|
||||
}
|
||||
FullBridge::RococoToWococo => {
|
||||
type Source = relay_rococo_client::Rococo;
|
||||
#[allow(dead_code)]
|
||||
type Target = relay_wococo_client::Wococo;
|
||||
|
||||
// Derive-account
|
||||
#[allow(unused_imports)]
|
||||
use bp_wococo::derive_account_from_rococo_id as derive_account;
|
||||
|
||||
// Relay-messages
|
||||
#[allow(unused_imports)]
|
||||
use crate::chains::rococo_messages_to_wococo::run as relay_messages;
|
||||
|
||||
// Send-message / Estimate-fee
|
||||
#[allow(unused_imports)]
|
||||
use bp_wococo::TO_WOCOCO_ESTIMATE_MESSAGE_FEE_METHOD as ESTIMATE_MESSAGE_FEE_METHOD;
|
||||
// Send-message
|
||||
#[allow(unused_imports)]
|
||||
use relay_rococo_client::runtime::rococo_to_wococo_account_ownership_digest as account_ownership_digest;
|
||||
|
||||
$generic
|
||||
}
|
||||
FullBridge::WococoToRococo => {
|
||||
type Source = relay_wococo_client::Wococo;
|
||||
#[allow(dead_code)]
|
||||
type Target = relay_rococo_client::Rococo;
|
||||
|
||||
// Derive-account
|
||||
#[allow(unused_imports)]
|
||||
use bp_rococo::derive_account_from_wococo_id as derive_account;
|
||||
|
||||
// Relay-messages
|
||||
#[allow(unused_imports)]
|
||||
use crate::chains::wococo_messages_to_rococo::run as relay_messages;
|
||||
|
||||
// Send-message / Estimate-fee
|
||||
#[allow(unused_imports)]
|
||||
use bp_rococo::TO_ROCOCO_ESTIMATE_MESSAGE_FEE_METHOD as ESTIMATE_MESSAGE_FEE_METHOD;
|
||||
// Send-message
|
||||
#[allow(unused_imports)]
|
||||
use relay_wococo_client::runtime::wococo_to_rococo_account_ownership_digest as account_ownership_digest;
|
||||
|
||||
$generic
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
use crate::cli::bridge::FullBridge;
|
||||
use crate::cli::{AccountId, Balance, CliChain, ExplicitOrMaximal, HexBytes, HexLaneId};
|
||||
use crate::select_full_bridge;
|
||||
use frame_support::dispatch::GetDispatchInfo;
|
||||
use frame_support::weights::DispatchInfo;
|
||||
use relay_substrate_client::Chain;
|
||||
use structopt::StructOpt;
|
||||
|
||||
@@ -85,6 +85,9 @@ pub trait CliEncodeCall: Chain {
|
||||
|
||||
/// Encode a CLI call.
|
||||
fn encode_call(call: &Call) -> anyhow::Result<Self::Call>;
|
||||
|
||||
/// Get dispatch info for the call.
|
||||
fn get_dispatch_info(call: &Self::Call) -> anyhow::Result<DispatchInfo>;
|
||||
}
|
||||
|
||||
impl EncodeCall {
|
||||
@@ -96,7 +99,7 @@ impl EncodeCall {
|
||||
let encoded = HexBytes::encode(&call);
|
||||
|
||||
log::info!(target: "bridge", "Generated {} call: {:#?}", Source::NAME, call);
|
||||
log::info!(target: "bridge", "Weight of {} call: {}", Source::NAME, call.get_dispatch_info().weight);
|
||||
log::info!(target: "bridge", "Weight of {} call: {}", Source::NAME, Source::get_dispatch_info(&call)?.weight);
|
||||
log::info!(target: "bridge", "Encoded {} call: {:?}", Source::NAME, encoded);
|
||||
|
||||
Ok(encoded)
|
||||
@@ -129,7 +132,7 @@ pub(crate) fn preprocess_call<Source: CliEncodeCall + CliChain, Target: CliEncod
|
||||
} => {
|
||||
if remark_payload.is_none() {
|
||||
*remark_payload = Some(HexBytes(generate_remark_payload(
|
||||
&remark_size,
|
||||
remark_size,
|
||||
compute_maximal_message_arguments_size(Source::max_extrinsic_size(), Target::max_extrinsic_size()),
|
||||
)));
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ mod tests {
|
||||
#[test]
|
||||
fn should_encode_raw_message() {
|
||||
// given
|
||||
let msg = "01000000e88514000000000002d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d3c040130000000000000000000000000";
|
||||
let msg = "01000000e88514000000000002d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d003c040130000000000000000000000000";
|
||||
let encode_message = EncodeMessage::from_iter(vec!["encode-message", "MillauToRialto", "raw", msg]);
|
||||
|
||||
// when
|
||||
@@ -101,6 +101,6 @@ mod tests {
|
||||
let hex = encode_message.encode().unwrap();
|
||||
|
||||
// then
|
||||
assert_eq!(format!("{:?}", hex), "0x01000000e88514000000000002d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d3c040130000000000000000000000000");
|
||||
assert_eq!(format!("{:?}", hex), "0x01000000b0d60f000000000002d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d003c040130000000000000000000000000");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ use crate::cli::bridge::FullBridge;
|
||||
use crate::cli::{Balance, CliChain, HexBytes, HexLaneId, SourceConnectionParams};
|
||||
use crate::select_full_bridge;
|
||||
use codec::{Decode, Encode};
|
||||
use relay_substrate_client::{Chain, ChainWithBalances};
|
||||
use relay_substrate_client::Chain;
|
||||
use structopt::StructOpt;
|
||||
|
||||
/// Estimate Delivery & Dispatch Fee command.
|
||||
@@ -52,7 +52,7 @@ impl EstimateFee {
|
||||
let lane = lane.into();
|
||||
let payload = Source::encode_message(payload).map_err(|e| anyhow::format_err!("{:?}", e))?;
|
||||
|
||||
let fee: <Source as ChainWithBalances>::NativeBalance =
|
||||
let fee: <Source as Chain>::Balance =
|
||||
estimate_message_delivery_and_dispatch_fee(&source_client, ESTIMATE_MESSAGE_FEE_METHOD, lane, payload)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -109,7 +109,9 @@ macro_rules! select_bridge {
|
||||
fn encode_init_bridge(
|
||||
init_data: InitializationData<<Source as ChainBase>::Header>,
|
||||
) -> <Target as Chain>::Call {
|
||||
bp_wococo::Call::BridgeGrandpaRococo(bp_wococo::BridgeGrandpaRococoCall::initialize(init_data))
|
||||
relay_wococo_client::runtime::Call::BridgeGrandpaRococo(
|
||||
relay_wococo_client::runtime::BridgeGrandpaRococoCall::initialize(init_data),
|
||||
)
|
||||
}
|
||||
|
||||
$generic
|
||||
@@ -121,7 +123,9 @@ macro_rules! select_bridge {
|
||||
fn encode_init_bridge(
|
||||
init_data: InitializationData<<Source as ChainBase>::Header>,
|
||||
) -> <Target as Chain>::Call {
|
||||
bp_rococo::Call::BridgeGrandpaWococo(bp_rococo::BridgeGrandpaWococoCall::initialize(init_data))
|
||||
relay_rococo_client::runtime::Call::BridgeGrandpaWococo(
|
||||
relay_rococo_client::runtime::BridgeGrandpaWococoCall::initialize(init_data),
|
||||
)
|
||||
}
|
||||
|
||||
$generic
|
||||
|
||||
@@ -406,7 +406,7 @@ macro_rules! declare_chain_options {
|
||||
port: self.[<$chain_prefix _port>],
|
||||
secure: self.[<$chain_prefix _secure>],
|
||||
})
|
||||
.await?
|
||||
.await
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,9 @@ pub struct RelayHeaders {
|
||||
/// A bridge instance to relay headers for.
|
||||
#[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.
|
||||
#[structopt(long)]
|
||||
only_mandatory_headers: bool,
|
||||
#[structopt(flatten)]
|
||||
source: SourceConnectionParams,
|
||||
#[structopt(flatten)]
|
||||
@@ -97,12 +100,14 @@ impl RelayHeaders {
|
||||
let target_client = self.target.to_client::<Target>().await?;
|
||||
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(
|
||||
Finality::new(target_client.clone(), target_sign),
|
||||
finality,
|
||||
source_client,
|
||||
target_client,
|
||||
false,
|
||||
self.only_mandatory_headers,
|
||||
metrics_params,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -35,14 +35,15 @@ use structopt::StructOpt;
|
||||
#[derive(StructOpt)]
|
||||
pub enum RelayHeadersAndMessages {
|
||||
MillauRialto(MillauRialtoHeadersAndMessages),
|
||||
RococoWococo(RococoWococoHeadersAndMessages),
|
||||
}
|
||||
|
||||
/// Parameters that have the same names across all bridges.
|
||||
#[derive(StructOpt)]
|
||||
pub struct HeadersAndMessagesSharedParams {
|
||||
/// Hex-encoded lane id that should be served by the relay. Defaults to `00000000`.
|
||||
/// Hex-encoded lane identifiers that should be served by the complex relay.
|
||||
#[structopt(long, default_value = "00000000")]
|
||||
lane: HexLaneId,
|
||||
lane: Vec<HexLaneId>,
|
||||
#[structopt(flatten)]
|
||||
prometheus_params: PrometheusParams,
|
||||
}
|
||||
@@ -102,6 +103,26 @@ macro_rules! select_bridge {
|
||||
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;
|
||||
|
||||
$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 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;
|
||||
|
||||
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;
|
||||
|
||||
$generic
|
||||
}
|
||||
}
|
||||
@@ -111,8 +132,11 @@ macro_rules! select_bridge {
|
||||
// All supported chains.
|
||||
declare_chain_options!(Millau, millau);
|
||||
declare_chain_options!(Rialto, rialto);
|
||||
declare_chain_options!(Rococo, rococo);
|
||||
declare_chain_options!(Wococo, wococo);
|
||||
// All supported bridges.
|
||||
declare_bridge_options!(Millau, Rialto);
|
||||
declare_bridge_options!(Rococo, Wococo);
|
||||
|
||||
impl RelayHeadersAndMessages {
|
||||
/// Run the command.
|
||||
@@ -125,7 +149,7 @@ impl RelayHeadersAndMessages {
|
||||
let right_client = params.right.to_client::<Right>().await?;
|
||||
let right_sign = params.right_sign.to_keypair::<Right>()?;
|
||||
|
||||
let lane = params.shared.lane.into();
|
||||
let lanes = params.shared.lane;
|
||||
|
||||
let metrics_params: MetricsParams = params.shared.prometheus_params.into();
|
||||
let metrics_params = relay_utils::relay_metrics(None, metrics_params).into_params();
|
||||
@@ -143,46 +167,49 @@ impl RelayHeadersAndMessages {
|
||||
MAX_MISSING_RIGHT_HEADERS_AT_LEFT,
|
||||
);
|
||||
|
||||
let left_to_right_messages = left_to_right_messages(MessagesRelayParams {
|
||||
source_client: left_client.clone(),
|
||||
source_sign: left_sign.clone(),
|
||||
target_client: right_client.clone(),
|
||||
target_sign: right_sign.clone(),
|
||||
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)),
|
||||
})
|
||||
.map_err(|e| anyhow::format_err!("{}", e))
|
||||
.boxed();
|
||||
let right_to_left_messages = right_to_left_messages(MessagesRelayParams {
|
||||
source_client: right_client,
|
||||
source_sign: right_sign,
|
||||
target_client: left_client.clone(),
|
||||
target_sign: left_sign.clone(),
|
||||
source_to_target_headers_relay: Some(right_to_left_on_demand_headers),
|
||||
target_to_source_headers_relay: Some(left_to_right_on_demand_headers),
|
||||
lane_id: lane,
|
||||
metrics_params: metrics_params
|
||||
.clone()
|
||||
.disable()
|
||||
.metrics_prefix(messages_relay::message_lane_loop::metrics_prefix::<RightToLeftMessages>(&lane)),
|
||||
})
|
||||
.map_err(|e| anyhow::format_err!("{}", e))
|
||||
.boxed();
|
||||
// Need 2x capacity since we consider both directions for each lane
|
||||
let mut message_relays = Vec::with_capacity(lanes.len() * 2);
|
||||
for lane in lanes {
|
||||
let lane = lane.into();
|
||||
let left_to_right_messages = left_to_right_messages(MessagesRelayParams {
|
||||
source_client: left_client.clone(),
|
||||
source_sign: left_sign.clone(),
|
||||
target_client: right_client.clone(),
|
||||
target_sign: right_sign.clone(),
|
||||
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),
|
||||
),
|
||||
})
|
||||
.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(),
|
||||
target_client: left_client.clone(),
|
||||
target_sign: left_sign.clone(),
|
||||
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),
|
||||
),
|
||||
})
|
||||
.map_err(|e| anyhow::format_err!("{}", e))
|
||||
.boxed();
|
||||
|
||||
message_relays.push(left_to_right_messages);
|
||||
message_relays.push(right_to_left_messages);
|
||||
}
|
||||
|
||||
relay_utils::relay_metrics(None, metrics_params)
|
||||
.expose()
|
||||
.await
|
||||
.map_err(|e| anyhow::format_err!("{}", e))?;
|
||||
|
||||
futures::future::select(left_to_right_messages, right_to_left_messages)
|
||||
.await
|
||||
.factor_first()
|
||||
.0
|
||||
futures::future::select_all(message_relays).await.0
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,9 @@ use crate::cli::{
|
||||
TargetSigningParams,
|
||||
};
|
||||
use bp_message_dispatch::{CallOrigin, MessagePayload};
|
||||
use bp_runtime::messages::DispatchFeePayment;
|
||||
use codec::Encode;
|
||||
use frame_support::{dispatch::GetDispatchInfo, weights::Weight};
|
||||
use frame_support::weights::Weight;
|
||||
use relay_substrate_client::{Chain, TransactionSignScheme};
|
||||
use sp_core::{Bytes, Pair};
|
||||
use sp_runtime::{traits::IdentifyAccount, AccountId32, MultiSignature, MultiSigner};
|
||||
@@ -40,9 +41,12 @@ pub struct SendMessage {
|
||||
source: SourceConnectionParams,
|
||||
#[structopt(flatten)]
|
||||
source_sign: SourceSigningParams,
|
||||
// TODO [#885] Move TargetSign to origins
|
||||
#[structopt(flatten)]
|
||||
target_sign: TargetSigningParams,
|
||||
/// 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>,
|
||||
/// Hex-encoded lane id. Defaults to `00000000`.
|
||||
#[structopt(long, default_value = "00000000")]
|
||||
lane: HexLaneId,
|
||||
@@ -68,7 +72,8 @@ impl SendMessage {
|
||||
crate::select_full_bridge!(self.bridge, {
|
||||
let SendMessage {
|
||||
source_sign,
|
||||
target_sign,
|
||||
target_signer,
|
||||
target_signer_password,
|
||||
ref mut message,
|
||||
dispatch_weight,
|
||||
origin,
|
||||
@@ -77,15 +82,14 @@ impl SendMessage {
|
||||
} = self;
|
||||
|
||||
let source_sign = source_sign.to_keypair::<Source>()?;
|
||||
let target_sign = target_sign.to_keypair::<Target>()?;
|
||||
|
||||
encode_call::preprocess_call::<Source, Target>(message, bridge.bridge_instance_index());
|
||||
let target_call = Target::encode_call(&message)?;
|
||||
let target_call = Target::encode_call(message)?;
|
||||
|
||||
let payload = {
|
||||
let target_call_weight = prepare_call_dispatch_weight(
|
||||
dispatch_weight,
|
||||
ExplicitOrMaximal::Explicit(target_call.get_dispatch_info().weight),
|
||||
ExplicitOrMaximal::Explicit(Target::get_dispatch_info(&target_call)?.weight),
|
||||
compute_maximal_message_dispatch_weight(Target::max_extrinsic_weight()),
|
||||
);
|
||||
let source_sender_public: MultiSigner = source_sign.public().into();
|
||||
@@ -97,6 +101,13 @@ 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,
|
||||
source_account_id.clone(),
|
||||
@@ -130,11 +141,12 @@ impl SendMessage {
|
||||
let fee = match self.fee {
|
||||
Some(fee) => fee,
|
||||
None => Balance(
|
||||
estimate_message_delivery_and_dispatch_fee::<
|
||||
<Source as relay_substrate_client::ChainWithBalances>::NativeBalance,
|
||||
_,
|
||||
_,
|
||||
>(&source_client, ESTIMATE_MESSAGE_FEE_METHOD, lane, payload.clone())
|
||||
estimate_message_delivery_and_dispatch_fee::<<Source as Chain>::Balance, _, _>(
|
||||
&source_client,
|
||||
ESTIMATE_MESSAGE_FEE_METHOD,
|
||||
lane,
|
||||
payload.clone(),
|
||||
)
|
||||
.await? as _,
|
||||
),
|
||||
};
|
||||
@@ -210,6 +222,7 @@ where
|
||||
spec_version,
|
||||
weight,
|
||||
origin,
|
||||
dispatch_fee_payment: DispatchFeePayment::AtSourceChain,
|
||||
call: HexBytes::encode(call),
|
||||
};
|
||||
|
||||
@@ -221,12 +234,14 @@ where
|
||||
spec_version,
|
||||
weight,
|
||||
origin,
|
||||
dispatch_fee_payment,
|
||||
call,
|
||||
} = payload;
|
||||
MessagePayload {
|
||||
spec_version,
|
||||
weight,
|
||||
origin,
|
||||
dispatch_fee_payment,
|
||||
call: call.0,
|
||||
}
|
||||
}
|
||||
@@ -250,8 +265,6 @@ mod tests {
|
||||
"1234",
|
||||
"--source-signer",
|
||||
"//Alice",
|
||||
"--target-signer",
|
||||
"//Bob",
|
||||
"remark",
|
||||
"--remark-payload",
|
||||
"1234",
|
||||
@@ -265,8 +278,9 @@ mod tests {
|
||||
payload,
|
||||
MessagePayload {
|
||||
spec_version: relay_millau_client::Millau::RUNTIME_VERSION.spec_version,
|
||||
weight: 1345000,
|
||||
weight: 1038000,
|
||||
origin: CallOrigin::SourceAccount(sp_keyring::AccountKeyring::Alice.to_account_id()),
|
||||
dispatch_fee_payment: DispatchFeePayment::AtSourceChain,
|
||||
call: hex!("0401081234").to_vec(),
|
||||
}
|
||||
);
|
||||
@@ -304,14 +318,35 @@ mod tests {
|
||||
payload,
|
||||
MessagePayload {
|
||||
spec_version: relay_millau_client::Millau::RUNTIME_VERSION.spec_version,
|
||||
weight: 1345000,
|
||||
weight: 1038000,
|
||||
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(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn target_signer_must_exist_if_origin_is_target() {
|
||||
// given
|
||||
let send_message = SendMessage::from_iter_safe(vec![
|
||||
"send-message",
|
||||
"MillauToRialto",
|
||||
"--source-port",
|
||||
"1234",
|
||||
"--source-signer",
|
||||
"//Alice",
|
||||
"--origin",
|
||||
"Target",
|
||||
"remark",
|
||||
"--remark-payload",
|
||||
"1234",
|
||||
]);
|
||||
|
||||
assert!(send_message.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,12 +26,12 @@ use sp_core::Bytes;
|
||||
use std::{fmt::Debug, marker::PhantomData, time::Duration};
|
||||
|
||||
/// Default synchronization loop timeout.
|
||||
const STALL_TIMEOUT: Duration = Duration::from_secs(120);
|
||||
pub(crate) const STALL_TIMEOUT: Duration = Duration::from_secs(120);
|
||||
/// Default limit of recent finality proofs.
|
||||
///
|
||||
/// Finality delay of 4096 blocks is unlikely to happen in practice in
|
||||
/// Substrate+GRANDPA based chains (good to know).
|
||||
const RECENT_FINALITY_PROOFS_LIMIT: usize = 4096;
|
||||
pub(crate) const RECENT_FINALITY_PROOFS_LIMIT: usize = 4096;
|
||||
|
||||
/// Headers sync pipeline for Substrate <-> Substrate relays.
|
||||
pub trait SubstrateFinalitySyncPipeline: FinalitySyncPipeline {
|
||||
@@ -46,6 +46,13 @@ pub trait SubstrateFinalitySyncPipeline: FinalitySyncPipeline {
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
/// Start finality relay guards.
|
||||
///
|
||||
/// Different finality bridges may have different set of guards - e.g. on ephemeral chains we
|
||||
/// don't need version guards, on test chains we don't care that much about relayer account
|
||||
/// balance, ... So the implementation is left to the specific bridges.
|
||||
fn start_relay_guards(&self) {}
|
||||
|
||||
/// Returns id of account that we're using to sign transactions at target chain.
|
||||
fn transactions_author(&self) -> <Self::TargetChain as Chain>::AccountId;
|
||||
|
||||
@@ -112,7 +119,7 @@ pub async fn run<SourceChain, TargetChain, P>(
|
||||
pipeline: P,
|
||||
source_client: Client<SourceChain>,
|
||||
target_client: Client<TargetChain>,
|
||||
is_on_demand_task: bool,
|
||||
only_mandatory_headers: bool,
|
||||
metrics_params: MetricsParams,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
@@ -135,13 +142,13 @@ where
|
||||
);
|
||||
|
||||
finality_relay::run(
|
||||
FinalitySource::new(source_client),
|
||||
FinalitySource::new(source_client, None),
|
||||
SubstrateFinalityTarget::new(target_client, pipeline),
|
||||
FinalitySyncParams {
|
||||
is_on_demand_task,
|
||||
tick: std::cmp::max(SourceChain::AVERAGE_BLOCK_INTERVAL, TargetChain::AVERAGE_BLOCK_INTERVAL),
|
||||
recent_finality_proofs_limit: RECENT_FINALITY_PROOFS_LIMIT,
|
||||
stall_timeout: STALL_TIMEOUT,
|
||||
only_mandatory_headers,
|
||||
},
|
||||
metrics_params,
|
||||
futures::future::pending(),
|
||||
|
||||
@@ -49,7 +49,7 @@ pub struct MessagesRelayParams<SC: Chain, SS, TC: Chain, TS> {
|
||||
/// 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_MESSAGES_DISPATCH_WEIGHT_METHOD: &'static str;
|
||||
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.
|
||||
@@ -139,6 +139,7 @@ where
|
||||
type MessagesProof = SubstrateMessagesProof<Source>;
|
||||
type MessagesReceivingProof = SubstrateMessagesReceivingProof<Target>;
|
||||
|
||||
type SourceChainBalance = Source::Balance;
|
||||
type SourceHeaderNumber = BlockNumberOf<Source>;
|
||||
type SourceHeaderHash = HashOf<Source>;
|
||||
|
||||
@@ -203,7 +204,7 @@ mod tests {
|
||||
// reserved for messages dispatch allows dispatch of non-trivial messages.
|
||||
//
|
||||
// Any significant change in this values should attract additional attention.
|
||||
(1013, 216_583_333_334),
|
||||
(782, 216_583_333_334),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,17 +23,16 @@ use crate::on_demand_headers::OnDemandHeadersRelay;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bp_messages::{LaneId, MessageNonce};
|
||||
use bp_runtime::InstanceId;
|
||||
use bp_runtime::{messages::DispatchFeePayment, ChainId};
|
||||
use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::{traits::Instance, weights::Weight};
|
||||
use messages_relay::{
|
||||
message_lane::{SourceHeaderIdOf, TargetHeaderIdOf},
|
||||
message_lane_loop::{
|
||||
ClientState, MessageProofParameters, MessageWeights, MessageWeightsMap, SourceClient, SourceClientState,
|
||||
ClientState, MessageDetails, MessageDetailsMap, MessageProofParameters, SourceClient, SourceClientState,
|
||||
},
|
||||
};
|
||||
use pallet_bridge_messages::Config as MessagesConfig;
|
||||
use relay_substrate_client::{Chain, Client, Error as SubstrateError, HashOf, HeaderIdOf};
|
||||
use relay_utils::{relay_loop::Client as RelayClient, BlockNumberBase, HeaderId};
|
||||
use sp_core::Bytes;
|
||||
@@ -46,22 +45,22 @@ use std::{marker::PhantomData, ops::RangeInclusive};
|
||||
pub type SubstrateMessagesProof<C> = (Weight, FromBridgedChainMessagesProof<HashOf<C>>);
|
||||
|
||||
/// Substrate client as Substrate messages source.
|
||||
pub struct SubstrateMessagesSource<C: Chain, P: SubstrateMessageLane, R, I> {
|
||||
pub struct SubstrateMessagesSource<C: Chain, P: SubstrateMessageLane, I> {
|
||||
client: Client<C>,
|
||||
lane: P,
|
||||
lane_id: LaneId,
|
||||
instance: InstanceId,
|
||||
instance: ChainId,
|
||||
target_to_source_headers_relay: Option<OnDemandHeadersRelay<P::TargetChain>>,
|
||||
_phantom: PhantomData<(R, I)>,
|
||||
_phantom: PhantomData<I>,
|
||||
}
|
||||
|
||||
impl<C: Chain, P: SubstrateMessageLane, R, I> SubstrateMessagesSource<C, P, R, I> {
|
||||
impl<C: Chain, P: SubstrateMessageLane, I> SubstrateMessagesSource<C, P, I> {
|
||||
/// Create new Substrate headers source.
|
||||
pub fn new(
|
||||
client: Client<C>,
|
||||
lane: P,
|
||||
lane_id: LaneId,
|
||||
instance: InstanceId,
|
||||
instance: ChainId,
|
||||
target_to_source_headers_relay: Option<OnDemandHeadersRelay<P::TargetChain>>,
|
||||
) -> Self {
|
||||
SubstrateMessagesSource {
|
||||
@@ -75,7 +74,7 @@ impl<C: Chain, P: SubstrateMessageLane, R, I> SubstrateMessagesSource<C, P, R, I
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Chain, P: SubstrateMessageLane, R, I> Clone for SubstrateMessagesSource<C, P, R, I> {
|
||||
impl<C: Chain, P: SubstrateMessageLane, I> Clone for SubstrateMessagesSource<C, P, I> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
client: self.client.clone(),
|
||||
@@ -89,11 +88,10 @@ impl<C: Chain, P: SubstrateMessageLane, R, I> Clone for SubstrateMessagesSource<
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C, P, R, I> RelayClient for SubstrateMessagesSource<C, P, R, I>
|
||||
impl<C, P, I> RelayClient for SubstrateMessagesSource<C, P, I>
|
||||
where
|
||||
C: Chain,
|
||||
P: SubstrateMessageLane,
|
||||
R: 'static + Send + Sync,
|
||||
I: Send + Sync + Instance,
|
||||
{
|
||||
type Error = SubstrateError;
|
||||
@@ -104,7 +102,7 @@ where
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C, P, R, I> SourceClient<P> for SubstrateMessagesSource<C, P, R, I>
|
||||
impl<C, P, I> SourceClient<P> for SubstrateMessagesSource<C, P, I>
|
||||
where
|
||||
C: Chain,
|
||||
C::Header: DeserializeOwned,
|
||||
@@ -112,6 +110,7 @@ where
|
||||
C::BlockNumber: BlockNumberBase,
|
||||
P: SubstrateMessageLane<
|
||||
MessagesProof = SubstrateMessagesProof<C>,
|
||||
SourceChainBalance = C::Balance,
|
||||
SourceHeaderNumber = <C::Header as HeaderT>::Number,
|
||||
SourceHeaderHash = <C::Header as HeaderT>::Hash,
|
||||
SourceChain = C,
|
||||
@@ -119,7 +118,6 @@ where
|
||||
P::TargetChain: Chain<Hash = P::TargetHeaderHash, BlockNumber = P::TargetHeaderNumber>,
|
||||
P::TargetHeaderNumber: Decode,
|
||||
P::TargetHeaderHash: Decode,
|
||||
R: Send + Sync + MessagesConfig<I>,
|
||||
I: Send + Sync + Instance,
|
||||
{
|
||||
async fn state(&self) -> Result<SourceClientState<P>, SubstrateError> {
|
||||
@@ -168,21 +166,21 @@ where
|
||||
Ok((id, latest_received_nonce))
|
||||
}
|
||||
|
||||
async fn generated_messages_weights(
|
||||
async fn generated_message_details(
|
||||
&self,
|
||||
id: SourceHeaderIdOf<P>,
|
||||
nonces: RangeInclusive<MessageNonce>,
|
||||
) -> Result<MessageWeightsMap, SubstrateError> {
|
||||
) -> Result<MessageDetailsMap<P::SourceChainBalance>, SubstrateError> {
|
||||
let encoded_response = self
|
||||
.client
|
||||
.state_call(
|
||||
P::OUTBOUND_LANE_MESSAGES_DISPATCH_WEIGHT_METHOD.into(),
|
||||
P::OUTBOUND_LANE_MESSAGE_DETAILS_METHOD.into(),
|
||||
Bytes((self.lane_id, nonces.start(), nonces.end()).encode()),
|
||||
Some(id.1),
|
||||
)
|
||||
.await?;
|
||||
|
||||
make_message_weights_map::<C>(
|
||||
make_message_details_map::<C>(
|
||||
Decode::decode(&mut &encoded_response.0[..]).map_err(SubstrateError::ResponseParseFailed)?,
|
||||
nonces,
|
||||
)
|
||||
@@ -197,7 +195,7 @@ where
|
||||
let mut storage_keys = Vec::with_capacity(nonces.end().saturating_sub(*nonces.start()) as usize + 1);
|
||||
let mut message_nonce = *nonces.start();
|
||||
while message_nonce <= *nonces.end() {
|
||||
let message_key = pallet_bridge_messages::storage_keys::message_key::<R, I>(&self.lane_id, message_nonce);
|
||||
let message_key = pallet_bridge_messages::storage_keys::message_key::<I>(&self.lane_id, message_nonce);
|
||||
storage_keys.push(message_key);
|
||||
message_nonce += 1;
|
||||
}
|
||||
@@ -239,9 +237,13 @@ where
|
||||
|
||||
async fn require_target_header_on_source(&self, id: TargetHeaderIdOf<P>) {
|
||||
if let Some(ref target_to_source_headers_relay) = self.target_to_source_headers_relay {
|
||||
target_to_source_headers_relay.require_finalized_header(id);
|
||||
target_to_source_headers_relay.require_finalized_header(id).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn estimate_confirmation_transaction(&self) -> P::SourceChainBalance {
|
||||
num_traits::Zero::zero() // TODO: https://github.com/paritytech/parity-bridges-common/issues/997
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn read_client_state<SelfChain, BridgedHeaderHash, BridgedHeaderNumber>(
|
||||
@@ -287,10 +289,10 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
fn make_message_weights_map<C: Chain>(
|
||||
weights: Vec<(MessageNonce, Weight, u32)>,
|
||||
fn make_message_details_map<C: Chain>(
|
||||
weights: Vec<bp_messages::MessageDetails<C::Balance>>,
|
||||
nonces: RangeInclusive<MessageNonce>,
|
||||
) -> Result<MessageWeightsMap, SubstrateError> {
|
||||
) -> Result<MessageDetailsMap<C::Balance>, SubstrateError> {
|
||||
let make_missing_nonce_error = |expected_nonce| {
|
||||
Err(SubstrateError::Custom(format!(
|
||||
"Missing nonce {} in messages_dispatch_weight call result. Expected all nonces from {:?}",
|
||||
@@ -298,7 +300,7 @@ fn make_message_weights_map<C: Chain>(
|
||||
)))
|
||||
};
|
||||
|
||||
let mut weights_map = MessageWeightsMap::new();
|
||||
let mut weights_map = MessageDetailsMap::new();
|
||||
|
||||
// this is actually prevented by external logic
|
||||
if nonces.is_empty() {
|
||||
@@ -308,7 +310,7 @@ fn make_message_weights_map<C: Chain>(
|
||||
// check if last nonce is missing - loop below is not checking this
|
||||
let last_nonce_is_missing = weights
|
||||
.last()
|
||||
.map(|(last_nonce, _, _)| last_nonce != nonces.end())
|
||||
.map(|details| details.nonce != *nonces.end())
|
||||
.unwrap_or(true);
|
||||
if last_nonce_is_missing {
|
||||
return make_missing_nonce_error(*nonces.end());
|
||||
@@ -317,8 +319,8 @@ fn make_message_weights_map<C: Chain>(
|
||||
let mut expected_nonce = *nonces.start();
|
||||
let mut is_at_head = true;
|
||||
|
||||
for (nonce, weight, size) in weights {
|
||||
match (nonce == expected_nonce, is_at_head) {
|
||||
for details in weights {
|
||||
match (details.nonce == expected_nonce, is_at_head) {
|
||||
(true, _) => (),
|
||||
(false, true) => {
|
||||
// this may happen if some messages were already pruned from the source node
|
||||
@@ -328,7 +330,7 @@ fn make_message_weights_map<C: Chain>(
|
||||
target: "bridge",
|
||||
"Some messages are missing from the {} node: {:?}. Target node may be out of sync?",
|
||||
C::NAME,
|
||||
expected_nonce..nonce,
|
||||
expected_nonce..details.nonce,
|
||||
);
|
||||
}
|
||||
(false, false) => {
|
||||
@@ -340,13 +342,16 @@ fn make_message_weights_map<C: Chain>(
|
||||
}
|
||||
|
||||
weights_map.insert(
|
||||
nonce,
|
||||
MessageWeights {
|
||||
weight,
|
||||
size: size as _,
|
||||
details.nonce,
|
||||
MessageDetails {
|
||||
dispatch_weight: details.dispatch_weight,
|
||||
size: details.size as _,
|
||||
// TODO: https://github.com/paritytech/parity-bridges-common/issues/997
|
||||
reward: num_traits::Zero::zero(),
|
||||
dispatch_fee_payment: DispatchFeePayment::AtSourceChain,
|
||||
},
|
||||
);
|
||||
expected_nonce = nonce + 1;
|
||||
expected_nonce = details.nonce + 1;
|
||||
is_at_head = false;
|
||||
}
|
||||
|
||||
@@ -357,15 +362,53 @@ fn make_message_weights_map<C: Chain>(
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn message_details_from_rpc(
|
||||
nonces: RangeInclusive<MessageNonce>,
|
||||
) -> Vec<bp_messages::MessageDetails<bp_rialto::Balance>> {
|
||||
nonces
|
||||
.into_iter()
|
||||
.map(|nonce| bp_messages::MessageDetails {
|
||||
nonce,
|
||||
dispatch_weight: 0,
|
||||
size: 0,
|
||||
delivery_and_dispatch_fee: 0,
|
||||
dispatch_fee_payment: DispatchFeePayment::AtSourceChain,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn make_message_weights_map_succeeds_if_no_messages_are_missing() {
|
||||
fn make_message_details_map_succeeds_if_no_messages_are_missing() {
|
||||
assert_eq!(
|
||||
make_message_weights_map::<relay_rialto_client::Rialto>(vec![(1, 0, 0), (2, 0, 0), (3, 0, 0)], 1..=3,)
|
||||
.unwrap(),
|
||||
make_message_details_map::<relay_rialto_client::Rialto>(message_details_from_rpc(1..=3), 1..=3,).unwrap(),
|
||||
vec![
|
||||
(1, MessageWeights { weight: 0, size: 0 }),
|
||||
(2, MessageWeights { weight: 0, size: 0 }),
|
||||
(3, MessageWeights { weight: 0, size: 0 }),
|
||||
(
|
||||
1,
|
||||
MessageDetails {
|
||||
dispatch_weight: 0,
|
||||
size: 0,
|
||||
reward: 0,
|
||||
dispatch_fee_payment: DispatchFeePayment::AtSourceChain,
|
||||
}
|
||||
),
|
||||
(
|
||||
2,
|
||||
MessageDetails {
|
||||
dispatch_weight: 0,
|
||||
size: 0,
|
||||
reward: 0,
|
||||
dispatch_fee_payment: DispatchFeePayment::AtSourceChain,
|
||||
}
|
||||
),
|
||||
(
|
||||
3,
|
||||
MessageDetails {
|
||||
dispatch_weight: 0,
|
||||
size: 0,
|
||||
reward: 0,
|
||||
dispatch_fee_payment: DispatchFeePayment::AtSourceChain,
|
||||
}
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
@@ -373,12 +416,28 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn make_message_weights_map_succeeds_if_head_messages_are_missing() {
|
||||
fn make_message_details_map_succeeds_if_head_messages_are_missing() {
|
||||
assert_eq!(
|
||||
make_message_weights_map::<relay_rialto_client::Rialto>(vec![(2, 0, 0), (3, 0, 0)], 1..=3,).unwrap(),
|
||||
make_message_details_map::<relay_rialto_client::Rialto>(message_details_from_rpc(2..=3), 1..=3,).unwrap(),
|
||||
vec![
|
||||
(2, MessageWeights { weight: 0, size: 0 }),
|
||||
(3, MessageWeights { weight: 0, size: 0 }),
|
||||
(
|
||||
2,
|
||||
MessageDetails {
|
||||
dispatch_weight: 0,
|
||||
size: 0,
|
||||
reward: 0,
|
||||
dispatch_fee_payment: DispatchFeePayment::AtSourceChain,
|
||||
}
|
||||
),
|
||||
(
|
||||
3,
|
||||
MessageDetails {
|
||||
dispatch_weight: 0,
|
||||
size: 0,
|
||||
reward: 0,
|
||||
dispatch_fee_payment: DispatchFeePayment::AtSourceChain,
|
||||
}
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
@@ -386,25 +445,27 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn make_message_weights_map_fails_if_mid_messages_are_missing() {
|
||||
fn make_message_details_map_fails_if_mid_messages_are_missing() {
|
||||
let mut message_details_from_rpc = message_details_from_rpc(1..=3);
|
||||
message_details_from_rpc.remove(1);
|
||||
assert!(matches!(
|
||||
make_message_weights_map::<relay_rialto_client::Rialto>(vec![(1, 0, 0), (3, 0, 0)], 1..=3,),
|
||||
make_message_details_map::<relay_rialto_client::Rialto>(message_details_from_rpc, 1..=3,),
|
||||
Err(SubstrateError::Custom(_))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn make_message_weights_map_fails_if_tail_messages_are_missing() {
|
||||
fn make_message_details_map_fails_if_tail_messages_are_missing() {
|
||||
assert!(matches!(
|
||||
make_message_weights_map::<relay_rialto_client::Rialto>(vec![(1, 0, 0), (2, 0, 0)], 1..=3,),
|
||||
make_message_details_map::<relay_rialto_client::Rialto>(message_details_from_rpc(1..=2), 1..=3,),
|
||||
Err(SubstrateError::Custom(_))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn make_message_weights_map_fails_if_all_messages_are_missing() {
|
||||
fn make_message_details_map_fails_if_all_messages_are_missing() {
|
||||
assert!(matches!(
|
||||
make_message_weights_map::<relay_rialto_client::Rialto>(vec![], 1..=3),
|
||||
make_message_details_map::<relay_rialto_client::Rialto>(vec![], 1..=3),
|
||||
Err(SubstrateError::Custom(_))
|
||||
));
|
||||
}
|
||||
|
||||
@@ -24,15 +24,14 @@ use crate::on_demand_headers::OnDemandHeadersRelay;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bp_messages::{LaneId, MessageNonce, UnrewardedRelayersState};
|
||||
use bp_runtime::InstanceId;
|
||||
use bp_runtime::ChainId;
|
||||
use bridge_runtime_common::messages::source::FromBridgedChainMessagesDeliveryProof;
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::traits::Instance;
|
||||
use frame_support::{traits::Instance, weights::Weight};
|
||||
use messages_relay::{
|
||||
message_lane::{SourceHeaderIdOf, TargetHeaderIdOf},
|
||||
message_lane_loop::{TargetClient, TargetClientState},
|
||||
};
|
||||
use pallet_bridge_messages::Config as MessagesConfig;
|
||||
use relay_substrate_client::{Chain, Client, Error as SubstrateError, HashOf};
|
||||
use relay_utils::{relay_loop::Client as RelayClient, BlockNumberBase};
|
||||
use sp_core::Bytes;
|
||||
@@ -46,22 +45,22 @@ pub type SubstrateMessagesReceivingProof<C> = (
|
||||
);
|
||||
|
||||
/// Substrate client as Substrate messages target.
|
||||
pub struct SubstrateMessagesTarget<C: Chain, P: SubstrateMessageLane, R, I> {
|
||||
pub struct SubstrateMessagesTarget<C: Chain, P: SubstrateMessageLane, I> {
|
||||
client: Client<C>,
|
||||
lane: P,
|
||||
lane_id: LaneId,
|
||||
instance: InstanceId,
|
||||
instance: ChainId,
|
||||
source_to_target_headers_relay: Option<OnDemandHeadersRelay<P::SourceChain>>,
|
||||
_phantom: PhantomData<(R, I)>,
|
||||
_phantom: PhantomData<I>,
|
||||
}
|
||||
|
||||
impl<C: Chain, P: SubstrateMessageLane, R, I> SubstrateMessagesTarget<C, P, R, 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: InstanceId,
|
||||
instance: ChainId,
|
||||
source_to_target_headers_relay: Option<OnDemandHeadersRelay<P::SourceChain>>,
|
||||
) -> Self {
|
||||
SubstrateMessagesTarget {
|
||||
@@ -75,7 +74,7 @@ impl<C: Chain, P: SubstrateMessageLane, R, I> SubstrateMessagesTarget<C, P, R, I
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Chain, P: SubstrateMessageLane, R, I> Clone for SubstrateMessagesTarget<C, P, R, I> {
|
||||
impl<C: Chain, P: SubstrateMessageLane, I> Clone for SubstrateMessagesTarget<C, P, I> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
client: self.client.clone(),
|
||||
@@ -89,11 +88,10 @@ impl<C: Chain, P: SubstrateMessageLane, R, I> Clone for SubstrateMessagesTarget<
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C, P, R, I> RelayClient for SubstrateMessagesTarget<C, P, R, I>
|
||||
impl<C, P, I> RelayClient for SubstrateMessagesTarget<C, P, I>
|
||||
where
|
||||
C: Chain,
|
||||
P: SubstrateMessageLane,
|
||||
R: 'static + Send + Sync,
|
||||
I: Send + Sync + Instance,
|
||||
{
|
||||
type Error = SubstrateError;
|
||||
@@ -104,7 +102,7 @@ where
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C, P, R, I> TargetClient<P> for SubstrateMessagesTarget<C, P, R, I>
|
||||
impl<C, P, I> TargetClient<P> for SubstrateMessagesTarget<C, P, I>
|
||||
where
|
||||
C: Chain,
|
||||
C::Header: DeserializeOwned,
|
||||
@@ -119,7 +117,6 @@ where
|
||||
P::SourceChain: Chain<Hash = P::SourceHeaderHash, BlockNumber = P::SourceHeaderNumber>,
|
||||
P::SourceHeaderNumber: Decode,
|
||||
P::SourceHeaderHash: Decode,
|
||||
R: Send + Sync + MessagesConfig<I>,
|
||||
I: Send + Sync + Instance,
|
||||
{
|
||||
async fn state(&self) -> Result<TargetClientState<P>, SubstrateError> {
|
||||
@@ -190,7 +187,7 @@ where
|
||||
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::<R, I>(&self.lane_id);
|
||||
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)
|
||||
@@ -226,7 +223,16 @@ where
|
||||
|
||||
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);
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,39 +16,38 @@
|
||||
|
||||
//! On-demand Substrate -> Substrate headers relay.
|
||||
|
||||
use crate::finality_pipeline::{SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate};
|
||||
use crate::finality_pipeline::{
|
||||
SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate, RECENT_FINALITY_PROOFS_LIMIT, STALL_TIMEOUT,
|
||||
};
|
||||
use crate::finality_target::SubstrateFinalityTarget;
|
||||
|
||||
use async_std::sync::{Arc, Mutex};
|
||||
use bp_header_chain::justification::GrandpaJustification;
|
||||
use finality_relay::{
|
||||
FinalitySyncPipeline, SourceClient as FinalitySourceClient, TargetClient as FinalityTargetClient,
|
||||
FinalitySyncParams, FinalitySyncPipeline, SourceClient as FinalitySourceClient, SourceHeader,
|
||||
TargetClient as FinalityTargetClient,
|
||||
};
|
||||
use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
select, FutureExt, StreamExt,
|
||||
};
|
||||
use num_traits::{CheckedSub, Zero};
|
||||
use futures::{select, FutureExt};
|
||||
use num_traits::{CheckedSub, One, Zero};
|
||||
use relay_substrate_client::{
|
||||
finality_source::FinalitySource as SubstrateFinalitySource, BlockNumberOf, Chain, Client, HashOf, HeaderIdOf,
|
||||
SyncHeader,
|
||||
finality_source::{FinalitySource as SubstrateFinalitySource, RequiredHeaderNumberRef},
|
||||
BlockNumberOf, Chain, Client, HashOf, HeaderIdOf, SyncHeader,
|
||||
};
|
||||
use relay_utils::{
|
||||
metrics::MetricsParams, relay_loop::Client as RelayClient, BlockNumberBase, FailedClient, HeaderId,
|
||||
MaybeConnectionError,
|
||||
metrics::MetricsParams, relay_loop::Client as RelayClient, BlockNumberBase, FailedClient, MaybeConnectionError,
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// On-demand Substrate <-> Substrate headers relay.
|
||||
///
|
||||
/// This relay may be started by messages whenever some other relay (e.g. messages relay) needs more
|
||||
/// headers to be relayed to continue its regular work. When enough headers are relayed, on-demand
|
||||
/// relay may be deactivated.
|
||||
/// This relay may be requested to sync more headers, whenever some other relay (e.g. messages relay) needs
|
||||
/// it to continue its regular work. When enough headers are relayed, on-demand stops syncing headers.
|
||||
#[derive(Clone)]
|
||||
pub struct OnDemandHeadersRelay<SourceChain: Chain> {
|
||||
/// Background task name.
|
||||
background_task_name: String,
|
||||
/// Required headers to background sender.
|
||||
required_header_tx: mpsc::Sender<HeaderId<SourceChain::Hash, SourceChain::BlockNumber>>,
|
||||
/// Relay task name.
|
||||
relay_task_name: String,
|
||||
/// Shared reference to maximal required finalized header number.
|
||||
required_header_number: RequiredHeaderNumberRef<SourceChain>,
|
||||
}
|
||||
|
||||
impl<SourceChain: Chain> OnDemandHeadersRelay<SourceChain> {
|
||||
@@ -75,49 +74,49 @@ impl<SourceChain: Chain> OnDemandHeadersRelay<SourceChain> {
|
||||
SubstrateFinalityTarget<TargetChain, SubstrateFinalityToSubstrate<SourceChain, TargetChain, TargetSign>>:
|
||||
FinalityTargetClient<SubstrateFinalityToSubstrate<SourceChain, TargetChain, TargetSign>>,
|
||||
{
|
||||
let (required_header_tx, required_header_rx) = mpsc::channel(1);
|
||||
let required_header_number = Arc::new(Mutex::new(Zero::zero()));
|
||||
let this = OnDemandHeadersRelay {
|
||||
relay_task_name: on_demand_headers_relay_name::<SourceChain, TargetChain>(),
|
||||
required_header_number: required_header_number.clone(),
|
||||
};
|
||||
async_std::task::spawn(async move {
|
||||
background_task(
|
||||
source_client,
|
||||
target_client,
|
||||
pipeline,
|
||||
maximal_headers_difference,
|
||||
required_header_rx,
|
||||
required_header_number,
|
||||
)
|
||||
.await;
|
||||
});
|
||||
|
||||
let background_task_name = format!(
|
||||
"{}-background",
|
||||
on_demand_headers_relay_name::<SourceChain, TargetChain>()
|
||||
);
|
||||
OnDemandHeadersRelay {
|
||||
background_task_name,
|
||||
required_header_tx,
|
||||
}
|
||||
this
|
||||
}
|
||||
|
||||
/// Someone is asking us to relay given finalized header.
|
||||
pub fn require_finalized_header(&self, header_id: HeaderIdOf<SourceChain>) {
|
||||
if let Err(error) = self.required_header_tx.clone().try_send(header_id) {
|
||||
log::error!(
|
||||
pub async fn require_finalized_header(&self, header_id: HeaderIdOf<SourceChain>) {
|
||||
let mut required_header_number = self.required_header_number.lock().await;
|
||||
if header_id.0 > *required_header_number {
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Failed to send require header id {:?} to {:?}: {:?}",
|
||||
header_id,
|
||||
self.background_task_name,
|
||||
error,
|
||||
"More {} headers required in {} relay. Going to sync up to the {}",
|
||||
SourceChain::NAME,
|
||||
self.relay_task_name,
|
||||
header_id.0,
|
||||
);
|
||||
|
||||
*required_header_number = header_id.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Background task that is responsible for starting and stopping headers relay when required.
|
||||
/// Background task that is responsible for starting headers relay.
|
||||
async fn background_task<SourceChain, TargetChain, TargetSign>(
|
||||
source_client: Client<SourceChain>,
|
||||
target_client: Client<TargetChain>,
|
||||
pipeline: SubstrateFinalityToSubstrate<SourceChain, TargetChain, TargetSign>,
|
||||
maximal_headers_difference: SourceChain::BlockNumber,
|
||||
mut required_header_rx: mpsc::Receiver<HeaderIdOf<SourceChain>>,
|
||||
required_header_number: RequiredHeaderNumberRef<SourceChain>,
|
||||
) where
|
||||
SourceChain: Chain + Debug,
|
||||
SourceChain::BlockNumber: BlockNumberBase,
|
||||
@@ -138,36 +137,20 @@ async fn background_task<SourceChain, TargetChain, TargetSign>(
|
||||
let mut finality_source = SubstrateFinalitySource::<
|
||||
_,
|
||||
SubstrateFinalityToSubstrate<SourceChain, TargetChain, TargetSign>,
|
||||
>::new(source_client.clone());
|
||||
>::new(source_client.clone(), Some(required_header_number.clone()));
|
||||
let mut finality_target = SubstrateFinalityTarget::new(target_client.clone(), pipeline.clone());
|
||||
let mut latest_non_mandatory_at_source = Zero::zero();
|
||||
|
||||
let mut active_headers_relay = None;
|
||||
let mut required_header_number = Zero::zero();
|
||||
let mut relay_exited_rx = futures::future::pending().left_future();
|
||||
let mut restart_relay = true;
|
||||
let finality_relay_task = futures::future::Fuse::terminated();
|
||||
futures::pin_mut!(finality_relay_task);
|
||||
|
||||
loop {
|
||||
// wait for next target block or for new required header
|
||||
select! {
|
||||
_ = async_std::task::sleep(TargetChain::AVERAGE_BLOCK_INTERVAL).fuse() => {},
|
||||
required_header_id = required_header_rx.next() => {
|
||||
match required_header_id {
|
||||
Some(required_header_id) => {
|
||||
if required_header_id.0 > required_header_number {
|
||||
required_header_number = required_header_id.0;
|
||||
}
|
||||
},
|
||||
None => {
|
||||
// that's the only way to exit background task - to drop `required_header_tx`
|
||||
break
|
||||
},
|
||||
}
|
||||
},
|
||||
_ = relay_exited_rx => {
|
||||
// there could be a situation when we're receiving exit signals after we
|
||||
// have already stopped relay or when we have already started new relay.
|
||||
// but it isn't critical, because even if we'll accidentally stop new relay
|
||||
// we'll restart it almost immediately
|
||||
stop_on_demand_headers_relay(active_headers_relay.take()).await;
|
||||
_ = finality_relay_task => {
|
||||
// this should never happen in practice given the current code
|
||||
restart_relay = true;
|
||||
},
|
||||
}
|
||||
|
||||
@@ -199,58 +182,187 @@ async fn background_task<SourceChain, TargetChain, TargetSign>(
|
||||
continue;
|
||||
}
|
||||
|
||||
// start or stop headers relay if required
|
||||
let action = select_on_demand_relay_action::<SourceChain>(
|
||||
// submit mandatory header if some headers are missing
|
||||
let best_finalized_source_header_at_target_fmt = format!("{:?}", best_finalized_source_header_at_target);
|
||||
let mandatory_scan_range = mandatory_headers_scan_range::<SourceChain>(
|
||||
best_finalized_source_header_at_source.ok(),
|
||||
best_finalized_source_header_at_target.ok(),
|
||||
required_header_number,
|
||||
maximal_headers_difference,
|
||||
&relay_task_name,
|
||||
active_headers_relay.is_some(),
|
||||
);
|
||||
match action {
|
||||
OnDemandRelayAction::Start => {
|
||||
let (relay_exited_tx, new_relay_exited_rx) = oneshot::channel();
|
||||
active_headers_relay = start_on_demand_headers_relay(
|
||||
relay_task_name.clone(),
|
||||
relay_exited_tx,
|
||||
source_client.clone(),
|
||||
target_client.clone(),
|
||||
pipeline.clone(),
|
||||
);
|
||||
if active_headers_relay.is_some() {
|
||||
relay_exited_rx = new_relay_exited_rx.right_future();
|
||||
&required_header_number,
|
||||
)
|
||||
.await;
|
||||
if let Some(mandatory_scan_range) = mandatory_scan_range {
|
||||
let relay_mandatory_header_result = relay_mandatory_header_from_range(
|
||||
&finality_source,
|
||||
&required_header_number,
|
||||
best_finalized_source_header_at_target_fmt,
|
||||
(
|
||||
std::cmp::max(mandatory_scan_range.0, latest_non_mandatory_at_source),
|
||||
mandatory_scan_range.1,
|
||||
),
|
||||
&relay_task_name,
|
||||
)
|
||||
.await;
|
||||
match relay_mandatory_header_result {
|
||||
Ok(true) => (),
|
||||
Ok(false) => {
|
||||
// there are no (or we don't need to relay them) mandatory headers in the range
|
||||
// => to avoid scanning the same headers over and over again, remember that
|
||||
latest_non_mandatory_at_source = mandatory_scan_range.1;
|
||||
}
|
||||
Err(e) => {
|
||||
if e.is_connection_error() {
|
||||
relay_utils::relay_loop::reconnect_failed_client(
|
||||
FailedClient::Source,
|
||||
relay_utils::relay_loop::RECONNECT_DELAY,
|
||||
&mut finality_source,
|
||||
&mut finality_target,
|
||||
)
|
||||
.await;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
OnDemandRelayAction::Stop => {
|
||||
stop_on_demand_headers_relay(active_headers_relay.take()).await;
|
||||
}
|
||||
OnDemandRelayAction::None => (),
|
||||
}
|
||||
|
||||
// start/restart relay
|
||||
if restart_relay {
|
||||
finality_relay_task.set(
|
||||
finality_relay::run(
|
||||
finality_source.clone(),
|
||||
finality_target.clone(),
|
||||
FinalitySyncParams {
|
||||
tick: std::cmp::max(SourceChain::AVERAGE_BLOCK_INTERVAL, TargetChain::AVERAGE_BLOCK_INTERVAL),
|
||||
recent_finality_proofs_limit: RECENT_FINALITY_PROOFS_LIMIT,
|
||||
stall_timeout: STALL_TIMEOUT,
|
||||
only_mandatory_headers: false,
|
||||
},
|
||||
MetricsParams::disabled(),
|
||||
futures::future::pending(),
|
||||
)
|
||||
.fuse(),
|
||||
);
|
||||
|
||||
restart_relay = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `Some()` with inclusive range of headers which must be scanned for manadatory headers
|
||||
/// and the first of such headers must be submitted to the target node.
|
||||
async fn mandatory_headers_scan_range<C: Chain>(
|
||||
best_finalized_source_header_at_source: Option<C::BlockNumber>,
|
||||
best_finalized_source_header_at_target: Option<C::BlockNumber>,
|
||||
maximal_headers_difference: C::BlockNumber,
|
||||
required_header_number: &RequiredHeaderNumberRef<C>,
|
||||
) -> Option<(C::BlockNumber, C::BlockNumber)> {
|
||||
let required_header_number = *required_header_number.lock().await;
|
||||
|
||||
// if we have been unable to read header number from the target, then let's assume
|
||||
// that it is the same as required header number. Otherwise we risk submitting
|
||||
// unneeded transactions
|
||||
let best_finalized_source_header_at_target =
|
||||
best_finalized_source_header_at_target.unwrap_or(required_header_number);
|
||||
|
||||
// if we have been unable to read header number from the source, then let's assume
|
||||
// that it is the same as at the target
|
||||
let best_finalized_source_header_at_source =
|
||||
best_finalized_source_header_at_source.unwrap_or(best_finalized_source_header_at_target);
|
||||
|
||||
// if there are too many source headers missing from the target node, sync mandatory
|
||||
// headers to target
|
||||
//
|
||||
// why do we need that? When complex headers+messages relay is used, it'll normally only relay
|
||||
// headers when there are undelivered messages/confirmations. But security model of the
|
||||
// `pallet-bridge-grandpa` module relies on the fact that headers are synced in real-time and
|
||||
// that it'll see authorities-change header before unbonding period will end for previous
|
||||
// authorities set.
|
||||
let current_headers_difference = best_finalized_source_header_at_source
|
||||
.checked_sub(&best_finalized_source_header_at_target)
|
||||
.unwrap_or_else(Zero::zero);
|
||||
if current_headers_difference <= maximal_headers_difference {
|
||||
return None;
|
||||
}
|
||||
|
||||
// if relay is already asked to sync headers, don't do anything yet
|
||||
if required_header_number > best_finalized_source_header_at_target {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((
|
||||
best_finalized_source_header_at_target + One::one(),
|
||||
best_finalized_source_header_at_source,
|
||||
))
|
||||
}
|
||||
|
||||
/// Try to find mandatory header in the inclusive headers range and, if one is found, ask to relay it.
|
||||
///
|
||||
/// Returns `true` if header was found and (asked to be) relayed and `false` otherwise.
|
||||
async fn relay_mandatory_header_from_range<SourceChain: Chain, P>(
|
||||
finality_source: &SubstrateFinalitySource<SourceChain, P>,
|
||||
required_header_number: &RequiredHeaderNumberRef<SourceChain>,
|
||||
best_finalized_source_header_at_target: String,
|
||||
range: (SourceChain::BlockNumber, SourceChain::BlockNumber),
|
||||
relay_task_name: &str,
|
||||
) -> Result<bool, relay_substrate_client::Error>
|
||||
where
|
||||
SubstrateFinalitySource<SourceChain, P>: FinalitySourceClient<P>,
|
||||
P: FinalitySyncPipeline<Number = SourceChain::BlockNumber>,
|
||||
{
|
||||
// search for mandatory header first
|
||||
let mandatory_source_header_number = find_mandatory_header_in_range(finality_source, range).await?;
|
||||
|
||||
// if there are no mandatory headers - we have nothing to do
|
||||
let mandatory_source_header_number = match mandatory_source_header_number {
|
||||
Some(mandatory_source_header_number) => mandatory_source_header_number,
|
||||
None => return Ok(false),
|
||||
};
|
||||
|
||||
// `find_mandatory_header` call may take a while => check if `required_header_number` is still
|
||||
// less than our `mandatory_source_header_number` before logging anything
|
||||
let mut required_header_number = required_header_number.lock().await;
|
||||
if *required_header_number >= mandatory_source_header_number {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Too many {} headers missing at target in {} relay ({} vs {}). Going to sync up to the mandatory {}",
|
||||
SourceChain::NAME,
|
||||
relay_task_name,
|
||||
best_finalized_source_header_at_target,
|
||||
range.1,
|
||||
mandatory_source_header_number,
|
||||
);
|
||||
|
||||
*required_header_number = mandatory_source_header_number;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Read best finalized source block number from source client.
|
||||
///
|
||||
/// Returns `None` if we have failed to read the number.
|
||||
async fn best_finalized_source_header_at_source<SourceChain: Chain, P>(
|
||||
finality_source: &SubstrateFinalitySource<SourceChain, P>,
|
||||
relay_task_name: &str,
|
||||
) -> Result<SourceChain::BlockNumber, <SubstrateFinalitySource<SourceChain, P> as RelayClient>::Error>
|
||||
) -> Result<SourceChain::BlockNumber, relay_substrate_client::Error>
|
||||
where
|
||||
SubstrateFinalitySource<SourceChain, P>: FinalitySourceClient<P>,
|
||||
P: FinalitySyncPipeline<Number = SourceChain::BlockNumber>,
|
||||
{
|
||||
finality_source.best_finalized_block_number().await.map_err(|error| {
|
||||
log::error!(
|
||||
target: "bridge",
|
||||
"Failed to read best finalized source header from source in {} relay: {:?}",
|
||||
relay_task_name,
|
||||
error,
|
||||
);
|
||||
finality_source
|
||||
.on_chain_best_finalized_block_number()
|
||||
.await
|
||||
.map_err(|error| {
|
||||
log::error!(
|
||||
target: "bridge",
|
||||
"Failed to read best finalized source header from source in {} relay: {:?}",
|
||||
relay_task_name,
|
||||
error,
|
||||
);
|
||||
|
||||
error
|
||||
})
|
||||
error
|
||||
})
|
||||
}
|
||||
|
||||
/// Read best finalized source block number from target client.
|
||||
@@ -279,68 +391,28 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
/// What to do with the on-demand relay task?
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum OnDemandRelayAction {
|
||||
Start,
|
||||
Stop,
|
||||
None,
|
||||
}
|
||||
|
||||
fn select_on_demand_relay_action<C: Chain>(
|
||||
best_finalized_source_header_at_source: Option<C::BlockNumber>,
|
||||
best_finalized_source_header_at_target: Option<C::BlockNumber>,
|
||||
mut required_source_header_at_target: C::BlockNumber,
|
||||
maximal_headers_difference: C::BlockNumber,
|
||||
relay_task_name: &str,
|
||||
is_active: bool,
|
||||
) -> OnDemandRelayAction {
|
||||
// if we have been unable to read header number from the target, then let's assume
|
||||
// that it is the same as required header number. Otherwise we risk submitting
|
||||
// unneeded transactions
|
||||
let best_finalized_source_header_at_target =
|
||||
best_finalized_source_header_at_target.unwrap_or(required_source_header_at_target);
|
||||
|
||||
// if we have been unable to read header number from the source, then let's assume
|
||||
// that it is the same as at the target
|
||||
let best_finalized_source_header_at_source =
|
||||
best_finalized_source_header_at_source.unwrap_or(best_finalized_source_header_at_target);
|
||||
|
||||
// if there are too many source headers missing from the target node, require some
|
||||
// new headers at target
|
||||
//
|
||||
// why do we need that? When complex headers+messages relay is used, it'll normally only relay
|
||||
// headers when there are undelivered messages/confirmations. But security model of the
|
||||
// `pallet-bridge-grandpa` module relies on the fact that headers are synced in real-time and
|
||||
// that it'll see authorities-change header before unbonding period will end for previous
|
||||
// authorities set.
|
||||
let current_headers_difference = best_finalized_source_header_at_source
|
||||
.checked_sub(&best_finalized_source_header_at_target)
|
||||
.unwrap_or_else(Zero::zero);
|
||||
if current_headers_difference > maximal_headers_difference {
|
||||
required_source_header_at_target = best_finalized_source_header_at_source;
|
||||
|
||||
// don't log if relay is already running
|
||||
if !is_active {
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Too many {} headers missing at target in {} relay ({} vs {}). Going to sync up to the {}",
|
||||
C::NAME,
|
||||
relay_task_name,
|
||||
best_finalized_source_header_at_source,
|
||||
best_finalized_source_header_at_target,
|
||||
best_finalized_source_header_at_source,
|
||||
);
|
||||
/// Read first mandatory header in given inclusive range.
|
||||
///
|
||||
/// Returns `Ok(None)` if there were no mandatory headers in the range.
|
||||
async fn find_mandatory_header_in_range<SourceChain: Chain, P>(
|
||||
finality_source: &SubstrateFinalitySource<SourceChain, P>,
|
||||
range: (SourceChain::BlockNumber, SourceChain::BlockNumber),
|
||||
) -> Result<Option<SourceChain::BlockNumber>, relay_substrate_client::Error>
|
||||
where
|
||||
SubstrateFinalitySource<SourceChain, P>: FinalitySourceClient<P>,
|
||||
P: FinalitySyncPipeline<Number = SourceChain::BlockNumber>,
|
||||
{
|
||||
let mut current = range.0;
|
||||
while current <= range.1 {
|
||||
let header: SyncHeader<SourceChain::Header> = finality_source.client().header_by_number(current).await?.into();
|
||||
if header.is_mandatory() {
|
||||
return Ok(Some(current));
|
||||
}
|
||||
|
||||
current += One::one();
|
||||
}
|
||||
|
||||
// now let's select what to do with relay
|
||||
let needs_to_be_active = required_source_header_at_target > best_finalized_source_header_at_target;
|
||||
match (needs_to_be_active, is_active) {
|
||||
(true, false) => OnDemandRelayAction::Start,
|
||||
(false, true) => OnDemandRelayAction::Stop,
|
||||
_ => OnDemandRelayAction::None,
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// On-demand headers relay task name.
|
||||
@@ -348,61 +420,6 @@ fn on_demand_headers_relay_name<SourceChain: Chain, TargetChain: Chain>() -> Str
|
||||
format!("on-demand-{}-to-{}", SourceChain::NAME, TargetChain::NAME)
|
||||
}
|
||||
|
||||
/// Start on-demand headers relay task.
|
||||
fn start_on_demand_headers_relay<SourceChain: Chain, TargetChain: Chain, TargetSign>(
|
||||
task_name: String,
|
||||
relay_exited_tx: oneshot::Sender<()>,
|
||||
source_client: Client<SourceChain>,
|
||||
target_client: Client<TargetChain>,
|
||||
pipeline: SubstrateFinalityToSubstrate<SourceChain, TargetChain, TargetSign>,
|
||||
) -> Option<async_std::task::JoinHandle<()>>
|
||||
where
|
||||
SourceChain::BlockNumber: BlockNumberBase,
|
||||
SubstrateFinalityToSubstrate<SourceChain, TargetChain, TargetSign>: SubstrateFinalitySyncPipeline<
|
||||
Hash = HashOf<SourceChain>,
|
||||
Number = BlockNumberOf<SourceChain>,
|
||||
Header = SyncHeader<SourceChain::Header>,
|
||||
FinalityProof = GrandpaJustification<SourceChain::Header>,
|
||||
TargetChain = TargetChain,
|
||||
>,
|
||||
TargetSign: 'static,
|
||||
{
|
||||
let headers_relay_future =
|
||||
crate::finality_pipeline::run(pipeline, source_client, target_client, true, MetricsParams::disabled());
|
||||
let closure_task_name = task_name.clone();
|
||||
async_std::task::Builder::new()
|
||||
.name(task_name.clone())
|
||||
.spawn(async move {
|
||||
log::info!(target: "bridge", "Starting {} headers relay", closure_task_name);
|
||||
let result = headers_relay_future.await;
|
||||
log::trace!(target: "bridge", "{} headers relay has exited. Result: {:?}", closure_task_name, result);
|
||||
let _ = relay_exited_tx.send(());
|
||||
})
|
||||
.map_err(|error| {
|
||||
log::error!(
|
||||
target: "bridge",
|
||||
"Failed to start {} relay: {:?}",
|
||||
task_name,
|
||||
error,
|
||||
);
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
|
||||
/// Stop on-demand headers relay task.
|
||||
async fn stop_on_demand_headers_relay(task: Option<async_std::task::JoinHandle<()>>) {
|
||||
if let Some(task) = task {
|
||||
let task_name = task
|
||||
.task()
|
||||
.name()
|
||||
.expect("on-demand tasks are always started with name; qed")
|
||||
.to_string();
|
||||
log::trace!(target: "bridge", "Cancelling {} headers relay", task_name);
|
||||
task.cancel().await;
|
||||
log::info!(target: "bridge", "Cancelled {} headers relay", task_name);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -412,42 +429,19 @@ mod tests {
|
||||
const AT_SOURCE: Option<bp_millau::BlockNumber> = Some(10);
|
||||
const AT_TARGET: Option<bp_millau::BlockNumber> = Some(1);
|
||||
|
||||
#[test]
|
||||
fn starts_relay_when_headers_are_required() {
|
||||
#[async_std::test]
|
||||
async fn mandatory_headers_scan_range_selects_range_if_too_many_headers_are_missing() {
|
||||
assert_eq!(
|
||||
select_on_demand_relay_action::<TestChain>(AT_SOURCE, AT_TARGET, 5, 100, "test", false),
|
||||
OnDemandRelayAction::Start,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
select_on_demand_relay_action::<TestChain>(AT_SOURCE, AT_TARGET, 5, 100, "test", true),
|
||||
OnDemandRelayAction::None,
|
||||
mandatory_headers_scan_range::<TestChain>(AT_SOURCE, AT_TARGET, 5, &Arc::new(Mutex::new(0))).await,
|
||||
Some((AT_TARGET.unwrap() + 1, AT_SOURCE.unwrap())),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn starts_relay_when_too_many_headers_missing() {
|
||||
#[async_std::test]
|
||||
async fn mandatory_headers_scan_range_selects_nothing_if_enough_headers_are_relayed() {
|
||||
assert_eq!(
|
||||
select_on_demand_relay_action::<TestChain>(AT_SOURCE, AT_TARGET, 0, 5, "test", false),
|
||||
OnDemandRelayAction::Start,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
select_on_demand_relay_action::<TestChain>(AT_SOURCE, AT_TARGET, 0, 5, "test", true),
|
||||
OnDemandRelayAction::None,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stops_relay_if_required_header_is_synced() {
|
||||
assert_eq!(
|
||||
select_on_demand_relay_action::<TestChain>(AT_SOURCE, AT_TARGET, AT_TARGET.unwrap(), 100, "test", true),
|
||||
OnDemandRelayAction::Stop,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
select_on_demand_relay_action::<TestChain>(AT_SOURCE, AT_TARGET, AT_TARGET.unwrap(), 100, "test", false),
|
||||
OnDemandRelayAction::None,
|
||||
mandatory_headers_scan_range::<TestChain>(AT_SOURCE, AT_TARGET, 10, &Arc::new(Mutex::new(0))).await,
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ edition = "2018"
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
|
||||
[dependencies]
|
||||
async-std = "1.6.5"
|
||||
bp-eth-poa = { path = "../../primitives/ethereum-poa" }
|
||||
codec = { package = "parity-scale-codec", version = "2.0.0" }
|
||||
headers-relay = { path = "../headers" }
|
||||
|
||||
@@ -22,6 +22,7 @@ use crate::types::{
|
||||
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;
|
||||
|
||||
/// Number of headers missing from the Ethereum node for us to consider node not synced.
|
||||
@@ -36,7 +37,28 @@ pub struct Client {
|
||||
|
||||
impl Client {
|
||||
/// Create a new Ethereum RPC Client.
|
||||
pub async fn new(params: ConnectionParams) -> Result<Self> {
|
||||
///
|
||||
/// This function will keep connecting to given Ethereum node until connection is established
|
||||
/// and is functional. If attempt fail, it will wait for `RECONNECT_DELAY` and retry again.
|
||||
pub async fn new(params: ConnectionParams) -> Self {
|
||||
loop {
|
||||
match Self::try_connect(params.clone()).await {
|
||||
Ok(client) => return client,
|
||||
Err(error) => log::error!(
|
||||
target: "bridge",
|
||||
"Failed to connect to Ethereum node: {:?}. Going to retry in {}s",
|
||||
error,
|
||||
RECONNECT_DELAY.as_secs(),
|
||||
),
|
||||
}
|
||||
|
||||
async_std::task::sleep(RECONNECT_DELAY).await;
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to connect to Ethereum node. Returns Ethereum RPC client if connection has been established
|
||||
/// or error otherwise.
|
||||
pub async fn try_connect(params: ConnectionParams) -> Result<Self> {
|
||||
Ok(Self {
|
||||
client: Self::build_client(¶ms).await?,
|
||||
params,
|
||||
|
||||
@@ -41,6 +41,7 @@ impl Chain for Kusama {
|
||||
type Index = bp_kusama::Nonce;
|
||||
type SignedBlock = bp_kusama::SignedBlock;
|
||||
type Call = ();
|
||||
type Balance = bp_kusama::Balance;
|
||||
}
|
||||
|
||||
/// Kusama header type used in headers sync.
|
||||
|
||||
@@ -44,11 +44,10 @@ impl Chain for Millau {
|
||||
type Index = millau_runtime::Index;
|
||||
type SignedBlock = millau_runtime::SignedBlock;
|
||||
type Call = millau_runtime::Call;
|
||||
type Balance = millau_runtime::Balance;
|
||||
}
|
||||
|
||||
impl ChainWithBalances for Millau {
|
||||
type NativeBalance = millau_runtime::Balance;
|
||||
|
||||
fn account_info_storage_key(account_id: &Self::AccountId) -> StorageKey {
|
||||
use frame_support::storage::generator::StorageMap;
|
||||
StorageKey(frame_system::Account::<millau_runtime::Runtime>::storage_map_final_key(
|
||||
|
||||
@@ -41,6 +41,7 @@ impl Chain for Polkadot {
|
||||
type Index = bp_polkadot::Nonce;
|
||||
type SignedBlock = bp_polkadot::SignedBlock;
|
||||
type Call = ();
|
||||
type Balance = bp_polkadot::Balance;
|
||||
}
|
||||
|
||||
/// Polkadot header type used in headers sync.
|
||||
|
||||
@@ -44,11 +44,10 @@ impl Chain for Rialto {
|
||||
type Index = rialto_runtime::Index;
|
||||
type SignedBlock = rialto_runtime::SignedBlock;
|
||||
type Call = rialto_runtime::Call;
|
||||
type Balance = rialto_runtime::Balance;
|
||||
}
|
||||
|
||||
impl ChainWithBalances for Rialto {
|
||||
type NativeBalance = rialto_runtime::Balance;
|
||||
|
||||
fn account_info_storage_key(account_id: &Self::AccountId) -> StorageKey {
|
||||
use frame_support::storage::generator::StorageMap;
|
||||
StorageKey(frame_system::Account::<rialto_runtime::Runtime>::storage_map_final_key(
|
||||
|
||||
@@ -12,7 +12,16 @@ relay-substrate-client = { path = "../client-substrate" }
|
||||
relay-utils = { path = "../utils" }
|
||||
|
||||
# Bridge dependencies
|
||||
bridge-runtime-common = { path = "../../bin/runtime-common" }
|
||||
bp-header-chain = { path = "../../primitives/header-chain" }
|
||||
bp-message-dispatch = { path = "../../primitives/message-dispatch" }
|
||||
bp-messages = { path = "../../primitives/messages" }
|
||||
bp-polkadot-core = { path = "../../primitives/polkadot-core" }
|
||||
bp-rococo = { path = "../../primitives/chain-rococo" }
|
||||
bp-runtime = { path = "../../primitives/runtime" }
|
||||
bp-wococo = { path = "../../primitives/chain-wococo" }
|
||||
pallet-bridge-dispatch = { path = "../../modules/dispatch" }
|
||||
pallet-bridge-messages = { path = "../../modules/messages" }
|
||||
|
||||
# Substrate Dependencies
|
||||
frame-system = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
|
||||
@@ -22,6 +22,8 @@ use sp_core::{storage::StorageKey, Pair};
|
||||
use sp_runtime::{generic::SignedPayload, traits::IdentifyAccount};
|
||||
use std::time::Duration;
|
||||
|
||||
pub mod runtime;
|
||||
|
||||
/// Rococo header id.
|
||||
pub type HeaderId = relay_utils::HeaderId<bp_rococo::Hash, bp_rococo::BlockNumber>;
|
||||
|
||||
@@ -46,12 +48,11 @@ impl Chain for Rococo {
|
||||
type AccountId = bp_rococo::AccountId;
|
||||
type Index = bp_rococo::Index;
|
||||
type SignedBlock = bp_rococo::SignedBlock;
|
||||
type Call = bp_rococo::Call;
|
||||
type Call = crate::runtime::Call;
|
||||
type Balance = bp_rococo::Balance;
|
||||
}
|
||||
|
||||
impl ChainWithBalances for Rococo {
|
||||
type NativeBalance = bp_rococo::Balance;
|
||||
|
||||
fn account_info_storage_key(account_id: &Self::AccountId) -> StorageKey {
|
||||
StorageKey(bp_rococo::account_info_storage_key(account_id))
|
||||
}
|
||||
@@ -60,7 +61,7 @@ impl ChainWithBalances for Rococo {
|
||||
impl TransactionSignScheme for Rococo {
|
||||
type Chain = Rococo;
|
||||
type AccountKeyPair = sp_core::sr25519::Pair;
|
||||
type SignedTransaction = bp_rococo::UncheckedExtrinsic;
|
||||
type SignedTransaction = crate::runtime::UncheckedExtrinsic;
|
||||
|
||||
fn sign_transaction(
|
||||
genesis_hash: <Self::Chain as ChainBase>::Hash,
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
// 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 Rococo runtime.
|
||||
|
||||
use bp_messages::{LaneId, UnrewardedRelayersState};
|
||||
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;
|
||||
|
||||
/// Unchecked Rococo extrinsic.
|
||||
pub type UncheckedExtrinsic = bp_polkadot_core::UncheckedExtrinsic<Call>;
|
||||
|
||||
/// Wococo account ownership digest from Rococo.
|
||||
///
|
||||
/// The byte vector returned by this function should be signed with a Wococo account private key.
|
||||
/// This way, the owner of `rococo_account_id` on Rococo proves that the Wococo account private key
|
||||
/// is also under his control.
|
||||
pub fn rococo_to_wococo_account_ownership_digest<Call, AccountId, SpecVersion>(
|
||||
wococo_call: &Call,
|
||||
rococo_account_id: AccountId,
|
||||
wococo_spec_version: SpecVersion,
|
||||
) -> Vec<u8>
|
||||
where
|
||||
Call: codec::Encode,
|
||||
AccountId: codec::Encode,
|
||||
SpecVersion: codec::Encode,
|
||||
{
|
||||
pallet_bridge_dispatch::account_ownership_digest(
|
||||
wococo_call,
|
||||
rococo_account_id,
|
||||
wococo_spec_version,
|
||||
bp_runtime::ROCOCO_CHAIN_ID,
|
||||
bp_runtime::WOCOCO_CHAIN_ID,
|
||||
)
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// 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
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
||||
pub enum Call {
|
||||
/// System pallet.
|
||||
#[codec(index = 0)]
|
||||
System(SystemCall),
|
||||
/// Wococo bridge pallet.
|
||||
#[codec(index = 41)]
|
||||
BridgeGrandpaWococo(BridgeGrandpaWococoCall),
|
||||
/// Wococo messages pallet.
|
||||
#[codec(index = 44)]
|
||||
BridgeMessagesWococo(BridgeMessagesWococoCall),
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum SystemCall {
|
||||
#[codec(index = 1)]
|
||||
remark(Vec<u8>),
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum BridgeGrandpaWococoCall {
|
||||
#[codec(index = 0)]
|
||||
submit_finality_proof(
|
||||
<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)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum BridgeMessagesWococoCall {
|
||||
#[codec(index = 3)]
|
||||
send_message(
|
||||
LaneId,
|
||||
bp_message_dispatch::MessagePayload<
|
||||
bp_rococo::AccountId,
|
||||
bp_wococo::AccountId,
|
||||
bp_wococo::AccountPublic,
|
||||
Vec<u8>,
|
||||
>,
|
||||
bp_rococo::Balance,
|
||||
),
|
||||
#[codec(index = 5)]
|
||||
receive_messages_proof(
|
||||
bp_wococo::AccountId,
|
||||
bridge_runtime_common::messages::target::FromBridgedChainMessagesProof<bp_wococo::Hash>,
|
||||
u32,
|
||||
Weight,
|
||||
),
|
||||
#[codec(index = 6)]
|
||||
receive_messages_delivery_proof(
|
||||
bridge_runtime_common::messages::source::FromBridgedChainMessagesDeliveryProof<bp_wococo::Hash>,
|
||||
UnrewardedRelayersState,
|
||||
),
|
||||
}
|
||||
|
||||
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,7 +6,7 @@ edition = "2018"
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
|
||||
[dependencies]
|
||||
async-std = "1.6.5"
|
||||
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"
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
use bp_runtime::Chain as ChainBase;
|
||||
use frame_support::Parameter;
|
||||
use jsonrpsee_ws_client::{DeserializeOwned, Serialize};
|
||||
use num_traits::{CheckedSub, Zero};
|
||||
use num_traits::{CheckedSub, SaturatingAdd, Zero};
|
||||
use sp_core::{storage::StorageKey, Pair};
|
||||
use sp_runtime::{
|
||||
generic::SignedBlock,
|
||||
@@ -54,14 +54,16 @@ pub trait Chain: ChainBase + Clone {
|
||||
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 suport 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;
|
||||
}
|
||||
|
||||
/// Substrate-based chain with `frame_system::Config::AccountData` set to
|
||||
/// the `pallet_balances::AccountData<NativeBalance>`.
|
||||
/// the `pallet_balances::AccountData<Balance>`.
|
||||
pub trait ChainWithBalances: Chain {
|
||||
/// Balance of an account in native tokens.
|
||||
type NativeBalance: Parameter + Member + DeserializeOwned + Clone + Copy + CheckedSub + PartialOrd + Zero;
|
||||
|
||||
/// Return runtime storage key for getting `frame_system::AccountInfo` of given account.
|
||||
fn account_info_storage_key(account_id: &Self::AccountId) -> StorageKey;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ use jsonrpsee_ws_client::{traits::SubscriptionClient, v2::params::JsonRpcParams,
|
||||
use jsonrpsee_ws_client::{Subscription, WsClient as RpcClient, WsClientBuilder as RpcClientBuilder};
|
||||
use num_traits::Zero;
|
||||
use pallet_balances::AccountData;
|
||||
use relay_utils::relay_loop::RECONNECT_DELAY;
|
||||
use sp_core::{storage::StorageKey, Bytes};
|
||||
use sp_trie::StorageProof;
|
||||
use sp_version::RuntimeVersion;
|
||||
@@ -77,7 +78,29 @@ impl<C: Chain> std::fmt::Debug for Client<C> {
|
||||
|
||||
impl<C: Chain> Client<C> {
|
||||
/// Returns client that is able to call RPCs on Substrate node over websocket connection.
|
||||
pub async fn new(params: ConnectionParams) -> Result<Self> {
|
||||
///
|
||||
/// This function will keep connecting to given Sustrate node until connection is established
|
||||
/// and is functional. If attempt fail, it will wait for `RECONNECT_DELAY` and retry again.
|
||||
pub async fn new(params: ConnectionParams) -> Self {
|
||||
loop {
|
||||
match Self::try_connect(params.clone()).await {
|
||||
Ok(client) => return client,
|
||||
Err(error) => log::error!(
|
||||
target: "bridge",
|
||||
"Failed to connect to {} node: {:?}. Going to retry in {}s",
|
||||
C::NAME,
|
||||
error,
|
||||
RECONNECT_DELAY.as_secs(),
|
||||
),
|
||||
}
|
||||
|
||||
async_std::task::sleep(RECONNECT_DELAY).await;
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 number: C::BlockNumber = Zero::zero();
|
||||
@@ -185,7 +208,7 @@ impl<C: Chain> Client<C> {
|
||||
}
|
||||
|
||||
/// Return native tokens balance of the account.
|
||||
pub async fn free_native_balance(&self, account: C::AccountId) -> Result<C::NativeBalance>
|
||||
pub async fn free_native_balance(&self, account: C::AccountId) -> Result<C::Balance>
|
||||
where
|
||||
C: ChainWithBalances,
|
||||
{
|
||||
@@ -194,7 +217,7 @@ impl<C: Chain> Client<C> {
|
||||
.await?
|
||||
.ok_or(Error::AccountDoesNotExist)?;
|
||||
let decoded_account_data =
|
||||
AccountInfo::<C::Index, AccountData<C::NativeBalance>>::decode(&mut &encoded_account_data.0[..])
|
||||
AccountInfo::<C::Index, AccountData<C::Balance>>::decode(&mut &encoded_account_data.0[..])
|
||||
.map_err(Error::ResponseParseFailed)?;
|
||||
Ok(decoded_account_data.data.free)
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ use crate::client::Client;
|
||||
use crate::error::Error;
|
||||
use crate::sync_header::SyncHeader;
|
||||
|
||||
use async_std::sync::{Arc, Mutex};
|
||||
use async_trait::async_trait;
|
||||
use bp_header_chain::justification::GrandpaJustification;
|
||||
use codec::Decode;
|
||||
@@ -30,26 +31,46 @@ use relay_utils::relay_loop::Client as RelayClient;
|
||||
use sp_runtime::traits::Header as HeaderT;
|
||||
use std::{marker::PhantomData, pin::Pin};
|
||||
|
||||
/// Shared updatable reference to the maximal header number that we want to sync from the source.
|
||||
pub type RequiredHeaderNumberRef<C> = Arc<Mutex<<C as bp_runtime::Chain>::BlockNumber>>;
|
||||
|
||||
/// Substrate node as finality source.
|
||||
pub struct FinalitySource<C: Chain, P> {
|
||||
client: Client<C>,
|
||||
maximal_header_number: Option<RequiredHeaderNumberRef<C>>,
|
||||
_phantom: PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<C: Chain, P> FinalitySource<C, P> {
|
||||
/// Create new headers source using given client.
|
||||
pub fn new(client: Client<C>) -> Self {
|
||||
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.
|
||||
pub fn client(&self) -> &Client<C> {
|
||||
&self.client
|
||||
}
|
||||
|
||||
/// Returns best finalized block number.
|
||||
pub async fn on_chain_best_finalized_block_number(&self) -> Result<C::BlockNumber, Error> {
|
||||
// we **CAN** continue to relay finality proofs if source node is out of sync, because
|
||||
// target node may be missing proofs that are already available at the source
|
||||
let finalized_header_hash = self.client.best_finalized_header_hash().await?;
|
||||
let finalized_header = self.client.header_by_hash(finalized_header_hash).await?;
|
||||
Ok(*finalized_header.number())
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Chain, P> Clone for FinalitySource<C, P> {
|
||||
fn clone(&self) -> Self {
|
||||
FinalitySource {
|
||||
client: self.client.clone(),
|
||||
maximal_header_number: self.maximal_header_number.clone(),
|
||||
_phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
@@ -80,11 +101,16 @@ where
|
||||
type FinalityProofsStream = Pin<Box<dyn Stream<Item = GrandpaJustification<C::Header>> + Send>>;
|
||||
|
||||
async fn best_finalized_block_number(&self) -> Result<P::Number, Error> {
|
||||
// we **CAN** continue to relay finality proofs if source node is out of sync, because
|
||||
// target node may be missing proofs that are already available at the source
|
||||
let finalized_header_hash = self.client.best_finalized_header_hash().await?;
|
||||
let finalized_header = self.client.header_by_hash(finalized_header_hash).await?;
|
||||
Ok(*finalized_header.number())
|
||||
let mut finalized_header_number = self.on_chain_best_finalized_block_number().await?;
|
||||
// never return block number larger than requested. This way we'll never sync headers
|
||||
// past `maximal_header_number`
|
||||
if let Some(ref maximal_header_number) = self.maximal_header_number {
|
||||
let maximal_header_number = *maximal_header_number.lock().await;
|
||||
if finalized_header_number > maximal_header_number {
|
||||
finalized_header_number = maximal_header_number;
|
||||
}
|
||||
}
|
||||
Ok(finalized_header_number)
|
||||
}
|
||||
|
||||
async fn header_and_finality_proof(
|
||||
|
||||
@@ -33,7 +33,7 @@ pub trait Environment<C: ChainWithBalances>: Send + Sync + 'static {
|
||||
/// Return current runtime version.
|
||||
async fn runtime_version(&mut self) -> Result<RuntimeVersion, String>;
|
||||
/// Return free native balance of the account on the chain.
|
||||
async fn free_native_balance(&mut self, account: C::AccountId) -> Result<C::NativeBalance, String>;
|
||||
async fn free_native_balance(&mut self, account: C::AccountId) -> Result<C::Balance, String>;
|
||||
|
||||
/// Return current time.
|
||||
fn now(&self) -> Instant {
|
||||
@@ -85,7 +85,7 @@ pub fn abort_on_spec_version_change<C: ChainWithBalances>(mut env: impl Environm
|
||||
pub fn abort_when_account_balance_decreased<C: ChainWithBalances>(
|
||||
mut env: impl Environment<C>,
|
||||
account_id: C::AccountId,
|
||||
maximal_decrease: C::NativeBalance,
|
||||
maximal_decrease: C::Balance,
|
||||
) {
|
||||
const DAY: Duration = Duration::from_secs(60 * 60 * 24);
|
||||
|
||||
@@ -155,7 +155,7 @@ impl<C: ChainWithBalances> Environment<C> for Client<C> {
|
||||
Client::<C>::runtime_version(self).await.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
async fn free_native_balance(&mut self, account: C::AccountId) -> Result<C::NativeBalance, String> {
|
||||
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())
|
||||
@@ -191,11 +191,10 @@ mod tests {
|
||||
type SignedBlock =
|
||||
sp_runtime::generic::SignedBlock<sp_runtime::generic::Block<Self::Header, sp_runtime::OpaqueExtrinsic>>;
|
||||
type Call = ();
|
||||
type Balance = u32;
|
||||
}
|
||||
|
||||
impl ChainWithBalances for TestChain {
|
||||
type NativeBalance = u32;
|
||||
|
||||
fn account_info_storage_key(_account_id: &u32) -> sp_core::storage::StorageKey {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
@@ -14,7 +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/>.
|
||||
|
||||
//! Tools to interact with (Open) Ethereum node using RPC methods.
|
||||
//! Tools to interact with Substrate node using RPC methods.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
|
||||
@@ -47,11 +47,10 @@ impl Chain for Westend {
|
||||
type Index = bp_westend::Nonce;
|
||||
type SignedBlock = bp_westend::SignedBlock;
|
||||
type Call = bp_westend::Call;
|
||||
type Balance = bp_westend::Balance;
|
||||
}
|
||||
|
||||
impl ChainWithBalances for Westend {
|
||||
type NativeBalance = bp_westend::Balance;
|
||||
|
||||
fn account_info_storage_key(account_id: &Self::AccountId) -> StorageKey {
|
||||
StorageKey(bp_westend::account_info_storage_key(account_id))
|
||||
}
|
||||
|
||||
@@ -12,7 +12,16 @@ relay-substrate-client = { path = "../client-substrate" }
|
||||
relay-utils = { path = "../utils" }
|
||||
|
||||
# Bridge dependencies
|
||||
bridge-runtime-common = { path = "../../bin/runtime-common" }
|
||||
bp-header-chain = { path = "../../primitives/header-chain" }
|
||||
bp-message-dispatch = { path = "../../primitives/message-dispatch" }
|
||||
bp-messages = { path = "../../primitives/messages" }
|
||||
bp-polkadot-core = { path = "../../primitives/polkadot-core" }
|
||||
bp-rococo = { path = "../../primitives/chain-rococo" }
|
||||
bp-runtime = { path = "../../primitives/runtime" }
|
||||
bp-wococo = { path = "../../primitives/chain-wococo" }
|
||||
pallet-bridge-dispatch = { path = "../../modules/dispatch" }
|
||||
pallet-bridge-messages = { path = "../../modules/messages" }
|
||||
|
||||
# Substrate Dependencies
|
||||
frame-system = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
|
||||
@@ -22,6 +22,8 @@ use sp_core::{storage::StorageKey, Pair};
|
||||
use sp_runtime::{generic::SignedPayload, traits::IdentifyAccount};
|
||||
use std::time::Duration;
|
||||
|
||||
pub mod runtime;
|
||||
|
||||
/// Wococo header id.
|
||||
pub type HeaderId = relay_utils::HeaderId<bp_wococo::Hash, bp_wococo::BlockNumber>;
|
||||
|
||||
@@ -46,12 +48,11 @@ impl Chain for Wococo {
|
||||
type AccountId = bp_wococo::AccountId;
|
||||
type Index = bp_wococo::Index;
|
||||
type SignedBlock = bp_wococo::SignedBlock;
|
||||
type Call = bp_wococo::Call;
|
||||
type Call = crate::runtime::Call;
|
||||
type Balance = bp_wococo::Balance;
|
||||
}
|
||||
|
||||
impl ChainWithBalances for Wococo {
|
||||
type NativeBalance = bp_wococo::Balance;
|
||||
|
||||
fn account_info_storage_key(account_id: &Self::AccountId) -> StorageKey {
|
||||
StorageKey(bp_wococo::account_info_storage_key(account_id))
|
||||
}
|
||||
@@ -60,7 +61,7 @@ impl ChainWithBalances for Wococo {
|
||||
impl TransactionSignScheme for Wococo {
|
||||
type Chain = Wococo;
|
||||
type AccountKeyPair = sp_core::sr25519::Pair;
|
||||
type SignedTransaction = bp_wococo::UncheckedExtrinsic;
|
||||
type SignedTransaction = crate::runtime::UncheckedExtrinsic;
|
||||
|
||||
fn sign_transaction(
|
||||
genesis_hash: <Self::Chain as ChainBase>::Hash,
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
// 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 Wococo runtime.
|
||||
|
||||
use bp_messages::{LaneId, UnrewardedRelayersState};
|
||||
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;
|
||||
|
||||
/// Unchecked Wococo extrinsic.
|
||||
pub type UncheckedExtrinsic = bp_polkadot_core::UncheckedExtrinsic<Call>;
|
||||
|
||||
/// Rococo account ownership digest from Wococo.
|
||||
///
|
||||
/// The byte vector returned by this function should be signed with a Rococo account private key.
|
||||
/// This way, the owner of `wococo_account_id` on Rococo proves that the Rococo account private key
|
||||
/// is also under his control.
|
||||
pub fn wococo_to_rococo_account_ownership_digest<Call, AccountId, SpecVersion>(
|
||||
rococo_call: &Call,
|
||||
wococo_account_id: AccountId,
|
||||
rococo_spec_version: SpecVersion,
|
||||
) -> Vec<u8>
|
||||
where
|
||||
Call: codec::Encode,
|
||||
AccountId: codec::Encode,
|
||||
SpecVersion: codec::Encode,
|
||||
{
|
||||
pallet_bridge_dispatch::account_ownership_digest(
|
||||
rococo_call,
|
||||
wococo_account_id,
|
||||
rococo_spec_version,
|
||||
bp_runtime::WOCOCO_CHAIN_ID,
|
||||
bp_runtime::ROCOCO_CHAIN_ID,
|
||||
)
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// 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
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
||||
pub enum Call {
|
||||
/// System pallet.
|
||||
#[codec(index = 0)]
|
||||
System(SystemCall),
|
||||
/// Rococo bridge pallet.
|
||||
#[codec(index = 40)]
|
||||
BridgeGrandpaRococo(BridgeGrandpaRococoCall),
|
||||
/// Rococo messages pallet.
|
||||
#[codec(index = 43)]
|
||||
BridgeMessagesRococo(BridgeMessagesRococoCall),
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum SystemCall {
|
||||
#[codec(index = 1)]
|
||||
remark(Vec<u8>),
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum BridgeGrandpaRococoCall {
|
||||
#[codec(index = 0)]
|
||||
submit_finality_proof(
|
||||
<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)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum BridgeMessagesRococoCall {
|
||||
#[codec(index = 3)]
|
||||
send_message(
|
||||
LaneId,
|
||||
bp_message_dispatch::MessagePayload<
|
||||
bp_rococo::AccountId,
|
||||
bp_wococo::AccountId,
|
||||
bp_wococo::AccountPublic,
|
||||
Vec<u8>,
|
||||
>,
|
||||
bp_rococo::Balance,
|
||||
),
|
||||
#[codec(index = 5)]
|
||||
receive_messages_proof(
|
||||
bp_rococo::AccountId,
|
||||
bridge_runtime_common::messages::target::FromBridgedChainMessagesProof<bp_rococo::Hash>,
|
||||
u32,
|
||||
Weight,
|
||||
),
|
||||
#[codec(index = 6)]
|
||||
receive_messages_delivery_proof(
|
||||
bridge_runtime_common::messages::source::FromBridgedChainMessagesDeliveryProof<bp_rococo::Hash>,
|
||||
UnrewardedRelayersState,
|
||||
),
|
||||
}
|
||||
|
||||
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.")
|
||||
}
|
||||
}
|
||||
@@ -324,7 +324,7 @@ async fn wait_transaction_mined<P: TransactionProofPipeline>(
|
||||
source_tx_hash: &TransactionHashOf<P>,
|
||||
) -> Result<(HeaderId<P>, usize), String> {
|
||||
loop {
|
||||
let source_header_and_tx = source_client.transaction_block(&source_tx_hash).await.map_err(|err| {
|
||||
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,
|
||||
@@ -363,7 +363,7 @@ async fn wait_header_imported<P: TransactionProofPipeline>(
|
||||
source_header_id: &HeaderId<P>,
|
||||
) -> Result<(), String> {
|
||||
loop {
|
||||
let is_header_known = target_client.is_header_known(&source_header_id).await.map_err(|err| {
|
||||
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,
|
||||
@@ -406,7 +406,7 @@ async fn wait_header_finalized<P: TransactionProofPipeline>(
|
||||
) -> Result<(), String> {
|
||||
loop {
|
||||
let is_header_finalized = target_client
|
||||
.is_header_finalized(&source_header_id)
|
||||
.is_header_finalized(source_header_id)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
format!(
|
||||
|
||||
@@ -215,7 +215,7 @@ async fn run_loop_iteration<P: TransactionProofPipeline>(
|
||||
state.best_processed_header_number = state.best_processed_header_number + One::one();
|
||||
storage.set_state(state);
|
||||
|
||||
if let Some(ref exchange_loop_metrics) = exchange_loop_metrics {
|
||||
if let Some(exchange_loop_metrics) = exchange_loop_metrics {
|
||||
exchange_loop_metrics.update::<P>(
|
||||
state.best_processed_header_number,
|
||||
best_finalized_header_id.0,
|
||||
|
||||
@@ -39,8 +39,6 @@ use std::{
|
||||
/// Finality proof synchronization loop parameters.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FinalitySyncParams {
|
||||
/// If `true`, then the separate async task for running finality loop is NOT spawned.
|
||||
pub is_on_demand_task: bool,
|
||||
/// Interval at which we check updates on both clients. Normally should be larger than
|
||||
/// `min(source_block_time, target_block_time)`.
|
||||
///
|
||||
@@ -60,6 +58,8 @@ pub struct FinalitySyncParams {
|
||||
pub recent_finality_proofs_limit: usize,
|
||||
/// Timeout before we treat our transactions as lost and restart the whole sync process.
|
||||
pub stall_timeout: Duration,
|
||||
/// If true, only mandatory headers are relayed.
|
||||
pub only_mandatory_headers: bool,
|
||||
}
|
||||
|
||||
/// Source client used in finality synchronization loop.
|
||||
@@ -107,7 +107,6 @@ pub async fn run<P: FinalitySyncPipeline>(
|
||||
) -> Result<(), String> {
|
||||
let exit_signal = exit_signal.shared();
|
||||
relay_utils::relay_loop(source_client, target_client)
|
||||
.spawn_loop_task(!sync_params.is_on_demand_task)
|
||||
.with_metrics(Some(metrics_prefix::<P>()), metrics_params)
|
||||
.loop_metric(|registry, prefix| SyncLoopMetrics::new(registry, prefix))?
|
||||
.standalone_metric(|registry, prefix| GlobalMetrics::new(registry, prefix))?
|
||||
@@ -367,7 +366,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
async fn select_header_to_submit<P, SC, TC>(
|
||||
pub(crate) async fn select_header_to_submit<P, SC, TC>(
|
||||
source_client: &SC,
|
||||
target_client: &TC,
|
||||
finality_proofs_stream: &mut RestartableFinalityProofsStream<SC::FinalityProofsStream>,
|
||||
@@ -400,6 +399,11 @@ 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))),
|
||||
_ 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)))
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
|
||||
use crate::finality_loop::{
|
||||
prune_recent_finality_proofs, read_finality_proofs_from_stream, run, select_better_recent_finality_proof,
|
||||
FinalityProofs, FinalitySyncParams, SourceClient, TargetClient,
|
||||
select_header_to_submit, FinalityProofs, FinalitySyncParams, RestartableFinalityProofsStream, SourceClient,
|
||||
TargetClient,
|
||||
};
|
||||
use crate::{FinalityProof, FinalitySyncPipeline, SourceHeader};
|
||||
|
||||
@@ -165,8 +166,11 @@ impl TargetClient<TestFinalitySyncPipeline> for TestTargetClient {
|
||||
}
|
||||
}
|
||||
|
||||
fn run_sync_loop(state_function: impl Fn(&mut ClientsData) -> bool + Send + Sync + 'static) -> ClientsData {
|
||||
let (exit_sender, exit_receiver) = futures::channel::mpsc::unbounded();
|
||||
fn prepare_test_clients(
|
||||
exit_sender: futures::channel::mpsc::UnboundedSender<()>,
|
||||
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();
|
||||
@@ -174,7 +178,30 @@ fn run_sync_loop(state_function: impl Fn(&mut ClientsData) -> bool + Send + Sync
|
||||
});
|
||||
let clients_data = Arc::new(Mutex::new(ClientsData {
|
||||
source_best_block_number: 10,
|
||||
source_headers: vec![
|
||||
source_headers,
|
||||
source_proofs: vec![TestFinalityProof(12), TestFinalityProof(14)],
|
||||
|
||||
target_best_block_number: 5,
|
||||
target_headers: vec![],
|
||||
}));
|
||||
(
|
||||
TestSourceClient {
|
||||
on_method_call: internal_state_function.clone(),
|
||||
data: clients_data.clone(),
|
||||
},
|
||||
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 {
|
||||
let (exit_sender, exit_receiver) = futures::channel::mpsc::unbounded();
|
||||
let (source_client, target_client) = prepare_test_clients(
|
||||
exit_sender,
|
||||
state_function,
|
||||
vec![
|
||||
(6, (TestSourceHeader(false, 6), None)),
|
||||
(7, (TestSourceHeader(false, 7), Some(TestFinalityProof(7)))),
|
||||
(8, (TestSourceHeader(true, 8), Some(TestFinalityProof(8)))),
|
||||
@@ -183,26 +210,15 @@ fn run_sync_loop(state_function: impl Fn(&mut ClientsData) -> bool + Send + Sync
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
source_proofs: vec![TestFinalityProof(12), TestFinalityProof(14)],
|
||||
|
||||
target_best_block_number: 5,
|
||||
target_headers: vec![],
|
||||
}));
|
||||
let source_client = TestSourceClient {
|
||||
on_method_call: internal_state_function.clone(),
|
||||
data: clients_data.clone(),
|
||||
};
|
||||
let target_client = TestTargetClient {
|
||||
on_method_call: internal_state_function,
|
||||
data: clients_data.clone(),
|
||||
};
|
||||
);
|
||||
let sync_params = FinalitySyncParams {
|
||||
is_on_demand_task: false,
|
||||
tick: Duration::from_secs(0),
|
||||
recent_finality_proofs_limit: 1024,
|
||||
stall_timeout: Duration::from_secs(1),
|
||||
only_mandatory_headers: false,
|
||||
};
|
||||
|
||||
let clients_data = source_client.data.clone();
|
||||
let _ = async_std::task::block_on(run(
|
||||
source_client,
|
||||
target_client,
|
||||
@@ -260,6 +276,65 @@ fn finality_sync_loop_works() {
|
||||
);
|
||||
}
|
||||
|
||||
fn run_only_mandatory_headers_mode_test(
|
||||
only_mandatory_headers: bool,
|
||||
has_mandatory_headers: bool,
|
||||
) -> Option<(TestSourceHeader, TestFinalityProof)> {
|
||||
let (exit_sender, _) = futures::channel::mpsc::unbounded();
|
||||
let (source_client, target_client) = prepare_test_clients(
|
||||
exit_sender,
|
||||
|_| false,
|
||||
vec![
|
||||
(6, (TestSourceHeader(false, 6), Some(TestFinalityProof(6)))),
|
||||
(7, (TestSourceHeader(false, 7), Some(TestFinalityProof(7)))),
|
||||
(
|
||||
8,
|
||||
(TestSourceHeader(has_mandatory_headers, 8), Some(TestFinalityProof(8))),
|
||||
),
|
||||
(9, (TestSourceHeader(false, 9), Some(TestFinalityProof(9)))),
|
||||
(10, (TestSourceHeader(false, 10), Some(TestFinalityProof(10)))),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
);
|
||||
async_std::task::block_on(select_header_to_submit(
|
||||
&source_client,
|
||||
&target_client,
|
||||
&mut RestartableFinalityProofsStream::from(futures::stream::empty().boxed()),
|
||||
&mut vec![],
|
||||
10,
|
||||
5,
|
||||
&FinalitySyncParams {
|
||||
tick: Duration::from_secs(0),
|
||||
recent_finality_proofs_limit: 0,
|
||||
stall_timeout: Duration::from_secs(0),
|
||||
only_mandatory_headers,
|
||||
},
|
||||
))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_header_to_submit_skips_non_mandatory_headers_when_only_mandatory_headers_are_required() {
|
||||
assert_eq!(run_only_mandatory_headers_mode_test(true, false), None);
|
||||
assert_eq!(
|
||||
run_only_mandatory_headers_mode_test(false, false),
|
||||
Some((TestSourceHeader(false, 10), TestFinalityProof(10))),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_header_to_submit_selects_mandatory_headers_when_only_mandatory_headers_are_required() {
|
||||
assert_eq!(
|
||||
run_only_mandatory_headers_mode_test(true, true),
|
||||
Some((TestSourceHeader(true, 8), TestFinalityProof(8))),
|
||||
);
|
||||
assert_eq!(
|
||||
run_only_mandatory_headers_mode_test(false, true),
|
||||
Some((TestSourceHeader(true, 8), TestFinalityProof(8))),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_better_recent_finality_proof_works() {
|
||||
// if there are no unjustified headers, nothing is changed
|
||||
@@ -343,7 +418,7 @@ fn read_finality_proofs_from_stream_works() {
|
||||
let mut stream = 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))]);
|
||||
assert_eq!(stream.needs_restart, false);
|
||||
assert!(!stream.needs_restart);
|
||||
|
||||
// when stream has entry with target, it is added to the recent proofs container
|
||||
let mut stream = futures::stream::iter(vec![TestFinalityProof(4)])
|
||||
@@ -354,7 +429,7 @@ fn read_finality_proofs_from_stream_works() {
|
||||
recent_finality_proofs,
|
||||
vec![(1, TestFinalityProof(1)), (4, TestFinalityProof(4))]
|
||||
);
|
||||
assert_eq!(stream.needs_restart, false);
|
||||
assert!(!stream.needs_restart);
|
||||
|
||||
// when stream has ended, we'll need to restart it
|
||||
let mut stream = futures::stream::empty().into();
|
||||
@@ -363,7 +438,7 @@ fn read_finality_proofs_from_stream_works() {
|
||||
recent_finality_proofs,
|
||||
vec![(1, TestFinalityProof(1)), (4, TestFinalityProof(4))]
|
||||
);
|
||||
assert_eq!(stream.needs_restart, true);
|
||||
assert!(stream.needs_restart);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -295,7 +295,7 @@ impl<P: HeadersSyncPipeline> QueuedHeaders<P> {
|
||||
&mut self.orphan,
|
||||
&mut self.known_headers,
|
||||
HeaderStatus::Orphan,
|
||||
&id,
|
||||
id,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -305,7 +305,7 @@ impl<P: HeadersSyncPipeline> QueuedHeaders<P> {
|
||||
&mut self.maybe_extra,
|
||||
&mut self.known_headers,
|
||||
HeaderStatus::MaybeExtra,
|
||||
&id,
|
||||
id,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -324,7 +324,7 @@ impl<P: HeadersSyncPipeline> QueuedHeaders<P> {
|
||||
destination_queue,
|
||||
&mut self.known_headers,
|
||||
destination_status,
|
||||
&id,
|
||||
id,
|
||||
|header| header,
|
||||
);
|
||||
}
|
||||
@@ -654,7 +654,7 @@ impl<P: HeadersSyncPipeline> QueuedHeaders<P> {
|
||||
// remember that the header itself is synced
|
||||
// (condition is here to avoid duplicate log messages)
|
||||
if !id_processed {
|
||||
set_header_status::<P>(&mut self.known_headers, &id, HeaderStatus::Synced);
|
||||
set_header_status::<P>(&mut self.known_headers, id, HeaderStatus::Synced);
|
||||
}
|
||||
|
||||
// now let's move all descendants from maybe_orphan && orphan queues to
|
||||
@@ -1505,7 +1505,7 @@ pub(crate) mod tests {
|
||||
let mut queue = QueuedHeaders::<TestHeadersSyncPipeline>::default();
|
||||
|
||||
// when we do not know header itself
|
||||
assert_eq!(queue.is_parent_incomplete(&id(50)), false);
|
||||
assert!(!queue.is_parent_incomplete(&id(50)));
|
||||
|
||||
// when we do not know parent
|
||||
queue
|
||||
@@ -1514,7 +1514,7 @@ pub(crate) mod tests {
|
||||
.or_default()
|
||||
.insert(hash(100), HeaderStatus::Incomplete);
|
||||
queue.incomplete.entry(100).or_default().insert(hash(100), header(100));
|
||||
assert_eq!(queue.is_parent_incomplete(&id(100)), false);
|
||||
assert!(!queue.is_parent_incomplete(&id(100)));
|
||||
|
||||
// when parent is inside incomplete queue (i.e. some other ancestor is actually incomplete)
|
||||
queue
|
||||
@@ -1523,7 +1523,7 @@ pub(crate) mod tests {
|
||||
.or_default()
|
||||
.insert(hash(101), HeaderStatus::Submitted);
|
||||
queue.submitted.entry(101).or_default().insert(hash(101), header(101));
|
||||
assert_eq!(queue.is_parent_incomplete(&id(101)), true);
|
||||
assert!(queue.is_parent_incomplete(&id(101)));
|
||||
|
||||
// when parent is the incomplete header and we do not have completion data
|
||||
queue.incomplete_headers.insert(id(199), None);
|
||||
@@ -1533,7 +1533,7 @@ pub(crate) mod tests {
|
||||
.or_default()
|
||||
.insert(hash(200), HeaderStatus::Submitted);
|
||||
queue.submitted.entry(200).or_default().insert(hash(200), header(200));
|
||||
assert_eq!(queue.is_parent_incomplete(&id(200)), true);
|
||||
assert!(queue.is_parent_incomplete(&id(200)));
|
||||
|
||||
// when parent is the incomplete header and we have completion data
|
||||
queue.completion_data.insert(id(299), 299_299);
|
||||
@@ -1543,7 +1543,7 @@ pub(crate) mod tests {
|
||||
.or_default()
|
||||
.insert(hash(300), HeaderStatus::Submitted);
|
||||
queue.submitted.entry(300).or_default().insert(hash(300), header(300));
|
||||
assert_eq!(queue.is_parent_incomplete(&id(300)), true);
|
||||
assert!(queue.is_parent_incomplete(&id(300)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -6,14 +6,16 @@ edition = "2018"
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
|
||||
[dependencies]
|
||||
async-std = "1.6.5"
|
||||
async-std = { version = "1.6.5", features = ["attributes"] }
|
||||
async-trait = "0.1.40"
|
||||
futures = "0.3.5"
|
||||
hex = "0.4"
|
||||
log = "0.4.11"
|
||||
num-traits = "0.2"
|
||||
parking_lot = "0.11.0"
|
||||
|
||||
# Bridge Dependencies
|
||||
|
||||
bp-messages = { path = "../../primitives/messages" }
|
||||
bp-runtime = { path = "../../primitives/runtime" }
|
||||
relay-utils = { path = "../utils" }
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
//! 1) relay new messages from source to target node;
|
||||
//! 2) relay proof-of-delivery from target to source node.
|
||||
|
||||
use num_traits::{SaturatingAdd, Zero};
|
||||
use relay_utils::{BlockNumberBase, HeaderId};
|
||||
use std::fmt::Debug;
|
||||
|
||||
@@ -34,6 +35,12 @@ pub trait MessageLane: 'static + Clone + Send + Sync {
|
||||
/// Messages receiving proof.
|
||||
type MessagesReceivingProof: Clone + Debug + Send + Sync;
|
||||
|
||||
/// The type of the source chain token balance, that is used to:
|
||||
///
|
||||
/// 1) pay transaction fees;
|
||||
/// 2) pay message delivery and dispatch fee;
|
||||
/// 3) pay relayer rewards.
|
||||
type SourceChainBalance: Clone + Copy + Debug + PartialOrd + SaturatingAdd + Zero + Send + Sync;
|
||||
/// Number of the source header.
|
||||
type SourceHeaderNumber: BlockNumberBase;
|
||||
/// Hash of the source header.
|
||||
|
||||
@@ -31,6 +31,7 @@ use crate::metrics::MessageLaneLoopMetrics;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bp_messages::{LaneId, MessageNonce, UnrewardedRelayersState, Weight};
|
||||
use bp_runtime::messages::DispatchFeePayment;
|
||||
use futures::{channel::mpsc::unbounded, future::FutureExt, stream::StreamExt};
|
||||
use relay_utils::{
|
||||
interval,
|
||||
@@ -58,6 +59,15 @@ pub struct Params {
|
||||
pub delivery_params: MessageDeliveryParams,
|
||||
}
|
||||
|
||||
/// Relayer operating mode.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
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.
|
||||
NoLosses,
|
||||
}
|
||||
|
||||
/// Message delivery race parameters.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MessageDeliveryParams {
|
||||
@@ -74,20 +84,26 @@ pub struct MessageDeliveryParams {
|
||||
/// Maximal cumulative dispatch weight of relayed messages in single delivery transaction.
|
||||
pub max_messages_weight_in_single_batch: Weight,
|
||||
/// Maximal cumulative size of relayed messages in single delivery transaction.
|
||||
pub max_messages_size_in_single_batch: usize,
|
||||
pub max_messages_size_in_single_batch: u32,
|
||||
/// Relayer operating mode.
|
||||
pub relayer_mode: RelayerMode,
|
||||
}
|
||||
|
||||
/// Message weights.
|
||||
/// Message details.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct MessageWeights {
|
||||
pub struct MessageDetails<SourceChainBalance> {
|
||||
/// Message dispatch weight.
|
||||
pub weight: Weight,
|
||||
pub dispatch_weight: Weight,
|
||||
/// Message size (number of bytes in encoded payload).
|
||||
pub size: usize,
|
||||
pub size: u32,
|
||||
/// The relayer reward paid in the source chain tokens.
|
||||
pub reward: SourceChainBalance,
|
||||
/// Where the fee for dispatching message is paid?
|
||||
pub dispatch_fee_payment: DispatchFeePayment,
|
||||
}
|
||||
|
||||
/// Messages weights map.
|
||||
pub type MessageWeightsMap = BTreeMap<MessageNonce, MessageWeights>;
|
||||
/// Messages details map.
|
||||
pub type MessageDetailsMap<SourceChainBalance> = BTreeMap<MessageNonce, MessageDetails<SourceChainBalance>>;
|
||||
|
||||
/// Message delivery race proof parameters.
|
||||
#[derive(Debug, PartialEq)]
|
||||
@@ -117,13 +133,13 @@ pub trait SourceClient<P: MessageLane>: RelayClient {
|
||||
|
||||
/// Returns mapping of message nonces, generated on this client, to their weights.
|
||||
///
|
||||
/// Some weights may be missing from returned map, if corresponding messages were pruned at
|
||||
/// Some messages may be missing from returned map, if corresponding messages were pruned at
|
||||
/// the source chain.
|
||||
async fn generated_messages_weights(
|
||||
async fn generated_message_details(
|
||||
&self,
|
||||
id: SourceHeaderIdOf<P>,
|
||||
nonces: RangeInclusive<MessageNonce>,
|
||||
) -> Result<MessageWeightsMap, Self::Error>;
|
||||
) -> Result<MessageDetailsMap<P::SourceChainBalance>, Self::Error>;
|
||||
|
||||
/// Prove messages in inclusive range [begin; end].
|
||||
async fn prove_messages(
|
||||
@@ -142,6 +158,9 @@ pub trait SourceClient<P: MessageLane>: RelayClient {
|
||||
|
||||
/// We need given finalized target header on source to continue synchronization.
|
||||
async fn require_target_header_on_source(&self, id: TargetHeaderIdOf<P>);
|
||||
|
||||
/// Estimate cost of single message confirmation transaction in source chain tokens.
|
||||
async fn estimate_confirmation_transaction(&self) -> P::SourceChainBalance;
|
||||
}
|
||||
|
||||
/// Target client trait.
|
||||
@@ -183,6 +202,17 @@ pub trait TargetClient<P: MessageLane>: RelayClient {
|
||||
|
||||
/// We need given finalized source header on target to continue synchronization.
|
||||
async fn require_source_header_on_target(&self, id: SourceHeaderIdOf<P>);
|
||||
|
||||
/// Estimate cost of messages delivery transaction in source chain tokens.
|
||||
///
|
||||
/// Please keep in mind that the returned cost must be converted to the source chain
|
||||
/// tokens, even though the transaction fee will be paid in the target chain tokens.
|
||||
async fn estimate_delivery_transaction_in_source_tokens(
|
||||
&self,
|
||||
nonces: RangeInclusive<MessageNonce>,
|
||||
total_dispatch_weight: Weight,
|
||||
total_size: u32,
|
||||
) -> P::SourceChainBalance;
|
||||
}
|
||||
|
||||
/// State of the client.
|
||||
@@ -426,6 +456,10 @@ pub(crate) mod tests {
|
||||
HeaderId(number, number)
|
||||
}
|
||||
|
||||
pub const CONFIRMATION_TRANSACTION_COST: TestSourceChainBalance = 1;
|
||||
pub const BASE_MESSAGE_DELIVERY_TRANSACTION_COST: TestSourceChainBalance = 1;
|
||||
|
||||
pub type TestSourceChainBalance = u64;
|
||||
pub type TestSourceHeaderId = HeaderId<TestSourceHeaderNumber, TestSourceHeaderHash>;
|
||||
pub type TestTargetHeaderId = HeaderId<TestTargetHeaderNumber, TestTargetHeaderHash>;
|
||||
|
||||
@@ -457,6 +491,7 @@ pub(crate) mod tests {
|
||||
type MessagesProof = TestMessagesProof;
|
||||
type MessagesReceivingProof = TestMessagesReceivingProof;
|
||||
|
||||
type SourceChainBalance = TestSourceChainBalance;
|
||||
type SourceHeaderNumber = TestSourceHeaderNumber;
|
||||
type SourceHeaderHash = TestSourceHeaderHash;
|
||||
|
||||
@@ -490,6 +525,15 @@ pub(crate) mod tests {
|
||||
tick: Arc<dyn Fn(&mut TestClientData) + Send + Sync>,
|
||||
}
|
||||
|
||||
impl Default for TestSourceClient {
|
||||
fn default() -> Self {
|
||||
TestSourceClient {
|
||||
data: Arc::new(Mutex::new(TestClientData::default())),
|
||||
tick: Arc::new(|_| {}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl RelayClient for TestSourceClient {
|
||||
type Error = TestError;
|
||||
@@ -536,13 +580,23 @@ pub(crate) mod tests {
|
||||
Ok((id, data.source_latest_confirmed_received_nonce))
|
||||
}
|
||||
|
||||
async fn generated_messages_weights(
|
||||
async fn generated_message_details(
|
||||
&self,
|
||||
_id: SourceHeaderIdOf<TestMessageLane>,
|
||||
nonces: RangeInclusive<MessageNonce>,
|
||||
) -> Result<MessageWeightsMap, TestError> {
|
||||
) -> Result<MessageDetailsMap<TestSourceChainBalance>, TestError> {
|
||||
Ok(nonces
|
||||
.map(|nonce| (nonce, MessageWeights { weight: 1, size: 1 }))
|
||||
.map(|nonce| {
|
||||
(
|
||||
nonce,
|
||||
MessageDetails {
|
||||
dispatch_weight: 1,
|
||||
size: 1,
|
||||
reward: 1,
|
||||
dispatch_fee_payment: DispatchFeePayment::AtSourceChain,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
@@ -596,6 +650,10 @@ pub(crate) mod tests {
|
||||
data.target_to_source_header_requirements.push(id);
|
||||
(self.tick)(&mut *data);
|
||||
}
|
||||
|
||||
async fn estimate_confirmation_transaction(&self) -> TestSourceChainBalance {
|
||||
CONFIRMATION_TRANSACTION_COST
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -604,6 +662,15 @@ pub(crate) mod tests {
|
||||
tick: Arc<dyn Fn(&mut TestClientData) + Send + Sync>,
|
||||
}
|
||||
|
||||
impl Default for TestTargetClient {
|
||||
fn default() -> Self {
|
||||
TestTargetClient {
|
||||
data: Arc::new(Mutex::new(TestClientData::default())),
|
||||
tick: Arc::new(|_| {}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl RelayClient for TestTargetClient {
|
||||
type Error = TestError;
|
||||
@@ -702,6 +769,17 @@ pub(crate) mod tests {
|
||||
data.source_to_target_header_requirements.push(id);
|
||||
(self.tick)(&mut *data);
|
||||
}
|
||||
|
||||
async fn estimate_delivery_transaction_in_source_tokens(
|
||||
&self,
|
||||
nonces: RangeInclusive<MessageNonce>,
|
||||
total_dispatch_weight: Weight,
|
||||
total_size: u32,
|
||||
) -> TestSourceChainBalance {
|
||||
BASE_MESSAGE_DELIVERY_TRANSACTION_COST * (nonces.end() - nonces.start() + 1)
|
||||
+ total_dispatch_weight
|
||||
+ total_size as TestSourceChainBalance
|
||||
}
|
||||
}
|
||||
|
||||
fn run_loop_test(
|
||||
@@ -734,6 +812,7 @@ pub(crate) mod tests {
|
||||
max_messages_in_single_batch: 4,
|
||||
max_messages_weight_in_single_batch: 4,
|
||||
max_messages_size_in_single_batch: 4,
|
||||
relayer_mode: RelayerMode::Altruistic,
|
||||
},
|
||||
},
|
||||
source_client,
|
||||
|
||||
@@ -15,24 +15,27 @@
|
||||
|
||||
use crate::message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf};
|
||||
use crate::message_lane_loop::{
|
||||
MessageDeliveryParams, MessageProofParameters, MessageWeightsMap, SourceClient as MessageLaneSourceClient,
|
||||
SourceClientState, TargetClient as MessageLaneTargetClient, TargetClientState,
|
||||
MessageDeliveryParams, MessageDetailsMap, MessageProofParameters, RelayerMode,
|
||||
SourceClient as MessageLaneSourceClient, SourceClientState, TargetClient as MessageLaneTargetClient,
|
||||
TargetClientState,
|
||||
};
|
||||
use crate::message_race_loop::{
|
||||
MessageRace, NoncesRange, RaceState, RaceStrategy, SourceClient, SourceClientNonces, TargetClient,
|
||||
TargetClientNonces,
|
||||
};
|
||||
use crate::message_race_strategy::BasicStrategy;
|
||||
use crate::message_race_strategy::{BasicStrategy, SourceRangesQueue};
|
||||
use crate::metrics::MessageLaneLoopMetrics;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bp_messages::{MessageNonce, UnrewardedRelayersState, Weight};
|
||||
use bp_runtime::messages::DispatchFeePayment;
|
||||
use futures::stream::FusedStream;
|
||||
use num_traits::{SaturatingAdd, Zero};
|
||||
use relay_utils::FailedClient;
|
||||
use std::{
|
||||
collections::{BTreeMap, VecDeque},
|
||||
collections::VecDeque,
|
||||
marker::PhantomData,
|
||||
ops::RangeInclusive,
|
||||
ops::{Range, RangeInclusive},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
@@ -48,24 +51,27 @@ pub async fn run<P: MessageLane>(
|
||||
) -> Result<(), FailedClient> {
|
||||
crate::message_race_loop::run(
|
||||
MessageDeliveryRaceSource {
|
||||
client: source_client,
|
||||
client: source_client.clone(),
|
||||
metrics_msg: metrics_msg.clone(),
|
||||
_phantom: Default::default(),
|
||||
},
|
||||
source_state_updates,
|
||||
MessageDeliveryRaceTarget {
|
||||
client: target_client,
|
||||
client: target_client.clone(),
|
||||
metrics_msg,
|
||||
_phantom: Default::default(),
|
||||
},
|
||||
target_state_updates,
|
||||
stall_timeout,
|
||||
MessageDeliveryStrategy::<P> {
|
||||
MessageDeliveryStrategy::<P, _, _> {
|
||||
lane_source_client: source_client,
|
||||
lane_target_client: target_client,
|
||||
max_unrewarded_relayer_entries_at_target: params.max_unrewarded_relayer_entries_at_target,
|
||||
max_unconfirmed_nonces_at_target: params.max_unconfirmed_nonces_at_target,
|
||||
max_messages_in_single_batch: params.max_messages_in_single_batch,
|
||||
max_messages_weight_in_single_batch: params.max_messages_weight_in_single_batch,
|
||||
max_messages_size_in_single_batch: params.max_messages_size_in_single_batch,
|
||||
relayer_mode: params.relayer_mode,
|
||||
latest_confirmed_nonces_at_source: VecDeque::new(),
|
||||
target_nonces: None,
|
||||
strategy: BasicStrategy::new(),
|
||||
@@ -107,7 +113,7 @@ where
|
||||
C: MessageLaneSourceClient<P>,
|
||||
{
|
||||
type Error = C::Error;
|
||||
type NoncesRange = MessageWeightsMap;
|
||||
type NoncesRange = MessageDetailsMap<P::SourceChainBalance>;
|
||||
type ProofParameters = MessageProofParameters;
|
||||
|
||||
async fn nonces(
|
||||
@@ -125,10 +131,10 @@ where
|
||||
|
||||
let new_nonces = if latest_generated_nonce > prev_latest_nonce {
|
||||
self.client
|
||||
.generated_messages_weights(at_block.clone(), prev_latest_nonce + 1..=latest_generated_nonce)
|
||||
.generated_message_details(at_block.clone(), prev_latest_nonce + 1..=latest_generated_nonce)
|
||||
.await?
|
||||
} else {
|
||||
MessageWeightsMap::new()
|
||||
MessageDetailsMap::new()
|
||||
};
|
||||
|
||||
Ok((
|
||||
@@ -222,7 +228,11 @@ struct DeliveryRaceTargetNoncesData {
|
||||
}
|
||||
|
||||
/// Messages delivery strategy.
|
||||
struct MessageDeliveryStrategy<P: MessageLane> {
|
||||
struct MessageDeliveryStrategy<P: MessageLane, SC, TC> {
|
||||
/// The client that is connected to the message lane source node.
|
||||
lane_source_client: SC,
|
||||
/// The client that is connected to the message lane target node.
|
||||
lane_target_client: TC,
|
||||
/// Maximal unrewarded relayer entries at target client.
|
||||
max_unrewarded_relayer_entries_at_target: MessageNonce,
|
||||
/// Maximal unconfirmed nonces at target client.
|
||||
@@ -232,7 +242,9 @@ struct MessageDeliveryStrategy<P: MessageLane> {
|
||||
/// Maximal cumulative messages weight in the single delivery transaction.
|
||||
max_messages_weight_in_single_batch: Weight,
|
||||
/// Maximal messages size in the single delivery transaction.
|
||||
max_messages_size_in_single_batch: usize,
|
||||
max_messages_size_in_single_batch: u32,
|
||||
/// Relayer operating mode.
|
||||
relayer_mode: RelayerMode,
|
||||
/// Latest confirmed nonces at the source client + the header id where we have first met this nonce.
|
||||
latest_confirmed_nonces_at_source: VecDeque<(SourceHeaderIdOf<P>, MessageNonce)>,
|
||||
/// Target nonces from the source client.
|
||||
@@ -246,11 +258,11 @@ type MessageDeliveryStrategyBase<P> = BasicStrategy<
|
||||
<P as MessageLane>::SourceHeaderHash,
|
||||
<P as MessageLane>::TargetHeaderNumber,
|
||||
<P as MessageLane>::TargetHeaderHash,
|
||||
MessageWeightsMap,
|
||||
MessageDetailsMap<<P as MessageLane>::SourceChainBalance>,
|
||||
<P as MessageLane>::MessagesProof,
|
||||
>;
|
||||
|
||||
impl<P: MessageLane> std::fmt::Debug for MessageDeliveryStrategy<P> {
|
||||
impl<P: MessageLane, SC, TC> std::fmt::Debug for MessageDeliveryStrategy<P, SC, TC> {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
fmt.debug_struct("MessageDeliveryStrategy")
|
||||
.field(
|
||||
@@ -280,10 +292,26 @@ impl<P: MessageLane> std::fmt::Debug for MessageDeliveryStrategy<P> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: MessageLane> RaceStrategy<SourceHeaderIdOf<P>, TargetHeaderIdOf<P>, P::MessagesProof>
|
||||
for MessageDeliveryStrategy<P>
|
||||
impl<P: MessageLane, SC, TC> MessageDeliveryStrategy<P, SC, TC> {
|
||||
/// Returns total weight of all undelivered messages.
|
||||
fn total_queued_dispatch_weight(&self) -> Weight {
|
||||
self.strategy
|
||||
.source_queue()
|
||||
.iter()
|
||||
.flat_map(|(_, range)| range.values().map(|details| details.dispatch_weight))
|
||||
.fold(0, |total, weight| total.saturating_add(weight))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<P, SC, TC> RaceStrategy<SourceHeaderIdOf<P>, TargetHeaderIdOf<P>, P::MessagesProof>
|
||||
for MessageDeliveryStrategy<P, SC, TC>
|
||||
where
|
||||
P: MessageLane,
|
||||
SC: MessageLaneSourceClient<P>,
|
||||
TC: MessageLaneTargetClient<P>,
|
||||
{
|
||||
type SourceNoncesRange = MessageWeightsMap;
|
||||
type SourceNoncesRange = MessageDetailsMap<P::SourceChainBalance>;
|
||||
type ProofParameters = MessageProofParameters;
|
||||
type TargetNoncesData = DeliveryRaceTargetNoncesData;
|
||||
|
||||
@@ -383,9 +411,9 @@ impl<P: MessageLane> RaceStrategy<SourceHeaderIdOf<P>, TargetHeaderIdOf<P>, P::M
|
||||
)
|
||||
}
|
||||
|
||||
fn select_nonces_to_deliver(
|
||||
async fn select_nonces_to_deliver(
|
||||
&mut self,
|
||||
race_state: &RaceState<SourceHeaderIdOf<P>, TargetHeaderIdOf<P>, P::MessagesProof>,
|
||||
race_state: RaceState<SourceHeaderIdOf<P>, TargetHeaderIdOf<P>, P::MessagesProof>,
|
||||
) -> Option<(RangeInclusive<MessageNonce>, Self::ProofParameters)> {
|
||||
let best_finalized_source_header_id_at_best_target =
|
||||
race_state.best_finalized_source_header_id_at_best_target.clone()?;
|
||||
@@ -473,87 +501,236 @@ impl<P: MessageLane> RaceStrategy<SourceHeaderIdOf<P>, TargetHeaderIdOf<P>, P::M
|
||||
let max_nonces = std::cmp::min(max_nonces, self.max_messages_in_single_batch);
|
||||
let max_messages_weight_in_single_batch = self.max_messages_weight_in_single_batch;
|
||||
let max_messages_size_in_single_batch = self.max_messages_size_in_single_batch;
|
||||
let mut selected_weight: Weight = 0;
|
||||
let mut selected_size: usize = 0;
|
||||
let mut selected_count: MessageNonce = 0;
|
||||
let relayer_mode = self.relayer_mode;
|
||||
let lane_source_client = self.lane_source_client.clone();
|
||||
let lane_target_client = self.lane_target_client.clone();
|
||||
|
||||
let selected_nonces = self
|
||||
.strategy
|
||||
.select_nonces_to_deliver_with_selector(race_state, |range| {
|
||||
let to_requeue = range
|
||||
.into_iter()
|
||||
.skip_while(|(_, weight)| {
|
||||
// Since we (hopefully) have some reserves in `max_messages_weight_in_single_batch`
|
||||
// and `max_messages_size_in_single_batch`, we may still try to submit transaction
|
||||
// with single message if message overflows these limits. The worst case would be if
|
||||
// transaction will be rejected by the target runtime, but at least we have tried.
|
||||
let maximal_source_queue_index = self.strategy.maximal_available_source_queue_index(race_state)?;
|
||||
let previous_total_dispatch_weight = self.total_queued_dispatch_weight();
|
||||
let source_queue = self.strategy.source_queue();
|
||||
let range_end = select_nonces_for_delivery_transaction(
|
||||
relayer_mode,
|
||||
max_nonces,
|
||||
max_messages_weight_in_single_batch,
|
||||
max_messages_size_in_single_batch,
|
||||
lane_source_client.clone(),
|
||||
lane_target_client.clone(),
|
||||
source_queue,
|
||||
0..maximal_source_queue_index + 1,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// limit messages in the batch by weight
|
||||
let new_selected_weight = match selected_weight.checked_add(weight.weight) {
|
||||
Some(new_selected_weight) if new_selected_weight <= max_messages_weight_in_single_batch => {
|
||||
new_selected_weight
|
||||
}
|
||||
new_selected_weight if selected_count == 0 => {
|
||||
log::warn!(
|
||||
target: "bridge",
|
||||
"Going to submit message delivery transaction with declared dispatch \
|
||||
weight {:?} that overflows maximal configured weight {}",
|
||||
new_selected_weight,
|
||||
max_messages_weight_in_single_batch,
|
||||
);
|
||||
new_selected_weight.unwrap_or(Weight::MAX)
|
||||
}
|
||||
_ => return false,
|
||||
};
|
||||
let range_begin = source_queue[0].1.begin();
|
||||
let selected_nonces = range_begin..=range_end;
|
||||
self.strategy.remove_le_nonces_from_source_queue(range_end);
|
||||
|
||||
// limit messages in the batch by size
|
||||
let new_selected_size = match selected_size.checked_add(weight.size) {
|
||||
Some(new_selected_size) if new_selected_size <= max_messages_size_in_single_batch => {
|
||||
new_selected_size
|
||||
}
|
||||
new_selected_size if selected_count == 0 => {
|
||||
log::warn!(
|
||||
target: "bridge",
|
||||
"Going to submit message delivery transaction with message \
|
||||
size {:?} that overflows maximal configured size {}",
|
||||
new_selected_size,
|
||||
max_messages_size_in_single_batch,
|
||||
);
|
||||
new_selected_size.unwrap_or(usize::MAX)
|
||||
}
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
// limit number of messages in the batch
|
||||
let new_selected_count = selected_count + 1;
|
||||
if new_selected_count > max_nonces {
|
||||
return false;
|
||||
}
|
||||
|
||||
selected_weight = new_selected_weight;
|
||||
selected_size = new_selected_size;
|
||||
selected_count = new_selected_count;
|
||||
true
|
||||
})
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
if to_requeue.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(to_requeue)
|
||||
}
|
||||
})?;
|
||||
let new_total_dispatch_weight = self.total_queued_dispatch_weight();
|
||||
let dispatch_weight = previous_total_dispatch_weight - new_total_dispatch_weight;
|
||||
|
||||
Some((
|
||||
selected_nonces,
|
||||
MessageProofParameters {
|
||||
outbound_state_proof_required,
|
||||
dispatch_weight: selected_weight,
|
||||
dispatch_weight,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl NoncesRange for MessageWeightsMap {
|
||||
/// From given set of source nonces, that are ready to be delivered, select nonces
|
||||
/// to fit into single delivery transaction.
|
||||
///
|
||||
/// The function returns nonces that are NOT selected for current batch and will be
|
||||
/// delivered later.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn select_nonces_for_delivery_transaction<P: MessageLane>(
|
||||
relayer_mode: RelayerMode,
|
||||
max_messages_in_this_batch: MessageNonce,
|
||||
max_messages_weight_in_single_batch: Weight,
|
||||
max_messages_size_in_single_batch: u32,
|
||||
lane_source_client: impl MessageLaneSourceClient<P>,
|
||||
lane_target_client: impl MessageLaneTargetClient<P>,
|
||||
nonces_queue: &SourceRangesQueue<
|
||||
P::SourceHeaderHash,
|
||||
P::SourceHeaderNumber,
|
||||
MessageDetailsMap<P::SourceChainBalance>,
|
||||
>,
|
||||
nonces_queue_range: Range<usize>,
|
||||
) -> Option<MessageNonce> {
|
||||
let mut hard_selected_count = 0;
|
||||
let mut soft_selected_count = 0;
|
||||
|
||||
let mut selected_weight: Weight = 0;
|
||||
let mut selected_unpaid_weight: Weight = 0;
|
||||
let mut selected_size: u32 = 0;
|
||||
let mut selected_count: MessageNonce = 0;
|
||||
|
||||
let mut total_reward = P::SourceChainBalance::zero();
|
||||
let mut total_confirmations_cost = P::SourceChainBalance::zero();
|
||||
let mut total_cost = P::SourceChainBalance::zero();
|
||||
|
||||
// technically, multiple confirmations will be delivered in a single transaction,
|
||||
// meaning less loses for relayer. But here we don't know the final relayer yet, so
|
||||
// we're adding a separate transaction for every message. Normally, this cost is covered
|
||||
// by the message sender. Probably reconsider this?
|
||||
let confirmation_transaction_cost = if relayer_mode != RelayerMode::Altruistic {
|
||||
lane_source_client.estimate_confirmation_transaction().await
|
||||
} else {
|
||||
Zero::zero()
|
||||
};
|
||||
|
||||
let all_ready_nonces = nonces_queue
|
||||
.range(nonces_queue_range.clone())
|
||||
.flat_map(|(_, ready_nonces)| ready_nonces.iter())
|
||||
.enumerate();
|
||||
for (index, (nonce, details)) in all_ready_nonces {
|
||||
// Since we (hopefully) have some reserves in `max_messages_weight_in_single_batch`
|
||||
// and `max_messages_size_in_single_batch`, we may still try to submit transaction
|
||||
// with single message if message overflows these limits. The worst case would be if
|
||||
// transaction will be rejected by the target runtime, but at least we have tried.
|
||||
|
||||
// limit messages in the batch by weight
|
||||
let new_selected_weight = match selected_weight.checked_add(details.dispatch_weight) {
|
||||
Some(new_selected_weight) if new_selected_weight <= max_messages_weight_in_single_batch => {
|
||||
new_selected_weight
|
||||
}
|
||||
new_selected_weight if selected_count == 0 => {
|
||||
log::warn!(
|
||||
target: "bridge",
|
||||
"Going to submit message delivery transaction with declared dispatch \
|
||||
weight {:?} that overflows maximal configured weight {}",
|
||||
new_selected_weight,
|
||||
max_messages_weight_in_single_batch,
|
||||
);
|
||||
new_selected_weight.unwrap_or(Weight::MAX)
|
||||
}
|
||||
_ => break,
|
||||
};
|
||||
|
||||
// limit messages in the batch by size
|
||||
let new_selected_size = match selected_size.checked_add(details.size) {
|
||||
Some(new_selected_size) if new_selected_size <= max_messages_size_in_single_batch => new_selected_size,
|
||||
new_selected_size if selected_count == 0 => {
|
||||
log::warn!(
|
||||
target: "bridge",
|
||||
"Going to submit message delivery transaction with message \
|
||||
size {:?} that overflows maximal configured size {}",
|
||||
new_selected_size,
|
||||
max_messages_size_in_single_batch,
|
||||
);
|
||||
new_selected_size.unwrap_or(u32::MAX)
|
||||
}
|
||||
_ => break,
|
||||
};
|
||||
|
||||
// limit number of messages in the batch
|
||||
let new_selected_count = selected_count + 1;
|
||||
if new_selected_count > max_messages_in_this_batch {
|
||||
break;
|
||||
}
|
||||
|
||||
// If dispatch fee has been paid at the source chain, it means that it is **relayer** who's
|
||||
// paying for dispatch at the target chain AND reward must cover this dispatch fee.
|
||||
//
|
||||
// If dispatch fee is paid at the target chain, it means that it'll be withdrawn from the
|
||||
// dispatch origin account AND reward is not covering this fee.
|
||||
//
|
||||
// So in the latter case we're not adding the dispatch weight to the delivery transaction weight.
|
||||
let new_selected_unpaid_weight = match details.dispatch_fee_payment {
|
||||
DispatchFeePayment::AtSourceChain => selected_unpaid_weight.saturating_add(details.dispatch_weight),
|
||||
DispatchFeePayment::AtTargetChain => selected_unpaid_weight,
|
||||
};
|
||||
|
||||
// now the message has passed all 'strong' checks, and we CAN deliver it. But do we WANT
|
||||
// to deliver it? It depends on the relayer strategy.
|
||||
match relayer_mode {
|
||||
RelayerMode::Altruistic => {
|
||||
soft_selected_count = index + 1;
|
||||
}
|
||||
RelayerMode::NoLosses => {
|
||||
let delivery_transaction_cost = lane_target_client
|
||||
.estimate_delivery_transaction_in_source_tokens(
|
||||
0..=(new_selected_count as MessageNonce - 1),
|
||||
new_selected_unpaid_weight,
|
||||
new_selected_size as u32,
|
||||
)
|
||||
.await;
|
||||
|
||||
// if it is the first message that makes reward less than cost, let's log it
|
||||
// if this message makes batch profitable again, let's log it
|
||||
let is_total_reward_less_than_cost = total_reward < total_cost;
|
||||
let prev_total_cost = total_cost;
|
||||
let prev_total_reward = total_reward;
|
||||
total_confirmations_cost = total_confirmations_cost.saturating_add(&confirmation_transaction_cost);
|
||||
total_reward = total_reward.saturating_add(&details.reward);
|
||||
total_cost = total_confirmations_cost.saturating_add(&delivery_transaction_cost);
|
||||
if !is_total_reward_less_than_cost && total_reward < total_cost {
|
||||
log::debug!(
|
||||
target: "bridge",
|
||||
"Message with nonce {} (reward = {:?}) changes total cost {:?}->{:?} and makes it larger than \
|
||||
total reward {:?}->{:?}",
|
||||
nonce,
|
||||
details.reward,
|
||||
prev_total_cost,
|
||||
total_cost,
|
||||
prev_total_reward,
|
||||
total_reward,
|
||||
);
|
||||
} else if is_total_reward_less_than_cost && total_reward >= total_cost {
|
||||
log::debug!(
|
||||
target: "bridge",
|
||||
"Message with nonce {} (reward = {:?}) changes total cost {:?}->{:?} and makes it less than or \
|
||||
equal to the total reward {:?}->{:?} (again)",
|
||||
nonce,
|
||||
details.reward,
|
||||
prev_total_cost,
|
||||
total_cost,
|
||||
prev_total_reward,
|
||||
total_reward,
|
||||
);
|
||||
}
|
||||
|
||||
// NoLosses relayer never want to lose his funds
|
||||
if total_reward >= total_cost {
|
||||
soft_selected_count = index + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hard_selected_count = index + 1;
|
||||
selected_weight = new_selected_weight;
|
||||
selected_unpaid_weight = new_selected_unpaid_weight;
|
||||
selected_size = new_selected_size;
|
||||
selected_count = new_selected_count;
|
||||
}
|
||||
|
||||
let hard_selected_begin_nonce = nonces_queue[nonces_queue_range.start].1.begin();
|
||||
if hard_selected_count != soft_selected_count {
|
||||
let hard_selected_end_nonce = hard_selected_begin_nonce + hard_selected_count as MessageNonce - 1;
|
||||
let soft_selected_begin_nonce = hard_selected_begin_nonce;
|
||||
let soft_selected_end_nonce = soft_selected_begin_nonce + soft_selected_count as MessageNonce - 1;
|
||||
log::warn!(
|
||||
target: "bridge",
|
||||
"Relayer may deliver nonces [{:?}; {:?}], but because of its strategy ({:?}) it has selected \
|
||||
nonces [{:?}; {:?}].",
|
||||
hard_selected_begin_nonce,
|
||||
hard_selected_end_nonce,
|
||||
relayer_mode,
|
||||
soft_selected_begin_nonce,
|
||||
soft_selected_end_nonce,
|
||||
);
|
||||
|
||||
hard_selected_count = soft_selected_count;
|
||||
}
|
||||
|
||||
if hard_selected_count != 0 {
|
||||
Some(hard_selected_begin_nonce + hard_selected_count as MessageNonce - 1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<SourceChainBalance: std::fmt::Debug> NoncesRange for MessageDetailsMap<SourceChainBalance> {
|
||||
fn begin(&self) -> MessageNonce {
|
||||
self.keys().next().cloned().unwrap_or_default()
|
||||
}
|
||||
@@ -576,12 +753,50 @@ impl NoncesRange for MessageWeightsMap {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::message_lane_loop::{
|
||||
tests::{header_id, TestMessageLane, TestMessagesProof, TestSourceHeaderId, TestTargetHeaderId},
|
||||
MessageWeights,
|
||||
tests::{
|
||||
header_id, TestMessageLane, TestMessagesProof, TestSourceChainBalance, TestSourceClient,
|
||||
TestSourceHeaderId, TestTargetClient, TestTargetHeaderId, BASE_MESSAGE_DELIVERY_TRANSACTION_COST,
|
||||
CONFIRMATION_TRANSACTION_COST,
|
||||
},
|
||||
MessageDetails,
|
||||
};
|
||||
use bp_runtime::messages::DispatchFeePayment::*;
|
||||
|
||||
const DEFAULT_DISPATCH_WEIGHT: Weight = 1;
|
||||
const DEFAULT_SIZE: u32 = 1;
|
||||
const DEFAULT_REWARD: TestSourceChainBalance = CONFIRMATION_TRANSACTION_COST
|
||||
+ BASE_MESSAGE_DELIVERY_TRANSACTION_COST
|
||||
+ DEFAULT_DISPATCH_WEIGHT
|
||||
+ (DEFAULT_SIZE as TestSourceChainBalance);
|
||||
|
||||
type TestRaceState = RaceState<TestSourceHeaderId, TestTargetHeaderId, TestMessagesProof>;
|
||||
type TestStrategy = MessageDeliveryStrategy<TestMessageLane>;
|
||||
type TestStrategy = MessageDeliveryStrategy<TestMessageLane, TestSourceClient, TestTargetClient>;
|
||||
|
||||
fn source_nonces(
|
||||
new_nonces: RangeInclusive<MessageNonce>,
|
||||
confirmed_nonce: MessageNonce,
|
||||
reward: TestSourceChainBalance,
|
||||
dispatch_fee_payment: DispatchFeePayment,
|
||||
) -> SourceClientNonces<MessageDetailsMap<TestSourceChainBalance>> {
|
||||
SourceClientNonces {
|
||||
new_nonces: new_nonces
|
||||
.into_iter()
|
||||
.map(|nonce| {
|
||||
(
|
||||
nonce,
|
||||
MessageDetails {
|
||||
dispatch_weight: DEFAULT_DISPATCH_WEIGHT,
|
||||
size: DEFAULT_SIZE,
|
||||
reward,
|
||||
dispatch_fee_payment,
|
||||
},
|
||||
)
|
||||
})
|
||||
.into_iter()
|
||||
.collect(),
|
||||
confirmed_nonce: Some(confirmed_nonce),
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_strategy() -> (TestRaceState, TestStrategy) {
|
||||
let mut race_state = RaceState {
|
||||
@@ -594,12 +809,15 @@ mod tests {
|
||||
};
|
||||
|
||||
let mut race_strategy = TestStrategy {
|
||||
relayer_mode: RelayerMode::Altruistic,
|
||||
max_unrewarded_relayer_entries_at_target: 4,
|
||||
max_unconfirmed_nonces_at_target: 4,
|
||||
max_messages_in_single_batch: 4,
|
||||
max_messages_weight_in_single_batch: 4,
|
||||
max_messages_size_in_single_batch: 4,
|
||||
latest_confirmed_nonces_at_source: vec![(header_id(1), 19)].into_iter().collect(),
|
||||
lane_source_client: TestSourceClient::default(),
|
||||
lane_target_client: TestTargetClient::default(),
|
||||
target_nonces: Some(TargetClientNonces {
|
||||
latest_nonce: 19,
|
||||
nonces_data: DeliveryRaceTargetNoncesData {
|
||||
@@ -614,20 +832,9 @@ mod tests {
|
||||
strategy: BasicStrategy::new(),
|
||||
};
|
||||
|
||||
race_strategy.strategy.source_nonces_updated(
|
||||
header_id(1),
|
||||
SourceClientNonces {
|
||||
new_nonces: vec![
|
||||
(20, MessageWeights { weight: 1, size: 1 }),
|
||||
(21, MessageWeights { weight: 1, size: 1 }),
|
||||
(22, MessageWeights { weight: 1, size: 1 }),
|
||||
(23, MessageWeights { weight: 1, size: 1 }),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
confirmed_nonce: Some(19),
|
||||
},
|
||||
);
|
||||
race_strategy
|
||||
.strategy
|
||||
.source_nonces_updated(header_id(1), source_nonces(20..=23, 19, DEFAULT_REWARD, AtSourceChain));
|
||||
|
||||
let target_nonces = TargetClientNonces {
|
||||
latest_nonce: 19,
|
||||
@@ -652,14 +859,16 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn weights_map_works_as_nonces_range() {
|
||||
fn build_map(range: RangeInclusive<MessageNonce>) -> MessageWeightsMap {
|
||||
fn build_map(range: RangeInclusive<MessageNonce>) -> MessageDetailsMap<TestSourceChainBalance> {
|
||||
range
|
||||
.map(|idx| {
|
||||
(
|
||||
idx,
|
||||
MessageWeights {
|
||||
weight: idx,
|
||||
MessageDetails {
|
||||
dispatch_weight: idx,
|
||||
size: idx as _,
|
||||
reward: idx as _,
|
||||
dispatch_fee_payment: AtSourceChain,
|
||||
},
|
||||
)
|
||||
})
|
||||
@@ -678,19 +887,19 @@ mod tests {
|
||||
assert_eq!(map.greater_than(30), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_delivery_strategy_selects_messages_to_deliver() {
|
||||
#[async_std::test]
|
||||
async fn message_delivery_strategy_selects_messages_to_deliver() {
|
||||
let (state, mut strategy) = prepare_strategy();
|
||||
|
||||
// both sides are ready to relay new messages
|
||||
assert_eq!(
|
||||
strategy.select_nonces_to_deliver(&state),
|
||||
strategy.select_nonces_to_deliver(state).await,
|
||||
Some(((20..=23), proof_parameters(false, 4)))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_delivery_strategy_selects_nothing_if_too_many_confirmations_missing() {
|
||||
#[async_std::test]
|
||||
async fn message_delivery_strategy_selects_nothing_if_too_many_confirmations_missing() {
|
||||
let (state, mut strategy) = prepare_strategy();
|
||||
|
||||
// if there are already `max_unconfirmed_nonces_at_target` messages on target,
|
||||
@@ -701,11 +910,11 @@ mod tests {
|
||||
)]
|
||||
.into_iter()
|
||||
.collect();
|
||||
assert_eq!(strategy.select_nonces_to_deliver(&state), None);
|
||||
assert_eq!(strategy.select_nonces_to_deliver(state).await, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_delivery_strategy_includes_outbound_state_proof_when_new_nonces_are_available() {
|
||||
#[async_std::test]
|
||||
async fn message_delivery_strategy_includes_outbound_state_proof_when_new_nonces_are_available() {
|
||||
let (state, mut strategy) = prepare_strategy();
|
||||
|
||||
// if there are new confirmed nonces on source, we want to relay this information
|
||||
@@ -713,13 +922,13 @@ mod tests {
|
||||
let prev_confirmed_nonce_at_source = strategy.latest_confirmed_nonces_at_source.back().unwrap().1;
|
||||
strategy.target_nonces.as_mut().unwrap().nonces_data.confirmed_nonce = prev_confirmed_nonce_at_source - 1;
|
||||
assert_eq!(
|
||||
strategy.select_nonces_to_deliver(&state),
|
||||
strategy.select_nonces_to_deliver(state).await,
|
||||
Some(((20..=23), proof_parameters(true, 4)))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_delivery_strategy_selects_nothing_if_there_are_too_many_unrewarded_relayers() {
|
||||
#[async_std::test]
|
||||
async fn message_delivery_strategy_selects_nothing_if_there_are_too_many_unrewarded_relayers() {
|
||||
let (state, mut strategy) = prepare_strategy();
|
||||
|
||||
// if there are already `max_unrewarded_relayer_entries_at_target` entries at target,
|
||||
@@ -729,11 +938,12 @@ mod tests {
|
||||
unrewarded_relayers.unrewarded_relayer_entries = strategy.max_unrewarded_relayer_entries_at_target;
|
||||
unrewarded_relayers.messages_in_oldest_entry = 4;
|
||||
}
|
||||
assert_eq!(strategy.select_nonces_to_deliver(&state), None);
|
||||
assert_eq!(strategy.select_nonces_to_deliver(state).await, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_delivery_strategy_selects_nothing_if_proved_rewards_is_not_enough_to_remove_oldest_unrewarded_entry() {
|
||||
#[async_std::test]
|
||||
async fn message_delivery_strategy_selects_nothing_if_proved_rewards_is_not_enough_to_remove_oldest_unrewarded_entry(
|
||||
) {
|
||||
let (state, mut strategy) = prepare_strategy();
|
||||
|
||||
// if there are already `max_unrewarded_relayer_entries_at_target` entries at target,
|
||||
@@ -746,11 +956,11 @@ mod tests {
|
||||
unrewarded_relayers.unrewarded_relayer_entries = strategy.max_unrewarded_relayer_entries_at_target;
|
||||
unrewarded_relayers.messages_in_oldest_entry = 4;
|
||||
}
|
||||
assert_eq!(strategy.select_nonces_to_deliver(&state), None);
|
||||
assert_eq!(strategy.select_nonces_to_deliver(state).await, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_delivery_strategy_includes_outbound_state_proof_if_proved_rewards_is_enough() {
|
||||
#[async_std::test]
|
||||
async fn message_delivery_strategy_includes_outbound_state_proof_if_proved_rewards_is_enough() {
|
||||
let (state, mut strategy) = prepare_strategy();
|
||||
|
||||
// if there are already `max_unrewarded_relayer_entries_at_target` entries at target,
|
||||
@@ -764,73 +974,77 @@ mod tests {
|
||||
unrewarded_relayers.messages_in_oldest_entry = 3;
|
||||
}
|
||||
assert_eq!(
|
||||
strategy.select_nonces_to_deliver(&state),
|
||||
strategy.select_nonces_to_deliver(state).await,
|
||||
Some(((20..=23), proof_parameters(true, 4)))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_delivery_strategy_limits_batch_by_messages_weight() {
|
||||
#[async_std::test]
|
||||
async fn message_delivery_strategy_limits_batch_by_messages_weight() {
|
||||
let (state, mut strategy) = prepare_strategy();
|
||||
|
||||
// not all queued messages may fit in the batch, because batch has max weight
|
||||
strategy.max_messages_weight_in_single_batch = 3;
|
||||
assert_eq!(
|
||||
strategy.select_nonces_to_deliver(&state),
|
||||
strategy.select_nonces_to_deliver(state).await,
|
||||
Some(((20..=22), proof_parameters(false, 3)))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_delivery_strategy_accepts_single_message_even_if_its_weight_overflows_maximal_weight() {
|
||||
#[async_std::test]
|
||||
async fn message_delivery_strategy_accepts_single_message_even_if_its_weight_overflows_maximal_weight() {
|
||||
let (state, mut strategy) = prepare_strategy();
|
||||
|
||||
// first message doesn't fit in the batch, because it has weight (10) that overflows max weight (4)
|
||||
strategy.strategy.source_queue_mut()[0].1.get_mut(&20).unwrap().weight = 10;
|
||||
strategy.strategy.source_queue_mut()[0]
|
||||
.1
|
||||
.get_mut(&20)
|
||||
.unwrap()
|
||||
.dispatch_weight = 10;
|
||||
assert_eq!(
|
||||
strategy.select_nonces_to_deliver(&state),
|
||||
strategy.select_nonces_to_deliver(state).await,
|
||||
Some(((20..=20), proof_parameters(false, 10)))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_delivery_strategy_limits_batch_by_messages_size() {
|
||||
#[async_std::test]
|
||||
async fn message_delivery_strategy_limits_batch_by_messages_size() {
|
||||
let (state, mut strategy) = prepare_strategy();
|
||||
|
||||
// not all queued messages may fit in the batch, because batch has max weight
|
||||
strategy.max_messages_size_in_single_batch = 3;
|
||||
assert_eq!(
|
||||
strategy.select_nonces_to_deliver(&state),
|
||||
strategy.select_nonces_to_deliver(state).await,
|
||||
Some(((20..=22), proof_parameters(false, 3)))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_delivery_strategy_accepts_single_message_even_if_its_weight_overflows_maximal_size() {
|
||||
#[async_std::test]
|
||||
async fn message_delivery_strategy_accepts_single_message_even_if_its_weight_overflows_maximal_size() {
|
||||
let (state, mut strategy) = prepare_strategy();
|
||||
|
||||
// first message doesn't fit in the batch, because it has weight (10) that overflows max weight (4)
|
||||
strategy.strategy.source_queue_mut()[0].1.get_mut(&20).unwrap().size = 10;
|
||||
assert_eq!(
|
||||
strategy.select_nonces_to_deliver(&state),
|
||||
strategy.select_nonces_to_deliver(state).await,
|
||||
Some(((20..=20), proof_parameters(false, 1)))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_delivery_strategy_limits_batch_by_messages_count_when_there_is_upper_limit() {
|
||||
#[async_std::test]
|
||||
async fn message_delivery_strategy_limits_batch_by_messages_count_when_there_is_upper_limit() {
|
||||
let (state, mut strategy) = prepare_strategy();
|
||||
|
||||
// not all queued messages may fit in the batch, because batch has max number of messages limit
|
||||
strategy.max_messages_in_single_batch = 3;
|
||||
assert_eq!(
|
||||
strategy.select_nonces_to_deliver(&state),
|
||||
strategy.select_nonces_to_deliver(state).await,
|
||||
Some(((20..=22), proof_parameters(false, 3)))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_delivery_strategy_limits_batch_by_messages_count_when_there_are_unconfirmed_nonces() {
|
||||
#[async_std::test]
|
||||
async fn message_delivery_strategy_limits_batch_by_messages_count_when_there_are_unconfirmed_nonces() {
|
||||
let (state, mut strategy) = prepare_strategy();
|
||||
|
||||
// 1 delivery confirmation from target to source is still missing, so we may only
|
||||
@@ -841,13 +1055,13 @@ mod tests {
|
||||
.collect();
|
||||
strategy.target_nonces.as_mut().unwrap().nonces_data.confirmed_nonce = prev_confirmed_nonce_at_source - 1;
|
||||
assert_eq!(
|
||||
strategy.select_nonces_to_deliver(&state),
|
||||
strategy.select_nonces_to_deliver(state).await,
|
||||
Some(((20..=22), proof_parameters(false, 3)))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_delivery_strategy_waits_for_confirmed_nonce_header_to_appear_on_target() {
|
||||
#[async_std::test]
|
||||
async fn message_delivery_strategy_waits_for_confirmed_nonce_header_to_appear_on_target() {
|
||||
// 1 delivery confirmation from target to source is still missing, so we may deliver
|
||||
// reward confirmation with our message delivery transaction. But the problem is that
|
||||
// the reward has been paid at header 2 && this header is still unknown to target node.
|
||||
@@ -864,7 +1078,7 @@ mod tests {
|
||||
strategy.target_nonces.as_mut().unwrap().nonces_data.confirmed_nonce = prev_confirmed_nonce_at_source - 1;
|
||||
state.best_finalized_source_header_id_at_best_target = Some(header_id(1));
|
||||
assert_eq!(
|
||||
strategy.select_nonces_to_deliver(&state),
|
||||
strategy.select_nonces_to_deliver(state).await,
|
||||
Some(((20..=22), proof_parameters(false, 3)))
|
||||
);
|
||||
|
||||
@@ -881,13 +1095,13 @@ mod tests {
|
||||
state.best_finalized_source_header_id_at_source = Some(header_id(2));
|
||||
state.best_finalized_source_header_id_at_best_target = Some(header_id(2));
|
||||
assert_eq!(
|
||||
strategy.select_nonces_to_deliver(&state),
|
||||
strategy.select_nonces_to_deliver(state).await,
|
||||
Some(((20..=23), proof_parameters(true, 4)))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn source_header_is_requied_when_confirmations_are_required() {
|
||||
#[async_std::test]
|
||||
async fn source_header_is_required_when_confirmations_are_required() {
|
||||
// let's prepare situation when:
|
||||
// - all messages [20; 23] have been generated at source block#1;
|
||||
let (mut state, mut strategy) = prepare_strategy();
|
||||
@@ -895,7 +1109,7 @@ mod tests {
|
||||
// relayers vector capacity;
|
||||
strategy.max_unconfirmed_nonces_at_target = 2;
|
||||
assert_eq!(
|
||||
strategy.select_nonces_to_deliver(&state),
|
||||
strategy.select_nonces_to_deliver(state.clone()).await,
|
||||
Some(((20..=21), proof_parameters(false, 2)))
|
||||
);
|
||||
strategy.finalized_target_nonces_updated(
|
||||
@@ -912,12 +1126,12 @@ mod tests {
|
||||
},
|
||||
&mut state,
|
||||
);
|
||||
assert_eq!(strategy.select_nonces_to_deliver(&state), None);
|
||||
assert_eq!(strategy.select_nonces_to_deliver(state).await, None);
|
||||
// - messages [1; 10] receiving confirmation has been delivered at source block#2;
|
||||
strategy.source_nonces_updated(
|
||||
header_id(2),
|
||||
SourceClientNonces {
|
||||
new_nonces: BTreeMap::new(),
|
||||
new_nonces: MessageDetailsMap::new(),
|
||||
confirmed_nonce: Some(21),
|
||||
},
|
||||
);
|
||||
@@ -927,4 +1141,107 @@ mod tests {
|
||||
Some(header_id(2))
|
||||
);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn no_losses_relayer_is_delivering_messages_if_cost_is_equal_to_reward() {
|
||||
let (state, mut strategy) = prepare_strategy();
|
||||
strategy.relayer_mode = RelayerMode::NoLosses;
|
||||
|
||||
// so now we have:
|
||||
// - 20..=23 with reward = cost
|
||||
// => strategy shall select all 20..=23
|
||||
assert_eq!(
|
||||
strategy.select_nonces_to_deliver(state).await,
|
||||
Some(((20..=23), proof_parameters(false, 4)))
|
||||
);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn no_losses_relayer_is_not_delivering_messages_if_cost_is_larger_than_reward() {
|
||||
let (mut state, mut strategy) = prepare_strategy();
|
||||
let nonces = source_nonces(
|
||||
24..=25,
|
||||
19,
|
||||
DEFAULT_REWARD - BASE_MESSAGE_DELIVERY_TRANSACTION_COST,
|
||||
AtSourceChain,
|
||||
);
|
||||
strategy.strategy.source_nonces_updated(header_id(2), nonces);
|
||||
state.best_finalized_source_header_id_at_best_target = Some(header_id(2));
|
||||
strategy.relayer_mode = RelayerMode::NoLosses;
|
||||
|
||||
// so now we have:
|
||||
// - 20..=23 with reward = cost
|
||||
// - 24..=25 with reward less than cost
|
||||
// => strategy shall only select 20..=23
|
||||
assert_eq!(
|
||||
strategy.select_nonces_to_deliver(state).await,
|
||||
Some(((20..=23), proof_parameters(false, 4)))
|
||||
);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn no_losses_relayer_is_delivering_unpaid_messages() {
|
||||
async fn test_with_dispatch_fee_payment(
|
||||
dispatch_fee_payment: DispatchFeePayment,
|
||||
) -> Option<(RangeInclusive<MessageNonce>, MessageProofParameters)> {
|
||||
let (mut state, mut strategy) = prepare_strategy();
|
||||
let nonces = source_nonces(
|
||||
24..=24,
|
||||
19,
|
||||
DEFAULT_REWARD - DEFAULT_DISPATCH_WEIGHT,
|
||||
dispatch_fee_payment,
|
||||
);
|
||||
strategy.strategy.source_nonces_updated(header_id(2), nonces);
|
||||
state.best_finalized_source_header_id_at_best_target = Some(header_id(2));
|
||||
strategy.max_unrewarded_relayer_entries_at_target = 100;
|
||||
strategy.max_unconfirmed_nonces_at_target = 100;
|
||||
strategy.max_messages_in_single_batch = 100;
|
||||
strategy.max_messages_weight_in_single_batch = 100;
|
||||
strategy.max_messages_size_in_single_batch = 100;
|
||||
strategy.relayer_mode = RelayerMode::NoLosses;
|
||||
|
||||
// so now we have:
|
||||
// - 20..=23 with reward = cost
|
||||
// - 24..=24 with reward less than cost, but we're deducting `DEFAULT_DISPATCH_WEIGHT` from the
|
||||
// cost, so it should be fine;
|
||||
// => when MSG#24 fee is paid at the target chain, strategy shall select all 20..=24
|
||||
// => when MSG#25 fee is paid at the source chain, strategy shall only select 20..=23
|
||||
strategy.select_nonces_to_deliver(state).await
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
test_with_dispatch_fee_payment(AtTargetChain).await,
|
||||
Some(((20..=24), proof_parameters(false, 5)))
|
||||
);
|
||||
assert_eq!(
|
||||
test_with_dispatch_fee_payment(AtSourceChain).await,
|
||||
Some(((20..=23), proof_parameters(false, 4)))
|
||||
);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn relayer_uses_flattened_view_of_the_source_queue_to_select_nonces() {
|
||||
// Real scenario that has happened on test deployments:
|
||||
// 1) relayer witnessed M1 at block 1 => it has separate entry in the `source_queue`
|
||||
// 2) relayer witnessed M2 at block 2 => it has separate entry in the `source_queue`
|
||||
// 3) if block 2 is known to the target node, then both M1 and M2 are selected for single delivery,
|
||||
// even though weight(M1+M2) > larger than largest allowed weight
|
||||
//
|
||||
// This was happening because selector (`select_nonces_for_delivery_transaction`) has been called
|
||||
// for every `source_queue` entry separately without preserving any context.
|
||||
let (mut state, mut strategy) = prepare_strategy();
|
||||
let nonces = source_nonces(24..=25, 19, DEFAULT_REWARD, AtSourceChain);
|
||||
strategy.strategy.source_nonces_updated(header_id(2), nonces);
|
||||
strategy.max_unrewarded_relayer_entries_at_target = 100;
|
||||
strategy.max_unconfirmed_nonces_at_target = 100;
|
||||
strategy.max_messages_in_single_batch = 5;
|
||||
strategy.max_messages_weight_in_single_batch = 100;
|
||||
strategy.max_messages_size_in_single_batch = 100;
|
||||
state.best_finalized_source_header_id_at_best_target = Some(header_id(2));
|
||||
|
||||
assert_eq!(
|
||||
strategy.select_nonces_to_deliver(state).await,
|
||||
Some(((20..=24), proof_parameters(false, 5)))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,6 +143,7 @@ pub trait TargetClient<P: MessageRace> {
|
||||
}
|
||||
|
||||
/// Race strategy.
|
||||
#[async_trait]
|
||||
pub trait RaceStrategy<SourceHeaderId, TargetHeaderId, Proof>: Debug {
|
||||
/// Type of nonces range expected from the source client.
|
||||
type SourceNoncesRange: NoncesRange;
|
||||
@@ -182,14 +183,14 @@ pub trait RaceStrategy<SourceHeaderId, TargetHeaderId, Proof>: Debug {
|
||||
/// Should return `Some(nonces)` if we need to deliver proof of `nonces` (and associated
|
||||
/// data) from source to target node.
|
||||
/// Additionally, parameters required to generate proof are returned.
|
||||
fn select_nonces_to_deliver(
|
||||
async fn select_nonces_to_deliver(
|
||||
&mut self,
|
||||
race_state: &RaceState<SourceHeaderId, TargetHeaderId, Proof>,
|
||||
race_state: RaceState<SourceHeaderId, TargetHeaderId, Proof>,
|
||||
) -> Option<(RangeInclusive<MessageNonce>, Self::ProofParameters)>;
|
||||
}
|
||||
|
||||
/// State of the race.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RaceState<SourceHeaderId, TargetHeaderId, Proof> {
|
||||
/// Best finalized source header id at the source client.
|
||||
pub best_finalized_source_header_id_at_source: Option<SourceHeaderId>,
|
||||
@@ -438,7 +439,7 @@ pub async fn run<P: MessageRace, SC: SourceClient<P>, TC: TargetClient<P>>(
|
||||
if source_client_is_online {
|
||||
source_client_is_online = false;
|
||||
|
||||
let nonces_to_deliver = select_nonces_to_deliver(&race_state, &mut strategy);
|
||||
let nonces_to_deliver = select_nonces_to_deliver(race_state.clone(), &mut strategy).await;
|
||||
let best_at_source = strategy.best_at_source();
|
||||
|
||||
if let Some((at_block, nonces_range, proof_parameters)) = nonces_to_deliver {
|
||||
@@ -554,27 +555,25 @@ where
|
||||
now_time
|
||||
}
|
||||
|
||||
fn select_nonces_to_deliver<SourceHeaderId, TargetHeaderId, Proof, Strategy>(
|
||||
race_state: &RaceState<SourceHeaderId, TargetHeaderId, Proof>,
|
||||
async fn select_nonces_to_deliver<SourceHeaderId, TargetHeaderId, Proof, Strategy>(
|
||||
race_state: RaceState<SourceHeaderId, TargetHeaderId, Proof>,
|
||||
strategy: &mut Strategy,
|
||||
) -> Option<(SourceHeaderId, RangeInclusive<MessageNonce>, Strategy::ProofParameters)>
|
||||
where
|
||||
SourceHeaderId: Clone,
|
||||
Strategy: RaceStrategy<SourceHeaderId, TargetHeaderId, Proof>,
|
||||
{
|
||||
race_state
|
||||
.best_finalized_source_header_id_at_best_target
|
||||
.as_ref()
|
||||
.and_then(|best_finalized_source_header_id_at_best_target| {
|
||||
strategy
|
||||
.select_nonces_to_deliver(&race_state)
|
||||
.map(|(nonces_range, proof_parameters)| {
|
||||
(
|
||||
best_finalized_source_header_id_at_best_target.clone(),
|
||||
nonces_range,
|
||||
proof_parameters,
|
||||
)
|
||||
})
|
||||
let best_finalized_source_header_id_at_best_target =
|
||||
race_state.best_finalized_source_header_id_at_best_target.clone()?;
|
||||
strategy
|
||||
.select_nonces_to_deliver(race_state)
|
||||
.await
|
||||
.map(|(nonces_range, proof_parameters)| {
|
||||
(
|
||||
best_finalized_source_header_id_at_best_target,
|
||||
nonces_range,
|
||||
proof_parameters,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -584,8 +583,8 @@ mod tests {
|
||||
use crate::message_race_strategy::BasicStrategy;
|
||||
use relay_utils::HeaderId;
|
||||
|
||||
#[test]
|
||||
fn proof_is_generated_at_best_block_known_to_target_node() {
|
||||
#[async_std::test]
|
||||
async fn proof_is_generated_at_best_block_known_to_target_node() {
|
||||
const GENERATED_AT: u64 = 6;
|
||||
const BEST_AT_SOURCE: u64 = 10;
|
||||
const BEST_AT_TARGET: u64 = 8;
|
||||
@@ -620,7 +619,7 @@ mod tests {
|
||||
|
||||
// the proof will be generated on source, but using BEST_AT_TARGET block
|
||||
assert_eq!(
|
||||
select_nonces_to_deliver(&race_state, &mut strategy),
|
||||
select_nonces_to_deliver(race_state, &mut strategy).await,
|
||||
Some((HeaderId(BEST_AT_TARGET, BEST_AT_TARGET), 6..=10, (),))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,10 +19,15 @@
|
||||
|
||||
use crate::message_race_loop::{NoncesRange, RaceState, RaceStrategy, SourceClientNonces, TargetClientNonces};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bp_messages::MessageNonce;
|
||||
use relay_utils::HeaderId;
|
||||
use std::{collections::VecDeque, fmt::Debug, marker::PhantomData, ops::RangeInclusive};
|
||||
|
||||
/// Queue of nonces known to the source node.
|
||||
pub type SourceRangesQueue<SourceHeaderHash, SourceHeaderNumber, SourceNoncesRange> =
|
||||
VecDeque<(HeaderId<SourceHeaderHash, SourceHeaderNumber>, SourceNoncesRange)>;
|
||||
|
||||
/// Nonces delivery strategy.
|
||||
#[derive(Debug)]
|
||||
pub struct BasicStrategy<
|
||||
@@ -34,7 +39,7 @@ pub struct BasicStrategy<
|
||||
Proof,
|
||||
> {
|
||||
/// All queued nonces.
|
||||
source_queue: VecDeque<(HeaderId<SourceHeaderHash, SourceHeaderNumber>, SourceNoncesRange)>,
|
||||
source_queue: SourceRangesQueue<SourceHeaderHash, SourceHeaderNumber, SourceNoncesRange>,
|
||||
/// Best nonce known to target node (at its best block). `None` if it has not been received yet.
|
||||
best_target_nonce: Option<MessageNonce>,
|
||||
/// Unused generic types dump.
|
||||
@@ -57,6 +62,13 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Reference to source queue.
|
||||
pub(crate) fn source_queue(
|
||||
&self,
|
||||
) -> &VecDeque<(HeaderId<SourceHeaderHash, SourceHeaderNumber>, SourceNoncesRange)> {
|
||||
&self.source_queue
|
||||
}
|
||||
|
||||
/// Mutable reference to source queue to use in tests.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn source_queue_mut(
|
||||
@@ -65,25 +77,21 @@ where
|
||||
&mut self.source_queue
|
||||
}
|
||||
|
||||
/// Should return `Some(nonces)` if we need to deliver proof of `nonces` (and associated
|
||||
/// data) from source to target node.
|
||||
/// Returns index of the latest source queue entry, that may be delivered to the target node.
|
||||
///
|
||||
/// The `selector` function receives range of nonces and should return `None` if the whole
|
||||
/// range needs to be delivered. If there are some nonces in the range that can't be delivered
|
||||
/// right now, it should return `Some` with 'undeliverable' nonces. Please keep in mind that
|
||||
/// this should be the sub-range that the passed range ends with, because nonces are always
|
||||
/// delivered in-order. Otherwise the function will panic.
|
||||
pub fn select_nonces_to_deliver_with_selector(
|
||||
&mut self,
|
||||
race_state: &RaceState<
|
||||
/// Returns `None` if no entries may be delivered. All entries before and including the `Some(_)`
|
||||
/// index are guaranteed to be witnessed at source blocks that are known to be finalized at the
|
||||
/// target node.
|
||||
pub fn maximal_available_source_queue_index(
|
||||
&self,
|
||||
race_state: RaceState<
|
||||
HeaderId<SourceHeaderHash, SourceHeaderNumber>,
|
||||
HeaderId<TargetHeaderHash, TargetHeaderNumber>,
|
||||
Proof,
|
||||
>,
|
||||
mut selector: impl FnMut(SourceNoncesRange) -> Option<SourceNoncesRange>,
|
||||
) -> Option<RangeInclusive<MessageNonce>> {
|
||||
) -> Option<usize> {
|
||||
// if we do not know best nonce at target node, we can't select anything
|
||||
let target_nonce = self.best_target_nonce?;
|
||||
let _ = self.best_target_nonce?;
|
||||
|
||||
// if we have already selected nonces that we want to submit, do nothing
|
||||
if race_state.nonces_to_submit.is_some() {
|
||||
@@ -99,60 +107,40 @@ where
|
||||
// 2) we can't deliver new nonce until header, that has emitted this nonce, is finalized
|
||||
// by target client
|
||||
// 3) selector is used for more complicated logic
|
||||
let best_header_at_target = &race_state.best_finalized_source_header_id_at_best_target.as_ref()?;
|
||||
let mut nonces_end = None;
|
||||
//
|
||||
// => let's first select range of entries inside deque that are already finalized at
|
||||
// the target client and pass this range to the selector
|
||||
let best_header_at_target = race_state.best_finalized_source_header_id_at_best_target?;
|
||||
self.source_queue
|
||||
.iter()
|
||||
.enumerate()
|
||||
.take_while(|(_, (queued_at, _))| queued_at.0 <= best_header_at_target.0)
|
||||
.map(|(index, _)| index)
|
||||
.last()
|
||||
}
|
||||
|
||||
/// Remove all nonces that are less than or equal to given nonce from the source queue.
|
||||
pub fn remove_le_nonces_from_source_queue(&mut self, nonce: MessageNonce) {
|
||||
while let Some((queued_at, queued_range)) = self.source_queue.pop_front() {
|
||||
// select (sub) range to deliver
|
||||
let queued_range_begin = queued_range.begin();
|
||||
let queued_range_end = queued_range.end();
|
||||
let range_to_requeue = if queued_at.0 > best_header_at_target.0 {
|
||||
// if header that has queued the range is not yet finalized at bridged chain,
|
||||
// we can't prove anything
|
||||
Some(queued_range)
|
||||
} else {
|
||||
// selector returns `Some(range)` if this `range` needs to be requeued
|
||||
selector(queued_range)
|
||||
};
|
||||
|
||||
// requeue (sub) range and update range to deliver
|
||||
match range_to_requeue {
|
||||
Some(range_to_requeue) => {
|
||||
assert!(
|
||||
range_to_requeue.begin() <= range_to_requeue.end()
|
||||
&& range_to_requeue.begin() >= queued_range_begin
|
||||
&& range_to_requeue.end() == queued_range_end,
|
||||
"Incorrect implementation of internal `selector` function. Expected original\
|
||||
range {:?} to end with returned range {:?}",
|
||||
queued_range_begin..=queued_range_end,
|
||||
range_to_requeue,
|
||||
);
|
||||
|
||||
if range_to_requeue.begin() != queued_range_begin {
|
||||
nonces_end = Some(range_to_requeue.begin() - 1);
|
||||
}
|
||||
self.source_queue.push_front((queued_at, range_to_requeue));
|
||||
break;
|
||||
}
|
||||
None => {
|
||||
nonces_end = Some(queued_range_end);
|
||||
}
|
||||
if let Some(range_to_requeue) = queued_range.greater_than(nonce) {
|
||||
self.source_queue.push_front((queued_at, range_to_requeue));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
nonces_end.map(|nonces_end| RangeInclusive::new(target_nonce + 1, nonces_end))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<SourceHeaderNumber, SourceHeaderHash, TargetHeaderNumber, TargetHeaderHash, SourceNoncesRange, Proof>
|
||||
RaceStrategy<HeaderId<SourceHeaderHash, SourceHeaderNumber>, HeaderId<TargetHeaderHash, TargetHeaderNumber>, Proof>
|
||||
for BasicStrategy<SourceHeaderNumber, SourceHeaderHash, TargetHeaderNumber, TargetHeaderHash, SourceNoncesRange, Proof>
|
||||
where
|
||||
SourceHeaderHash: Clone + Debug,
|
||||
SourceHeaderNumber: Clone + Ord + Debug,
|
||||
SourceNoncesRange: NoncesRange + Debug,
|
||||
TargetHeaderHash: Debug,
|
||||
TargetHeaderNumber: Debug,
|
||||
Proof: Debug,
|
||||
SourceHeaderHash: Clone + Debug + Send,
|
||||
SourceHeaderNumber: Clone + Ord + Debug + Send,
|
||||
SourceNoncesRange: NoncesRange + Debug + Send,
|
||||
TargetHeaderHash: Debug + Send,
|
||||
TargetHeaderNumber: Debug + Send,
|
||||
Proof: Debug + Send,
|
||||
{
|
||||
type SourceNoncesRange = SourceNoncesRange;
|
||||
type ProofParameters = ();
|
||||
@@ -271,16 +259,19 @@ where
|
||||
));
|
||||
}
|
||||
|
||||
fn select_nonces_to_deliver(
|
||||
async fn select_nonces_to_deliver(
|
||||
&mut self,
|
||||
race_state: &RaceState<
|
||||
race_state: RaceState<
|
||||
HeaderId<SourceHeaderHash, SourceHeaderNumber>,
|
||||
HeaderId<TargetHeaderHash, TargetHeaderNumber>,
|
||||
Proof,
|
||||
>,
|
||||
) -> Option<(RangeInclusive<MessageNonce>, Self::ProofParameters)> {
|
||||
self.select_nonces_to_deliver_with_selector(race_state, |_| None)
|
||||
.map(|range| (range, ()))
|
||||
let maximal_source_queue_index = self.maximal_available_source_queue_index(race_state)?;
|
||||
let range_begin = self.source_queue[0].1.begin();
|
||||
let range_end = self.source_queue[maximal_source_queue_index].1.end();
|
||||
self.remove_le_nonces_from_source_queue(range_end);
|
||||
Some((range_begin..=range_end, ()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,7 +279,9 @@ where
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::message_lane::MessageLane;
|
||||
use crate::message_lane_loop::tests::{header_id, TestMessageLane, TestMessagesProof};
|
||||
use crate::message_lane_loop::tests::{
|
||||
header_id, TestMessageLane, TestMessagesProof, TestSourceHeaderHash, TestSourceHeaderNumber,
|
||||
};
|
||||
|
||||
type SourceNoncesRange = RangeInclusive<MessageNonce>;
|
||||
|
||||
@@ -318,9 +311,9 @@ mod tests {
|
||||
#[test]
|
||||
fn strategy_is_empty_works() {
|
||||
let mut strategy = BasicStrategy::<TestMessageLane>::new();
|
||||
assert_eq!(strategy.is_empty(), true);
|
||||
assert!(strategy.is_empty());
|
||||
strategy.source_nonces_updated(header_id(1), source_nonces(1..=1));
|
||||
assert_eq!(strategy.is_empty(), false);
|
||||
assert!(!strategy.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -396,28 +389,28 @@ mod tests {
|
||||
assert!(state.nonces_submitted.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nothing_is_selected_if_something_is_already_selected() {
|
||||
#[async_std::test]
|
||||
async fn nothing_is_selected_if_something_is_already_selected() {
|
||||
let mut state = RaceState::default();
|
||||
let mut strategy = BasicStrategy::<TestMessageLane>::new();
|
||||
state.nonces_to_submit = Some((header_id(1), 1..=10, (1..=10, None)));
|
||||
strategy.best_target_nonces_updated(target_nonces(0), &mut state);
|
||||
strategy.source_nonces_updated(header_id(1), source_nonces(1..=10));
|
||||
assert_eq!(strategy.select_nonces_to_deliver(&state), None);
|
||||
assert_eq!(strategy.select_nonces_to_deliver(state.clone()).await, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nothing_is_selected_if_something_is_already_submitted() {
|
||||
#[async_std::test]
|
||||
async fn nothing_is_selected_if_something_is_already_submitted() {
|
||||
let mut state = RaceState::default();
|
||||
let mut strategy = BasicStrategy::<TestMessageLane>::new();
|
||||
state.nonces_submitted = Some(1..=10);
|
||||
strategy.best_target_nonces_updated(target_nonces(0), &mut state);
|
||||
strategy.source_nonces_updated(header_id(1), source_nonces(1..=10));
|
||||
assert_eq!(strategy.select_nonces_to_deliver(&state), None);
|
||||
assert_eq!(strategy.select_nonces_to_deliver(state.clone()).await, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_nonces_to_deliver_works() {
|
||||
#[async_std::test]
|
||||
async fn select_nonces_to_deliver_works() {
|
||||
let mut state = RaceState::<_, _, TestMessagesProof>::default();
|
||||
let mut strategy = BasicStrategy::<TestMessageLane>::new();
|
||||
strategy.best_target_nonces_updated(target_nonces(0), &mut state);
|
||||
@@ -427,62 +420,75 @@ mod tests {
|
||||
strategy.source_nonces_updated(header_id(5), source_nonces(7..=8));
|
||||
|
||||
state.best_finalized_source_header_id_at_best_target = Some(header_id(4));
|
||||
assert_eq!(strategy.select_nonces_to_deliver(&state), Some((1..=6, ())));
|
||||
assert_eq!(
|
||||
strategy.select_nonces_to_deliver(state.clone()).await,
|
||||
Some((1..=6, ()))
|
||||
);
|
||||
strategy.best_target_nonces_updated(target_nonces(6), &mut state);
|
||||
assert_eq!(strategy.select_nonces_to_deliver(&state), None);
|
||||
assert_eq!(strategy.select_nonces_to_deliver(state.clone()).await, None);
|
||||
|
||||
state.best_finalized_source_header_id_at_best_target = Some(header_id(5));
|
||||
assert_eq!(strategy.select_nonces_to_deliver(&state), Some((7..=8, ())));
|
||||
assert_eq!(
|
||||
strategy.select_nonces_to_deliver(state.clone()).await,
|
||||
Some((7..=8, ()))
|
||||
);
|
||||
strategy.best_target_nonces_updated(target_nonces(8), &mut state);
|
||||
assert_eq!(strategy.select_nonces_to_deliver(&state), None);
|
||||
assert_eq!(strategy.select_nonces_to_deliver(state.clone()).await, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_nonces_to_deliver_able_to_split_ranges_with_selector() {
|
||||
fn maximal_available_source_queue_index_works() {
|
||||
let mut state = RaceState::<_, _, TestMessagesProof>::default();
|
||||
let mut strategy = BasicStrategy::<TestMessageLane>::new();
|
||||
strategy.best_target_nonces_updated(target_nonces(0), &mut state);
|
||||
strategy.source_nonces_updated(header_id(1), source_nonces(1..=100));
|
||||
strategy.source_nonces_updated(header_id(1), source_nonces(1..=3));
|
||||
strategy.source_nonces_updated(header_id(2), source_nonces(4..=6));
|
||||
strategy.source_nonces_updated(header_id(3), source_nonces(7..=9));
|
||||
|
||||
state.best_finalized_source_header_id_at_best_target = Some(header_id(0));
|
||||
assert_eq!(strategy.maximal_available_source_queue_index(state.clone()), None);
|
||||
|
||||
state.best_finalized_source_header_id_at_source = Some(header_id(1));
|
||||
state.best_finalized_source_header_id_at_best_target = Some(header_id(1));
|
||||
state.best_target_header_id = Some(header_id(1));
|
||||
assert_eq!(strategy.maximal_available_source_queue_index(state.clone()), Some(0));
|
||||
|
||||
assert_eq!(
|
||||
strategy.select_nonces_to_deliver_with_selector(&state, |_| Some(50..=100)),
|
||||
Some(1..=49),
|
||||
);
|
||||
state.best_finalized_source_header_id_at_best_target = Some(header_id(2));
|
||||
assert_eq!(strategy.maximal_available_source_queue_index(state.clone()), Some(1));
|
||||
|
||||
state.best_finalized_source_header_id_at_best_target = Some(header_id(3));
|
||||
assert_eq!(strategy.maximal_available_source_queue_index(state.clone()), Some(2));
|
||||
|
||||
state.best_finalized_source_header_id_at_best_target = Some(header_id(4));
|
||||
assert_eq!(strategy.maximal_available_source_queue_index(state), Some(2));
|
||||
}
|
||||
|
||||
fn run_panic_test_for_incorrect_selector(
|
||||
invalid_selector: impl Fn(SourceNoncesRange) -> Option<SourceNoncesRange>,
|
||||
) {
|
||||
#[test]
|
||||
fn remove_le_nonces_from_source_queue_works() {
|
||||
let mut state = RaceState::<_, _, TestMessagesProof>::default();
|
||||
let mut strategy = BasicStrategy::<TestMessageLane>::new();
|
||||
strategy.source_nonces_updated(header_id(1), source_nonces(1..=100));
|
||||
strategy.best_target_nonces_updated(target_nonces(50), &mut state);
|
||||
state.best_finalized_source_header_id_at_source = Some(header_id(1));
|
||||
state.best_finalized_source_header_id_at_best_target = Some(header_id(1));
|
||||
state.best_target_header_id = Some(header_id(1));
|
||||
strategy.select_nonces_to_deliver_with_selector(&state, invalid_selector);
|
||||
}
|
||||
strategy.best_target_nonces_updated(target_nonces(0), &mut state);
|
||||
strategy.source_nonces_updated(header_id(1), source_nonces(1..=3));
|
||||
strategy.source_nonces_updated(header_id(2), source_nonces(4..=6));
|
||||
strategy.source_nonces_updated(header_id(3), source_nonces(7..=9));
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn select_nonces_to_deliver_panics_if_selector_returns_empty_range() {
|
||||
#[allow(clippy::reversed_empty_ranges)]
|
||||
run_panic_test_for_incorrect_selector(|_| Some(2..=1))
|
||||
}
|
||||
fn source_queue_nonces(
|
||||
source_queue: &SourceRangesQueue<TestSourceHeaderHash, TestSourceHeaderNumber, SourceNoncesRange>,
|
||||
) -> Vec<MessageNonce> {
|
||||
source_queue.iter().flat_map(|(_, range)| range.clone()).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn select_nonces_to_deliver_panics_if_selector_returns_range_that_starts_before_passed_range() {
|
||||
run_panic_test_for_incorrect_selector(|range| Some(range.begin() - 1..=*range.end()))
|
||||
}
|
||||
strategy.remove_le_nonces_from_source_queue(1);
|
||||
assert_eq!(
|
||||
source_queue_nonces(&strategy.source_queue),
|
||||
vec![2, 3, 4, 5, 6, 7, 8, 9],
|
||||
);
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn select_nonces_to_deliver_panics_if_selector_returns_range_with_mismatched_end() {
|
||||
run_panic_test_for_incorrect_selector(|range| Some(range.begin()..=*range.end() + 1))
|
||||
strategy.remove_le_nonces_from_source_queue(5);
|
||||
assert_eq!(source_queue_nonces(&strategy.source_queue), vec![6, 7, 8, 9],);
|
||||
|
||||
strategy.remove_le_nonces_from_source_queue(9);
|
||||
assert_eq!(source_queue_nonces(&strategy.source_queue), Vec::<MessageNonce>::new(),);
|
||||
|
||||
strategy.remove_le_nonces_from_source_queue(100);
|
||||
assert_eq!(source_queue_nonces(&strategy.source_queue), Vec::<MessageNonce>::new(),);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,6 @@ pub trait Client: 'static + Clone + Send + Sync {
|
||||
pub fn relay_loop<SC, TC>(source_client: SC, target_client: TC) -> Loop<SC, TC, ()> {
|
||||
Loop {
|
||||
reconnect_delay: RECONNECT_DELAY,
|
||||
spawn_loop_task: true,
|
||||
source_client,
|
||||
target_client,
|
||||
loop_metric: None,
|
||||
@@ -50,7 +49,6 @@ pub fn relay_metrics(prefix: Option<String>, params: MetricsParams) -> LoopMetri
|
||||
LoopMetrics {
|
||||
relay_loop: Loop {
|
||||
reconnect_delay: RECONNECT_DELAY,
|
||||
spawn_loop_task: true,
|
||||
source_client: (),
|
||||
target_client: (),
|
||||
loop_metric: None,
|
||||
@@ -65,7 +63,6 @@ pub fn relay_metrics(prefix: Option<String>, params: MetricsParams) -> LoopMetri
|
||||
/// Generic relay loop.
|
||||
pub struct Loop<SC, TC, LM> {
|
||||
reconnect_delay: Duration,
|
||||
spawn_loop_task: bool,
|
||||
source_client: SC,
|
||||
target_client: TC,
|
||||
loop_metric: Option<LM>,
|
||||
@@ -87,23 +84,11 @@ impl<SC, TC, LM> Loop<SC, TC, LM> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set spawn-dedicated-loop-task flag.
|
||||
///
|
||||
/// If `true` (default), separate async task is spawned to run relay loop. This is the default
|
||||
/// behavior for all loops. If `false`, then loop is executed as a part of the current
|
||||
/// task. The `false` is used for on-demand tasks, which are cancelled from time to time
|
||||
/// and there's already a dedicated on-demand task for running such loops.
|
||||
pub fn spawn_loop_task(mut self, spawn_loop_task: bool) -> Self {
|
||||
self.spawn_loop_task = spawn_loop_task;
|
||||
self
|
||||
}
|
||||
|
||||
/// Start building loop metrics using given prefix.
|
||||
pub fn with_metrics(self, prefix: Option<String>, params: MetricsParams) -> LoopMetrics<SC, TC, ()> {
|
||||
LoopMetrics {
|
||||
relay_loop: Loop {
|
||||
reconnect_delay: self.reconnect_delay,
|
||||
spawn_loop_task: self.spawn_loop_task,
|
||||
source_client: self.source_client,
|
||||
target_client: self.target_client,
|
||||
loop_metric: None,
|
||||
@@ -128,7 +113,6 @@ impl<SC, TC, LM> Loop<SC, TC, LM> {
|
||||
TC: 'static + Client,
|
||||
LM: 'static + Send + Clone,
|
||||
{
|
||||
let spawn_loop_task = self.spawn_loop_task;
|
||||
let run_loop_task = async move {
|
||||
crate::initialize::initialize_loop(loop_name);
|
||||
|
||||
@@ -156,11 +140,7 @@ impl<SC, TC, LM> Loop<SC, TC, LM> {
|
||||
Ok(())
|
||||
};
|
||||
|
||||
if spawn_loop_task {
|
||||
async_std::task::spawn(run_loop_task).await
|
||||
} else {
|
||||
run_loop_task.await
|
||||
}
|
||||
async_std::task::spawn(run_loop_task).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,7 +216,6 @@ impl<SC, TC, LM> LoopMetrics<SC, TC, LM> {
|
||||
|
||||
Ok(Loop {
|
||||
reconnect_delay: self.relay_loop.reconnect_delay,
|
||||
spawn_loop_task: self.relay_loop.spawn_loop_task,
|
||||
source_client: self.relay_loop.source_client,
|
||||
target_client: self.relay_loop.target_client,
|
||||
loop_metric: self.loop_metric,
|
||||
|
||||
Reference in New Issue
Block a user