From bc26ff5003871fda1b760f8f5698b2e30671d32c Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 12 Apr 2024 13:18:01 +0300 Subject: [PATCH 01/70] move bridge extensions to a separate folder --- .../extensions/check_obsolete_extension.rs | 205 ++++++++++++++++++ .../bin/runtime-common/src/extensions/mod.rs | 21 ++ .../{ => extensions}/priority_calculator.rs | 0 .../refund_relayer_extension.rs | 5 +- bridges/bin/runtime-common/src/lib.rs | 189 +--------------- .../primitives/relayers/src/registration.rs | 2 +- .../src/bridge_to_bulletin_config.rs | 10 +- .../src/bridge_to_westend_config.rs | 10 +- .../src/bridge_to_rococo_config.rs | 10 +- 9 files changed, 246 insertions(+), 206 deletions(-) create mode 100644 bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs create mode 100644 bridges/bin/runtime-common/src/extensions/mod.rs rename bridges/bin/runtime-common/src/{ => extensions}/priority_calculator.rs (100%) rename bridges/bin/runtime-common/src/{ => extensions}/refund_relayer_extension.rs (99%) diff --git a/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs b/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs new file mode 100644 index 000000000000..4b0c052df800 --- /dev/null +++ b/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs @@ -0,0 +1,205 @@ +// Copyright (C) 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 . + +//! Transaction extension that rejects bridge-related transactions, that include +//! obsolete (duplicated) data or do not pass some additional pallet-specific +//! checks. + +use crate::messages_call_ext::MessagesCallSubType; +use pallet_bridge_grandpa::CallSubType as GrandpaCallSubType; +use pallet_bridge_parachains::CallSubType as ParachainsCallSubtype; +use sp_runtime::transaction_validity::TransactionValidity; + +/// A duplication of the `FilterCall` trait. +/// +/// We need this trait in order to be able to implement it for the messages pallet, +/// since the implementation is done outside of the pallet crate. +pub trait BridgeRuntimeFilterCall { + /// Checks if a runtime call is valid. + fn validate(call: &Call) -> TransactionValidity; +} + +impl BridgeRuntimeFilterCall for pallet_bridge_grandpa::Pallet +where + T: pallet_bridge_grandpa::Config, + T::RuntimeCall: GrandpaCallSubType, +{ + fn validate(call: &T::RuntimeCall) -> TransactionValidity { + GrandpaCallSubType::::check_obsolete_submit_finality_proof(call) + } +} + +impl BridgeRuntimeFilterCall + for pallet_bridge_parachains::Pallet +where + T: pallet_bridge_parachains::Config, + T::RuntimeCall: ParachainsCallSubtype, +{ + fn validate(call: &T::RuntimeCall) -> TransactionValidity { + ParachainsCallSubtype::::check_obsolete_submit_parachain_heads(call) + } +} + +impl, I: 'static> BridgeRuntimeFilterCall + for pallet_bridge_messages::Pallet +where + T::RuntimeCall: MessagesCallSubType, +{ + /// Validate messages in order to avoid "mining" messages delivery and delivery confirmation + /// transactions, that are delivering outdated messages/confirmations. Without this validation, + /// even honest relayers may lose their funds if there are multiple relays running and + /// submitting the same messages/confirmations. + fn validate(call: &T::RuntimeCall) -> TransactionValidity { + call.check_obsolete_call() + } +} + +/// Declares a runtime-specific `BridgeRejectObsoleteHeadersAndMessages` signed extension. +/// +/// ## Example +/// +/// ```nocompile +/// generate_bridge_reject_obsolete_headers_and_messages!{ +/// Call, AccountId +/// BridgeRococoGrandpa, BridgeRococoMessages, +/// BridgeRococoParachains +/// } +/// ``` +/// +/// The goal of this extension is to avoid "mining" transactions that provide outdated bridged +/// headers and messages. Without that extension, even honest relayers may lose their funds if +/// there are multiple relays running and submitting the same information. +#[macro_export] +macro_rules! generate_bridge_reject_obsolete_headers_and_messages { + ($call:ty, $account_id:ty, $($filter_call:ty),*) => { + #[derive(Clone, codec::Decode, Default, codec::Encode, Eq, PartialEq, sp_runtime::RuntimeDebug, scale_info::TypeInfo)] + pub struct BridgeRejectObsoleteHeadersAndMessages; + impl sp_runtime::traits::SignedExtension for BridgeRejectObsoleteHeadersAndMessages { + const IDENTIFIER: &'static str = "BridgeRejectObsoleteHeadersAndMessages"; + type AccountId = $account_id; + type Call = $call; + type AdditionalSigned = (); + type Pre = (); + + fn additional_signed(&self) -> sp_std::result::Result< + (), + sp_runtime::transaction_validity::TransactionValidityError, + > { + Ok(()) + } + + fn validate( + &self, + _who: &Self::AccountId, + call: &Self::Call, + _info: &sp_runtime::traits::DispatchInfoOf, + _len: usize, + ) -> sp_runtime::transaction_validity::TransactionValidity { + let valid = sp_runtime::transaction_validity::ValidTransaction::default(); + $( + let valid = valid + .combine_with(<$filter_call as $crate::extensions::check_obsolete_extension::BridgeRuntimeFilterCall<$call>>::validate(call)?); + )* + Ok(valid) + } + + fn pre_dispatch( + self, + who: &Self::AccountId, + call: &Self::Call, + info: &sp_runtime::traits::DispatchInfoOf, + len: usize, + ) -> Result { + self.validate(who, call, info, len).map(drop) + } + } + }; +} + +#[cfg(test)] +mod tests { + use super::*; + use frame_support::{assert_err, assert_ok}; + use sp_runtime::{ + traits::SignedExtension, + transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction}, + }; + + pub struct MockCall { + data: u32, + } + + impl sp_runtime::traits::Dispatchable for MockCall { + type RuntimeOrigin = (); + type Config = (); + type Info = (); + type PostInfo = (); + + fn dispatch( + self, + _origin: Self::RuntimeOrigin, + ) -> sp_runtime::DispatchResultWithInfo { + unimplemented!() + } + } + + struct FirstFilterCall; + impl BridgeRuntimeFilterCall for FirstFilterCall { + fn validate(call: &MockCall) -> TransactionValidity { + if call.data <= 1 { + return InvalidTransaction::Custom(1).into() + } + + Ok(ValidTransaction { priority: 1, ..Default::default() }) + } + } + + struct SecondFilterCall; + impl BridgeRuntimeFilterCall for SecondFilterCall { + fn validate(call: &MockCall) -> TransactionValidity { + if call.data <= 2 { + return InvalidTransaction::Custom(2).into() + } + + Ok(ValidTransaction { priority: 2, ..Default::default() }) + } + } + + #[test] + fn test() { + generate_bridge_reject_obsolete_headers_and_messages!( + MockCall, + (), + FirstFilterCall, + SecondFilterCall + ); + + assert_err!( + BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 1 }, &(), 0), + InvalidTransaction::Custom(1) + ); + + assert_err!( + BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 2 }, &(), 0), + InvalidTransaction::Custom(2) + ); + + assert_ok!( + BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 3 }, &(), 0), + ValidTransaction { priority: 3, ..Default::default() } + ) + } +} diff --git a/bridges/bin/runtime-common/src/extensions/mod.rs b/bridges/bin/runtime-common/src/extensions/mod.rs new file mode 100644 index 000000000000..3f1b506aaae3 --- /dev/null +++ b/bridges/bin/runtime-common/src/extensions/mod.rs @@ -0,0 +1,21 @@ +// Copyright (C) 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 . + +//! Bridge-specific transaction extensions. + +pub mod check_obsolete_extension; +pub mod priority_calculator; +pub mod refund_relayer_extension; diff --git a/bridges/bin/runtime-common/src/priority_calculator.rs b/bridges/bin/runtime-common/src/extensions/priority_calculator.rs similarity index 100% rename from bridges/bin/runtime-common/src/priority_calculator.rs rename to bridges/bin/runtime-common/src/extensions/priority_calculator.rs diff --git a/bridges/bin/runtime-common/src/refund_relayer_extension.rs b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs similarity index 99% rename from bridges/bin/runtime-common/src/refund_relayer_extension.rs rename to bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs index 455392a0a277..f97b23ecaaa9 100644 --- a/bridges/bin/runtime-common/src/refund_relayer_extension.rs +++ b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs @@ -520,8 +520,9 @@ where } // compute priority boost - let priority_boost = - crate::priority_calculator::compute_priority_boost::(bundled_messages); + let priority_boost = crate::extensions::priority_calculator::compute_priority_boost::< + T::Priority, + >(bundled_messages); let valid_transaction = ValidTransactionBuilder::default().priority(priority_boost); log::trace!( diff --git a/bridges/bin/runtime-common/src/lib.rs b/bridges/bin/runtime-common/src/lib.rs index 2722f6f1c6d1..5679acd6006c 100644 --- a/bridges/bin/runtime-common/src/lib.rs +++ b/bridges/bin/runtime-common/src/lib.rs @@ -19,11 +19,7 @@ #![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] -use crate::messages_call_ext::MessagesCallSubType; -use pallet_bridge_grandpa::CallSubType as GrandpaCallSubType; -use pallet_bridge_parachains::CallSubType as ParachainsCallSubtype; -use sp_runtime::transaction_validity::TransactionValidity; - +pub mod extensions; pub mod messages; pub mod messages_api; pub mod messages_benchmarking; @@ -31,8 +27,6 @@ pub mod messages_call_ext; pub mod messages_generation; pub mod messages_xcm_extension; pub mod parachains_benchmarking; -pub mod priority_calculator; -pub mod refund_relayer_extension; mod mock; @@ -40,184 +34,3 @@ mod mock; pub mod integrity; const LOG_TARGET_BRIDGE_DISPATCH: &str = "runtime::bridge-dispatch"; - -/// A duplication of the `FilterCall` trait. -/// -/// We need this trait in order to be able to implement it for the messages pallet, -/// since the implementation is done outside of the pallet crate. -pub trait BridgeRuntimeFilterCall { - /// Checks if a runtime call is valid. - fn validate(call: &Call) -> TransactionValidity; -} - -impl BridgeRuntimeFilterCall for pallet_bridge_grandpa::Pallet -where - T: pallet_bridge_grandpa::Config, - T::RuntimeCall: GrandpaCallSubType, -{ - fn validate(call: &T::RuntimeCall) -> TransactionValidity { - GrandpaCallSubType::::check_obsolete_submit_finality_proof(call) - } -} - -impl BridgeRuntimeFilterCall - for pallet_bridge_parachains::Pallet -where - T: pallet_bridge_parachains::Config, - T::RuntimeCall: ParachainsCallSubtype, -{ - fn validate(call: &T::RuntimeCall) -> TransactionValidity { - ParachainsCallSubtype::::check_obsolete_submit_parachain_heads(call) - } -} - -impl, I: 'static> BridgeRuntimeFilterCall - for pallet_bridge_messages::Pallet -where - T::RuntimeCall: MessagesCallSubType, -{ - /// Validate messages in order to avoid "mining" messages delivery and delivery confirmation - /// transactions, that are delivering outdated messages/confirmations. Without this validation, - /// even honest relayers may lose their funds if there are multiple relays running and - /// submitting the same messages/confirmations. - fn validate(call: &T::RuntimeCall) -> TransactionValidity { - call.check_obsolete_call() - } -} - -/// Declares a runtime-specific `BridgeRejectObsoleteHeadersAndMessages` signed extension. -/// -/// ## Example -/// -/// ```nocompile -/// generate_bridge_reject_obsolete_headers_and_messages!{ -/// Call, AccountId -/// BridgeRococoGrandpa, BridgeRococoMessages, -/// BridgeRococoParachains -/// } -/// ``` -/// -/// The goal of this extension is to avoid "mining" transactions that provide outdated bridged -/// headers and messages. Without that extension, even honest relayers may lose their funds if -/// there are multiple relays running and submitting the same information. -#[macro_export] -macro_rules! generate_bridge_reject_obsolete_headers_and_messages { - ($call:ty, $account_id:ty, $($filter_call:ty),*) => { - #[derive(Clone, codec::Decode, Default, codec::Encode, Eq, PartialEq, sp_runtime::RuntimeDebug, scale_info::TypeInfo)] - pub struct BridgeRejectObsoleteHeadersAndMessages; - impl sp_runtime::traits::SignedExtension for BridgeRejectObsoleteHeadersAndMessages { - const IDENTIFIER: &'static str = "BridgeRejectObsoleteHeadersAndMessages"; - type AccountId = $account_id; - type Call = $call; - type AdditionalSigned = (); - type Pre = (); - - fn additional_signed(&self) -> sp_std::result::Result< - (), - sp_runtime::transaction_validity::TransactionValidityError, - > { - Ok(()) - } - - fn validate( - &self, - _who: &Self::AccountId, - call: &Self::Call, - _info: &sp_runtime::traits::DispatchInfoOf, - _len: usize, - ) -> sp_runtime::transaction_validity::TransactionValidity { - let valid = sp_runtime::transaction_validity::ValidTransaction::default(); - $( - let valid = valid - .combine_with(<$filter_call as $crate::BridgeRuntimeFilterCall<$call>>::validate(call)?); - )* - Ok(valid) - } - - fn pre_dispatch( - self, - who: &Self::AccountId, - call: &Self::Call, - info: &sp_runtime::traits::DispatchInfoOf, - len: usize, - ) -> Result { - self.validate(who, call, info, len).map(drop) - } - } - }; -} - -#[cfg(test)] -mod tests { - use crate::BridgeRuntimeFilterCall; - use frame_support::{assert_err, assert_ok}; - use sp_runtime::{ - traits::SignedExtension, - transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction}, - }; - - pub struct MockCall { - data: u32, - } - - impl sp_runtime::traits::Dispatchable for MockCall { - type RuntimeOrigin = (); - type Config = (); - type Info = (); - type PostInfo = (); - - fn dispatch( - self, - _origin: Self::RuntimeOrigin, - ) -> sp_runtime::DispatchResultWithInfo { - unimplemented!() - } - } - - struct FirstFilterCall; - impl BridgeRuntimeFilterCall for FirstFilterCall { - fn validate(call: &MockCall) -> TransactionValidity { - if call.data <= 1 { - return InvalidTransaction::Custom(1).into() - } - - Ok(ValidTransaction { priority: 1, ..Default::default() }) - } - } - - struct SecondFilterCall; - impl BridgeRuntimeFilterCall for SecondFilterCall { - fn validate(call: &MockCall) -> TransactionValidity { - if call.data <= 2 { - return InvalidTransaction::Custom(2).into() - } - - Ok(ValidTransaction { priority: 2, ..Default::default() }) - } - } - - #[test] - fn test() { - generate_bridge_reject_obsolete_headers_and_messages!( - MockCall, - (), - FirstFilterCall, - SecondFilterCall - ); - - assert_err!( - BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 1 }, &(), 0), - InvalidTransaction::Custom(1) - ); - - assert_err!( - BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 2 }, &(), 0), - InvalidTransaction::Custom(2) - ); - - assert_ok!( - BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 3 }, &(), 0), - ValidTransaction { priority: 3, ..Default::default() } - ) - } -} diff --git a/bridges/primitives/relayers/src/registration.rs b/bridges/primitives/relayers/src/registration.rs index bc2d0d127aef..38fa7c2d9075 100644 --- a/bridges/primitives/relayers/src/registration.rs +++ b/bridges/primitives/relayers/src/registration.rs @@ -21,7 +21,7 @@ //! required finality proofs). This extension boosts priority of message delivery //! transactions, based on the number of bundled messages. So transaction with more //! messages has larger priority than the transaction with less messages. -//! See `bridge_runtime_common::priority_calculator` for details; +//! See `bridge_runtime_common::extensions::priority_calculator` for details; //! //! This encourages relayers to include more messages to their delivery transactions. //! At the same time, we are not verifying storage proofs before boosting diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs index 6dbf96edc2ab..8845f0538b5c 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs @@ -29,6 +29,10 @@ use crate::{ use bp_messages::LaneId; use bp_runtime::Chain; use bridge_runtime_common::{ + extensions::refund_relayer_extension::{ + ActualFeeRefund, RefundBridgedGrandpaMessages, RefundSignedExtensionAdapter, + RefundableMessagesLane, + }, messages, messages::{ source::{FromBridgedChainMessagesDeliveryProof, TargetHeaderChainAdapter}, @@ -39,10 +43,6 @@ use bridge_runtime_common::{ SenderAndLane, XcmAsPlainPayload, XcmBlobHauler, XcmBlobHaulerAdapter, XcmBlobMessageDispatch, XcmVersionOfDestAndRemoteBridge, }, - refund_relayer_extension::{ - ActualFeeRefund, RefundBridgedGrandpaMessages, RefundSignedExtensionAdapter, - RefundableMessagesLane, - }, }; use frame_support::{parameter_types, traits::PalletInfoAccess}; @@ -273,7 +273,7 @@ mod tests { // Bulletin chain - it has the same (almost) runtime for Polkadot Bulletin and Rococo // Bulletin, so we have to adhere Polkadot names here - bridge_runtime_common::priority_calculator::ensure_priority_boost_is_sane::< + bridge_runtime_common::extensions::priority_calculator::ensure_priority_boost_is_sane::< Runtime, WithRococoBulletinMessagesInstance, PriorityBoostPerMessage, diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs index 5d55d7afbacf..e5a00073407f 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs @@ -28,6 +28,10 @@ use crate::{ use bp_messages::LaneId; use bp_runtime::Chain; use bridge_runtime_common::{ + extensions::refund_relayer_extension::{ + ActualFeeRefund, RefundBridgedParachainMessages, RefundSignedExtensionAdapter, + RefundableMessagesLane, RefundableParachain, + }, messages, messages::{ source::{FromBridgedChainMessagesDeliveryProof, TargetHeaderChainAdapter}, @@ -38,10 +42,6 @@ use bridge_runtime_common::{ SenderAndLane, XcmAsPlainPayload, XcmBlobHauler, XcmBlobHaulerAdapter, XcmBlobMessageDispatch, XcmVersionOfDestAndRemoteBridge, }, - refund_relayer_extension::{ - ActualFeeRefund, RefundBridgedParachainMessages, RefundSignedExtensionAdapter, - RefundableMessagesLane, RefundableParachain, - }, }; use codec::Encode; @@ -318,7 +318,7 @@ mod tests { }, }); - bridge_runtime_common::priority_calculator::ensure_priority_boost_is_sane::< + bridge_runtime_common::extensions::priority_calculator::ensure_priority_boost_is_sane::< Runtime, WithBridgeHubWestendMessagesInstance, PriorityBoostPerMessage, diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs index bce722aa5f87..d5da41cce286 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs @@ -25,6 +25,10 @@ use bp_messages::LaneId; use bp_parachains::SingleParaStoredHeaderDataBuilder; use bp_runtime::Chain; use bridge_runtime_common::{ + extensions::refund_relayer_extension::{ + ActualFeeRefund, RefundBridgedParachainMessages, RefundSignedExtensionAdapter, + RefundableMessagesLane, RefundableParachain, + }, messages, messages::{ source::{FromBridgedChainMessagesDeliveryProof, TargetHeaderChainAdapter}, @@ -35,10 +39,6 @@ use bridge_runtime_common::{ SenderAndLane, XcmAsPlainPayload, XcmBlobHauler, XcmBlobHaulerAdapter, XcmBlobMessageDispatch, XcmVersionOfDestAndRemoteBridge, }, - refund_relayer_extension::{ - ActualFeeRefund, RefundBridgedParachainMessages, RefundSignedExtensionAdapter, - RefundableMessagesLane, RefundableParachain, - }, }; use codec::Encode; use frame_support::{ @@ -352,7 +352,7 @@ mod tests { }, }); - bridge_runtime_common::priority_calculator::ensure_priority_boost_is_sane::< + bridge_runtime_common::extensions::priority_calculator::ensure_priority_boost_is_sane::< Runtime, WithBridgeHubRococoMessagesInstance, PriorityBoostPerMessage, From 812d4747288679a628fd4e01dd8fbe82bf52d04e Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 12 Mar 2024 15:51:21 +0300 Subject: [PATCH 02/70] added FreeHeadersInterval to pallet-bridge-grandpa configuration --- bridges/bin/runtime-common/src/mock.rs | 3 +- bridges/modules/grandpa/src/lib.rs | 146 ++++++++++++++++++++----- bridges/modules/grandpa/src/mock.rs | 6 +- bridges/modules/parachains/src/mock.rs | 6 +- 4 files changed, 129 insertions(+), 32 deletions(-) diff --git a/bridges/bin/runtime-common/src/mock.rs b/bridges/bin/runtime-common/src/mock.rs index ad71cd0d456d..3beb52bbe7a7 100644 --- a/bridges/bin/runtime-common/src/mock.rs +++ b/bridges/bin/runtime-common/src/mock.rs @@ -183,7 +183,8 @@ impl pallet_transaction_payment::Config for TestRuntime { impl pallet_bridge_grandpa::Config for TestRuntime { type RuntimeEvent = RuntimeEvent; type BridgedChain = BridgedUnderlyingChain; - type MaxFreeMandatoryHeadersPerBlock = ConstU32<4>; + type MaxFreeHeadersPerBlock = ConstU32<4>; + type FreeHeadersInterval = ConstU32<1_024>; type HeadersToKeep = ConstU32<8>; type WeightInfo = pallet_bridge_grandpa::weights::BridgeWeight; } diff --git a/bridges/modules/grandpa/src/lib.rs b/bridges/modules/grandpa/src/lib.rs index 9e095651ef81..b1aa43a7a479 100644 --- a/bridges/modules/grandpa/src/lib.rs +++ b/bridges/modules/grandpa/src/lib.rs @@ -44,6 +44,7 @@ use bp_header_chain::{ }; use bp_runtime::{BlockNumberOf, HashOf, HasherOf, HeaderId, HeaderOf, OwnedBridgeModule}; use frame_support::{dispatch::PostDispatchInfo, ensure, DefaultNoBound}; +use sp_consensus_grandpa::SetId; use sp_runtime::{ traits::{Header as HeaderT, Zero}, SaturatedConversion, @@ -101,17 +102,30 @@ pub mod pallet { /// The chain we are bridging to here. type BridgedChain: ChainWithGrandpa; - /// Maximal number of "free" mandatory header transactions per block. + /// Maximal number of "free" header transactions per block. /// /// To be able to track the bridged chain, the pallet requires all headers that are /// changing GRANDPA authorities set at the bridged chain (we call them mandatory). - /// So it is a common good deed to submit mandatory headers to the pallet. However, if the - /// bridged chain gets compromised, its validators may generate as many mandatory headers - /// as they want. And they may fill the whole block (at this chain) for free. This constants - /// limits number of calls that we may refund in a single block. All calls above this - /// limit are accepted, but are not refunded. + /// So it is a common good deed to submit mandatory headers to the pallet. + /// + /// The pallet may be configured (see `[Self::FreeHeadersInterval]`) to import some + /// non-mandatory headers for free as well. It also may be treated as a common good + /// deed, because it may help to reduce bridge fees - this cost may be deducted from + /// bridge fees, paid by message senders. + /// + /// However, if the bridged chain gets compromised, its validators may generate as many + /// "free" headers as they want. And they may fill the whole block (at this chain) for + /// free. This constants limits number of calls that we may refund in a single block. + /// All calls above this limit are accepted, but are not refunded. #[pallet::constant] - type MaxFreeMandatoryHeadersPerBlock: Get; + type MaxFreeHeadersPerBlock: Get; + + /// The distance between bridged chain headers, that may be submitted for free. The + /// first free header is header number zero, the next one is header number + /// `FreeHeadersInterval::get()`. In other words, header with number that + /// is divisible by `FreeHeadersInterval` may be submitted for free. + #[pallet::constant] + type FreeHeadersInterval: Get>; /// Maximal number of finalized headers to keep in the storage. /// @@ -133,12 +147,12 @@ pub mod pallet { #[pallet::hooks] impl, I: 'static> Hooks> for Pallet { fn on_initialize(_n: BlockNumberFor) -> Weight { - FreeMandatoryHeadersRemaining::::put(T::MaxFreeMandatoryHeadersPerBlock::get()); + FreeHeadersRemaining::::put(T::MaxFreeHeadersPerBlock::get()); Weight::zero() } fn on_finalize(_n: BlockNumberFor) { - FreeMandatoryHeadersRemaining::::kill(); + FreeHeadersRemaining::::kill(); } } @@ -283,16 +297,14 @@ pub mod pallet { let maybe_new_authority_set = try_enact_authority_change::(&finality_target, set_id)?; - let may_refund_call_fee = maybe_new_authority_set.is_some() && - // if we have seen too many mandatory headers in this block, we don't want to refund - Self::free_mandatory_headers_remaining() > 0 && - // if arguments out of expected bounds, we don't want to refund - submit_finality_proof_info_from_args::(&finality_target, &justification, Some(current_set_id)) - .fits_limits(); + let may_refund_call_fee = may_refund_call_fee::( + maybe_new_authority_set.is_some(), + &finality_target, + &justification, + current_set_id, + ); if may_refund_call_fee { - FreeMandatoryHeadersRemaining::::mutate(|count| { - *count = count.saturating_sub(1) - }); + FreeHeadersRemaining::::mutate(|count| *count = count.saturating_sub(1)); } insert_header::(*finality_target, hash); log::info!( @@ -335,19 +347,18 @@ pub mod pallet { } } - /// Number mandatory headers that we may accept in the current block for free (returning - /// `Pays::No`). + /// Number of free header submissions that we may yet accept in the current block. /// - /// If the `FreeMandatoryHeadersRemaining` hits zero, all following mandatory headers in the + /// If the `FreeHeadersRemaining` hits zero, all following mandatory headers in the /// current block are accepted with fee (`Pays::Yes` is returned). /// - /// The `FreeMandatoryHeadersRemaining` is an ephemeral value that is set to - /// `MaxFreeMandatoryHeadersPerBlock` at each block initialization and is killed on block + /// The `FreeHeadersRemaining` is an ephemeral value that is set to + /// `MaxFreeHeadersPerBlock` at each block initialization and is killed on block /// finalization. So it never ends up in the storage trie. #[pallet::storage] #[pallet::whitelist_storage] #[pallet::getter(fn free_mandatory_headers_remaining)] - pub(super) type FreeMandatoryHeadersRemaining, I: 'static = ()> = + pub(super) type FreeHeadersRemaining, I: 'static = ()> = StorageValue<_, u32, ValueQuery>; /// Hash of the header used to bootstrap the pallet. @@ -475,6 +486,48 @@ pub mod pallet { InvalidAuthoritySetId, } + /// Return true if we want to refund + fn may_refund_call_fee, I: 'static>( + is_mandatory_header: bool, + finality_target: &BridgedHeader, + justification: &GrandpaJustification>, + current_set_id: SetId, + ) -> bool { + // if we have refunded too much at this block => not refunding + if FreeHeadersRemaining::::get() == 0 { + // TODO: attacker may watch tx pool for our free tx of header #1000 and then + // submit its own `FreeHeadersRemaining` transactions with tip + // => our transaction will be paid. Cut this off in the extension. + + return false; + } + + // if size/weight of call is larger than expected => not refunding + let call_info = submit_finality_proof_info_from_args::( + &finality_target, + &justification, + Some(current_set_id), + ); + if !call_info.fits_limits() { + return false; + } + + // if that's a mandatory header => refund + if is_mandatory_header { + return true; + } + + // if configuration allows free non-mandatory headers and the header + // matches criteria => refund + if let Some(free_headers_interval) = T::FreeHeadersInterval::get() { + if *finality_target.number() % free_headers_interval.into() == Zero::zero() { + return true; + } + } + + false + } + /// Check the given header for a GRANDPA scheduled authority set change. If a change /// is found it will be enacted immediately. /// @@ -692,8 +745,8 @@ pub fn initialize_for_benchmarks, I: 'static>(header: BridgedHeader mod tests { use super::*; use crate::mock::{ - run_test, test_header, RuntimeEvent as TestEvent, RuntimeOrigin, System, TestBridgedChain, - TestHeader, TestNumber, TestRuntime, MAX_BRIDGED_AUTHORITIES, + run_test, test_header, FreeHeadersInterval, RuntimeEvent as TestEvent, RuntimeOrigin, + System, TestBridgedChain, TestHeader, TestNumber, TestRuntime, MAX_BRIDGED_AUTHORITIES, }; use bp_header_chain::BridgeGrandpaCall; use bp_runtime::BasicOperatingMode; @@ -1355,7 +1408,7 @@ mod tests { initialize_substrate_bridge(); - for _ in 0..::MaxFreeMandatoryHeadersPerBlock::get() + 1 { + for _ in 0..::MaxFreeHeadersPerBlock::get() + 1 { assert_err!(submit_invalid_request(), >::InvalidJustification); } @@ -1423,6 +1476,45 @@ mod tests { }) } + #[test] + fn may_import_non_mandatory_header_for_free() { + run_test(|| { + initialize_substrate_bridge(); + + // non-mandatory header is imported with fee + let non_free_header_number = FreeHeadersInterval::get() as u8 - 1; + let result = submit_finality_proof(non_free_header_number); + assert_eq!(result.unwrap().pays_fee, Pays::Yes); + + // non-mandatory free header is imported without fee + let free_header_number = FreeHeadersInterval::get() as u8; + let result = submit_finality_proof(free_header_number); + assert_eq!(result.unwrap().pays_fee, Pays::No); + + // another non-mandatory free header is imported without fee + let free_header_number = FreeHeadersInterval::get() as u8 * 2; + let result = submit_finality_proof(free_header_number); + assert_eq!(result.unwrap().pays_fee, Pays::No); + + // now the rate limiter starts charging fees even for free headers + let free_header_number = FreeHeadersInterval::get() as u8 * 3; + let result = submit_finality_proof(free_header_number); + assert_eq!(result.unwrap().pays_fee, Pays::Yes); + + next_block(); + + // check that the rate limiter shares the counter between mandatory + // and free non-mandatory headers + let free_header_number = FreeHeadersInterval::get() as u8 * 4; + let result = submit_finality_proof(free_header_number); + assert_eq!(result.unwrap().pays_fee, Pays::No); + let result = submit_mandatory_finality_proof(free_header_number + 1, 1); + assert_eq!(result.expect("call failed").pays_fee, Pays::No); + let result = submit_mandatory_finality_proof(free_header_number + 2, 2); + assert_eq!(result.expect("call failed").pays_fee, Pays::Yes); + }); + } + #[test] fn should_prune_headers_over_headers_to_keep_parameter() { run_test(|| { diff --git a/bridges/modules/grandpa/src/mock.rs b/bridges/modules/grandpa/src/mock.rs index e689e520c92f..27df9d9c78f5 100644 --- a/bridges/modules/grandpa/src/mock.rs +++ b/bridges/modules/grandpa/src/mock.rs @@ -48,14 +48,16 @@ impl frame_system::Config for TestRuntime { } parameter_types! { - pub const MaxFreeMandatoryHeadersPerBlock: u32 = 2; + pub const MaxFreeHeadersPerBlock: u32 = 2; + pub const FreeHeadersInterval: u32 = 32; pub const HeadersToKeep: u32 = 5; } impl grandpa::Config for TestRuntime { type RuntimeEvent = RuntimeEvent; type BridgedChain = TestBridgedChain; - type MaxFreeMandatoryHeadersPerBlock = MaxFreeMandatoryHeadersPerBlock; + type MaxFreeHeadersPerBlock = MaxFreeHeadersPerBlock; + type FreeHeadersInterval = FreeHeadersInterval; type HeadersToKeep = HeadersToKeep; type WeightInfo = (); } diff --git a/bridges/modules/parachains/src/mock.rs b/bridges/modules/parachains/src/mock.rs index d9cbabf850ec..12e54e830102 100644 --- a/bridges/modules/parachains/src/mock.rs +++ b/bridges/modules/parachains/src/mock.rs @@ -173,7 +173,8 @@ parameter_types! { impl pallet_bridge_grandpa::Config for TestRuntime { type RuntimeEvent = RuntimeEvent; type BridgedChain = TestBridgedChain; - type MaxFreeMandatoryHeadersPerBlock = ConstU32<2>; + type MaxFreeHeadersPerBlock = ConstU32<2>; + type FreeHeadersInterval = ConstU32<1_024>; type HeadersToKeep = HeadersToKeep; type WeightInfo = (); } @@ -181,7 +182,8 @@ impl pallet_bridge_grandpa::Config for TestRun impl pallet_bridge_grandpa::Config for TestRuntime { type RuntimeEvent = RuntimeEvent; type BridgedChain = TestBridgedChain; - type MaxFreeMandatoryHeadersPerBlock = ConstU32<2>; + type MaxFreeHeadersPerBlock = ConstU32<2>; + type FreeHeadersInterval = ConstU32<1_024>; type HeadersToKeep = HeadersToKeep; type WeightInfo = (); } From b45fde592cd15adcfb7142f0f613c7b83498aa34 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 12 Mar 2024 17:30:37 +0300 Subject: [PATCH 03/70] added is_free_execution_expected parameter to the submit_finality_proof_ex + check it from the transaction extension --- bridges/modules/grandpa/src/call_ext.rs | 93 ++++++++++++++++++++++--- bridges/modules/grandpa/src/lib.rs | 38 ++++++++-- bridges/modules/parachains/src/lib.rs | 1 + 3 files changed, 117 insertions(+), 15 deletions(-) diff --git a/bridges/modules/grandpa/src/call_ext.rs b/bridges/modules/grandpa/src/call_ext.rs index 4a7ebb3cc8d4..c90402b10361 100644 --- a/bridges/modules/grandpa/src/call_ext.rs +++ b/bridges/modules/grandpa/src/call_ext.rs @@ -16,7 +16,7 @@ use crate::{ weights::WeightInfo, BridgedBlockNumber, BridgedHeader, Config, CurrentAuthoritySet, Error, - Pallet, + FreeHeadersRemaining, Pallet, }; use bp_header_chain::{ justification::GrandpaJustification, max_expected_submit_finality_proof_arguments_size, @@ -40,6 +40,9 @@ pub struct SubmitFinalityProofInfo { /// An identifier of the validators set that has signed the submitted justification. /// It might be `None` if deprecated version of the `submit_finality_proof` is used. pub current_set_id: Option, + /// If `true`, then the call must be free (assuming that everything else is valid) to + /// be treated as valid. + pub is_free_execution_expected: bool, /// Extra weight that we assume is included in the call. /// /// We have some assumptions about headers and justifications of the bridged chain. @@ -67,9 +70,36 @@ pub struct SubmitFinalityProofHelper, I: 'static> { } impl, I: 'static> SubmitFinalityProofHelper { + /// Check that the: (1) GRANDPA head provided by the `SubmitFinalityProof` is better than the + /// best one we know (2) if `current_set_id` matches the current authority set id, if specified + /// and (3) whether transaction MAY be free for the submitter if `is_free_execution_expected` + /// is `true`. + pub fn check_obsolete_from_extension( + call_info: &SubmitFinalityProofInfo>, + ) -> Result<(), Error> { + // do basic checks first + Self::check_obsolete(call_info.block_number, call_info.current_set_id)?; + + // if submitter has NOT specified that it wants free execution, then we are done + if !call_info.is_free_execution_expected { + return Ok(()); + } + + // else - if we can not accept more free headers, "reject" the transaction + if FreeHeadersRemaining::::get() == 0 { + return Err(Error::::CannotAcceptMoreFreeHeaders); + } + + // we do not check whether the header matches free submission criteria here - it is the + // relayer responsibility to check that + + Ok(()) + } + /// Check that the GRANDPA head provided by the `SubmitFinalityProof` is better than the best /// one we know. Additionally, checks if `current_set_id` matches the current authority set - /// id, if specified. + /// id, if specified. This method is called by the call code and the transaction extension, + /// so it does not check the free execution. pub fn check_obsolete( finality_target: BlockNumberOf, current_set_id: Option, @@ -135,17 +165,20 @@ pub trait CallSubType, I: 'static>: finality_target, justification, None, + false, )) } else if let Some(crate::Call::::submit_finality_proof_ex { finality_target, justification, current_set_id, + is_free_execution_expected, }) = self.is_sub_type() { return Some(submit_finality_proof_info_from_args::( finality_target, justification, Some(*current_set_id), + *is_free_execution_expected, )) } @@ -159,7 +192,7 @@ pub trait CallSubType, I: 'static>: where Self: Sized, { - let finality_target = match self.submit_finality_proof_info() { + let call_info = match self.submit_finality_proof_info() { Some(finality_proof) => finality_proof, _ => return Ok(ValidTransaction::default()), }; @@ -168,10 +201,7 @@ pub trait CallSubType, I: 'static>: return InvalidTransaction::Call.into() } - match SubmitFinalityProofHelper::::check_obsolete( - finality_target.block_number, - finality_target.current_set_id, - ) { + match SubmitFinalityProofHelper::::check_obsolete_from_extension(&call_info) { Ok(_) => Ok(ValidTransaction::default()), Err(Error::::OldHeader) => InvalidTransaction::Stale.into(), Err(_) => InvalidTransaction::Call.into(), @@ -189,6 +219,7 @@ pub(crate) fn submit_finality_proof_info_from_args, I: 'static>( finality_target: &BridgedHeader, justification: &GrandpaJustification>, current_set_id: Option, + is_free_execution_expected: bool, ) -> SubmitFinalityProofInfo> { let block_number = *finality_target.number(); @@ -230,7 +261,13 @@ pub(crate) fn submit_finality_proof_info_from_args, I: 'static>( ); let extra_size = actual_call_size.saturating_sub(max_expected_call_size); - SubmitFinalityProofInfo { block_number, current_set_id, extra_weight, extra_size } + SubmitFinalityProofInfo { + block_number, + current_set_id, + is_free_execution_expected, + extra_weight, + extra_size, + } } #[cfg(test)] @@ -238,8 +275,8 @@ mod tests { use crate::{ call_ext::CallSubType, mock::{run_test, test_header, RuntimeCall, TestBridgedChain, TestNumber, TestRuntime}, - BestFinalized, Config, CurrentAuthoritySet, PalletOperatingMode, StoredAuthoritySet, - SubmitFinalityProofInfo, WeightInfo, + BestFinalized, Config, CurrentAuthoritySet, FreeHeadersRemaining, PalletOperatingMode, + StoredAuthoritySet, SubmitFinalityProofInfo, WeightInfo, }; use bp_header_chain::ChainWithGrandpa; use bp_runtime::{BasicOperatingMode, HeaderId}; @@ -256,6 +293,7 @@ mod tests { justification: make_default_justification(&test_header(num)), // not initialized => zero current_set_id: 0, + is_free_execution_expected: false, }; RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa( bridge_grandpa_call, @@ -311,6 +349,34 @@ mod tests { }); } + #[test] + fn extension_rejects_new_header_if_free_execution_is_requested_and_free_submissions_are_not_accepted( + ) { + run_test(|| { + let bridge_grandpa_call = crate::Call::::submit_finality_proof_ex { + finality_target: Box::new(test_header(15)), + justification: make_default_justification(&test_header(15)), + current_set_id: 0, + is_free_execution_expected: true, + }; + sync_to_header_10(); + + // when we can accept free headers => Ok + FreeHeadersRemaining::::put(2); + assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa( + bridge_grandpa_call.clone(), + )) + .is_ok()); + + // when we can NOT accept free headers => Err + FreeHeadersRemaining::::put(0); + assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa( + bridge_grandpa_call, + )) + .is_err()); + }) + } + #[test] fn extension_accepts_new_header() { run_test(|| { @@ -336,6 +402,7 @@ mod tests { current_set_id: None, extra_weight: Weight::zero(), extra_size: 0, + is_free_execution_expected: false, }) ); @@ -345,6 +412,7 @@ mod tests { finality_target: Box::new(test_header(42)), justification: make_default_justification(&test_header(42)), current_set_id: 777, + is_free_execution_expected: false, }); assert_eq!( deprecated_call.submit_finality_proof_info(), @@ -353,6 +421,7 @@ mod tests { current_set_id: Some(777), extra_weight: Weight::zero(), extra_size: 0, + is_free_execution_expected: false, }) ); } @@ -370,6 +439,7 @@ mod tests { finality_target: Box::new(small_finality_target), justification: small_justification, current_set_id: TEST_GRANDPA_SET_ID, + is_free_execution_expected: false, }); assert_eq!(small_call.submit_finality_proof_info().unwrap().extra_size, 0); @@ -387,6 +457,7 @@ mod tests { finality_target: Box::new(large_finality_target), justification: large_justification, current_set_id: TEST_GRANDPA_SET_ID, + is_free_execution_expected: false, }); assert_ne!(large_call.submit_finality_proof_info().unwrap().extra_size, 0); } @@ -406,6 +477,7 @@ mod tests { finality_target: Box::new(finality_target.clone()), justification, current_set_id: TEST_GRANDPA_SET_ID, + is_free_execution_expected: false, }); assert_eq!(call.submit_finality_proof_info().unwrap().extra_weight, Weight::zero()); @@ -420,6 +492,7 @@ mod tests { finality_target: Box::new(finality_target), justification, current_set_id: TEST_GRANDPA_SET_ID, + is_free_execution_expected: false, }); assert_eq!(call.submit_finality_proof_info().unwrap().extra_weight, call_weight); } diff --git a/bridges/modules/grandpa/src/lib.rs b/bridges/modules/grandpa/src/lib.rs index b1aa43a7a479..f625ebd0b8a9 100644 --- a/bridges/modules/grandpa/src/lib.rs +++ b/bridges/modules/grandpa/src/lib.rs @@ -189,6 +189,8 @@ pub mod pallet { // the `submit_finality_proof_ex` also reads this value, but it is done from the // cache, so we don't treat it as an additional db access >::get().set_id, + // cannot enforce free execution using this call + false, ) } @@ -264,6 +266,12 @@ pub mod pallet { /// - verification is not optimized or invalid; /// /// - header contains forced authorities set change or change with non-zero delay. + /// + /// The `is_free_execution_expected` parameter is not really used inside the call. It is + /// used by the transaction extension, which should be registered at the runtime level. If + /// this parameter is `true`, the transaction will be treated as invalid, if the call won't + /// be executed for free. If transaction extension is not used by the runtime, this + /// parameter is not used at all. #[pallet::call_index(4)] #[pallet::weight(::submit_finality_proof( justification.commit.precommits.len().saturated_into(), @@ -274,6 +282,7 @@ pub mod pallet { finality_target: Box>, justification: GrandpaJustification>, current_set_id: sp_consensus_grandpa::SetId, + _is_free_execution_expected: bool, ) -> DispatchResultWithPostInfo { Self::ensure_not_halted().map_err(Error::::BridgeModule)?; ensure_signed(origin)?; @@ -484,9 +493,13 @@ pub mod pallet { /// The `current_set_id` argument of the `submit_finality_proof_ex` doesn't match /// the id of the current set, known to the pallet. InvalidAuthoritySetId, + /// The submitter wanted free execution, but we can't fit more free transactions + /// to the block. + CannotAcceptMoreFreeHeaders, } - /// Return true if we want to refund + /// Return true if we may refund transaction cost to the submitter. In other words, + /// this transaction is considered as common good deed w.r.t to pallet configuration. fn may_refund_call_fee, I: 'static>( is_mandatory_header: bool, finality_target: &BridgedHeader, @@ -495,10 +508,6 @@ pub mod pallet { ) -> bool { // if we have refunded too much at this block => not refunding if FreeHeadersRemaining::::get() == 0 { - // TODO: attacker may watch tx pool for our free tx of header #1000 and then - // submit its own `FreeHeadersRemaining` transactions with tip - // => our transaction will be paid. Cut this off in the extension. - return false; } @@ -507,6 +516,10 @@ pub mod pallet { &finality_target, &justification, Some(current_set_id), + // this function is called from the transaction body and we do not want + // to do MAY-be-free-executed checks here - they had to be done in the + // transaction extension before + false, ); if !call_info.fits_limits() { return false; @@ -800,6 +813,7 @@ mod tests { Box::new(header), justification, TEST_GRANDPA_SET_ID, + false, ) } @@ -819,6 +833,7 @@ mod tests { Box::new(header), justification, set_id, + false, ) } @@ -847,6 +862,7 @@ mod tests { Box::new(header), justification, set_id, + false, ) } @@ -1062,6 +1078,7 @@ mod tests { Box::new(header.clone()), justification.clone(), TEST_GRANDPA_SET_ID, + false, ), >::InvalidJustification ); @@ -1071,6 +1088,7 @@ mod tests { Box::new(header), justification, next_set_id, + false, ), >::InvalidAuthoritySetId ); @@ -1092,6 +1110,7 @@ mod tests { Box::new(header), justification, TEST_GRANDPA_SET_ID, + false, ), >::InvalidJustification ); @@ -1122,6 +1141,7 @@ mod tests { Box::new(header), justification, TEST_GRANDPA_SET_ID, + false, ), >::InvalidAuthoritySet ); @@ -1161,6 +1181,7 @@ mod tests { Box::new(header.clone()), justification.clone(), TEST_GRANDPA_SET_ID, + false, ); assert_ok!(result); assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::No); @@ -1224,6 +1245,7 @@ mod tests { Box::new(header.clone()), justification, TEST_GRANDPA_SET_ID, + false, ); assert_ok!(result); assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::Yes); @@ -1256,6 +1278,7 @@ mod tests { Box::new(header.clone()), justification, TEST_GRANDPA_SET_ID, + false, ); assert_ok!(result); assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::Yes); @@ -1286,6 +1309,7 @@ mod tests { Box::new(header), justification, TEST_GRANDPA_SET_ID, + false, ), >::UnsupportedScheduledChange ); @@ -1312,6 +1336,7 @@ mod tests { Box::new(header), justification, TEST_GRANDPA_SET_ID, + false, ), >::UnsupportedScheduledChange ); @@ -1338,6 +1363,7 @@ mod tests { Box::new(header), justification, TEST_GRANDPA_SET_ID, + false, ), >::TooManyAuthoritiesInSet ); @@ -1403,6 +1429,7 @@ mod tests { Box::new(header), invalid_justification, TEST_GRANDPA_SET_ID, + false, ) }; @@ -1611,6 +1638,7 @@ mod tests { Box::new(header), justification, TEST_GRANDPA_SET_ID, + false, ), DispatchError::BadOrigin, ); diff --git a/bridges/modules/parachains/src/lib.rs b/bridges/modules/parachains/src/lib.rs index 1363a637604d..3c778ddccb88 100644 --- a/bridges/modules/parachains/src/lib.rs +++ b/bridges/modules/parachains/src/lib.rs @@ -783,6 +783,7 @@ pub(crate) mod tests { Box::new(header), justification.clone(), TEST_GRANDPA_SET_ID, + false, ) ); From 1161805e6793457ff982a4130fbf5f55207bb52b Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 13 Mar 2024 10:24:28 +0300 Subject: [PATCH 04/70] move grandpa call result check from RefundTransactionExtensionAdapter to RefundBridgedGrandpaMessages --- .../extensions/refund_relayer_extension.rs | 130 +++++++++++------- 1 file changed, 77 insertions(+), 53 deletions(-) diff --git a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs index f97b23ecaaa9..102d4d49059d 100644 --- a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs +++ b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs @@ -24,7 +24,7 @@ use crate::messages_call_ext::{ }; use bp_messages::{LaneId, MessageNonce}; use bp_relayers::{RewardsAccountOwner, RewardsAccountParams}; -use bp_runtime::{Chain, Parachain, ParachainIdOf, RangeInclusiveExt, StaticStrProvider}; +use bp_runtime::{Parachain, ParachainIdOf, RangeInclusiveExt, StaticStrProvider}; use codec::{Codec, Decode, Encode}; use frame_support::{ dispatch::{CallableCallFor, DispatchInfo, PostDispatchInfo}, @@ -33,8 +33,7 @@ use frame_support::{ CloneNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, }; use pallet_bridge_grandpa::{ - CallSubType as GrandpaCallSubType, Config as GrandpaConfig, SubmitFinalityProofHelper, - SubmitFinalityProofInfo, + CallSubType as GrandpaCallSubType, SubmitFinalityProofHelper, SubmitFinalityProofInfo, }; use pallet_bridge_messages::Config as MessagesConfig; use pallet_bridge_parachains::{ @@ -242,17 +241,11 @@ pub enum RelayerAccountAction { /// Everything common among our refund signed extensions. pub trait RefundSignedExtension: 'static + Clone + Codec + sp_std::fmt::Debug + Default + Eq + PartialEq + Send + Sync + TypeInfo -where - >::BridgedChain: - Chain, { /// This chain runtime. type Runtime: UtilityConfig> - + GrandpaConfig + MessagesConfig<::Instance> + RelayersConfig; - /// Grandpa pallet reference. - type GrandpaInstance: 'static; /// Messages pallet and lane reference. type Msgs: RefundableMessagesLaneId; /// Refund amount calculator. @@ -276,11 +269,13 @@ where call: &CallOf, ) -> Result<&CallOf, TransactionValidityError>; - /// Called from post-dispatch and shall perform additional checks (apart from relay - /// chain finality and messages transaction finality) of given call result. + /// Called from post-dispatch and shall perform additional checks (apart from messages + /// transaction success) of given call result. fn additional_call_result_check( relayer: &AccountIdOf, call_info: &CallInfo, + extra_weight: &mut Weight, + extra_size: &mut u32, ) -> bool; /// Given post-dispatch information, analyze the outcome of relayer call and return @@ -348,35 +343,6 @@ where return slash_relayer_if_delivery_result } - // check if relay chain state has been updated - if let Some(finality_proof_info) = call_info.submit_finality_proof_info() { - if !SubmitFinalityProofHelper::::was_successful( - finality_proof_info.block_number, - ) { - // we only refund relayer if all calls have updated chain state - log::trace!( - target: "runtime::bridge", - "{} via {:?}: relayer {:?} has submitted invalid relay chain finality proof", - Self::Id::STR, - ::Id::get(), - relayer, - ); - return slash_relayer_if_delivery_result - } - - // there's a conflict between how bridge GRANDPA pallet works and a `utility.batchAll` - // transaction. If relay chain header is mandatory, the GRANDPA pallet returns - // `Pays::No`, because such transaction is mandatory for operating the bridge. But - // `utility.batchAll` transaction always requires payment. But in both cases we'll - // refund relayer - either explicitly here, or using `Pays::No` if he's choosing - // to submit dedicated transaction. - - // submitter has means to include extra weight/bytes in the `submit_finality_proof` - // call, so let's subtract extra weight/size to avoid refunding for this extra stuff - extra_weight = finality_proof_info.extra_weight; - extra_size = finality_proof_info.extra_size; - } - // Check if the `ReceiveMessagesProof` call delivered at least some of the messages that // it contained. If this happens, we consider the transaction "helpful" and refund it. let msgs_call_info = call_info.messages_call_info(); @@ -391,8 +357,13 @@ where return slash_relayer_if_delivery_result } - // do additional check - if !Self::additional_call_result_check(&relayer, &call_info) { + // do additional checks + if !Self::additional_call_result_check( + &relayer, + &call_info, + &mut extra_weight, + &mut extra_size, + ) { return slash_relayer_if_delivery_result } @@ -468,18 +439,12 @@ where RuntimeDebugNoBound, TypeInfo, )] -pub struct RefundSignedExtensionAdapter(T) -where - >::BridgedChain: - Chain; +pub struct RefundSignedExtensionAdapter(T); impl SignedExtension for RefundSignedExtensionAdapter where - >::BridgedChain: - Chain, CallOf: Dispatchable + IsSubType, T::Runtime>> - + GrandpaCallSubType + MessagesCallSubType::Instance>, { const IDENTIFIER: &'static str = T::Id::STR; @@ -641,6 +606,14 @@ impl RefundSignedExtension for RefundBridgedParachainMessages where Self: 'static + Send + Sync, + RefundBridgedGrandpaMessages< + Runtime, + Runtime::BridgesGrandpaPalletInstance, + Msgs, + Refund, + Priority, + Id, + >: 'static + Send + Sync, Runtime: UtilityConfig> + BoundedBridgeGrandpaConfig + ParachainsConfig @@ -658,7 +631,6 @@ where + MessagesCallSubType, { type Runtime = Runtime; - type GrandpaInstance = Runtime::BridgesGrandpaPalletInstance; type Msgs = Msgs; type Refund = Refund; type Priority = Priority; @@ -708,7 +680,26 @@ where Ok(call) } - fn additional_call_result_check(relayer: &Runtime::AccountId, call_info: &CallInfo) -> bool { + fn additional_call_result_check( + relayer: &Runtime::AccountId, + call_info: &CallInfo, + extra_weight: &mut Weight, + extra_size: &mut u32, + ) -> bool { + // check if relay chain state has been updated + let is_granda_call_succeeded = + RefundBridgedGrandpaMessages::< + Runtime, + Runtime::BridgesGrandpaPalletInstance, + Msgs, + Refund, + Priority, + Id, + >::additional_call_result_check(relayer, call_info, extra_weight, extra_size); + if !is_granda_call_succeeded { + return false + } + // check if parachain state has been updated if let Some(para_proof_info) = call_info.submit_parachain_heads_info() { if !SubmitParachainHeadsHelper::::was_successful( @@ -791,7 +782,6 @@ where + MessagesCallSubType, { type Runtime = Runtime; - type GrandpaInstance = GrandpaInstance; type Msgs = Msgs; type Refund = Refund; type Priority = Priority; @@ -833,7 +823,41 @@ where Ok(call) } - fn additional_call_result_check(_relayer: &Runtime::AccountId, _call_info: &CallInfo) -> bool { + fn additional_call_result_check( + relayer: &Runtime::AccountId, + call_info: &CallInfo, + extra_weight: &mut Weight, + extra_size: &mut u32, + ) -> bool { + // check if relay chain state has been updated + if let Some(finality_proof_info) = call_info.submit_finality_proof_info() { + if !SubmitFinalityProofHelper::::was_successful( + finality_proof_info.block_number, + ) { + // we only refund relayer if all calls have updated chain state + log::trace!( + target: "runtime::bridge", + "{} via {:?}: relayer {:?} has submitted invalid relay chain finality proof", + Self::Id::STR, + ::Id::get(), + relayer, + ); + return false + } + + // there's a conflict between how bridge GRANDPA pallet works and a `utility.batchAll` + // transaction. If relay chain header is mandatory, the GRANDPA pallet returns + // `Pays::No`, because such transaction is mandatory for operating the bridge. But + // `utility.batchAll` transaction always requires payment. But in both cases we'll + // refund relayer - either explicitly here, or using `Pays::No` if he's choosing + // to submit dedicated transaction. + + // submitter has means to include extra weight/bytes in the `submit_finality_proof` + // call, so let's subtract extra weight/size to avoid refunding for this extra stuff + *extra_weight = (*extra_weight).saturating_add(finality_proof_info.extra_weight); + *extra_size = (*extra_size).saturating_add(finality_proof_info.extra_size); + } + true } } From 148d9b300b390408df249e20453825190cc5e8af Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 13 Mar 2024 11:01:31 +0300 Subject: [PATCH 05/70] added RefundBridgedMessages to refund and bump priority of standalone messages transactions --- .../extensions/refund_relayer_extension.rs | 406 +++++++++++++++--- 1 file changed, 336 insertions(+), 70 deletions(-) diff --git a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs index 102d4d49059d..77935462d129 100644 --- a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs +++ b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs @@ -243,8 +243,7 @@ pub trait RefundSignedExtension: 'static + Clone + Codec + sp_std::fmt::Debug + Default + Eq + PartialEq + Send + Sync + TypeInfo { /// This chain runtime. - type Runtime: UtilityConfig> - + MessagesConfig<::Instance> + type Runtime: MessagesConfig<::Instance> + RelayersConfig; /// Messages pallet and lane reference. type Msgs: RefundableMessagesLaneId; @@ -444,7 +443,6 @@ pub struct RefundSignedExtensionAdapter(T); impl SignedExtension for RefundSignedExtensionAdapter where CallOf: Dispatchable - + IsSubType, T::Runtime>> + MessagesCallSubType::Instance>, { const IDENTIFIER: &'static str = T::Id::STR; @@ -862,6 +860,84 @@ where } } +/// Transaction extension that refunds a relayer for standalone messages delivery and confirmation +/// transactions. Finality transactions are not refunded. +#[derive( + DefaultNoBound, + CloneNoBound, + Decode, + Encode, + EqNoBound, + PartialEqNoBound, + RuntimeDebugNoBound, + TypeInfo, +)] +#[scale_info(skip_type_params(Runtime, GrandpaInstance, Msgs, Refund, Priority, Id))] +pub struct RefundBridgedMessages( + PhantomData<( + // runtime with `pallet-bridge-messages` and `pallet-bridge-relayers` pallets deployed + Runtime, + // implementation of `RefundableMessagesLaneId` trait, which specifies the instance of + // the used `pallet-bridge-messages` pallet and the lane within this pallet + Msgs, + // implementation of the `RefundCalculator` trait, that is used to compute refund that + // we give to relayer for his transaction + Refund, + // getter for per-message `TransactionPriority` boost that we give to message + // delivery transactions + Priority, + // the runtime-unique identifier of this signed extension + Id, + )>, +); + +impl RefundSignedExtension + for RefundBridgedMessages +where + Self: 'static + Send + Sync, + Runtime: MessagesConfig + RelayersConfig, + Msgs: RefundableMessagesLaneId, + Refund: RefundCalculator, + Priority: Get, + Id: StaticStrProvider, + CallOf: Dispatchable + + MessagesCallSubType, +{ + type Runtime = Runtime; + type Msgs = Msgs; + type Refund = Refund; + type Priority = Priority; + type Id = Id; + + fn expand_call(call: &CallOf) -> Vec<&CallOf> { + vec![call] + } + + fn parse_and_check_for_obsolete_call( + call: &CallOf, + ) -> Result, TransactionValidityError> { + let call = Self::check_obsolete_parsed_call(call)?; + Ok(call.call_info_for(Msgs::Id::get()).map(CallInfo::Msgs)) + } + + fn check_obsolete_parsed_call( + call: &CallOf, + ) -> Result<&CallOf, TransactionValidityError> { + call.check_obsolete_call()?; + Ok(call) + } + + fn additional_call_result_check( + _relayer: &Runtime::AccountId, + _call_info: &CallInfo, + _extra_weight: &mut Weight, + _extra_size: &mut u32, + ) -> bool { + // everything is checked by the `RefundTransactionExtension` + true + } +} + #[cfg(test)] mod tests { use super::*; @@ -916,6 +992,14 @@ mod tests { bp_runtime::generate_static_str_provider!(TestExtension); + type TestMessagesExtensionProvider = RefundBridgedMessages< + TestRuntime, + RefundableMessagesLane<(), TestLaneId>, + ActualFeeRefund, + ConstU64<1>, + StrTestExtension, + >; + type TestMessagesExtension = RefundSignedExtensionAdapter; type TestGrandpaExtensionProvider = RefundBridgedGrandpaMessages< TestRuntime, (), @@ -1029,6 +1113,7 @@ mod tests { finality_target: Box::new(relay_header), justification: relay_justification, current_set_id: TEST_GRANDPA_SET_ID, + is_free_execution_expected: false, }) } @@ -1215,6 +1300,7 @@ mod tests { current_set_id: None, extra_weight: Weight::zero(), extra_size: 0, + is_free_execution_expected: false, }, SubmitParachainHeadsInfo { at_relay_block_number: 200, @@ -1252,6 +1338,7 @@ mod tests { current_set_id: None, extra_weight: Weight::zero(), extra_size: 0, + is_free_execution_expected: false, }, SubmitParachainHeadsInfo { at_relay_block_number: 200, @@ -1285,6 +1372,7 @@ mod tests { current_set_id: None, extra_weight: Weight::zero(), extra_size: 0, + is_free_execution_expected: false, }, MessagesCallInfo::ReceiveMessagesProof(ReceiveMessagesProofInfo { base: BaseMessagesProofInfo { @@ -1317,6 +1405,7 @@ mod tests { current_set_id: None, extra_weight: Weight::zero(), extra_size: 0, + is_free_execution_expected: false, }, MessagesCallInfo::ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo( BaseMessagesProofInfo { @@ -1442,8 +1531,14 @@ mod tests { extension.validate(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0) } - fn run_validate_ignore_priority(call: RuntimeCall) -> TransactionValidity { - run_validate(call).map(|mut tx| { + fn run_messages_validate(call: RuntimeCall) -> TransactionValidity { + let extension: TestMessagesExtension = + RefundSignedExtensionAdapter(RefundBridgedMessages(PhantomData)); + extension.validate(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0) + } + + fn ignore_priority(tx: TransactionValidity) -> TransactionValidity { + tx.map(|mut tx| { tx.priority = 0; tx }) @@ -1465,6 +1560,14 @@ mod tests { extension.pre_dispatch(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0) } + fn run_messages_pre_dispatch( + call: RuntimeCall, + ) -> Result>, TransactionValidityError> { + let extension: TestMessagesExtension = + RefundSignedExtensionAdapter(RefundBridgedMessages(PhantomData)); + extension.pre_dispatch(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0) + } + fn dispatch_info() -> DispatchInfo { DispatchInfo { weight: Weight::from_parts( @@ -1523,40 +1626,48 @@ mod tests { Balances::set_balance(&relayer_account_at_this_chain(), ExistentialDeposit::get()); // message delivery is failing - assert_eq!(run_validate(message_delivery_call(200)), Ok(Default::default()),); - assert_eq!( - run_validate(parachain_finality_and_delivery_batch_call(200, 200)), - Ok(Default::default()), - ); - assert_eq!( - run_validate(all_finality_and_delivery_batch_call(200, 200, 200)), - Ok(Default::default()), - ); + let fns = [run_validate, run_grandpa_validate, run_messages_validate]; + for f in fns { + assert_eq!(f(message_delivery_call(200)), Ok(Default::default()),); + assert_eq!( + f(parachain_finality_and_delivery_batch_call(200, 200)), + Ok(Default::default()), + ); + assert_eq!( + f(all_finality_and_delivery_batch_call(200, 200, 200)), + Ok(Default::default()), + ); + assert_eq!( + f(all_finality_and_delivery_batch_call_ex(200, 200, 200)), + Ok(Default::default()), + ); + } + + // message confirmation validation is passing assert_eq!( - run_validate(all_finality_and_delivery_batch_call_ex(200, 200, 200)), + ignore_priority(run_validate(message_confirmation_call(200))), Ok(Default::default()), ); - // message confirmation validation is passing assert_eq!( - run_validate_ignore_priority(message_confirmation_call(200)), + ignore_priority(run_messages_validate(message_confirmation_call(200))), Ok(Default::default()), ); assert_eq!( - run_validate_ignore_priority(parachain_finality_and_confirmation_batch_call( + ignore_priority(run_validate(parachain_finality_and_confirmation_batch_call( 200, 200 - )), + ))), Ok(Default::default()), ); assert_eq!( - run_validate_ignore_priority(all_finality_and_confirmation_batch_call( + ignore_priority(run_validate(all_finality_and_confirmation_batch_call( 200, 200, 200 - )), + ))), Ok(Default::default()), ); assert_eq!( - run_validate_ignore_priority(all_finality_and_confirmation_batch_call_ex( + ignore_priority(run_validate(all_finality_and_confirmation_batch_call_ex( 200, 200, 200 - )), + ))), Ok(Default::default()), ); }); @@ -1570,25 +1681,28 @@ mod tests { BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000) .unwrap(); - let priority_of_100_messages_delivery = - run_validate(message_delivery_call(200)).unwrap().priority; - let priority_of_200_messages_delivery = - run_validate(message_delivery_call(300)).unwrap().priority; - assert!( - priority_of_200_messages_delivery > priority_of_100_messages_delivery, - "Invalid priorities: {} for 200 messages vs {} for 100 messages", - priority_of_200_messages_delivery, - priority_of_100_messages_delivery, - ); + let fns = [run_validate, run_grandpa_validate, run_messages_validate]; + for f in fns { + let priority_of_100_messages_delivery = + f(message_delivery_call(200)).unwrap().priority; + let priority_of_200_messages_delivery = + f(message_delivery_call(300)).unwrap().priority; + assert!( + priority_of_200_messages_delivery > priority_of_100_messages_delivery, + "Invalid priorities: {} for 200 messages vs {} for 100 messages", + priority_of_200_messages_delivery, + priority_of_100_messages_delivery, + ); - let priority_of_100_messages_confirmation = - run_validate(message_confirmation_call(200)).unwrap().priority; - let priority_of_200_messages_confirmation = - run_validate(message_confirmation_call(300)).unwrap().priority; - assert_eq!( - priority_of_100_messages_confirmation, - priority_of_200_messages_confirmation - ); + let priority_of_100_messages_confirmation = + f(message_confirmation_call(200)).unwrap().priority; + let priority_of_200_messages_confirmation = + f(message_confirmation_call(300)).unwrap().priority; + assert_eq!( + priority_of_100_messages_confirmation, + priority_of_200_messages_confirmation + ); + } }); } @@ -1600,23 +1714,24 @@ mod tests { BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000) .unwrap(); - let priority_of_max_messages_delivery = run_validate(message_delivery_call( - 100 + MaxUnconfirmedMessagesAtInboundLane::get(), - )) - .unwrap() - .priority; - let priority_of_more_than_max_messages_delivery = run_validate(message_delivery_call( - 100 + MaxUnconfirmedMessagesAtInboundLane::get() + 1, - )) - .unwrap() - .priority; - - assert!( - priority_of_max_messages_delivery > priority_of_more_than_max_messages_delivery, - "Invalid priorities: {} for MAX messages vs {} for MAX+1 messages", - priority_of_max_messages_delivery, - priority_of_more_than_max_messages_delivery, - ); + let fns = [run_validate, run_grandpa_validate, run_messages_validate]; + for f in fns { + let priority_of_max_messages_delivery = + f(message_delivery_call(100 + MaxUnconfirmedMessagesAtInboundLane::get())) + .unwrap() + .priority; + let priority_of_more_than_max_messages_delivery = + f(message_delivery_call(100 + MaxUnconfirmedMessagesAtInboundLane::get() + 1)) + .unwrap() + .priority; + + assert!( + priority_of_max_messages_delivery > priority_of_more_than_max_messages_delivery, + "Invalid priorities: {} for MAX messages vs {} for MAX+1 messages", + priority_of_max_messages_delivery, + priority_of_more_than_max_messages_delivery, + ); + } }); } @@ -1626,45 +1741,54 @@ mod tests { initialize_environment(100, 100, 100); assert_eq!( - run_validate_ignore_priority(message_delivery_call(200)), + ignore_priority(run_validate(message_delivery_call(200))), + Ok(ValidTransaction::default()), + ); + assert_eq!( + ignore_priority(run_validate(message_confirmation_call(200))), + Ok(ValidTransaction::default()), + ); + + assert_eq!( + ignore_priority(run_messages_validate(message_delivery_call(200))), Ok(ValidTransaction::default()), ); assert_eq!( - run_validate_ignore_priority(message_confirmation_call(200)), + ignore_priority(run_messages_validate(message_confirmation_call(200))), Ok(ValidTransaction::default()), ); assert_eq!( - run_validate_ignore_priority(parachain_finality_and_delivery_batch_call(200, 200)), + ignore_priority(run_validate(parachain_finality_and_delivery_batch_call(200, 200))), Ok(ValidTransaction::default()), ); assert_eq!( - run_validate_ignore_priority(parachain_finality_and_confirmation_batch_call( + ignore_priority(run_validate(parachain_finality_and_confirmation_batch_call( 200, 200 - )), + ))), Ok(ValidTransaction::default()), ); assert_eq!( - run_validate_ignore_priority(all_finality_and_delivery_batch_call(200, 200, 200)), + ignore_priority(run_validate(all_finality_and_delivery_batch_call(200, 200, 200))), Ok(ValidTransaction::default()), ); assert_eq!( - run_validate_ignore_priority(all_finality_and_delivery_batch_call_ex( + ignore_priority(run_validate(all_finality_and_delivery_batch_call_ex( 200, 200, 200 - )), + ))), Ok(ValidTransaction::default()), ); assert_eq!( - run_validate_ignore_priority(all_finality_and_confirmation_batch_call( + ignore_priority(run_validate(all_finality_and_confirmation_batch_call( 200, 200, 200 - )), + ))), Ok(ValidTransaction::default()), ); assert_eq!( - run_validate_ignore_priority(all_finality_and_confirmation_batch_call_ex( + ignore_priority(run_validate(all_finality_and_confirmation_batch_call_ex( 200, 200, 200 - )), + ))), Ok(ValidTransaction::default()), ); }); @@ -2339,6 +2463,148 @@ mod tests { }); } + #[test] + fn messages_ext_only_parses_standalone_transactions() { + run_test(|| { + initialize_environment(100, 100, 100); + + // relay + parachain + message delivery calls batch is ignored + assert_eq!( + TestMessagesExtensionProvider::parse_and_check_for_obsolete_call( + &all_finality_and_delivery_batch_call(200, 200, 200) + ), + Ok(None), + ); + assert_eq!( + TestMessagesExtensionProvider::parse_and_check_for_obsolete_call( + &all_finality_and_delivery_batch_call_ex(200, 200, 200) + ), + Ok(None), + ); + + // relay + parachain + message confirmation calls batch is ignored + assert_eq!( + TestMessagesExtensionProvider::parse_and_check_for_obsolete_call( + &all_finality_and_confirmation_batch_call(200, 200, 200) + ), + Ok(None), + ); + assert_eq!( + TestMessagesExtensionProvider::parse_and_check_for_obsolete_call( + &all_finality_and_confirmation_batch_call_ex(200, 200, 200) + ), + Ok(None), + ); + + // parachain + message delivery call batch is ignored + assert_eq!( + TestMessagesExtensionProvider::parse_and_check_for_obsolete_call( + ¶chain_finality_and_delivery_batch_call(200, 200) + ), + Ok(None), + ); + + // parachain + message confirmation call batch is ignored + assert_eq!( + TestMessagesExtensionProvider::parse_and_check_for_obsolete_call( + ¶chain_finality_and_confirmation_batch_call(200, 200) + ), + Ok(None), + ); + + // relay + message delivery call batch is ignored + assert_eq!( + TestMessagesExtensionProvider::parse_and_check_for_obsolete_call( + &relay_finality_and_delivery_batch_call(200, 200) + ), + Ok(None), + ); + assert_eq!( + TestMessagesExtensionProvider::parse_and_check_for_obsolete_call( + &relay_finality_and_delivery_batch_call_ex(200, 200) + ), + Ok(None), + ); + + // relay + message confirmation call batch is ignored + assert_eq!( + TestMessagesExtensionProvider::parse_and_check_for_obsolete_call( + &relay_finality_and_confirmation_batch_call(200, 200) + ), + Ok(None), + ); + assert_eq!( + TestMessagesExtensionProvider::parse_and_check_for_obsolete_call( + &relay_finality_and_confirmation_batch_call_ex(200, 200) + ), + Ok(None), + ); + + // message delivery call batch is accepted + assert_eq!( + TestMessagesExtensionProvider::parse_and_check_for_obsolete_call( + &message_delivery_call(200) + ), + Ok(Some(delivery_pre_dispatch_data().call_info)), + ); + + // message confirmation call batch is accepted + assert_eq!( + TestMessagesExtensionProvider::parse_and_check_for_obsolete_call( + &message_confirmation_call(200) + ), + Ok(Some(confirmation_pre_dispatch_data().call_info)), + ); + }); + } + + #[test] + fn messages_ext_rejects_calls_with_obsolete_messages() { + run_test(|| { + initialize_environment(100, 100, 100); + + assert_eq!( + run_messages_pre_dispatch(message_delivery_call(100)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); + assert_eq!( + run_messages_pre_dispatch(message_confirmation_call(100)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); + + assert_eq!( + run_messages_validate(message_delivery_call(100)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); + assert_eq!( + run_messages_validate(message_confirmation_call(100)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); + }); + } + + #[test] + fn messages_ext_accepts_calls_with_new_messages() { + run_test(|| { + initialize_environment(100, 100, 100); + + assert_eq!( + run_messages_pre_dispatch(message_delivery_call(200)), + Ok(Some(delivery_pre_dispatch_data())), + ); + assert_eq!( + run_messages_pre_dispatch(message_confirmation_call(200)), + Ok(Some(confirmation_pre_dispatch_data())), + ); + + assert_eq!(run_messages_validate(message_delivery_call(200)), Ok(Default::default()),); + assert_eq!( + run_messages_validate(message_confirmation_call(200)), + Ok(Default::default()), + ); + }); + } + #[test] fn grandpa_ext_only_parses_valid_batches() { run_test(|| { From 3cfd66608870fb135f5ae48fcd2a62393d663f58 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 13 Mar 2024 11:32:59 +0300 Subject: [PATCH 06/70] check_obsolete_submit_finality_proof may now boost transaction priority (this is not used yet - see next commit) --- .../extensions/check_obsolete_extension.rs | 2 +- .../extensions/refund_relayer_extension.rs | 4 +- bridges/modules/grandpa/src/call_ext.rs | 137 ++++++++++++++---- 3 files changed, 110 insertions(+), 33 deletions(-) diff --git a/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs b/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs index 4b0c052df800..64060503848a 100644 --- a/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs +++ b/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs @@ -38,7 +38,7 @@ where T::RuntimeCall: GrandpaCallSubType, { fn validate(call: &T::RuntimeCall) -> TransactionValidity { - GrandpaCallSubType::::check_obsolete_submit_finality_proof(call) + GrandpaCallSubType::::check_obsolete_submit_finality_proof(call, 0) } } diff --git a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs index 77935462d129..563c1621dc40 100644 --- a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs +++ b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs @@ -672,7 +672,7 @@ where fn check_obsolete_parsed_call( call: &CallOf, ) -> Result<&CallOf, TransactionValidityError> { - call.check_obsolete_submit_finality_proof()?; + call.check_obsolete_submit_finality_proof(0)?; call.check_obsolete_submit_parachain_heads()?; call.check_obsolete_call()?; Ok(call) @@ -816,7 +816,7 @@ where fn check_obsolete_parsed_call( call: &CallOf, ) -> Result<&CallOf, TransactionValidityError> { - call.check_obsolete_submit_finality_proof()?; + call.check_obsolete_submit_finality_proof(0)?; call.check_obsolete_call()?; Ok(call) } diff --git a/bridges/modules/grandpa/src/call_ext.rs b/bridges/modules/grandpa/src/call_ext.rs index c90402b10361..fde6bd32245c 100644 --- a/bridges/modules/grandpa/src/call_ext.rs +++ b/bridges/modules/grandpa/src/call_ext.rs @@ -27,9 +27,12 @@ use codec::Encode; use frame_support::{dispatch::CallableCallFor, traits::IsSubType, weights::Weight}; use sp_consensus_grandpa::SetId; use sp_runtime::{ - traits::{Header, Zero}, - transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction}, - RuntimeDebug, SaturatedConversion, + traits::{CheckedSub, Header, One, UniqueSaturatedInto, Zero}, + transaction_validity::{ + InvalidTransaction, TransactionPriority, TransactionValidity, ValidTransaction, + ValidTransactionBuilder, + }, + RuntimeDebug, SaturatedConversion, Saturating, }; /// Info about a `SubmitParachainHeads` call which tries to update a single parachain. @@ -74,15 +77,18 @@ impl, I: 'static> SubmitFinalityProofHelper { /// best one we know (2) if `current_set_id` matches the current authority set id, if specified /// and (3) whether transaction MAY be free for the submitter if `is_free_execution_expected` /// is `true`. + /// + /// Returns number of headers between the current best finalized header, known to the pallet + /// and the bundled header. pub fn check_obsolete_from_extension( call_info: &SubmitFinalityProofInfo>, - ) -> Result<(), Error> { + ) -> Result, Error> { // do basic checks first - Self::check_obsolete(call_info.block_number, call_info.current_set_id)?; + let improved_by = Self::check_obsolete(call_info.block_number, call_info.current_set_id)?; // if submitter has NOT specified that it wants free execution, then we are done if !call_info.is_free_execution_expected { - return Ok(()); + return Ok(improved_by); } // else - if we can not accept more free headers, "reject" the transaction @@ -93,17 +99,20 @@ impl, I: 'static> SubmitFinalityProofHelper { // we do not check whether the header matches free submission criteria here - it is the // relayer responsibility to check that - Ok(()) + Ok(improved_by) } /// Check that the GRANDPA head provided by the `SubmitFinalityProof` is better than the best /// one we know. Additionally, checks if `current_set_id` matches the current authority set /// id, if specified. This method is called by the call code and the transaction extension, /// so it does not check the free execution. + /// + /// Returns number of headers between the current best finalized header, known to the pallet + /// and the bundled header. pub fn check_obsolete( finality_target: BlockNumberOf, current_set_id: Option, - ) -> Result<(), Error> { + ) -> Result, Error> { let best_finalized = crate::BestFinalized::::get().ok_or_else(|| { log::trace!( target: crate::LOG_TARGET, @@ -113,16 +122,19 @@ impl, I: 'static> SubmitFinalityProofHelper { >::NotInitialized })?; - if best_finalized.number() >= finality_target { - log::trace!( - target: crate::LOG_TARGET, - "Cannot finalize obsolete header: bundled {:?}, best {:?}", - finality_target, - best_finalized, - ); + let improved_by = match finality_target.checked_sub(&best_finalized.number()) { + Some(improved_by) if improved_by > Zero::zero() => improved_by, + _ => { + log::trace!( + target: crate::LOG_TARGET, + "Cannot finalize obsolete header: bundled {:?}, best {:?}", + finality_target, + best_finalized, + ); - return Err(Error::::OldHeader) - } + return Err(Error::::OldHeader) + }, + }; if let Some(current_set_id) = current_set_id { let actual_set_id = >::get().set_id; @@ -138,7 +150,7 @@ impl, I: 'static> SubmitFinalityProofHelper { } } - Ok(()) + Ok(improved_by) } /// Check if the `SubmitFinalityProof` was successfully executed. @@ -188,7 +200,16 @@ pub trait CallSubType, I: 'static>: /// Validate Grandpa headers in order to avoid "mining" transactions that provide outdated /// bridged chain headers. Without this validation, even honest relayers may lose their funds /// if there are multiple relays running and submitting the same information. - fn check_obsolete_submit_finality_proof(&self) -> TransactionValidity + /// + /// It also adds `priority_boost` for every missed header between best finalized header, known + /// to the pallet and bundled header, staring from the second header. So if + /// `BestFinalized` header is header number `100` and transaction brings header + /// `101` there's no priority boost. If transaction brings header `102`, then + /// priority is boosted by `priority_boost` and so on. + fn check_obsolete_submit_finality_proof( + &self, + priority_boost: TransactionPriority, + ) -> TransactionValidity where Self: Sized, { @@ -201,8 +222,14 @@ pub trait CallSubType, I: 'static>: return InvalidTransaction::Call.into() } - match SubmitFinalityProofHelper::::check_obsolete_from_extension(&call_info) { - Ok(_) => Ok(ValidTransaction::default()), + let result = SubmitFinalityProofHelper::::check_obsolete_from_extension(&call_info); + match result { + Ok(improved_by) => { + let improved_by: TransactionPriority = + improved_by.saturating_sub(One::one()).unique_saturated_into(); + let total_priority_boost = improved_by.saturating_mul(priority_boost); + ValidTransactionBuilder::default().priority(total_priority_boost).build() + }, Err(Error::::OldHeader) => InvalidTransaction::Stale.into(), Err(_) => InvalidTransaction::Call.into(), } @@ -295,9 +322,10 @@ mod tests { current_set_id: 0, is_free_execution_expected: false, }; - RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa( - bridge_grandpa_call, - )) + RuntimeCall::check_obsolete_submit_finality_proof( + &RuntimeCall::Grandpa(bridge_grandpa_call), + 0, + ) .is_ok() } @@ -363,16 +391,18 @@ mod tests { // when we can accept free headers => Ok FreeHeadersRemaining::::put(2); - assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa( - bridge_grandpa_call.clone(), - )) + assert!(RuntimeCall::check_obsolete_submit_finality_proof( + &RuntimeCall::Grandpa(bridge_grandpa_call.clone(),), + 0 + ) .is_ok()); // when we can NOT accept free headers => Err FreeHeadersRemaining::::put(0); - assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa( - bridge_grandpa_call, - )) + assert!(RuntimeCall::check_obsolete_submit_finality_proof( + &RuntimeCall::Grandpa(bridge_grandpa_call,), + 0 + ) .is_err()); }) } @@ -496,4 +526,51 @@ mod tests { }); assert_eq!(call.submit_finality_proof_info().unwrap().extra_weight, call_weight); } + + #[test] + fn check_obsolete_submit_finality_proof_boosts_priority() { + run_test(|| { + fn make_call(number: u64) -> RuntimeCall { + RuntimeCall::Grandpa(crate::Call::::submit_finality_proof_ex { + finality_target: Box::new(test_header(number)), + justification: make_default_justification(&test_header(number)), + current_set_id: 0, + is_free_execution_expected: true, + }) + } + + // when priority boost is zero, total boost is also zero + sync_to_header_10(); + assert_eq!( + RuntimeCall::check_obsolete_submit_finality_proof(&make_call(15), 0) + .unwrap() + .priority, + 0, + ); + + // when the difference between headers is 1, no boost + assert_eq!( + RuntimeCall::check_obsolete_submit_finality_proof(&make_call(11), 100) + .unwrap() + .priority, + 0, + ); + + // when the difference between headers is 2 => boost + assert_eq!( + RuntimeCall::check_obsolete_submit_finality_proof(&make_call(12), 100) + .unwrap() + .priority, + 100, + ); + + // when the difference between headers is 3 => 2 * boost + assert_eq!( + RuntimeCall::check_obsolete_submit_finality_proof(&make_call(13), 100) + .unwrap() + .priority, + 200, + ); + }) + } } From 38c558beee998916cead276eaaafa00e27aeaf39 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 13 Mar 2024 12:08:25 +0300 Subject: [PATCH 07/70] added CheckAndBoostBridgeGrandpaTransactions that checks whether bridge GRANDPA transactions are obsolete and, if not, it may apply priority boost to --- .../extensions/check_obsolete_extension.rs | 131 ++++++++++++++---- .../extensions/refund_relayer_extension.rs | 8 +- 2 files changed, 107 insertions(+), 32 deletions(-) diff --git a/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs b/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs index 64060503848a..60ed9214954f 100644 --- a/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs +++ b/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs @@ -21,40 +21,69 @@ use crate::messages_call_ext::MessagesCallSubType; use pallet_bridge_grandpa::CallSubType as GrandpaCallSubType; use pallet_bridge_parachains::CallSubType as ParachainsCallSubtype; -use sp_runtime::transaction_validity::TransactionValidity; +use pallet_bridge_relayers::Pallet as RelayersPallet; +use sp_runtime::{ + traits::{Get, PhantomData}, + transaction_validity::{TransactionPriority, TransactionValidity}, +}; /// A duplication of the `FilterCall` trait. /// /// We need this trait in order to be able to implement it for the messages pallet, /// since the implementation is done outside of the pallet crate. -pub trait BridgeRuntimeFilterCall { +pub trait BridgeRuntimeFilterCall { /// Checks if a runtime call is valid. - fn validate(call: &Call) -> TransactionValidity; + fn validate(who: &AccountId, call: &Call) -> TransactionValidity; } -impl BridgeRuntimeFilterCall for pallet_bridge_grandpa::Pallet +/// Wrapper for the bridge GRANDPA pallet that checks calls for obsolete submissions +/// and also boosts transaction priority if it has submitted by registered relayer. +/// The boost is computed as +/// `(BundledHeaderNumber - 1 - BestFinalizedHeaderNumber) * Priority::get()`. +/// The boost is only applied if submitter has active registration in the relayers +/// pallet. +pub struct CheckAndBoostBridgeGrandpaTransactions(PhantomData<(T, I, Priority)>); + +impl> + BridgeRuntimeFilterCall + for CheckAndBoostBridgeGrandpaTransactions +where + T: pallet_bridge_relayers::Config + pallet_bridge_grandpa::Config, + T::RuntimeCall: GrandpaCallSubType, +{ + fn validate(who: &T::AccountId, call: &T::RuntimeCall) -> TransactionValidity { + // we only boost priority if relayer has staked required balance + let is_relayer_registration_active = RelayersPallet::::is_registration_active(who); + let boost_per_header = if is_relayer_registration_active { Priority::get() } else { 0 }; + + GrandpaCallSubType::::check_obsolete_submit_finality_proof(call, boost_per_header) + } +} + +impl BridgeRuntimeFilterCall + for pallet_bridge_grandpa::Pallet where T: pallet_bridge_grandpa::Config, T::RuntimeCall: GrandpaCallSubType, { - fn validate(call: &T::RuntimeCall) -> TransactionValidity { + fn validate(_who: &T::AccountId, call: &T::RuntimeCall) -> TransactionValidity { GrandpaCallSubType::::check_obsolete_submit_finality_proof(call, 0) } } -impl BridgeRuntimeFilterCall +impl BridgeRuntimeFilterCall for pallet_bridge_parachains::Pallet where T: pallet_bridge_parachains::Config, T::RuntimeCall: ParachainsCallSubtype, { - fn validate(call: &T::RuntimeCall) -> TransactionValidity { + fn validate(_who: &T::AccountId, call: &T::RuntimeCall) -> TransactionValidity { ParachainsCallSubtype::::check_obsolete_submit_parachain_heads(call) } } -impl, I: 'static> BridgeRuntimeFilterCall - for pallet_bridge_messages::Pallet +impl, I: 'static> + BridgeRuntimeFilterCall for pallet_bridge_messages::Pallet where T::RuntimeCall: MessagesCallSubType, { @@ -62,7 +91,7 @@ where /// transactions, that are delivering outdated messages/confirmations. Without this validation, /// even honest relayers may lose their funds if there are multiple relays running and /// submitting the same messages/confirmations. - fn validate(call: &T::RuntimeCall) -> TransactionValidity { + fn validate(_who: &T::AccountId, call: &T::RuntimeCall) -> TransactionValidity { call.check_obsolete_call() } } @@ -103,17 +132,22 @@ macro_rules! generate_bridge_reject_obsolete_headers_and_messages { fn validate( &self, - _who: &Self::AccountId, + who: &Self::AccountId, call: &Self::Call, _info: &sp_runtime::traits::DispatchInfoOf, _len: usize, ) -> sp_runtime::transaction_validity::TransactionValidity { - let valid = sp_runtime::transaction_validity::ValidTransaction::default(); + let tx_validity = sp_runtime::transaction_validity::ValidTransaction::default(); $( - let valid = valid - .combine_with(<$filter_call as $crate::extensions::check_obsolete_extension::BridgeRuntimeFilterCall<$call>>::validate(call)?); + let call_filter_validity = < + $filter_call as + $crate::extensions::check_obsolete_extension::BridgeRuntimeFilterCall< + Self::AccountId, + $call, + >>::validate(&who, call)?; + let tx_validity = tx_validity.combine_with(call_filter_validity); )* - Ok(valid) + Ok(tx_validity) } fn pre_dispatch( @@ -132,9 +166,15 @@ macro_rules! generate_bridge_reject_obsolete_headers_and_messages { #[cfg(test)] mod tests { use super::*; - use frame_support::{assert_err, assert_ok}; + use crate::{ + extensions::refund_relayer_extension::tests::{ + initialize_environment, relayer_account_at_this_chain, submit_relay_header_call_ex, + }, + mock::*, + }; + use frame_support::assert_err; use sp_runtime::{ - traits::SignedExtension, + traits::{ConstU64, SignedExtension}, transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction}, }; @@ -143,7 +183,7 @@ mod tests { } impl sp_runtime::traits::Dispatchable for MockCall { - type RuntimeOrigin = (); + type RuntimeOrigin = u64; type Config = (); type Info = (); type PostInfo = (); @@ -157,8 +197,8 @@ mod tests { } struct FirstFilterCall; - impl BridgeRuntimeFilterCall for FirstFilterCall { - fn validate(call: &MockCall) -> TransactionValidity { + impl BridgeRuntimeFilterCall for FirstFilterCall { + fn validate(_who: &u64, call: &MockCall) -> TransactionValidity { if call.data <= 1 { return InvalidTransaction::Custom(1).into() } @@ -168,8 +208,8 @@ mod tests { } struct SecondFilterCall; - impl BridgeRuntimeFilterCall for SecondFilterCall { - fn validate(call: &MockCall) -> TransactionValidity { + impl BridgeRuntimeFilterCall for SecondFilterCall { + fn validate(_who: &u64, call: &MockCall) -> TransactionValidity { if call.data <= 2 { return InvalidTransaction::Custom(2).into() } @@ -182,24 +222,59 @@ mod tests { fn test() { generate_bridge_reject_obsolete_headers_and_messages!( MockCall, - (), + u64, FirstFilterCall, SecondFilterCall ); assert_err!( - BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 1 }, &(), 0), + BridgeRejectObsoleteHeadersAndMessages.validate(&42, &MockCall { data: 1 }, &(), 0), InvalidTransaction::Custom(1) ); assert_err!( - BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 2 }, &(), 0), + BridgeRejectObsoleteHeadersAndMessages.validate(&42, &MockCall { data: 2 }, &(), 0), InvalidTransaction::Custom(2) ); - assert_ok!( - BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 3 }, &(), 0), - ValidTransaction { priority: 3, ..Default::default() } + assert_eq!( + BridgeRejectObsoleteHeadersAndMessages.validate(&42, &MockCall { data: 3 }, &(), 0), + Ok(ValidTransaction { priority: 3, ..Default::default() }) ) } + + type BridgeGrandpaWrapper = + CheckAndBoostBridgeGrandpaTransactions>; + + #[test] + fn grandpa_wrapper_does_not_boost_extensions_for_unregistered_relayer() { + run_test(|| { + initialize_environment(100, 100, 100); + + let priority_boost = BridgeGrandpaWrapper::validate( + &relayer_account_at_this_chain(), + &submit_relay_header_call_ex(200), + ) + .unwrap() + .priority; + assert_eq!(priority_boost, 0); + }) + } + + #[test] + fn grandpa_wrapper_boosts_extensions_for_unregistered_relayer() { + run_test(|| { + initialize_environment(100, 100, 100); + BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000) + .unwrap(); + + let priority_boost = BridgeGrandpaWrapper::validate( + &relayer_account_at_this_chain(), + &submit_relay_header_call_ex(200), + ) + .unwrap() + .priority; + assert_eq!(priority_boost, 99_000); + }) + } } diff --git a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs index 563c1621dc40..56f2dc59711f 100644 --- a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs +++ b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs @@ -939,7 +939,7 @@ where } #[cfg(test)] -mod tests { +pub(crate) mod tests { use super::*; use crate::{ messages::{ @@ -1035,7 +1035,7 @@ mod tests { TestPaymentProcedure::rewards_account(MsgDeliveryProofsRewardsAccount::get()) } - fn relayer_account_at_this_chain() -> ThisChainAccountId { + pub fn relayer_account_at_this_chain() -> ThisChainAccountId { 0 } @@ -1043,7 +1043,7 @@ mod tests { 0 } - fn initialize_environment( + pub fn initialize_environment( best_relay_header_number: RelayBlockNumber, parachain_head_at_relay_header_number: RelayBlockNumber, best_message: MessageNonce, @@ -1099,7 +1099,7 @@ mod tests { }) } - fn submit_relay_header_call_ex(relay_header_number: RelayBlockNumber) -> RuntimeCall { + pub fn submit_relay_header_call_ex(relay_header_number: RelayBlockNumber) -> RuntimeCall { let relay_header = BridgedChainHeader::new( relay_header_number, Default::default(), From ad8edb5f8a7ef3e1b6e95d8b9e80aa5a95b1498a Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 13 Mar 2024 14:29:01 +0300 Subject: [PATCH 08/70] relayer may be slashed to explicit account --- .../extensions/refund_relayer_extension.rs | 7 ++-- bridges/modules/relayers/src/lib.rs | 7 ++-- bridges/modules/relayers/src/stake_adapter.rs | 36 ++++++++++++++----- bridges/primitives/relayers/src/lib.rs | 2 +- .../primitives/relayers/src/registration.rs | 13 +++++-- 5 files changed, 49 insertions(+), 16 deletions(-) diff --git a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs index 56f2dc59711f..ee6000f20875 100644 --- a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs +++ b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs @@ -23,7 +23,7 @@ use crate::messages_call_ext::{ CallHelper as MessagesCallHelper, CallInfo as MessagesCallInfo, MessagesCallSubType, }; use bp_messages::{LaneId, MessageNonce}; -use bp_relayers::{RewardsAccountOwner, RewardsAccountParams}; +use bp_relayers::{ExplicitOrAccountParams, RewardsAccountOwner, RewardsAccountParams}; use bp_runtime::{Parachain, ParachainIdOf, RangeInclusiveExt, StaticStrProvider}; use codec::{Codec, Decode, Encode}; use frame_support::{ @@ -552,7 +552,10 @@ where ); }, RelayerAccountAction::Slash(relayer, slash_account) => - RelayersPallet::::slash_and_deregister(&relayer, slash_account), + RelayersPallet::::slash_and_deregister( + &relayer, + ExplicitOrAccountParams::Params(slash_account), + ), } Ok(()) diff --git a/bridges/modules/relayers/src/lib.rs b/bridges/modules/relayers/src/lib.rs index ce66c9df48e0..7a3a0f9ea94c 100644 --- a/bridges/modules/relayers/src/lib.rs +++ b/bridges/modules/relayers/src/lib.rs @@ -21,7 +21,8 @@ #![warn(missing_docs)] use bp_relayers::{ - PaymentProcedure, Registration, RelayerRewardsKeyProvider, RewardsAccountParams, StakeAndSlash, + ExplicitOrAccountParams, PaymentProcedure, Registration, RelayerRewardsKeyProvider, + RewardsAccountParams, StakeAndSlash, }; use bp_runtime::StorageDoubleMapKeyProvider; use frame_support::fail; @@ -242,7 +243,7 @@ pub mod pallet { /// It may fail inside, but error is swallowed and we only log it. pub fn slash_and_deregister( relayer: &T::AccountId, - slash_destination: RewardsAccountParams, + slash_destination: ExplicitOrAccountParams, ) { let registration = match RegisteredRelayers::::take(relayer) { Some(registration) => registration, @@ -259,7 +260,7 @@ pub mod pallet { match T::StakeAndSlash::repatriate_reserved( relayer, - slash_destination, + slash_destination.clone(), registration.stake, ) { Ok(failed_to_slash) if failed_to_slash.is_zero() => { diff --git a/bridges/modules/relayers/src/stake_adapter.rs b/bridges/modules/relayers/src/stake_adapter.rs index 88af9b1877bf..7ba90d91dfd9 100644 --- a/bridges/modules/relayers/src/stake_adapter.rs +++ b/bridges/modules/relayers/src/stake_adapter.rs @@ -17,7 +17,7 @@ //! Code that allows `NamedReservableCurrency` to be used as a `StakeAndSlash` //! mechanism of the relayers pallet. -use bp_relayers::{PayRewardFromAccount, RewardsAccountParams, StakeAndSlash}; +use bp_relayers::{ExplicitOrAccountParams, PayRewardFromAccount, StakeAndSlash}; use codec::Codec; use frame_support::traits::{tokens::BalanceStatus, NamedReservableCurrency}; use sp_runtime::{traits::Get, DispatchError, DispatchResult}; @@ -55,11 +55,14 @@ where fn repatriate_reserved( relayer: &AccountId, - beneficiary: RewardsAccountParams, + beneficiary: ExplicitOrAccountParams, amount: Currency::Balance, ) -> Result { - let beneficiary_account = - PayRewardFromAccount::<(), AccountId>::rewards_account(beneficiary); + let beneficiary_account = match beneficiary { + ExplicitOrAccountParams::Explicit(account) => account, + ExplicitOrAccountParams::Params(params) => + PayRewardFromAccount::<(), AccountId>::rewards_account(params), + }; Currency::repatriate_reserved_named( &ReserveId::get(), relayer, @@ -134,7 +137,11 @@ mod tests { Balances::mint_into(&beneficiary_account, expected_balance).unwrap(); assert_eq!( - TestStakeAndSlash::repatriate_reserved(&1, beneficiary, test_stake()), + TestStakeAndSlash::repatriate_reserved( + &1, + ExplicitOrAccountParams::Params(beneficiary), + test_stake() + ), Ok(test_stake()) ); assert_eq!(Balances::free_balance(1), 0); @@ -146,7 +153,11 @@ mod tests { Balances::mint_into(&2, test_stake() * 2).unwrap(); TestStakeAndSlash::reserve(&2, test_stake() / 3).unwrap(); assert_eq!( - TestStakeAndSlash::repatriate_reserved(&2, beneficiary, test_stake()), + TestStakeAndSlash::repatriate_reserved( + &2, + ExplicitOrAccountParams::Params(beneficiary), + test_stake() + ), Ok(test_stake() - test_stake() / 3) ); assert_eq!(Balances::free_balance(2), test_stake() * 2 - test_stake() / 3); @@ -158,7 +169,11 @@ mod tests { Balances::mint_into(&3, test_stake() * 2).unwrap(); TestStakeAndSlash::reserve(&3, test_stake()).unwrap(); assert_eq!( - TestStakeAndSlash::repatriate_reserved(&3, beneficiary, test_stake()), + TestStakeAndSlash::repatriate_reserved( + &3, + ExplicitOrAccountParams::Params(beneficiary), + test_stake() + ), Ok(0) ); assert_eq!(Balances::free_balance(3), test_stake()); @@ -176,7 +191,12 @@ mod tests { Balances::mint_into(&3, test_stake() * 2).unwrap(); TestStakeAndSlash::reserve(&3, test_stake()).unwrap(); - assert!(TestStakeAndSlash::repatriate_reserved(&3, beneficiary, test_stake()).is_err()); + assert!(TestStakeAndSlash::repatriate_reserved( + &3, + ExplicitOrAccountParams::Params(beneficiary), + test_stake() + ) + .is_err()); assert_eq!(Balances::free_balance(3), test_stake()); assert_eq!(Balances::reserved_balance(3), test_stake()); assert_eq!(Balances::free_balance(beneficiary_account), 0); diff --git a/bridges/primitives/relayers/src/lib.rs b/bridges/primitives/relayers/src/lib.rs index c808c437b54c..2a9ef6a8e1e9 100644 --- a/bridges/primitives/relayers/src/lib.rs +++ b/bridges/primitives/relayers/src/lib.rs @@ -19,7 +19,7 @@ #![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] -pub use registration::{Registration, StakeAndSlash}; +pub use registration::{ExplicitOrAccountParams, Registration, StakeAndSlash}; use bp_messages::LaneId; use bp_runtime::{ChainId, StorageDoubleMapKeyProvider}; diff --git a/bridges/primitives/relayers/src/registration.rs b/bridges/primitives/relayers/src/registration.rs index 38fa7c2d9075..5f5c588d6c22 100644 --- a/bridges/primitives/relayers/src/registration.rs +++ b/bridges/primitives/relayers/src/registration.rs @@ -46,6 +46,15 @@ use sp_runtime::{ DispatchError, DispatchResult, }; +/// Either explicit account reference or `RewardsAccountParams`. +#[derive(Clone, Debug)] +pub enum ExplicitOrAccountParams { + /// Explicit account reference. + Explicit(AccountId), + /// Account, referenced using `RewardsAccountParams`. + Params(RewardsAccountParams), +} + /// Relayer registration. #[derive(Copy, Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo, MaxEncodedLen)] pub struct Registration { @@ -90,7 +99,7 @@ pub trait StakeAndSlash { /// Returns `Ok(_)` with non-zero balance if we have failed to repatriate some portion of stake. fn repatriate_reserved( relayer: &AccountId, - beneficiary: RewardsAccountParams, + beneficiary: ExplicitOrAccountParams, amount: Balance, ) -> Result; } @@ -113,7 +122,7 @@ where fn repatriate_reserved( _relayer: &AccountId, - _beneficiary: RewardsAccountParams, + _beneficiary: ExplicitOrAccountParams, _amount: Balance, ) -> Result { Ok(Zero::zero()) From 791ea6a0b84a13906de2ed4c6cf9289eacece211 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 13 Mar 2024 15:29:09 +0300 Subject: [PATCH 09/70] slash registered relayer if it has submitted failed GRANDPA transaction --- Cargo.lock | 7 + bridges/bin/runtime-common/Cargo.toml | 2 + .../extensions/check_obsolete_extension.rs | 185 +++++++++++++++--- .../extensions/refund_relayer_extension.rs | 4 +- bridges/modules/grandpa/src/call_ext.rs | 56 ++++-- 5 files changed, 202 insertions(+), 52 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 61bc895e706e..9efd4f0220e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2341,6 +2341,7 @@ dependencies = [ "staging-xcm", "staging-xcm-builder", "static_assertions", + "tuplex", ] [[package]] @@ -21991,6 +21992,12 @@ dependencies = [ "utf-8", ] +[[package]] +name = "tuplex" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "676ac81d5454c4dcf37955d34fa8626ede3490f744b86ca14a7b90168d2a08aa" + [[package]] name = "twox-hash" version = "1.6.3" diff --git a/bridges/bin/runtime-common/Cargo.toml b/bridges/bin/runtime-common/Cargo.toml index 67b91a16a302..74049031afe6 100644 --- a/bridges/bin/runtime-common/Cargo.toml +++ b/bridges/bin/runtime-common/Cargo.toml @@ -16,6 +16,7 @@ hash-db = { version = "0.16.0", default-features = false } log = { workspace = true } scale-info = { version = "2.11.1", default-features = false, features = ["derive"] } static_assertions = { version = "1.1", optional = true } +tuplex = { version = "0.1", default-features = false } # Bridge dependencies @@ -82,6 +83,7 @@ std = [ "sp-runtime/std", "sp-std/std", "sp-trie/std", + "tuplex/std", "xcm-builder/std", "xcm/std", ] diff --git a/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs b/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs index 60ed9214954f..83ac8c1051e2 100644 --- a/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs +++ b/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs @@ -19,7 +19,10 @@ //! checks. use crate::messages_call_ext::MessagesCallSubType; -use pallet_bridge_grandpa::CallSubType as GrandpaCallSubType; +use bp_relayers::ExplicitOrAccountParams; +use pallet_bridge_grandpa::{ + BridgedBlockNumber, CallSubType as GrandpaCallSubType, SubmitFinalityProofHelper, +}; use pallet_bridge_parachains::CallSubType as ParachainsCallSubtype; use pallet_bridge_relayers::Pallet as RelayersPallet; use sp_runtime::{ @@ -32,8 +35,14 @@ use sp_runtime::{ /// We need this trait in order to be able to implement it for the messages pallet, /// since the implementation is done outside of the pallet crate. pub trait BridgeRuntimeFilterCall { - /// Checks if a runtime call is valid. - fn validate(who: &AccountId, call: &Call) -> TransactionValidity; + /// Data that may be passed from the validate to `on_failure`. + type ToPostDispatch; + /// Called during validation. Needs to checks whether a runtime call, submitted + /// by the `who` is valid. `who` may be `None` if transaction is not signed + /// by a regular account. + fn validate(who: &AccountId, call: &Call) -> (Self::ToPostDispatch, TransactionValidity); + /// Called after transaction is dispatched. + fn post_dispatch(_who: &AccountId, _has_failed: bool, _to_on_failure: Self::ToPostDispatch) {} } /// Wrapper for the bridge GRANDPA pallet that checks calls for obsolete submissions @@ -42,22 +51,52 @@ pub trait BridgeRuntimeFilterCall { /// `(BundledHeaderNumber - 1 - BestFinalizedHeaderNumber) * Priority::get()`. /// The boost is only applied if submitter has active registration in the relayers /// pallet. -pub struct CheckAndBoostBridgeGrandpaTransactions(PhantomData<(T, I, Priority)>); +pub struct CheckAndBoostBridgeGrandpaTransactions( + PhantomData<(T, I, Priority, SlashAccount)>, +); -impl> +impl, SlashAccount: Get> BridgeRuntimeFilterCall - for CheckAndBoostBridgeGrandpaTransactions + for CheckAndBoostBridgeGrandpaTransactions where T: pallet_bridge_relayers::Config + pallet_bridge_grandpa::Config, T::RuntimeCall: GrandpaCallSubType, { - fn validate(who: &T::AccountId, call: &T::RuntimeCall) -> TransactionValidity { + // bridged header number, bundled in transaction + type ToPostDispatch = Option>; + + fn validate( + who: &T::AccountId, + call: &T::RuntimeCall, + ) -> (Self::ToPostDispatch, TransactionValidity) { // we only boost priority if relayer has staked required balance let is_relayer_registration_active = RelayersPallet::::is_registration_active(who); let boost_per_header = if is_relayer_registration_active { Priority::get() } else { 0 }; GrandpaCallSubType::::check_obsolete_submit_finality_proof(call, boost_per_header) } + + fn post_dispatch( + relayer: &T::AccountId, + has_failed: bool, + bundled_block_number: Self::ToPostDispatch, + ) { + // we are only interested in associated pallet submissions + let Some(bundled_block_number) = bundled_block_number else { return }; + // we are only interested in failed or unneeded transactions + let has_failed = + has_failed || !SubmitFinalityProofHelper::::was_successful(bundled_block_number); + + if !has_failed { + return + } + + // let's slash registered relayer + RelayersPallet::::slash_and_deregister( + relayer, + ExplicitOrAccountParams::Explicit(SlashAccount::get()), + ); + } } impl BridgeRuntimeFilterCall @@ -66,8 +105,9 @@ where T: pallet_bridge_grandpa::Config, T::RuntimeCall: GrandpaCallSubType, { - fn validate(_who: &T::AccountId, call: &T::RuntimeCall) -> TransactionValidity { - GrandpaCallSubType::::check_obsolete_submit_finality_proof(call, 0) + type ToPostDispatch = (); + fn validate(_who: &T::AccountId, call: &T::RuntimeCall) -> ((), TransactionValidity) { + ((), GrandpaCallSubType::::check_obsolete_submit_finality_proof(call, 0).1) } } @@ -77,8 +117,9 @@ where T: pallet_bridge_parachains::Config, T::RuntimeCall: ParachainsCallSubtype, { - fn validate(_who: &T::AccountId, call: &T::RuntimeCall) -> TransactionValidity { - ParachainsCallSubtype::::check_obsolete_submit_parachain_heads(call) + type ToPostDispatch = (); + fn validate(_who: &T::AccountId, call: &T::RuntimeCall) -> ((), TransactionValidity) { + ((), ParachainsCallSubtype::::check_obsolete_submit_parachain_heads(call)) } } @@ -87,12 +128,13 @@ impl, I: 'static> where T::RuntimeCall: MessagesCallSubType, { + type ToPostDispatch = (); /// Validate messages in order to avoid "mining" messages delivery and delivery confirmation /// transactions, that are delivering outdated messages/confirmations. Without this validation, /// even honest relayers may lose their funds if there are multiple relays running and /// submitting the same messages/confirmations. - fn validate(_who: &T::AccountId, call: &T::RuntimeCall) -> TransactionValidity { - call.check_obsolete_call() + fn validate(_who: &T::AccountId, call: &T::RuntimeCall) -> ((), TransactionValidity) { + ((), call.check_obsolete_call()) } } @@ -121,7 +163,15 @@ macro_rules! generate_bridge_reject_obsolete_headers_and_messages { type AccountId = $account_id; type Call = $call; type AdditionalSigned = (); - type Pre = (); + type Pre = ( + $account_id, + ( $( + <$filter_call as $crate::extensions::check_obsolete_extension::BridgeRuntimeFilterCall< + $account_id, + $call, + >>::ToPostDispatch, + )* ), + ); fn additional_signed(&self) -> sp_std::result::Result< (), @@ -130,6 +180,7 @@ macro_rules! generate_bridge_reject_obsolete_headers_and_messages { Ok(()) } + #[allow(unused_variables)] fn validate( &self, who: &Self::AccountId, @@ -138,26 +189,65 @@ macro_rules! generate_bridge_reject_obsolete_headers_and_messages { _len: usize, ) -> sp_runtime::transaction_validity::TransactionValidity { let tx_validity = sp_runtime::transaction_validity::ValidTransaction::default(); + let to_prepare = (); $( - let call_filter_validity = < + let (from_validate, call_filter_validity) = < $filter_call as $crate::extensions::check_obsolete_extension::BridgeRuntimeFilterCall< Self::AccountId, $call, - >>::validate(&who, call)?; - let tx_validity = tx_validity.combine_with(call_filter_validity); + >>::validate(&who, call); + let tx_validity = tx_validity.combine_with(call_filter_validity?); )* Ok(tx_validity) } + #[allow(unused_variables)] fn pre_dispatch( self, - who: &Self::AccountId, + relayer: &Self::AccountId, call: &Self::Call, info: &sp_runtime::traits::DispatchInfoOf, len: usize, ) -> Result { - self.validate(who, call, info, len).map(drop) + use tuplex::PushBack; + let to_post_dispatch = (); + $( + let (from_validate, call_filter_validity) = < + $filter_call as + $crate::extensions::check_obsolete_extension::BridgeRuntimeFilterCall< + $account_id, + $call, + >>::validate(&relayer, call); + let _ = call_filter_validity?; + let to_post_dispatch = to_post_dispatch.push_back(from_validate); + )* + Ok((relayer.clone(), to_post_dispatch)) + } + + #[allow(unused_variables)] + fn post_dispatch( + to_post_dispatch: Option, + info: &sp_runtime::traits::DispatchInfoOf, + post_info: &sp_runtime::traits::PostDispatchInfoOf, + len: usize, + result: &sp_runtime::DispatchResult, + ) -> Result<(), sp_runtime::transaction_validity::TransactionValidityError> { + // TODO: check me: removed if result.is_ok() { return Ok(()); } + use tuplex::PopFront; + let has_failed = result.is_err(); + // TODO: check me: return if `to_post_dispatch` is `None` + let Some((relayer, to_post_dispatch)) = to_post_dispatch else { return Ok(()) }; + $( + let (item, to_post_dispatch) = to_post_dispatch.pop_front(); + < + $filter_call as + $crate::extensions::check_obsolete_extension::BridgeRuntimeFilterCall< + $account_id, + $call, + >>::post_dispatch(&relayer, has_failed, item); + )* + Ok(()) } } }; @@ -196,30 +286,32 @@ mod tests { } } - struct FirstFilterCall; + pub struct FirstFilterCall; impl BridgeRuntimeFilterCall for FirstFilterCall { - fn validate(_who: &u64, call: &MockCall) -> TransactionValidity { + type ToPostDispatch = u64; + fn validate(_who: &u64, call: &MockCall) -> (u64, TransactionValidity) { if call.data <= 1 { - return InvalidTransaction::Custom(1).into() + return (1, InvalidTransaction::Custom(1).into()) } - Ok(ValidTransaction { priority: 1, ..Default::default() }) + (1, Ok(ValidTransaction { priority: 1, ..Default::default() })) } } - struct SecondFilterCall; + pub struct SecondFilterCall; impl BridgeRuntimeFilterCall for SecondFilterCall { - fn validate(_who: &u64, call: &MockCall) -> TransactionValidity { + type ToPostDispatch = u64; + fn validate(_who: &u64, call: &MockCall) -> (u64, TransactionValidity) { if call.data <= 2 { - return InvalidTransaction::Custom(2).into() + return (2, InvalidTransaction::Custom(2).into()) } - Ok(ValidTransaction { priority: 2, ..Default::default() }) + (2, Ok(ValidTransaction { priority: 2, ..Default::default() })) } } #[test] - fn test() { + fn test_generated_obsolete_extension() { generate_bridge_reject_obsolete_headers_and_messages!( MockCall, u64, @@ -227,6 +319,8 @@ mod tests { SecondFilterCall ); + // TODO: add tests for both validate and pre_dispatch here? + assert_err!( BridgeRejectObsoleteHeadersAndMessages.validate(&42, &MockCall { data: 1 }, &(), 0), InvalidTransaction::Custom(1) @@ -238,13 +332,25 @@ mod tests { ); assert_eq!( - BridgeRejectObsoleteHeadersAndMessages.validate(&42, &MockCall { data: 3 }, &(), 0), - Ok(ValidTransaction { priority: 3, ..Default::default() }) - ) + BridgeRejectObsoleteHeadersAndMessages + .validate(&42, &MockCall { data: 3 }, &(), 0) + .unwrap(), + ValidTransaction { priority: 3, ..Default::default() }, + ); + assert_eq!( + BridgeRejectObsoleteHeadersAndMessages + .pre_dispatch(&42, &MockCall { data: 3 }, &(), 0) + .unwrap(), + (42, (1, 2)), + ); + } + + frame_support::parameter_types! { + pub SlashDestination: ThisChainAccountId = 42; } type BridgeGrandpaWrapper = - CheckAndBoostBridgeGrandpaTransactions>; + CheckAndBoostBridgeGrandpaTransactions, SlashDestination>; #[test] fn grandpa_wrapper_does_not_boost_extensions_for_unregistered_relayer() { @@ -255,6 +361,7 @@ mod tests { &relayer_account_at_this_chain(), &submit_relay_header_call_ex(200), ) + .1 .unwrap() .priority; assert_eq!(priority_boost, 0); @@ -272,9 +379,23 @@ mod tests { &relayer_account_at_this_chain(), &submit_relay_header_call_ex(200), ) + .1 .unwrap() .priority; assert_eq!(priority_boost, 99_000); }) } + + #[test] + fn grandpa_wrapper_slashes_registered_relayer_if_transaction_fails() { + run_test(|| { + initialize_environment(100, 100, 100); + BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000) + .unwrap(); + + assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain())); + BridgeGrandpaWrapper::post_dispatch(&relayer_account_at_this_chain(), true, Some(150)); + assert!(!BridgeRelayers::is_registration_active(&relayer_account_at_this_chain())); + }) + } } diff --git a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs index ee6000f20875..512d5f6ca059 100644 --- a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs +++ b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs @@ -675,7 +675,7 @@ where fn check_obsolete_parsed_call( call: &CallOf, ) -> Result<&CallOf, TransactionValidityError> { - call.check_obsolete_submit_finality_proof(0)?; + call.check_obsolete_submit_finality_proof(0).1?; call.check_obsolete_submit_parachain_heads()?; call.check_obsolete_call()?; Ok(call) @@ -819,7 +819,7 @@ where fn check_obsolete_parsed_call( call: &CallOf, ) -> Result<&CallOf, TransactionValidityError> { - call.check_obsolete_submit_finality_proof(0)?; + call.check_obsolete_submit_finality_proof(0).1?; call.check_obsolete_call()?; Ok(call) } diff --git a/bridges/modules/grandpa/src/call_ext.rs b/bridges/modules/grandpa/src/call_ext.rs index fde6bd32245c..1ae5fca95234 100644 --- a/bridges/modules/grandpa/src/call_ext.rs +++ b/bridges/modules/grandpa/src/call_ext.rs @@ -206,33 +206,40 @@ pub trait CallSubType, I: 'static>: /// `BestFinalized` header is header number `100` and transaction brings header /// `101` there's no priority boost. If transaction brings header `102`, then /// priority is boosted by `priority_boost` and so on. + /// + /// If first item in the tuple is true, then the call is the `submit_finality_proof_info` + /// (or `submit_finality_proof_info_ex` call) of the associated pallet instance. fn check_obsolete_submit_finality_proof( &self, priority_boost: TransactionPriority, - ) -> TransactionValidity + ) -> (Option>, TransactionValidity) where Self: Sized, { let call_info = match self.submit_finality_proof_info() { Some(finality_proof) => finality_proof, - _ => return Ok(ValidTransaction::default()), + _ => return (None, Ok(ValidTransaction::default())), }; + let block_number = Some(call_info.block_number); if Pallet::::ensure_not_halted().is_err() { - return InvalidTransaction::Call.into() + return (block_number, InvalidTransaction::Call.into()) } let result = SubmitFinalityProofHelper::::check_obsolete_from_extension(&call_info); - match result { - Ok(improved_by) => { - let improved_by: TransactionPriority = - improved_by.saturating_sub(One::one()).unique_saturated_into(); - let total_priority_boost = improved_by.saturating_mul(priority_boost); - ValidTransactionBuilder::default().priority(total_priority_boost).build() + ( + block_number, + match result { + Ok(improved_by) => { + let improved_by: TransactionPriority = + improved_by.saturating_sub(One::one()).unique_saturated_into(); + let total_priority_boost = improved_by.saturating_mul(priority_boost); + ValidTransactionBuilder::default().priority(total_priority_boost).build() + }, + Err(Error::::OldHeader) => InvalidTransaction::Stale.into(), + Err(_) => InvalidTransaction::Call.into(), }, - Err(Error::::OldHeader) => InvalidTransaction::Stale.into(), - Err(_) => InvalidTransaction::Call.into(), - } + ) } } @@ -326,6 +333,7 @@ mod tests { &RuntimeCall::Grandpa(bridge_grandpa_call), 0, ) + .1 .is_ok() } @@ -395,6 +403,7 @@ mod tests { &RuntimeCall::Grandpa(bridge_grandpa_call.clone(),), 0 ) + .1 .is_ok()); // when we can NOT accept free headers => Err @@ -403,6 +412,7 @@ mod tests { &RuntimeCall::Grandpa(bridge_grandpa_call,), 0 ) + .1 .is_err()); }) } @@ -541,16 +551,14 @@ mod tests { // when priority boost is zero, total boost is also zero sync_to_header_10(); - assert_eq!( - RuntimeCall::check_obsolete_submit_finality_proof(&make_call(15), 0) - .unwrap() - .priority, - 0, - ); + let result = RuntimeCall::check_obsolete_submit_finality_proof(&make_call(15), 0); + assert_eq!(result.0, Some(15)); + assert_eq!(result.1.unwrap().priority, 0,); // when the difference between headers is 1, no boost assert_eq!( RuntimeCall::check_obsolete_submit_finality_proof(&make_call(11), 100) + .1 .unwrap() .priority, 0, @@ -559,6 +567,7 @@ mod tests { // when the difference between headers is 2 => boost assert_eq!( RuntimeCall::check_obsolete_submit_finality_proof(&make_call(12), 100) + .1 .unwrap() .priority, 100, @@ -567,10 +576,21 @@ mod tests { // when the difference between headers is 3 => 2 * boost assert_eq!( RuntimeCall::check_obsolete_submit_finality_proof(&make_call(13), 100) + .1 .unwrap() .priority, 200, ); }) } + + #[test] + fn check_obsolete_submit_finality_proof_ignores_other_calls() { + run_test(|| { + let call = + RuntimeCall::System(frame_system::Call::::remark { remark: vec![42] }); + + assert_eq!(RuntimeCall::check_obsolete_submit_finality_proof(&call, 0).0, None); + }) + } } From b90615aaea850f27f43a2fac92cadb5e8f9f3796 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 13 Mar 2024 16:00:48 +0300 Subject: [PATCH 10/70] allow accepting some parachain headers for free --- bridges/bin/runtime-common/src/mock.rs | 1 + bridges/modules/parachains/src/lib.rs | 73 +++++++++++++++++++++++++- bridges/modules/parachains/src/mock.rs | 13 +++++ 3 files changed, 85 insertions(+), 2 deletions(-) diff --git a/bridges/bin/runtime-common/src/mock.rs b/bridges/bin/runtime-common/src/mock.rs index 3beb52bbe7a7..fcc4c665cf2f 100644 --- a/bridges/bin/runtime-common/src/mock.rs +++ b/bridges/bin/runtime-common/src/mock.rs @@ -193,6 +193,7 @@ impl pallet_bridge_parachains::Config for TestRuntime { type RuntimeEvent = RuntimeEvent; type BridgesGrandpaPalletInstance = (); type ParasPalletName = BridgedParasPalletName; + type FreeHeadsUpdateFilter = (); type ParaStoredHeaderDataBuilder = SingleParaStoredHeaderDataBuilder; type HeadsToKeep = ConstU32<8>; diff --git a/bridges/modules/parachains/src/lib.rs b/bridges/modules/parachains/src/lib.rs index 3c778ddccb88..9afd03fde8ac 100644 --- a/bridges/modules/parachains/src/lib.rs +++ b/bridges/modules/parachains/src/lib.rs @@ -65,6 +65,24 @@ pub type RelayBlockNumber = bp_polkadot_core::BlockNumber; /// Hasher of the bridged relay chain. pub type RelayBlockHasher = bp_polkadot_core::Hasher; +/// A filter of parachain head updates. +pub trait ParachainHeadsUpdateFilter { + /// Returns true if the update passes the filter. + fn is_free( + at_relay_block: (RelayBlockNumber, RelayBlockHash), + parachains: &[(ParaId, ParaHash)], + ) -> bool; +} + +impl ParachainHeadsUpdateFilter for () { + fn is_free( + _at_relay_block: (RelayBlockNumber, RelayBlockHash), + _parachains: &[(ParaId, ParaHash)], + ) -> bool { + false + } +} + /// Artifacts of the parachains head update. struct UpdateParachainHeadArtifacts { /// New best head of the parachain. @@ -194,6 +212,20 @@ pub mod pallet { /// we're interested in. type BridgesGrandpaPalletInstance: 'static; + /// A way to make `submit_parachain_heads` free for the submitter. If update passes + /// this filter AND at least one parachain head has been updated in the call, the + /// submission will be free for the submitter. + /// + /// It can be used to reduce bridge fees by deducting parachain finality submission cost + /// from the total fee. Instead, relayers may submit some headers for free, allowing + /// queued messages (and confirmations) to be proved without providing additional + /// finality proofs. + /// + /// **IMPORTANT**: we are NOT limiting number of free calls per block. The filter must + /// take that into account or anyone could fill the block with parachain head updates + /// for free. + type FreeHeadsUpdateFilter: ParachainHeadsUpdateFilter; + /// Name of the original `paras` pallet in the `construct_runtime!()` call at the bridged /// chain. /// @@ -339,6 +371,9 @@ pub mod pallet { Self::ensure_not_halted().map_err(Error::::BridgeModule)?; ensure_signed(origin)?; + // check whether this submission may be refunded + let may_be_free = T::FreeHeadsUpdateFilter::is_free(at_relay_block, ¶chains); + // we'll need relay chain header to verify that parachains heads are always increasing. let (relay_block_number, relay_block_hash) = at_relay_block; let relay_block = pallet_bridge_grandpa::ImportedHeaders::< @@ -358,6 +393,7 @@ pub mod pallet { parachains.len() as _, ); + let mut is_updated_something = false; let mut storage = GrandpaPalletOf::::storage_proof_checker( relay_block_hash, parachain_heads_proof.storage_proof, @@ -437,6 +473,7 @@ pub mod pallet { parachain_head_data, parachain_head_hash, )?; + is_updated_something = true; *stored_best_head = Some(artifacts.best_head); Ok(artifacts.prune_happened) }); @@ -467,7 +504,11 @@ pub mod pallet { Error::::HeaderChainStorageProof(HeaderChainError::StorageProof(e)) })?; - Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee: Pays::Yes }) + // we allow free submissions only if the update passes filter and something + // has been updated + let pays_fee = if is_updated_something && may_be_free { Pays::No } else { Pays::Yes }; + + Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee }) } /// Change `PalletOwner`. @@ -736,6 +777,7 @@ pub(crate) mod tests { use frame_support::{ assert_noop, assert_ok, dispatch::DispatchResultWithPostInfo, + pallet_prelude::Pays, storage::generator::{StorageDoubleMap, StorageMap}, traits::{Get, OnInitialize}, weights::Weight, @@ -990,7 +1032,8 @@ pub(crate) mod tests { run_test(|| { // start with relay block #0 and import head#5 of parachain#1 initialize(state_root_5); - assert_ok!(import_parachain_1_head(0, state_root_5, parachains_5, proof_5)); + let result = import_parachain_1_head(0, state_root_5, parachains_5, proof_5); + assert_eq!(result.unwrap().pays_fee, Pays::Yes); assert_eq!( ParasInfo::::get(ParaId(1)), Some(ParaInfo { @@ -1648,4 +1691,30 @@ pub(crate) mod tests { ); }) } + + #[test] + fn may_be_free_for_submitter() { + run_test(|| { + let (state_root, proof, parachains) = + prepare_parachain_heads_proof::(vec![(2, head_data(2, 5))]); + // start with relay block #0 and import head#5 of parachain#2 + initialize(state_root); + // first submission is free + let result = Pallet::::submit_parachain_heads( + RuntimeOrigin::signed(1), + (0, test_relay_header(0, state_root).hash()), + parachains.clone(), + proof.clone(), + ); + assert_eq!(result.unwrap().pays_fee, Pays::No); + // next submission is NOT free, because we haven't updated anything + let result = Pallet::::submit_parachain_heads( + RuntimeOrigin::signed(1), + (0, test_relay_header(0, state_root).hash()), + parachains, + proof, + ); + assert_eq!(result.unwrap().pays_fee, Pays::Yes); + }) + } } diff --git a/bridges/modules/parachains/src/mock.rs b/bridges/modules/parachains/src/mock.rs index 12e54e830102..7be24aa431b8 100644 --- a/bridges/modules/parachains/src/mock.rs +++ b/bridges/modules/parachains/src/mock.rs @@ -27,6 +27,7 @@ use sp_runtime::{ }; use crate as pallet_bridge_parachains; +use crate::{ParaHash, ParachainHeadsUpdateFilter, RelayBlockHash, RelayBlockNumber}; pub type AccountId = u64; @@ -198,12 +199,24 @@ impl pallet_bridge_parachains::Config for TestRuntime { type RuntimeEvent = RuntimeEvent; type WeightInfo = (); type BridgesGrandpaPalletInstance = pallet_bridge_grandpa::Instance1; + type FreeHeadsUpdateFilter = FreeForParachain2; type ParasPalletName = ParasPalletName; type ParaStoredHeaderDataBuilder = (Parachain1, Parachain2, Parachain3, BigParachain); type HeadsToKeep = HeadsToKeep; type MaxParaHeadDataSize = ConstU32; } +pub struct FreeForParachain2; + +impl ParachainHeadsUpdateFilter for FreeForParachain2 { + fn is_free( + _at_relay_block: (RelayBlockNumber, RelayBlockHash), + parachains: &[(ParaId, ParaHash)], + ) -> bool { + parachains.len() == 1 && parachains[0].0 .0 == Parachain2::PARACHAIN_ID + } +} + #[cfg(feature = "runtime-benchmarks")] impl pallet_bridge_parachains::benchmarking::Config<()> for TestRuntime { fn parachains() -> Vec { From df1928f22b87868c749c0772bd0991fae9562cd6 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 13 Mar 2024 16:37:56 +0300 Subject: [PATCH 11/70] added FreeParachainUpdateForFreeRelayHeader --- .../extensions/refund_relayer_extension.rs | 41 ++---- bridges/bin/runtime-common/src/lib.rs | 118 ++++++++++++++++++ 2 files changed, 125 insertions(+), 34 deletions(-) diff --git a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs index 512d5f6ca059..f2c7ecc3078f 100644 --- a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs +++ b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs @@ -19,12 +19,15 @@ //! with calls that are: delivering new message and all necessary underlying headers //! (parachain or relay chain). -use crate::messages_call_ext::{ - CallHelper as MessagesCallHelper, CallInfo as MessagesCallInfo, MessagesCallSubType, +use crate::{ + messages_call_ext::{ + CallHelper as MessagesCallHelper, CallInfo as MessagesCallInfo, MessagesCallSubType, + }, + RefundableParachainId, }; use bp_messages::{LaneId, MessageNonce}; use bp_relayers::{ExplicitOrAccountParams, RewardsAccountOwner, RewardsAccountParams}; -use bp_runtime::{Parachain, ParachainIdOf, RangeInclusiveExt, StaticStrProvider}; +use bp_runtime::{RangeInclusiveExt, StaticStrProvider}; use codec::{Codec, Decode, Encode}; use frame_support::{ dispatch::{CallableCallFor, DispatchInfo, PostDispatchInfo}, @@ -61,37 +64,6 @@ type BalanceOf = <::OnChargeTransaction as OnChargeTransaction>::Balance; type CallOf = ::RuntimeCall; -/// Trait identifying a bridged parachain. A relayer might be refunded for delivering messages -/// coming from this parachain. -pub trait RefundableParachainId { - /// The instance of the bridge parachains pallet. - type Instance; - /// The parachain Id. - type Id: Get; -} - -/// Default implementation of `RefundableParachainId`. -pub struct DefaultRefundableParachainId(PhantomData<(Instance, Id)>); - -impl RefundableParachainId for DefaultRefundableParachainId -where - Id: Get, -{ - type Instance = Instance; - type Id = Id; -} - -/// Implementation of `RefundableParachainId` for `trait Parachain`. -pub struct RefundableParachain(PhantomData<(Instance, Para)>); - -impl RefundableParachainId for RefundableParachain -where - Para: Parachain, -{ - type Instance = Instance; - type Id = ParachainIdOf; -} - /// Trait identifying a bridged messages lane. A relayer might be refunded for delivering messages /// coming from this lane. pub trait RefundableMessagesLaneId { @@ -953,6 +925,7 @@ pub(crate) mod tests { UnrewardedRelayerOccupation, }, mock::*, + DefaultRefundableParachainId, }; use bp_messages::{ DeliveredMessages, InboundLaneData, MessageNonce, MessagesOperatingMode, OutboundLaneData, diff --git a/bridges/bin/runtime-common/src/lib.rs b/bridges/bin/runtime-common/src/lib.rs index 5679acd6006c..8fe36884090b 100644 --- a/bridges/bin/runtime-common/src/lib.rs +++ b/bridges/bin/runtime-common/src/lib.rs @@ -19,6 +19,14 @@ #![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] +use bp_polkadot_core::parachains::{ParaHash, ParaId}; +use bp_runtime::{Parachain, ParachainIdOf}; +use pallet_bridge_grandpa::Config as GrandpaConfig; +use pallet_bridge_parachains::{ + Config as ParachainsConfig, ParachainHeadsUpdateFilter, RelayBlockHash, RelayBlockNumber, +}; +use sp_runtime::traits::{Get, PhantomData}; + pub mod extensions; pub mod messages; pub mod messages_api; @@ -34,3 +42,113 @@ mod mock; pub mod integrity; const LOG_TARGET_BRIDGE_DISPATCH: &str = "runtime::bridge-dispatch"; + +/// Trait identifying a bridged parachain. A relayer might be refunded for delivering messages +/// coming from this parachain. +pub trait RefundableParachainId { + /// The instance of the bridge parachains pallet. + type Instance: 'static; + /// The parachain Id. + type Id: Get; +} + +/// Default implementation of `RefundableParachainId`. +pub struct DefaultRefundableParachainId(PhantomData<(Instance, Id)>); + +impl RefundableParachainId for DefaultRefundableParachainId +where + Instance: 'static, + Id: Get, +{ + type Instance = Instance; + type Id = Id; +} + +/// Implementation of `RefundableParachainId` for `trait Parachain`. +pub struct RefundableParachain(PhantomData<(Instance, Para)>); + +impl RefundableParachainId for RefundableParachain +where + Instance: 'static, + Para: Parachain, +{ + type Instance = Instance; + type Id = ParachainIdOf; +} + +/// A filter that allows one free parachain head submissions for every free +/// relay chain header. It DOES NOT refund for parachains, finalized at +/// mandatory relay chain blocks. +/// +/// The number of free submissions is implicitly limited by the +/// `T::MaxFreeHeadersPerBlock` - there can be at most one parachain head +/// submission for every free relay chain header. And since number of free +/// relay chain headers is limited by this parameter, free parachain head +/// updates is also limited. +pub struct FreeParachainUpdateForFreeRelayHeader(PhantomData<(T, GI, P)>); + +impl ParachainHeadsUpdateFilter for FreeParachainUpdateForFreeRelayHeader +where + T: GrandpaConfig + ParachainsConfig, + GI: 'static, + P: RefundableParachainId, +{ + fn is_free( + at_relay_block: (RelayBlockNumber, RelayBlockHash), + parachains: &[(ParaId, ParaHash)], + ) -> bool { + // just one parachain that we are interested in + if parachains.len() != 1 || parachains[0].0 .0 != P::Id::get() { + return false; + } + + // we only refund for parachains, finalized at free relay chain blocks + let Some(free_headers_interval) = >::FreeHeadersInterval::get() + else { + return false + }; + if at_relay_block.0 != 0 && at_relay_block.0 % free_headers_interval == 0 { + return true + } + + false + } +} + +#[cfg(test)] +mod tests { + use super::*; + use mock::*; + + type RefundableParachain = super::RefundableParachain<(), BridgedUnderlyingParachain>; + type FreeBridgedParachainUpdate = + FreeParachainUpdateForFreeRelayHeader; + + #[test] + fn free_parachain_update_for_free_relay_header_works() { + let free_interval = >::FreeHeadersInterval::get(); + // not free when there are multiple parachains + assert!(!FreeBridgedParachainUpdate::is_free( + (free_interval, Default::default()), + &[ + (ParaId(BridgedUnderlyingParachain::PARACHAIN_ID), Default::default()), + (ParaId(BridgedUnderlyingParachain::PARACHAIN_ID + 1), Default::default()), + ], + )); + // not free when finalized at non-free relay chain header + assert!(!FreeBridgedParachainUpdate::is_free( + (free_interval + 1, Default::default()), + &[(ParaId(BridgedUnderlyingParachain::PARACHAIN_ID), Default::default()),], + )); + // not free when finalized at relay chain genesis + assert!(!FreeBridgedParachainUpdate::is_free( + (0, Default::default()), + &[(ParaId(BridgedUnderlyingParachain::PARACHAIN_ID), Default::default()),], + )); + // free when finalized at free relay chain header + assert!(FreeBridgedParachainUpdate::is_free( + (free_interval, Default::default()), + &[(ParaId(BridgedUnderlyingParachain::PARACHAIN_ID), Default::default()),], + )); + } +} From 6d852d4b4fc059b8bc4043bda6f2b7ac8a791c94 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 15 Mar 2024 10:25:35 +0300 Subject: [PATCH 12/70] fix benchmarks compilation --- bridges/modules/relayers/src/benchmarking.rs | 2 +- bridges/primitives/relayers/src/registration.rs | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/bridges/modules/relayers/src/benchmarking.rs b/bridges/modules/relayers/src/benchmarking.rs index 00c3814a4c38..ca312d44edfd 100644 --- a/bridges/modules/relayers/src/benchmarking.rs +++ b/bridges/modules/relayers/src/benchmarking.rs @@ -106,7 +106,7 @@ benchmarks! { let slash_destination = RewardsAccountParams::new(lane, *b"test", RewardsAccountOwner::ThisChain); T::prepare_rewards_account(slash_destination, Zero::zero()); }: { - crate::Pallet::::slash_and_deregister(&relayer, slash_destination) + crate::Pallet::::slash_and_deregister(&relayer, slash_destination.into()) } verify { assert!(!crate::Pallet::::is_registration_active(&relayer)); diff --git a/bridges/primitives/relayers/src/registration.rs b/bridges/primitives/relayers/src/registration.rs index 5f5c588d6c22..9d9b7e481220 100644 --- a/bridges/primitives/relayers/src/registration.rs +++ b/bridges/primitives/relayers/src/registration.rs @@ -55,6 +55,12 @@ pub enum ExplicitOrAccountParams { Params(RewardsAccountParams), } +impl From for ExplicitOrAccountParams { + fn from(params: RewardsAccountParams) -> Self { + ExplicitOrAccountParams::Params(params) + } +} + /// Relayer registration. #[derive(Copy, Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo, MaxEncodedLen)] pub struct Registration { From 32090531848660d420a9ce946a07b84a379503ed Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 15 Mar 2024 12:04:33 +0300 Subject: [PATCH 13/70] added WeightInfoExt for pallet-bridge-grandpa to include extra wight coming from CheckAndBoostBridgeGrandpaTransactions --- bridges/modules/grandpa/src/lib.rs | 8 +-- bridges/modules/grandpa/src/weights_ext.rs | 58 ++++++++++++++++++++++ 2 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 bridges/modules/grandpa/src/weights_ext.rs diff --git a/bridges/modules/grandpa/src/lib.rs b/bridges/modules/grandpa/src/lib.rs index f625ebd0b8a9..e1c3d5ad98af 100644 --- a/bridges/modules/grandpa/src/lib.rs +++ b/bridges/modules/grandpa/src/lib.rs @@ -58,6 +58,7 @@ mod storage_types; /// Module, containing weights for this pallet. pub mod weights; +pub mod weights_ext; #[cfg(feature = "runtime-benchmarks")] pub mod benchmarking; @@ -66,6 +67,7 @@ pub mod benchmarking; pub use call_ext::*; pub use pallet::*; pub use weights::WeightInfo; +pub use weights_ext::WeightInfoExt; /// The target that will be used when publishing logs related to this pallet. pub const LOG_TARGET: &str = "runtime::bridge-grandpa"; @@ -138,7 +140,7 @@ pub mod pallet { type HeadersToKeep: Get; /// Weights gathered through benchmarking. - type WeightInfo: WeightInfo; + type WeightInfo: WeightInfoExt; } #[pallet::pallet] @@ -169,7 +171,7 @@ pub mod pallet { /// `submit_finality_proof_ex` instead. Semantically, this call is an equivalent of the /// `submit_finality_proof_ex` call without current authority set id check. #[pallet::call_index(0)] - #[pallet::weight(::submit_finality_proof( + #[pallet::weight(T::WeightInfo::submit_finality_proof_weight( justification.commit.precommits.len().saturated_into(), justification.votes_ancestries.len().saturated_into(), ))] @@ -273,7 +275,7 @@ pub mod pallet { /// be executed for free. If transaction extension is not used by the runtime, this /// parameter is not used at all. #[pallet::call_index(4)] - #[pallet::weight(::submit_finality_proof( + #[pallet::weight(T::WeightInfo::submit_finality_proof_weight( justification.commit.precommits.len().saturated_into(), justification.votes_ancestries.len().saturated_into(), ))] diff --git a/bridges/modules/grandpa/src/weights_ext.rs b/bridges/modules/grandpa/src/weights_ext.rs new file mode 100644 index 000000000000..66edea6fb6a6 --- /dev/null +++ b/bridges/modules/grandpa/src/weights_ext.rs @@ -0,0 +1,58 @@ +// Copyright (C) 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 . + +//! Weight-related utilities. + +use crate::weights::{BridgeWeight, WeightInfo}; + +use frame_support::weights::Weight; + +/// Extended weight info. +pub trait WeightInfoExt: WeightInfo { + // Our configuration assumes that the runtime has special signed extensions used to: + // + // 1) boost priority of `submit_finality_proof` transactions; + // + // 2) slash relayer if he submits an invalid transaction. + // + // We read and update storage values of other pallets (`pallet-bridge-relayers` and + // balances/assets pallet). So we need to add this weight to the weight of our call. + // Hence two following methods. + + /// Extra weight that is added to the `submit_finality_proof` call weight by signed extensions + /// that are declared at runtime level. + fn submit_finality_proof_overhead_from_runtime() -> Weight; + + // Functions that are directly mapped to extrinsics weights. + + /// Weight of message delivery extrinsic. + fn submit_finality_proof_weight(precommits_len: u32, votes_ancestries_len: u32) -> Weight { + let base_weight = Self::submit_finality_proof(precommits_len, votes_ancestries_len); + base_weight.saturating_add(Self::submit_finality_proof_overhead_from_runtime()) + } +} + +impl WeightInfoExt for BridgeWeight { + fn submit_finality_proof_overhead_from_runtime() -> Weight { + Weight::zero() + } +} + +impl WeightInfoExt for () { + fn submit_finality_proof_overhead_from_runtime() -> Weight { + Weight::zero() + } +} From 3f95fb0ac94e1de3ba85e90d6b778611de8e5396 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 20 Mar 2024 11:35:07 +0300 Subject: [PATCH 14/70] also refund for submitting initial parachain head --- bridges/modules/parachains/src/lib.rs | 105 ++++++++++++++++++++++++-- 1 file changed, 97 insertions(+), 8 deletions(-) diff --git a/bridges/modules/parachains/src/lib.rs b/bridges/modules/parachains/src/lib.rs index 9afd03fde8ac..a6b87b204a22 100644 --- a/bridges/modules/parachains/src/lib.rs +++ b/bridges/modules/parachains/src/lib.rs @@ -371,8 +371,12 @@ pub mod pallet { Self::ensure_not_halted().map_err(Error::::BridgeModule)?; ensure_signed(origin)?; - // check whether this submission may be refunded + // the pallet allows two kind of free submissions: + // 1) check whether filter allows this submission for free let may_be_free = T::FreeHeadsUpdateFilter::is_free(at_relay_block, ¶chains); + // 2) refund allowed if all parachain heads are first headers known to us + let total_parachains = parachains.len(); + let mut first_parachain_heads = 0; // we'll need relay chain header to verify that parachains heads are always increasing. let (relay_block_number, relay_block_hash) = at_relay_block; @@ -466,6 +470,7 @@ pub mod pallet { let update_result: Result<_, ()> = ParasInfo::::try_mutate(parachain, |stored_best_head| { + let is_first = stored_best_head.is_none(); let artifacts = Pallet::::update_parachain_head( parachain, stored_best_head.take(), @@ -473,7 +478,12 @@ pub mod pallet { parachain_head_data, parachain_head_hash, )?; + is_updated_something = true; + if is_first { + first_parachain_heads = first_parachain_heads + 1; + } + *stored_best_head = Some(artifacts.best_head); Ok(artifacts.prune_happened) }); @@ -504,9 +514,14 @@ pub mod pallet { Error::::HeaderChainStorageProof(HeaderChainError::StorageProof(e)) })?; - // we allow free submissions only if the update passes filter and something + // we allow free submissions if the update passes filter and something // has been updated - let pays_fee = if is_updated_something && may_be_free { Pays::No } else { Pays::Yes }; + // + // we allow free submission of the first parachain head + let is_free_by_filter_criteria = is_updated_something && may_be_free; + let is_free_by_first_criteria = total_parachains == first_parachain_heads; + let is_free = is_free_by_filter_criteria || is_free_by_first_criteria; + let pays_fee = if is_free { Pays::No } else { Pays::Yes }; Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee }) } @@ -951,7 +966,7 @@ pub(crate) mod tests { run_test(|| { initialize(state_root); - // we're trying to update heads of parachains 1, 2 and 3 + // we're trying to update heads of parachains 1 and 3 let expected_weight = WeightInfo::submit_parachain_heads_weight(DbWeight::get(), &proof, 2); let result = Pallet::::submit_parachain_heads( @@ -961,9 +976,10 @@ pub(crate) mod tests { proof, ); assert_ok!(result); + assert_eq!(result.expect("checked above").pays_fee, Pays::No); assert_eq!(result.expect("checked above").actual_weight, Some(expected_weight)); - // but only 1 and 2 are updated, because proof is missing head of parachain#2 + // 1 and 3 are updated, because proof is missing head of parachain#2 assert_eq!(ParasInfo::::get(ParaId(1)), Some(initial_best_head(1))); assert_eq!(ParasInfo::::get(ParaId(2)), None); assert_eq!( @@ -1033,7 +1049,8 @@ pub(crate) mod tests { // start with relay block #0 and import head#5 of parachain#1 initialize(state_root_5); let result = import_parachain_1_head(0, state_root_5, parachains_5, proof_5); - assert_eq!(result.unwrap().pays_fee, Pays::Yes); + // first parachain head is imported for free + assert_eq!(result.unwrap().pays_fee, Pays::No); assert_eq!( ParasInfo::::get(ParaId(1)), Some(ParaInfo { @@ -1068,7 +1085,9 @@ pub(crate) mod tests { // import head#10 of parachain#1 at relay block #1 let (relay_1_hash, justification) = proceed(1, state_root_10); - assert_ok!(import_parachain_1_head(1, state_root_10, parachains_10, proof_10)); + let result = import_parachain_1_head(1, state_root_10, parachains_10, proof_10); + // second parachain head is imported for fee + assert_eq!(result.unwrap().pays_fee, Pays::Yes); assert_eq!( ParasInfo::::get(ParaId(1)), Some(ParaInfo { @@ -1693,7 +1712,7 @@ pub(crate) mod tests { } #[test] - fn may_be_free_for_submitter() { + fn may_be_free_for_submitting_filtered_heads() { run_test(|| { let (state_root, proof, parachains) = prepare_parachain_heads_proof::(vec![(2, head_data(2, 5))]); @@ -1717,4 +1736,74 @@ pub(crate) mod tests { assert_eq!(result.unwrap().pays_fee, Pays::Yes); }) } + + #[test] + fn may_be_free_for_submitting_first_heads() { + fn run_sub_test(pre: impl FnOnce(), heads: Vec<(u32, ParaHead)>) -> Pays { + run_test(|| { + let (state_root, proof, parachains) = + prepare_parachain_heads_proof::(heads); + let relay_header_hash = initialize(state_root); + pre(); + let result = Pallet::::submit_parachain_heads( + RuntimeOrigin::signed(1), + (0, relay_header_hash), + parachains, + proof, + ); + result.unwrap().pays_fee + }) + } + + // if we submit 2 heads and both are first => Pays::No + assert_eq!(run_sub_test(|| (), vec![(1, head_data(1, 5)), (2, head_data(2, 5))]), Pays::No); + // if we submit 2 heads and one is not first => Pays::Yes + assert_eq!( + run_sub_test( + || { + ParasInfo::::insert( + ParaId(1), + ParaInfo { + best_head_hash: BestParaHeadHash { + at_relay_block_number: 1, + head_hash: Default::default(), + }, + next_imported_hash_position: 1, + }, + ); + }, + vec![(1, head_data(1, 5)), (2, head_data(2, 5))] + ), + Pays::Yes + ); + // if we submit 2 heads and both are not first => Pays::Yes + assert_eq!( + run_sub_test( + || { + ParasInfo::::insert( + ParaId(1), + ParaInfo { + best_head_hash: BestParaHeadHash { + at_relay_block_number: 1, + head_hash: Default::default(), + }, + next_imported_hash_position: 1, + }, + ); + ParasInfo::::insert( + ParaId(2), + ParaInfo { + best_head_hash: BestParaHeadHash { + at_relay_block_number: 2, + head_hash: Default::default(), + }, + next_imported_hash_position: 1, + }, + ); + }, + vec![(1, head_data(1, 5)), (2, head_data(2, 5))] + ), + Pays::Yes + ); + } } From 84c97575ae1ff1546433958bfb8e9e2a96acb064 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 20 Mar 2024 13:09:04 +0300 Subject: [PATCH 15/70] make FreeHeadersRemaining an Option => we know that when we are outside of tx body, it is `None` and otherwise it is `Some` --- bridges/modules/grandpa/src/call_ext.rs | 29 ++++++++++++++++++++++--- bridges/modules/grandpa/src/lib.rs | 11 +++++++--- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/bridges/modules/grandpa/src/call_ext.rs b/bridges/modules/grandpa/src/call_ext.rs index 1ae5fca95234..9bccb0b78bb9 100644 --- a/bridges/modules/grandpa/src/call_ext.rs +++ b/bridges/modules/grandpa/src/call_ext.rs @@ -22,7 +22,7 @@ use bp_header_chain::{ justification::GrandpaJustification, max_expected_submit_finality_proof_arguments_size, ChainWithGrandpa, GrandpaConsensusLogReader, }; -use bp_runtime::{BlockNumberOf, OwnedBridgeModule}; +use bp_runtime::{BlockNumberOf, Chain, OwnedBridgeModule}; use codec::Encode; use frame_support::{dispatch::CallableCallFor, traits::IsSubType, weights::Weight}; use sp_consensus_grandpa::SetId; @@ -92,7 +92,21 @@ impl, I: 'static> SubmitFinalityProofHelper { } // else - if we can not accept more free headers, "reject" the transaction - if FreeHeadersRemaining::::get() == 0 { + // + // `unwrap_or(u32::MAX)` means that if `FreeHeadersRemaining` is `None`, we may accept + // this header for free. That is a small cheat - is is `None` if executed outside of + // transaction (e.g. during block initialization). Normal relayer would never submit + // such calls, but if he did, that is not our problem. During normal transactions, + // the `FreeHeadersRemaining` is always `Some(_)`. + let free_headers_remaining = FreeHeadersRemaining::::get().unwrap_or(u32::MAX); + if free_headers_remaining == 0 { + log::trace!( + target: crate::LOG_TARGET, + "Cannot accept free {:?} header {:?}. No more free slots remaining", + T::BridgedChain::ID, + call_info.block_number, + ); + return Err(Error::::CannotAcceptMoreFreeHeaders); } @@ -409,11 +423,20 @@ mod tests { // when we can NOT accept free headers => Err FreeHeadersRemaining::::put(0); assert!(RuntimeCall::check_obsolete_submit_finality_proof( - &RuntimeCall::Grandpa(bridge_grandpa_call,), + &RuntimeCall::Grandpa(bridge_grandpa_call.clone(),), 0 ) .1 .is_err()); + + // when called outside of transaction => Ok + FreeHeadersRemaining::::kill(); + assert!(RuntimeCall::check_obsolete_submit_finality_proof( + &RuntimeCall::Grandpa(bridge_grandpa_call,), + 0 + ) + .1 + .is_ok()); }) } diff --git a/bridges/modules/grandpa/src/lib.rs b/bridges/modules/grandpa/src/lib.rs index e1c3d5ad98af..285836b4d340 100644 --- a/bridges/modules/grandpa/src/lib.rs +++ b/bridges/modules/grandpa/src/lib.rs @@ -315,7 +315,12 @@ pub mod pallet { current_set_id, ); if may_refund_call_fee { - FreeHeadersRemaining::::mutate(|count| *count = count.saturating_sub(1)); + FreeHeadersRemaining::::mutate(|count| { + *count = match *count { + Some(count) if count > 1 => Some(count - 1), + _ => None, + } + }); } insert_header::(*finality_target, hash); log::info!( @@ -370,7 +375,7 @@ pub mod pallet { #[pallet::whitelist_storage] #[pallet::getter(fn free_mandatory_headers_remaining)] pub(super) type FreeHeadersRemaining, I: 'static = ()> = - StorageValue<_, u32, ValueQuery>; + StorageValue<_, u32, OptionQuery>; /// Hash of the header used to bootstrap the pallet. #[pallet::storage] @@ -509,7 +514,7 @@ pub mod pallet { current_set_id: SetId, ) -> bool { // if we have refunded too much at this block => not refunding - if FreeHeadersRemaining::::get() == 0 { + if FreeHeadersRemaining::::get().unwrap_or(0) == 0 { return false; } From a3831e22c787525bdf5b2efb7f28a24c6f207c1b Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 20 Mar 2024 15:34:24 +0300 Subject: [PATCH 16/70] add more traces to pallets --- bridges/modules/grandpa/src/lib.rs | 12 +++++++----- bridges/modules/parachains/src/lib.rs | 11 +++++++++-- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/bridges/modules/grandpa/src/lib.rs b/bridges/modules/grandpa/src/lib.rs index 285836b4d340..aba310ad13e1 100644 --- a/bridges/modules/grandpa/src/lib.rs +++ b/bridges/modules/grandpa/src/lib.rs @@ -323,11 +323,6 @@ pub mod pallet { }); } insert_header::(*finality_target, hash); - log::info!( - target: LOG_TARGET, - "Successfully imported finalized header with hash {:?}!", - hash - ); // mandatory header is a header that changes authorities set. The pallet can't go // further without importing this header. So every bridge MUST import mandatory headers. @@ -339,6 +334,13 @@ pub mod pallet { // to pay for the transaction. let pays_fee = if may_refund_call_fee { Pays::No } else { Pays::Yes }; + log::info!( + target: LOG_TARGET, + "Successfully imported finalized header with hash {:?}! Free: {}", + hash, + if may_refund_call_fee { "No" } else { "Yes" }, + ); + // the proof size component of the call weight assumes that there are // `MaxBridgedAuthorities` in the `CurrentAuthoritySet` (we use `MaxEncodedLen` // estimation). But if their number is lower, then we may "refund" some `proof_size`, diff --git a/bridges/modules/parachains/src/lib.rs b/bridges/modules/parachains/src/lib.rs index a6b87b204a22..2027bce469d8 100644 --- a/bridges/modules/parachains/src/lib.rs +++ b/bridges/modules/parachains/src/lib.rs @@ -521,7 +521,13 @@ pub mod pallet { let is_free_by_filter_criteria = is_updated_something && may_be_free; let is_free_by_first_criteria = total_parachains == first_parachain_heads; let is_free = is_free_by_filter_criteria || is_free_by_first_criteria; - let pays_fee = if is_free { Pays::No } else { Pays::Yes }; + let pays_fee = if is_free { + log::trace!(target: LOG_TARGET, "Parachain heads update transaction is free"); + Pays::No + } else { + log::trace!(target: LOG_TARGET, "Parachain heads update transaction is paid"); + Pays::Yes + }; Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee }) } @@ -666,9 +672,10 @@ pub mod pallet { ImportedParaHeads::::insert(parachain, new_head_hash, updated_head_data); log::trace!( target: LOG_TARGET, - "Updated head of parachain {:?} to {}", + "Updated head of parachain {:?} to {} at relay block {}", parachain, new_head_hash, + new_at_relay_block_number, ); // remove old head From 1a6baf9c17a5a32d2614b42d00a4fe1ad6e27732 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 20 Mar 2024 16:10:32 +0300 Subject: [PATCH 17/70] check for reorgs in signed ext for parachains module --- .../extensions/refund_relayer_extension.rs | 8 +- bridges/modules/parachains/src/call_ext.rs | 125 +++++++++++++----- bridges/modules/parachains/src/lib.rs | 17 ++- bridges/modules/parachains/src/mock.rs | 2 +- 4 files changed, 109 insertions(+), 43 deletions(-) diff --git a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs index f2c7ecc3078f..8c21be5318dd 100644 --- a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs +++ b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs @@ -1279,7 +1279,7 @@ pub(crate) mod tests { is_free_execution_expected: false, }, SubmitParachainHeadsInfo { - at_relay_block_number: 200, + at_relay_block: (200, [0u8; 32].into()), para_id: ParaId(TestParachain::get()), para_head_hash: [200u8; 32].into(), }, @@ -1317,7 +1317,7 @@ pub(crate) mod tests { is_free_execution_expected: false, }, SubmitParachainHeadsInfo { - at_relay_block_number: 200, + at_relay_block: (200, [0u8; 32].into()), para_id: ParaId(TestParachain::get()), para_head_hash: [200u8; 32].into(), }, @@ -1406,7 +1406,7 @@ pub(crate) mod tests { relayer: relayer_account_at_this_chain(), call_info: CallInfo::ParachainFinalityAndMsgs( SubmitParachainHeadsInfo { - at_relay_block_number: 200, + at_relay_block: (200, [0u8; 32].into()), para_id: ParaId(TestParachain::get()), para_head_hash: [200u8; 32].into(), }, @@ -1430,7 +1430,7 @@ pub(crate) mod tests { relayer: relayer_account_at_this_chain(), call_info: CallInfo::ParachainFinalityAndMsgs( SubmitParachainHeadsInfo { - at_relay_block_number: 200, + at_relay_block: (200, [0u8; 32].into()), para_id: ParaId(TestParachain::get()), para_head_hash: [200u8; 32].into(), }, diff --git a/bridges/modules/parachains/src/call_ext.rs b/bridges/modules/parachains/src/call_ext.rs index da91a40a2322..5747cb1007c6 100644 --- a/bridges/modules/parachains/src/call_ext.rs +++ b/bridges/modules/parachains/src/call_ext.rs @@ -14,7 +14,10 @@ // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . -use crate::{Config, Pallet, RelayBlockNumber}; +use crate::{ + Config, GrandpaPalletOf, Pallet, ParachainHeadsUpdateFilter, RelayBlockHash, RelayBlockNumber, +}; +use bp_header_chain::HeaderChain; use bp_parachains::BestParaHeadHash; use bp_polkadot_core::parachains::{ParaHash, ParaId}; use bp_runtime::OwnedBridgeModule; @@ -27,8 +30,9 @@ use sp_runtime::{ /// Info about a `SubmitParachainHeads` call which tries to update a single parachain. #[derive(PartialEq, RuntimeDebug)] pub struct SubmitParachainHeadsInfo { - /// Number of the finalized relay block that has been used to prove parachain finality. - pub at_relay_block_number: RelayBlockNumber, + /// Number and hash of the finalized relay block that has been used to prove parachain + /// finality. + pub at_relay_block: (RelayBlockNumber, RelayBlockHash), /// Parachain identifier. pub para_id: ParaId, /// Hash of the bundled parachain head. @@ -44,34 +48,60 @@ impl, I: 'static> SubmitParachainHeadsHelper { /// Check if the para head provided by the `SubmitParachainHeads` is better than the best one /// we know. pub fn is_obsolete(update: &SubmitParachainHeadsInfo) -> bool { - let stored_best_head = match crate::ParasInfo::::get(update.para_id) { - Some(stored_best_head) => stored_best_head, - None => return false, + // check if we know better parachain head already + let is_free_execution_expected = match crate::ParasInfo::::get(update.para_id) { + Some(stored_best_head) => { + if stored_best_head.best_head_hash.at_relay_block_number >= update.at_relay_block.0 + { + log::trace!( + target: crate::LOG_TARGET, + "The parachain head can't be updated. The parachain head for {:?} \ + was already updated at better relay chain block {} >= {}.", + update.para_id, + stored_best_head.best_head_hash.at_relay_block_number, + update.at_relay_block.0 + ); + return true + } + + if stored_best_head.best_head_hash.head_hash == update.para_head_hash { + log::trace!( + target: crate::LOG_TARGET, + "The parachain head can't be updated. The parachain head hash for {:?} \ + was already updated to {} at block {} < {}.", + update.para_id, + update.para_head_hash, + stored_best_head.best_head_hash.at_relay_block_number, + update.at_relay_block.0 + ); + return true + } + + T::FreeHeadsUpdateFilter::is_free( + update.at_relay_block, + &[(update.para_id, update.para_head_hash)], + ) + }, + None => true, }; - if stored_best_head.best_head_hash.at_relay_block_number >= update.at_relay_block_number { - log::trace!( - target: crate::LOG_TARGET, - "The parachain head can't be updated. The parachain head for {:?} \ - was already updated at better relay chain block {} >= {}.", - update.para_id, - stored_best_head.best_head_hash.at_relay_block_number, - update.at_relay_block_number - ); - return true - } + // the relayer may expect free execution, so let's double check if our chain has no + // reorgs and we still know the relay chain header used to craft the proof + if is_free_execution_expected { + if GrandpaPalletOf::::finalized_header_state_root(update.at_relay_block.1) + .is_none() + { + log::trace!( + target: crate::LOG_TARGET, + "The parachain {:?} head can't be updated. Relay chain header {}/{} used to create \ + parachain proof is missing from the storage.", + update.para_id, + update.at_relay_block.0, + update.at_relay_block.1, + ); - if stored_best_head.best_head_hash.head_hash == update.para_head_hash { - log::trace!( - target: crate::LOG_TARGET, - "The parachain head can't be updated. The parachain head hash for {:?} \ - was already updated to {} at block {} < {}.", - update.para_id, - update.para_head_hash, - stored_best_head.best_head_hash.at_relay_block_number, - update.at_relay_block_number - ); - return true + return true + } } false @@ -83,7 +113,7 @@ impl, I: 'static> SubmitParachainHeadsHelper { Some(stored_best_head) => stored_best_head.best_head_hash == BestParaHeadHash { - at_relay_block_number: update.at_relay_block_number, + at_relay_block_number: update.at_relay_block.0, head_hash: update.para_head_hash, }, None => false, @@ -106,7 +136,7 @@ pub trait CallSubType, I: 'static>: { if let &[(para_id, para_head_hash)] = parachains.as_slice() { return Some(SubmitParachainHeadsInfo { - at_relay_block_number: at_relay_block.0, + at_relay_block: *at_relay_block, para_id, para_head_hash, }) @@ -165,8 +195,9 @@ where mod tests { use crate::{ mock::{run_test, RuntimeCall, TestRuntime}, - CallSubType, PalletOperatingMode, ParaInfo, ParasInfo, RelayBlockNumber, + CallSubType, PalletOperatingMode, ParaInfo, ParasInfo, RelayBlockHash, RelayBlockNumber, }; + use bp_header_chain::StoredHeaderData; use bp_parachains::BestParaHeadHash; use bp_polkadot_core::parachains::{ParaHash, ParaHeadsProof, ParaId}; use bp_runtime::BasicOperatingMode; @@ -176,7 +207,7 @@ mod tests { parachains: Vec<(ParaId, ParaHash)>, ) -> bool { RuntimeCall::Parachains(crate::Call::::submit_parachain_heads { - at_relay_block: (num, Default::default()), + at_relay_block: (num, [num as u8; 32].into()), parachains, parachain_heads_proof: ParaHeadsProof { storage_proof: Vec::new() }, }) @@ -184,6 +215,13 @@ mod tests { .is_ok() } + fn insert_relay_block(num: RelayBlockNumber) { + pallet_bridge_grandpa::ImportedHeaders::::insert( + RelayBlockHash::from([num as u8; 32]), + StoredHeaderData { number: num, state_root: RelayBlockHash::from([10u8; 32]) }, + ); + } + fn sync_to_relay_header_10() { ParasInfo::::insert( ParaId(1), @@ -260,4 +298,27 @@ mod tests { )); }); } + + #[test] + fn extension_rejects_initial_parachain_head_if_missing_relay_chain_header() { + run_test(|| { + // when relay chain header is unknown => "obsolete" + assert!(!validate_submit_parachain_heads(10, vec![(ParaId(1), [1u8; 32].into())])); + // when relay chain header is unknown => "ok" + insert_relay_block(10); + assert!(validate_submit_parachain_heads(10, vec![(ParaId(1), [1u8; 32].into())])); + }); + } + + #[test] + fn extension_rejects_free_parachain_head_if_missing_relay_chain_header() { + run_test(|| { + sync_to_relay_header_10(); + // when relay chain header is unknown => "obsolete" + assert!(!validate_submit_parachain_heads(15, vec![(ParaId(2), [15u8; 32].into())])); + // when relay chain header is unknown => "ok" + insert_relay_block(15); + assert!(validate_submit_parachain_heads(15, vec![(ParaId(2), [15u8; 32].into())])); + }); + } } diff --git a/bridges/modules/parachains/src/lib.rs b/bridges/modules/parachains/src/lib.rs index 2027bce469d8..f472a7651b25 100644 --- a/bridges/modules/parachains/src/lib.rs +++ b/bridges/modules/parachains/src/lib.rs @@ -110,7 +110,8 @@ pub mod pallet { BoundedStorageValue<>::MaxParaHeadDataSize, ParaStoredHeaderData>; /// Weight info of the given parachains pallet. pub type WeightInfoOf = >::WeightInfo; - type GrandpaPalletOf = + /// Bridge GRANDPA pallet that is used to verify parachain proofs. + pub type GrandpaPalletOf = pallet_bridge_grandpa::Pallet>::BridgesGrandpaPalletInstance>; #[pallet::event] @@ -224,6 +225,10 @@ pub mod pallet { /// **IMPORTANT**: we are NOT limiting number of free calls per block. The filter must /// take that into account or anyone could fill the block with parachain head updates /// for free. + /// + /// **IMPORTANT**: we expect that the filter don't touch any storage values or makes + /// any heavy computations. It is called by transaction pool during transaction validation, + /// so it should be lightweight. type FreeHeadsUpdateFilter: ParachainHeadsUpdateFilter; /// Name of the original `paras` pallet in the `construct_runtime!()` call at the bridged @@ -474,7 +479,7 @@ pub mod pallet { let artifacts = Pallet::::update_parachain_head( parachain, stored_best_head.take(), - relay_block_number, + (relay_block_number, relay_block_hash), parachain_head_data, parachain_head_hash, )?; @@ -607,14 +612,14 @@ pub mod pallet { pub(super) fn update_parachain_head( parachain: ParaId, stored_best_head: Option, - new_at_relay_block_number: RelayBlockNumber, + new_at_relay_block: (RelayBlockNumber, RelayBlockHash), new_head_data: ParaStoredHeaderData, new_head_hash: ParaHash, ) -> Result { // check if head has been already updated at better relay chain block. Without this // check, we may import heads in random order let update = SubmitParachainHeadsInfo { - at_relay_block_number: new_at_relay_block_number, + at_relay_block: new_at_relay_block, para_id: parachain, para_head_hash: new_head_hash, }; @@ -658,7 +663,7 @@ pub mod pallet { ImportedParaHashes::::try_get(parachain, next_imported_hash_position); let updated_best_para_head = ParaInfo { best_head_hash: BestParaHeadHash { - at_relay_block_number: new_at_relay_block_number, + at_relay_block_number: new_at_relay_block.0, head_hash: new_head_hash, }, next_imported_hash_position: (next_imported_hash_position + 1) % @@ -675,7 +680,7 @@ pub mod pallet { "Updated head of parachain {:?} to {} at relay block {}", parachain, new_head_hash, - new_at_relay_block_number, + new_at_relay_block.0, ); // remove old head diff --git a/bridges/modules/parachains/src/mock.rs b/bridges/modules/parachains/src/mock.rs index 7be24aa431b8..43fe189543ed 100644 --- a/bridges/modules/parachains/src/mock.rs +++ b/bridges/modules/parachains/src/mock.rs @@ -175,7 +175,7 @@ impl pallet_bridge_grandpa::Config for TestRun type RuntimeEvent = RuntimeEvent; type BridgedChain = TestBridgedChain; type MaxFreeHeadersPerBlock = ConstU32<2>; - type FreeHeadersInterval = ConstU32<1_024>; + type FreeHeadersInterval = ConstU32<15>; type HeadersToKeep = HeadersToKeep; type WeightInfo = (); } From 3922d6a5af8866beb04e12b711e0390c6a52a5a2 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 20 Mar 2024 17:09:57 +0300 Subject: [PATCH 18/70] fix benchmarks compilation --- bridges/modules/parachains/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/modules/parachains/src/lib.rs b/bridges/modules/parachains/src/lib.rs index f472a7651b25..354b11167330 100644 --- a/bridges/modules/parachains/src/lib.rs +++ b/bridges/modules/parachains/src/lib.rs @@ -771,7 +771,7 @@ pub fn initialize_for_benchmarks, I: 'static, PC: Parachain::update_parachain_head( parachain, None, - 0, + (0, Default::default()), updated_head_data, parachain_head.hash(), ) From 666aad23000e60e0c7aecc0293ae5245027e0d29 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 22 Mar 2024 14:23:49 +0300 Subject: [PATCH 19/70] free interval instead of fixed free headers --- .../extensions/refund_relayer_extension.rs | 9 + bridges/bin/runtime-common/src/lib.rs | 82 ------ bridges/bin/runtime-common/src/mock.rs | 1 - bridges/modules/grandpa/src/call_ext.rs | 26 +- bridges/modules/grandpa/src/lib.rs | 43 ++- bridges/modules/parachains/src/call_ext.rs | 183 ++++++++---- bridges/modules/parachains/src/lib.rs | 276 +++++++++--------- bridges/modules/parachains/src/mock.rs | 18 +- 8 files changed, 325 insertions(+), 313 deletions(-) diff --git a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs index 8c21be5318dd..79f4600bd818 100644 --- a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs +++ b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs @@ -927,6 +927,7 @@ pub(crate) mod tests { mock::*, DefaultRefundableParachainId, }; + use bp_header_chain::StoredHeaderDataBuilder; use bp_messages::{ DeliveredMessages, InboundLaneData, MessageNonce, MessagesOperatingMode, OutboundLaneData, UnrewardedRelayer, UnrewardedRelayersState, @@ -1030,6 +1031,10 @@ pub(crate) mod tests { StoredAuthoritySet::try_new(authorities, TEST_GRANDPA_SET_ID).unwrap(), ); pallet_bridge_grandpa::BestFinalized::::put(best_relay_header); + pallet_bridge_grandpa::ImportedHeaders::::insert( + best_relay_header.hash(), + bp_test_utils::test_header::(0).build(), + ); let para_id = ParaId(TestParachain::get()); let para_info = ParaInfo { @@ -1282,6 +1287,7 @@ pub(crate) mod tests { at_relay_block: (200, [0u8; 32].into()), para_id: ParaId(TestParachain::get()), para_head_hash: [200u8; 32].into(), + is_free_execution_expected: false, }, MessagesCallInfo::ReceiveMessagesProof(ReceiveMessagesProofInfo { base: BaseMessagesProofInfo { @@ -1320,6 +1326,7 @@ pub(crate) mod tests { at_relay_block: (200, [0u8; 32].into()), para_id: ParaId(TestParachain::get()), para_head_hash: [200u8; 32].into(), + is_free_execution_expected: false, }, MessagesCallInfo::ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo( BaseMessagesProofInfo { @@ -1409,6 +1416,7 @@ pub(crate) mod tests { at_relay_block: (200, [0u8; 32].into()), para_id: ParaId(TestParachain::get()), para_head_hash: [200u8; 32].into(), + is_free_execution_expected: false, }, MessagesCallInfo::ReceiveMessagesProof(ReceiveMessagesProofInfo { base: BaseMessagesProofInfo { @@ -1433,6 +1441,7 @@ pub(crate) mod tests { at_relay_block: (200, [0u8; 32].into()), para_id: ParaId(TestParachain::get()), para_head_hash: [200u8; 32].into(), + is_free_execution_expected: false, }, MessagesCallInfo::ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo( BaseMessagesProofInfo { diff --git a/bridges/bin/runtime-common/src/lib.rs b/bridges/bin/runtime-common/src/lib.rs index 8fe36884090b..1a5f2067453a 100644 --- a/bridges/bin/runtime-common/src/lib.rs +++ b/bridges/bin/runtime-common/src/lib.rs @@ -19,12 +19,7 @@ #![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] -use bp_polkadot_core::parachains::{ParaHash, ParaId}; use bp_runtime::{Parachain, ParachainIdOf}; -use pallet_bridge_grandpa::Config as GrandpaConfig; -use pallet_bridge_parachains::{ - Config as ParachainsConfig, ParachainHeadsUpdateFilter, RelayBlockHash, RelayBlockNumber, -}; use sp_runtime::traits::{Get, PhantomData}; pub mod extensions; @@ -75,80 +70,3 @@ where type Instance = Instance; type Id = ParachainIdOf; } - -/// A filter that allows one free parachain head submissions for every free -/// relay chain header. It DOES NOT refund for parachains, finalized at -/// mandatory relay chain blocks. -/// -/// The number of free submissions is implicitly limited by the -/// `T::MaxFreeHeadersPerBlock` - there can be at most one parachain head -/// submission for every free relay chain header. And since number of free -/// relay chain headers is limited by this parameter, free parachain head -/// updates is also limited. -pub struct FreeParachainUpdateForFreeRelayHeader(PhantomData<(T, GI, P)>); - -impl ParachainHeadsUpdateFilter for FreeParachainUpdateForFreeRelayHeader -where - T: GrandpaConfig + ParachainsConfig, - GI: 'static, - P: RefundableParachainId, -{ - fn is_free( - at_relay_block: (RelayBlockNumber, RelayBlockHash), - parachains: &[(ParaId, ParaHash)], - ) -> bool { - // just one parachain that we are interested in - if parachains.len() != 1 || parachains[0].0 .0 != P::Id::get() { - return false; - } - - // we only refund for parachains, finalized at free relay chain blocks - let Some(free_headers_interval) = >::FreeHeadersInterval::get() - else { - return false - }; - if at_relay_block.0 != 0 && at_relay_block.0 % free_headers_interval == 0 { - return true - } - - false - } -} - -#[cfg(test)] -mod tests { - use super::*; - use mock::*; - - type RefundableParachain = super::RefundableParachain<(), BridgedUnderlyingParachain>; - type FreeBridgedParachainUpdate = - FreeParachainUpdateForFreeRelayHeader; - - #[test] - fn free_parachain_update_for_free_relay_header_works() { - let free_interval = >::FreeHeadersInterval::get(); - // not free when there are multiple parachains - assert!(!FreeBridgedParachainUpdate::is_free( - (free_interval, Default::default()), - &[ - (ParaId(BridgedUnderlyingParachain::PARACHAIN_ID), Default::default()), - (ParaId(BridgedUnderlyingParachain::PARACHAIN_ID + 1), Default::default()), - ], - )); - // not free when finalized at non-free relay chain header - assert!(!FreeBridgedParachainUpdate::is_free( - (free_interval + 1, Default::default()), - &[(ParaId(BridgedUnderlyingParachain::PARACHAIN_ID), Default::default()),], - )); - // not free when finalized at relay chain genesis - assert!(!FreeBridgedParachainUpdate::is_free( - (0, Default::default()), - &[(ParaId(BridgedUnderlyingParachain::PARACHAIN_ID), Default::default()),], - )); - // free when finalized at free relay chain header - assert!(FreeBridgedParachainUpdate::is_free( - (free_interval, Default::default()), - &[(ParaId(BridgedUnderlyingParachain::PARACHAIN_ID), Default::default()),], - )); - } -} diff --git a/bridges/bin/runtime-common/src/mock.rs b/bridges/bin/runtime-common/src/mock.rs index fcc4c665cf2f..3beb52bbe7a7 100644 --- a/bridges/bin/runtime-common/src/mock.rs +++ b/bridges/bin/runtime-common/src/mock.rs @@ -193,7 +193,6 @@ impl pallet_bridge_parachains::Config for TestRuntime { type RuntimeEvent = RuntimeEvent; type BridgesGrandpaPalletInstance = (); type ParasPalletName = BridgedParasPalletName; - type FreeHeadsUpdateFilter = (); type ParaStoredHeaderDataBuilder = SingleParaStoredHeaderDataBuilder; type HeadsToKeep = ConstU32<8>; diff --git a/bridges/modules/grandpa/src/call_ext.rs b/bridges/modules/grandpa/src/call_ext.rs index 9bccb0b78bb9..f4d6732423bc 100644 --- a/bridges/modules/grandpa/src/call_ext.rs +++ b/bridges/modules/grandpa/src/call_ext.rs @@ -73,6 +73,23 @@ pub struct SubmitFinalityProofHelper, I: 'static> { } impl, I: 'static> SubmitFinalityProofHelper { + /// Returns `true` if we may fit more free headers into the current block. If `false` is + /// returned, the call will be paid even if `is_free_execution_expected` has been set + /// to `true`. + pub fn can_import_anything_for_free() -> bool { + // `unwrap_or(u32::MAX)` means that if `FreeHeadersRemaining` is `None`, we may accept + // this header for free. That is a small cheat - is is `None` if executed outside of + // transaction (e.g. during block initialization). Normal relayer would never submit + // such calls, but if he did, that is not our problem. During normal transactions, + // the `FreeHeadersRemaining` is always `Some(_)`. + let free_headers_remaining = FreeHeadersRemaining::::get().unwrap_or(u32::MAX); + if free_headers_remaining == 0 { + return false + } + + true + } + /// Check that the: (1) GRANDPA head provided by the `SubmitFinalityProof` is better than the /// best one we know (2) if `current_set_id` matches the current authority set id, if specified /// and (3) whether transaction MAY be free for the submitter if `is_free_execution_expected` @@ -92,14 +109,7 @@ impl, I: 'static> SubmitFinalityProofHelper { } // else - if we can not accept more free headers, "reject" the transaction - // - // `unwrap_or(u32::MAX)` means that if `FreeHeadersRemaining` is `None`, we may accept - // this header for free. That is a small cheat - is is `None` if executed outside of - // transaction (e.g. during block initialization). Normal relayer would never submit - // such calls, but if he did, that is not our problem. During normal transactions, - // the `FreeHeadersRemaining` is always `Some(_)`. - let free_headers_remaining = FreeHeadersRemaining::::get().unwrap_or(u32::MAX); - if free_headers_remaining == 0 { + if !Self::can_import_anything_for_free() { log::trace!( target: crate::LOG_TARGET, "Cannot accept free {:?} header {:?}. No more free slots remaining", diff --git a/bridges/modules/grandpa/src/lib.rs b/bridges/modules/grandpa/src/lib.rs index aba310ad13e1..74a00405a381 100644 --- a/bridges/modules/grandpa/src/lib.rs +++ b/bridges/modules/grandpa/src/lib.rs @@ -124,8 +124,9 @@ pub mod pallet { /// The distance between bridged chain headers, that may be submitted for free. The /// first free header is header number zero, the next one is header number - /// `FreeHeadersInterval::get()`. In other words, header with number that - /// is divisible by `FreeHeadersInterval` may be submitted for free. + /// `FreeHeadersInterval::get()` or any of its descendant if that header has not + /// bee submitted. In other words, interval between free headers should be at least + /// `FreeHeadersInterval`. #[pallet::constant] type FreeHeadersInterval: Get>; @@ -298,7 +299,8 @@ pub mod pallet { // it checks whether the `number` is better than the current best block number // and whether the `current_set_id` matches the best known set id - SubmitFinalityProofHelper::::check_obsolete(number, Some(current_set_id))?; + let improved_by = + SubmitFinalityProofHelper::::check_obsolete(number, Some(current_set_id))?; let authority_set = >::get(); let unused_proof_size = authority_set.unused_proof_size(); @@ -313,6 +315,7 @@ pub mod pallet { &finality_target, &justification, current_set_id, + improved_by, ); if may_refund_call_fee { FreeHeadersRemaining::::mutate(|count| { @@ -376,7 +379,7 @@ pub mod pallet { #[pallet::storage] #[pallet::whitelist_storage] #[pallet::getter(fn free_mandatory_headers_remaining)] - pub(super) type FreeHeadersRemaining, I: 'static = ()> = + pub type FreeHeadersRemaining, I: 'static = ()> = StorageValue<_, u32, OptionQuery>; /// Hash of the header used to bootstrap the pallet. @@ -514,6 +517,7 @@ pub mod pallet { finality_target: &BridgedHeader, justification: &GrandpaJustification>, current_set_id: SetId, + improved_by: BridgedBlockNumber, ) -> bool { // if we have refunded too much at this block => not refunding if FreeHeadersRemaining::::get().unwrap_or(0) == 0 { @@ -542,7 +546,7 @@ pub mod pallet { // if configuration allows free non-mandatory headers and the header // matches criteria => refund if let Some(free_headers_interval) = T::FreeHeadersInterval::get() { - if *finality_target.number() % free_headers_interval.into() == Zero::zero() { + if improved_by >= free_headers_interval.into() { return true; } } @@ -1517,31 +1521,50 @@ mod tests { run_test(|| { initialize_substrate_bridge(); + // set best finalized to `100` + const BEST: u8 = 12; + fn reset_best() { + BestFinalized::::set(Some(HeaderId( + BEST as _, + Default::default(), + ))); + } + // non-mandatory header is imported with fee - let non_free_header_number = FreeHeadersInterval::get() as u8 - 1; + reset_best(); + let non_free_header_number = BEST + FreeHeadersInterval::get() as u8 - 1; let result = submit_finality_proof(non_free_header_number); assert_eq!(result.unwrap().pays_fee, Pays::Yes); // non-mandatory free header is imported without fee - let free_header_number = FreeHeadersInterval::get() as u8; + reset_best(); + let free_header_number = BEST + FreeHeadersInterval::get() as u8; let result = submit_finality_proof(free_header_number); assert_eq!(result.unwrap().pays_fee, Pays::No); // another non-mandatory free header is imported without fee - let free_header_number = FreeHeadersInterval::get() as u8 * 2; + let free_header_number = BEST + FreeHeadersInterval::get() as u8 * 2; let result = submit_finality_proof(free_header_number); assert_eq!(result.unwrap().pays_fee, Pays::No); // now the rate limiter starts charging fees even for free headers - let free_header_number = FreeHeadersInterval::get() as u8 * 3; + let free_header_number = BEST + FreeHeadersInterval::get() as u8 * 3; let result = submit_finality_proof(free_header_number); assert_eq!(result.unwrap().pays_fee, Pays::Yes); + // check that we can import for free if `improved_by` is larger + // than the free interval next_block(); + reset_best(); + let free_header_number = FreeHeadersInterval::get() as u8 + 42; + let result = submit_finality_proof(free_header_number); + assert_eq!(result.unwrap().pays_fee, Pays::No); // check that the rate limiter shares the counter between mandatory // and free non-mandatory headers - let free_header_number = FreeHeadersInterval::get() as u8 * 4; + next_block(); + reset_best(); + let free_header_number = BEST + FreeHeadersInterval::get() as u8 * 4; let result = submit_finality_proof(free_header_number); assert_eq!(result.unwrap().pays_fee, Pays::No); let result = submit_mandatory_finality_proof(free_header_number + 1, 1); diff --git a/bridges/modules/parachains/src/call_ext.rs b/bridges/modules/parachains/src/call_ext.rs index 5747cb1007c6..1bb46d24d5c6 100644 --- a/bridges/modules/parachains/src/call_ext.rs +++ b/bridges/modules/parachains/src/call_ext.rs @@ -14,14 +14,16 @@ // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . -use crate::{ - Config, GrandpaPalletOf, Pallet, ParachainHeadsUpdateFilter, RelayBlockHash, RelayBlockNumber, -}; +use crate::{Config, GrandpaPalletOf, Pallet, RelayBlockHash, RelayBlockNumber}; use bp_header_chain::HeaderChain; use bp_parachains::BestParaHeadHash; use bp_polkadot_core::parachains::{ParaHash, ParaId}; use bp_runtime::OwnedBridgeModule; -use frame_support::{dispatch::CallableCallFor, traits::IsSubType}; +use frame_support::{ + dispatch::CallableCallFor, + traits::{Get, IsSubType}, +}; +use pallet_bridge_grandpa::SubmitFinalityProofHelper; use sp_runtime::{ transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction}, RuntimeDebug, @@ -37,6 +39,9 @@ pub struct SubmitParachainHeadsInfo { pub para_id: ParaId, /// Hash of the bundled parachain head. pub para_head_hash: ParaHash, + /// If `true`, then the call must be free (assuming that everything else is valid) to + /// be treated as valid. + pub is_free_execution_expected: bool, } /// Helper struct that provides methods for working with the `SubmitParachainHeads` call. @@ -45,24 +50,86 @@ pub struct SubmitParachainHeadsHelper, I: 'static> { } impl, I: 'static> SubmitParachainHeadsHelper { + /// Check that is called from signed extension and takes the `is_free_execution_expected` + /// into account. + pub fn is_obsolete_from_extension(update: &SubmitParachainHeadsInfo) -> bool { + // first do all base checks + let (is_obsolete, improved_by) = Self::is_obsolete(update); + if is_obsolete { + return true; + } + + // if we don't expect free execution - no more checks + if !update.is_free_execution_expected { + return false; + } + + // reject if no more free slots remaining in the block + if !SubmitFinalityProofHelper::::can_import_anything_for_free() { + log::trace!( + target: crate::LOG_TARGET, + "The free parachain {:?} head can't be updated: no more free slots \ + left in the block.", + update.para_id, + ); + + return true; + } + + // reject if we are importing parachain headers too often + if let Some(free_headers_interval) = T::FreeHeadersInterval::get() { + let reject = improved_by < free_headers_interval; + + if reject { + log::trace!( + target: crate::LOG_TARGET, + "The free parachain {:?} head can't be updated: it improves previous + best head by {} while at least {} is expected.", + update.para_id, + improved_by, + free_headers_interval, + ); + } + + reject + } else { + // free headers interval is not configured and call is expected to execute + // for free => reject it + log::trace!( + target: crate::LOG_TARGET, + "The free parachain {:?} head can't be updated: free interval is not \ + configured in the runtime.", + update.para_id, + ); + + true + } + } + /// Check if the para head provided by the `SubmitParachainHeads` is better than the best one /// we know. - pub fn is_obsolete(update: &SubmitParachainHeadsInfo) -> bool { + pub fn is_obsolete(update: &SubmitParachainHeadsInfo) -> (bool, RelayBlockNumber) { // check if we know better parachain head already - let is_free_execution_expected = match crate::ParasInfo::::get(update.para_id) { + let improved_by = match crate::ParasInfo::::get(update.para_id) { Some(stored_best_head) => { - if stored_best_head.best_head_hash.at_relay_block_number >= update.at_relay_block.0 + let improved_by = match update + .at_relay_block + .0 + .checked_sub(stored_best_head.best_head_hash.at_relay_block_number) { - log::trace!( - target: crate::LOG_TARGET, - "The parachain head can't be updated. The parachain head for {:?} \ - was already updated at better relay chain block {} >= {}.", - update.para_id, - stored_best_head.best_head_hash.at_relay_block_number, - update.at_relay_block.0 - ); - return true - } + Some(improved_by) if improved_by != 0 => improved_by, + _ => { + log::trace!( + target: crate::LOG_TARGET, + "The parachain head can't be updated. The parachain head for {:?} \ + was already updated at better relay chain block {} >= {}.", + update.para_id, + stored_best_head.best_head_hash.at_relay_block_number, + update.at_relay_block.0 + ); + return (true, 0) + }, + }; if stored_best_head.best_head_hash.head_hash == update.para_head_hash { log::trace!( @@ -74,37 +141,30 @@ impl, I: 'static> SubmitParachainHeadsHelper { stored_best_head.best_head_hash.at_relay_block_number, update.at_relay_block.0 ); - return true + return (true, 0) } - T::FreeHeadsUpdateFilter::is_free( - update.at_relay_block, - &[(update.para_id, update.para_head_hash)], - ) + improved_by }, - None => true, + None => RelayBlockNumber::MAX, }; - // the relayer may expect free execution, so let's double check if our chain has no - // reorgs and we still know the relay chain header used to craft the proof - if is_free_execution_expected { - if GrandpaPalletOf::::finalized_header_state_root(update.at_relay_block.1) - .is_none() - { - log::trace!( - target: crate::LOG_TARGET, - "The parachain {:?} head can't be updated. Relay chain header {}/{} used to create \ - parachain proof is missing from the storage.", - update.para_id, - update.at_relay_block.0, - update.at_relay_block.1, - ); - - return true - } + // let's check if our chain had no reorgs and we still know the relay chain header + // used to craft the proof + if GrandpaPalletOf::::finalized_header_state_root(update.at_relay_block.1).is_none() { + log::trace!( + target: crate::LOG_TARGET, + "The parachain {:?} head can't be updated. Relay chain header {}/{} used to create \ + parachain proof is missing from the storage.", + update.para_id, + update.at_relay_block.0, + update.at_relay_block.1, + ); + + return (true, 0) } - false + (false, improved_by) } /// Check if the `SubmitParachainHeads` was successfully executed. @@ -128,22 +188,36 @@ pub trait CallSubType, I: 'static>: /// Create a new instance of `SubmitParachainHeadsInfo` from a `SubmitParachainHeads` call with /// one single parachain entry. fn one_entry_submit_parachain_heads_info(&self) -> Option { - if let Some(crate::Call::::submit_parachain_heads { - ref at_relay_block, - ref parachains, - .. - }) = self.is_sub_type() - { - if let &[(para_id, para_head_hash)] = parachains.as_slice() { - return Some(SubmitParachainHeadsInfo { + match self.is_sub_type() { + Some(crate::Call::::submit_parachain_heads { + ref at_relay_block, + ref parachains, + .. + }) => match ¶chains[..] { + &[(para_id, para_head_hash)] => Some(SubmitParachainHeadsInfo { at_relay_block: *at_relay_block, para_id, para_head_hash, - }) - } + is_free_execution_expected: false, + }), + _ => None, + }, + Some(crate::Call::::submit_parachain_heads_ex { + ref at_relay_block, + ref parachains, + is_free_execution_expected, + .. + }) => match ¶chains[..] { + &[(para_id, para_head_hash)] => Some(SubmitParachainHeadsInfo { + at_relay_block: *at_relay_block, + para_id, + para_head_hash, + is_free_execution_expected: *is_free_execution_expected, + }), + _ => None, + }, + _ => None, } - - None } /// Create a new instance of `SubmitParachainHeadsInfo` from a `SubmitParachainHeads` call with @@ -176,7 +250,7 @@ pub trait CallSubType, I: 'static>: return InvalidTransaction::Call.into() } - if SubmitParachainHeadsHelper::::is_obsolete(&update) { + if SubmitParachainHeadsHelper::::is_obsolete_from_extension(&update) { return InvalidTransaction::Stale.into() } @@ -282,6 +356,7 @@ mod tests { // when current best finalized is #10 and we're trying to import header#15 => tx is // accepted sync_to_relay_header_10(); + insert_relay_block(15); assert!(validate_submit_parachain_heads(15, vec![(ParaId(1), [2u8; 32].into())])); }); } diff --git a/bridges/modules/parachains/src/lib.rs b/bridges/modules/parachains/src/lib.rs index 354b11167330..48256c15ab39 100644 --- a/bridges/modules/parachains/src/lib.rs +++ b/bridges/modules/parachains/src/lib.rs @@ -32,6 +32,7 @@ use bp_parachains::{parachain_head_storage_key_at_source, ParaInfo, ParaStoredHe use bp_polkadot_core::parachains::{ParaHash, ParaHead, ParaHeadsProof, ParaId}; use bp_runtime::{Chain, HashOf, HeaderId, HeaderIdOf, Parachain, StorageProofError}; use frame_support::{dispatch::PostDispatchInfo, DefaultNoBound}; +use pallet_bridge_grandpa::SubmitFinalityProofHelper; use sp_std::{marker::PhantomData, vec::Vec}; #[cfg(feature = "runtime-benchmarks")] @@ -65,24 +66,6 @@ pub type RelayBlockNumber = bp_polkadot_core::BlockNumber; /// Hasher of the bridged relay chain. pub type RelayBlockHasher = bp_polkadot_core::Hasher; -/// A filter of parachain head updates. -pub trait ParachainHeadsUpdateFilter { - /// Returns true if the update passes the filter. - fn is_free( - at_relay_block: (RelayBlockNumber, RelayBlockHash), - parachains: &[(ParaId, ParaHash)], - ) -> bool; -} - -impl ParachainHeadsUpdateFilter for () { - fn is_free( - _at_relay_block: (RelayBlockNumber, RelayBlockHash), - _parachains: &[(ParaId, ParaHash)], - ) -> bool { - false - } -} - /// Artifacts of the parachains head update. struct UpdateParachainHeadArtifacts { /// New best head of the parachain. @@ -211,25 +194,22 @@ pub mod pallet { /// /// The GRANDPA pallet instance must be configured to import headers of relay chain that /// we're interested in. - type BridgesGrandpaPalletInstance: 'static; - - /// A way to make `submit_parachain_heads` free for the submitter. If update passes - /// this filter AND at least one parachain head has been updated in the call, the - /// submission will be free for the submitter. /// - /// It can be used to reduce bridge fees by deducting parachain finality submission cost - /// from the total fee. Instead, relayers may submit some headers for free, allowing - /// queued messages (and confirmations) to be proved without providing additional - /// finality proofs. + /// The associated GRANDPA pallet is also used to configure free parachain heads submissions. + /// The parachain head submission will be free if: + /// + /// 1) the submission contains exactly one parachain head update that succeeds; /// - /// **IMPORTANT**: we are NOT limiting number of free calls per block. The filter must - /// take that into account or anyone could fill the block with parachain head updates - /// for free. + /// 2) the difference between relay chain block numbers, used to prove new parachain head and + /// previous best parachain head is larger than the `FreeHeadersInterval`, configured at + /// the associated GRANDPA pallet; /// - /// **IMPORTANT**: we expect that the filter don't touch any storage values or makes - /// any heavy computations. It is called by transaction pool during transaction validation, - /// so it should be lightweight. - type FreeHeadsUpdateFilter: ParachainHeadsUpdateFilter; + /// 3) there are slots for free submissions, remaining at the block. This is also configured + /// at the associated GRANDPA pallet using `MaxFreeHeadersPerBlock` parameter. + /// + /// First parachain head submission is also free for the submitted, if free submissions + /// are yet accepted to this block. + type BridgesGrandpaPalletInstance: 'static; /// Name of the original `paras` pallet in the `construct_runtime!()` call at the bridged /// chain. @@ -372,16 +352,82 @@ pub mod pallet { at_relay_block: (RelayBlockNumber, RelayBlockHash), parachains: Vec<(ParaId, ParaHash)>, parachain_heads_proof: ParaHeadsProof, + ) -> DispatchResultWithPostInfo { + Self::submit_parachain_heads_ex( + origin, + at_relay_block, + parachains, + parachain_heads_proof, + false, + ) + } + + /// Change `PalletOwner`. + /// + /// May only be called either by root, or by `PalletOwner`. + #[pallet::call_index(1)] + #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] + pub fn set_owner(origin: OriginFor, new_owner: Option) -> DispatchResult { + >::set_owner(origin, new_owner) + } + + /// Halt or resume all pallet operations. + /// + /// May only be called either by root, or by `PalletOwner`. + #[pallet::call_index(2)] + #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] + pub fn set_operating_mode( + origin: OriginFor, + operating_mode: BasicOperatingMode, + ) -> DispatchResult { + >::set_operating_mode(origin, operating_mode) + } + + /// Submit proof of one or several parachain heads. + /// + /// The proof is supposed to be proof of some `Heads` entries from the + /// `polkadot-runtime-parachains::paras` pallet instance, deployed at the bridged chain. + /// The proof is supposed to be crafted at the `relay_header_hash` that must already be + /// imported by corresponding GRANDPA pallet at this chain. + /// + /// The call fails if: + /// + /// - the pallet is halted; + /// + /// - the relay chain block `at_relay_block` is not imported by the associated bridge + /// GRANDPA pallet. + /// + /// The call may succeed, but some heads may not be updated e.g. because pallet knows + /// better head or it isn't tracked by the pallet. + /// + /// The `is_free_execution_expected` parameter is not really used inside the call. It is + /// used by the transaction extension, which should be registered at the runtime level. If + /// this parameter is `true`, the transaction will be treated as invalid, if the call won't + /// be executed for free. If transaction extension is not used by the runtime, this + /// parameter is not used at all. + #[pallet::call_index(3)] + #[pallet::weight(WeightInfoOf::::submit_parachain_heads_weight( + T::DbWeight::get(), + parachain_heads_proof, + parachains.len() as _, + ))] + pub fn submit_parachain_heads_ex( + origin: OriginFor, + at_relay_block: (RelayBlockNumber, RelayBlockHash), + parachains: Vec<(ParaId, ParaHash)>, + parachain_heads_proof: ParaHeadsProof, + _is_free_execution_expected: bool, ) -> DispatchResultWithPostInfo { Self::ensure_not_halted().map_err(Error::::BridgeModule)?; ensure_signed(origin)?; - // the pallet allows two kind of free submissions: - // 1) check whether filter allows this submission for free - let may_be_free = T::FreeHeadsUpdateFilter::is_free(at_relay_block, ¶chains); - // 2) refund allowed if all parachain heads are first headers known to us let total_parachains = parachains.len(); - let mut first_parachain_heads = 0; + let free_headers_interval = + T::FreeHeadersInterval::get().unwrap_or(RelayBlockNumber::MAX); + // the pallet allows two kind of free submissions + // 1) if distance between all parachain heads is gte than the [`T::FreeHeadersInterval`] + // 2) if all heads are the first heads of their parachains + let mut free_parachain_heads = 0; // we'll need relay chain header to verify that parachains heads are always increasing. let (relay_block_number, relay_block_hash) = at_relay_block; @@ -475,7 +521,15 @@ pub mod pallet { let update_result: Result<_, ()> = ParasInfo::::try_mutate(parachain, |stored_best_head| { - let is_first = stored_best_head.is_none(); + let is_free = match stored_best_head { + Some(ref best_head) + if at_relay_block.0.saturating_sub(best_head + .best_head_hash + .at_relay_block_number) >= free_headers_interval => + true, + Some(_) => false, + None => true, + }; let artifacts = Pallet::::update_parachain_head( parachain, stored_best_head.take(), @@ -484,9 +538,11 @@ pub mod pallet { parachain_head_hash, )?; + // TODO: do we want to refund submissions of large heads as we do for GRANDPA? + is_updated_something = true; - if is_first { - first_parachain_heads = first_parachain_heads + 1; + if is_free { + free_parachain_heads = free_parachain_heads + 1; } *stored_best_head = Some(artifacts.best_head); @@ -519,13 +575,11 @@ pub mod pallet { Error::::HeaderChainStorageProof(HeaderChainError::StorageProof(e)) })?; - // we allow free submissions if the update passes filter and something - // has been updated - // - // we allow free submission of the first parachain head - let is_free_by_filter_criteria = is_updated_something && may_be_free; - let is_free_by_first_criteria = total_parachains == first_parachain_heads; - let is_free = is_free_by_filter_criteria || is_free_by_first_criteria; + // check if we allow this submission for free +log::trace!("=== {} {} {}", total_parachains == 1, free_parachain_heads == total_parachains, SubmitFinalityProofHelper::::can_import_anything_for_free()); + let is_free = total_parachains == 1 + && free_parachain_heads == total_parachains + && SubmitFinalityProofHelper::::can_import_anything_for_free(); let pays_fee = if is_free { log::trace!(target: LOG_TARGET, "Parachain heads update transaction is free"); Pays::No @@ -536,27 +590,6 @@ pub mod pallet { Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee }) } - - /// Change `PalletOwner`. - /// - /// May only be called either by root, or by `PalletOwner`. - #[pallet::call_index(1)] - #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] - pub fn set_owner(origin: OriginFor, new_owner: Option) -> DispatchResult { - >::set_owner(origin, new_owner) - } - - /// Halt or resume all pallet operations. - /// - /// May only be called either by root, or by `PalletOwner`. - #[pallet::call_index(2)] - #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] - pub fn set_operating_mode( - origin: OriginFor, - operating_mode: BasicOperatingMode, - ) -> DispatchResult { - >::set_operating_mode(origin, operating_mode) - } } impl, I: 'static> Pallet { @@ -622,8 +655,10 @@ pub mod pallet { at_relay_block: new_at_relay_block, para_id: parachain, para_head_hash: new_head_hash, + // don't actually matter here + is_free_execution_expected: false, }; - if SubmitParachainHeadsHelper::::is_obsolete(&update) { + if SubmitParachainHeadsHelper::::is_obsolete(&update).0 { Self::deposit_event(Event::RejectedObsoleteParachainHead { parachain, parachain_head_hash: new_head_hash, @@ -782,7 +817,7 @@ pub fn initialize_for_benchmarks, I: 'static, PC: Parachain::DbWeight; pub(crate) fn initialize(state_root: RelayBlockHash) -> RelayBlockHash { + pallet_bridge_grandpa::FreeHeadersRemaining::::set(Some(100)); pallet_bridge_grandpa::Pallet::::initialize( RuntimeOrigin::root(), bp_header_chain::InitializationData { @@ -988,7 +1024,7 @@ pub(crate) mod tests { proof, ); assert_ok!(result); - assert_eq!(result.expect("checked above").pays_fee, Pays::No); + assert_eq!(result.expect("checked above").pays_fee, Pays::Yes); assert_eq!(result.expect("checked above").actual_weight, Some(expected_weight)); // 1 and 3 are updated, because proof is missing head of parachain#2 @@ -1746,76 +1782,30 @@ pub(crate) mod tests { proof, ); assert_eq!(result.unwrap().pays_fee, Pays::Yes); + // then we submit new head, proved at relay block `FreeHeadersInterval - 1` => Pays::Yes + let (state_root, proof, parachains) = + prepare_parachain_heads_proof::(vec![(2, head_data(2, 50))]); + let relay_block_number = FreeHeadersInterval::get() - 1; + proceed(relay_block_number, state_root); + let result = Pallet::::submit_parachain_heads( + RuntimeOrigin::signed(1), + (relay_block_number, test_relay_header(relay_block_number, state_root).hash()), + parachains, + proof, + ); + assert_eq!(result.unwrap().pays_fee, Pays::Yes); + // then we submit new head, proved after `FreeHeadersInterval` => Pays::No + let (state_root, proof, parachains) = + prepare_parachain_heads_proof::(vec![(2, head_data(2, 100))]); + let relay_block_number = relay_block_number + FreeHeadersInterval::get(); + proceed(relay_block_number, state_root); + let result = Pallet::::submit_parachain_heads( + RuntimeOrigin::signed(1), + (relay_block_number, test_relay_header(relay_block_number, state_root).hash()), + parachains, + proof, + ); + assert_eq!(result.unwrap().pays_fee, Pays::No); }) } - - #[test] - fn may_be_free_for_submitting_first_heads() { - fn run_sub_test(pre: impl FnOnce(), heads: Vec<(u32, ParaHead)>) -> Pays { - run_test(|| { - let (state_root, proof, parachains) = - prepare_parachain_heads_proof::(heads); - let relay_header_hash = initialize(state_root); - pre(); - let result = Pallet::::submit_parachain_heads( - RuntimeOrigin::signed(1), - (0, relay_header_hash), - parachains, - proof, - ); - result.unwrap().pays_fee - }) - } - - // if we submit 2 heads and both are first => Pays::No - assert_eq!(run_sub_test(|| (), vec![(1, head_data(1, 5)), (2, head_data(2, 5))]), Pays::No); - // if we submit 2 heads and one is not first => Pays::Yes - assert_eq!( - run_sub_test( - || { - ParasInfo::::insert( - ParaId(1), - ParaInfo { - best_head_hash: BestParaHeadHash { - at_relay_block_number: 1, - head_hash: Default::default(), - }, - next_imported_hash_position: 1, - }, - ); - }, - vec![(1, head_data(1, 5)), (2, head_data(2, 5))] - ), - Pays::Yes - ); - // if we submit 2 heads and both are not first => Pays::Yes - assert_eq!( - run_sub_test( - || { - ParasInfo::::insert( - ParaId(1), - ParaInfo { - best_head_hash: BestParaHeadHash { - at_relay_block_number: 1, - head_hash: Default::default(), - }, - next_imported_hash_position: 1, - }, - ); - ParasInfo::::insert( - ParaId(2), - ParaInfo { - best_head_hash: BestParaHeadHash { - at_relay_block_number: 2, - head_hash: Default::default(), - }, - next_imported_hash_position: 1, - }, - ); - }, - vec![(1, head_data(1, 5)), (2, head_data(2, 5))] - ), - Pays::Yes - ); - } } diff --git a/bridges/modules/parachains/src/mock.rs b/bridges/modules/parachains/src/mock.rs index 43fe189543ed..2c5de95b14dc 100644 --- a/bridges/modules/parachains/src/mock.rs +++ b/bridges/modules/parachains/src/mock.rs @@ -27,7 +27,6 @@ use sp_runtime::{ }; use crate as pallet_bridge_parachains; -use crate::{ParaHash, ParachainHeadsUpdateFilter, RelayBlockHash, RelayBlockNumber}; pub type AccountId = u64; @@ -169,13 +168,14 @@ impl frame_system::Config for TestRuntime { parameter_types! { pub const HeadersToKeep: u32 = 5; + pub const FreeHeadersInterval: u32 = 15; } impl pallet_bridge_grandpa::Config for TestRuntime { type RuntimeEvent = RuntimeEvent; type BridgedChain = TestBridgedChain; type MaxFreeHeadersPerBlock = ConstU32<2>; - type FreeHeadersInterval = ConstU32<15>; + type FreeHeadersInterval = FreeHeadersInterval; type HeadersToKeep = HeadersToKeep; type WeightInfo = (); } @@ -184,7 +184,7 @@ impl pallet_bridge_grandpa::Config for TestRun type RuntimeEvent = RuntimeEvent; type BridgedChain = TestBridgedChain; type MaxFreeHeadersPerBlock = ConstU32<2>; - type FreeHeadersInterval = ConstU32<1_024>; + type FreeHeadersInterval = FreeHeadersInterval; type HeadersToKeep = HeadersToKeep; type WeightInfo = (); } @@ -199,24 +199,12 @@ impl pallet_bridge_parachains::Config for TestRuntime { type RuntimeEvent = RuntimeEvent; type WeightInfo = (); type BridgesGrandpaPalletInstance = pallet_bridge_grandpa::Instance1; - type FreeHeadsUpdateFilter = FreeForParachain2; type ParasPalletName = ParasPalletName; type ParaStoredHeaderDataBuilder = (Parachain1, Parachain2, Parachain3, BigParachain); type HeadsToKeep = HeadsToKeep; type MaxParaHeadDataSize = ConstU32; } -pub struct FreeForParachain2; - -impl ParachainHeadsUpdateFilter for FreeForParachain2 { - fn is_free( - _at_relay_block: (RelayBlockNumber, RelayBlockHash), - parachains: &[(ParaId, ParaHash)], - ) -> bool { - parachains.len() == 1 && parachains[0].0 .0 == Parachain2::PARACHAIN_ID - } -} - #[cfg(feature = "runtime-benchmarks")] impl pallet_bridge_parachains::benchmarking::Config<()> for TestRuntime { fn parachains() -> Vec { From efeba93cc789d0366397197c97bd5701ffc64734 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 22 Mar 2024 16:00:21 +0300 Subject: [PATCH 20/70] fixed trace --- bridges/modules/grandpa/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/modules/grandpa/src/lib.rs b/bridges/modules/grandpa/src/lib.rs index 74a00405a381..d64424fbf712 100644 --- a/bridges/modules/grandpa/src/lib.rs +++ b/bridges/modules/grandpa/src/lib.rs @@ -341,7 +341,7 @@ pub mod pallet { target: LOG_TARGET, "Successfully imported finalized header with hash {:?}! Free: {}", hash, - if may_refund_call_fee { "No" } else { "Yes" }, + if may_refund_call_fee { "Yes" } else { "No" }, ); // the proof size component of the call weight assumes that there are From 85a5a40752f109ef0450121a88762cfece4d7206 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 22 Mar 2024 16:03:21 +0300 Subject: [PATCH 21/70] CannotAcceptMoreFreeHeaders -> FreeHeadersLimitExceded --- bridges/modules/grandpa/src/call_ext.rs | 2 +- bridges/modules/grandpa/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bridges/modules/grandpa/src/call_ext.rs b/bridges/modules/grandpa/src/call_ext.rs index f4d6732423bc..d0d83281359c 100644 --- a/bridges/modules/grandpa/src/call_ext.rs +++ b/bridges/modules/grandpa/src/call_ext.rs @@ -117,7 +117,7 @@ impl, I: 'static> SubmitFinalityProofHelper { call_info.block_number, ); - return Err(Error::::CannotAcceptMoreFreeHeaders); + return Err(Error::::FreeHeadersLimitExceded); } // we do not check whether the header matches free submission criteria here - it is the diff --git a/bridges/modules/grandpa/src/lib.rs b/bridges/modules/grandpa/src/lib.rs index d64424fbf712..8a36617120e3 100644 --- a/bridges/modules/grandpa/src/lib.rs +++ b/bridges/modules/grandpa/src/lib.rs @@ -507,7 +507,7 @@ pub mod pallet { InvalidAuthoritySetId, /// The submitter wanted free execution, but we can't fit more free transactions /// to the block. - CannotAcceptMoreFreeHeaders, + FreeHeadersLimitExceded, } /// Return true if we may refund transaction cost to the submitter. In other words, From 0c87d48b128495677a5324377cc33ee5118b0114 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 22 Mar 2024 16:03:32 +0300 Subject: [PATCH 22/70] additional_call_result_check -> check_call_result_ex --- .../src/extensions/refund_relayer_extension.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs index 79f4600bd818..9ac952e65fce 100644 --- a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs +++ b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs @@ -242,7 +242,7 @@ pub trait RefundSignedExtension: /// Called from post-dispatch and shall perform additional checks (apart from messages /// transaction success) of given call result. - fn additional_call_result_check( + fn check_call_result_ex( relayer: &AccountIdOf, call_info: &CallInfo, extra_weight: &mut Weight, @@ -329,7 +329,7 @@ pub trait RefundSignedExtension: } // do additional checks - if !Self::additional_call_result_check( + if !Self::check_call_result_ex( &relayer, &call_info, &mut extra_weight, @@ -653,7 +653,7 @@ where Ok(call) } - fn additional_call_result_check( + fn check_call_result_ex( relayer: &Runtime::AccountId, call_info: &CallInfo, extra_weight: &mut Weight, @@ -668,7 +668,7 @@ where Refund, Priority, Id, - >::additional_call_result_check(relayer, call_info, extra_weight, extra_size); + >::check_call_result_ex(relayer, call_info, extra_weight, extra_size); if !is_granda_call_succeeded { return false } @@ -796,7 +796,7 @@ where Ok(call) } - fn additional_call_result_check( + fn check_call_result_ex( relayer: &Runtime::AccountId, call_info: &CallInfo, extra_weight: &mut Weight, @@ -902,7 +902,7 @@ where Ok(call) } - fn additional_call_result_check( + fn check_call_result_ex( _relayer: &Runtime::AccountId, _call_info: &CallInfo, _extra_weight: &mut Weight, From c3f0bc55feb604c8ce5f461d741cf0fe045a9290 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 22 Mar 2024 16:04:07 +0300 Subject: [PATCH 23/70] is_granda_call_succeeded -> is_grandpa_call_successful --- .../runtime-common/src/extensions/refund_relayer_extension.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs index 9ac952e65fce..2f692222166c 100644 --- a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs +++ b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs @@ -660,7 +660,7 @@ where extra_size: &mut u32, ) -> bool { // check if relay chain state has been updated - let is_granda_call_succeeded = + let is_grandpa_call_successful = RefundBridgedGrandpaMessages::< Runtime, Runtime::BridgesGrandpaPalletInstance, @@ -669,7 +669,7 @@ where Priority, Id, >::check_call_result_ex(relayer, call_info, extra_weight, extra_size); - if !is_granda_call_succeeded { + if !is_grandpa_call_successful { return false } From 8e710d293c33088472e3995a72b4fdfc48790624 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 22 Mar 2024 16:33:23 +0300 Subject: [PATCH 24/70] move tx priority boost from check_obsolete_submit_finality_proof to extension --- .../extensions/check_obsolete_extension.rs | 29 +++- .../extensions/refund_relayer_extension.rs | 11 +- bridges/modules/grandpa/src/call_ext.rs | 129 +++++++----------- bridges/modules/parachains/src/lib.rs | 37 ++--- 4 files changed, 100 insertions(+), 106 deletions(-) diff --git a/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs b/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs index 83ac8c1051e2..3f937b5c66f6 100644 --- a/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs +++ b/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs @@ -26,8 +26,9 @@ use pallet_bridge_grandpa::{ use pallet_bridge_parachains::CallSubType as ParachainsCallSubtype; use pallet_bridge_relayers::Pallet as RelayersPallet; use sp_runtime::{ - traits::{Get, PhantomData}, - transaction_validity::{TransactionPriority, TransactionValidity}, + traits::{Get, One, PhantomData, UniqueSaturatedInto}, + transaction_validity::{TransactionPriority, TransactionValidity, ValidTransactionBuilder}, + Saturating, }; /// A duplication of the `FilterCall` trait. @@ -71,9 +72,23 @@ where ) -> (Self::ToPostDispatch, TransactionValidity) { // we only boost priority if relayer has staked required balance let is_relayer_registration_active = RelayersPallet::::is_registration_active(who); - let boost_per_header = if is_relayer_registration_active { Priority::get() } else { 0 }; - GrandpaCallSubType::::check_obsolete_submit_finality_proof(call, boost_per_header) + match GrandpaCallSubType::::check_obsolete_submit_finality_proof(call) { + Ok(Some(our_tx)) => { + let block_number = Some(our_tx.base.block_number); + let improved_by: TransactionPriority = + our_tx.improved_by.saturating_sub(One::one()).unique_saturated_into(); + let boost_per_header = + if is_relayer_registration_active { Priority::get() } else { 0 }; + let total_priority_boost = improved_by.saturating_mul(boost_per_header); + ( + block_number, + ValidTransactionBuilder::default().priority(total_priority_boost).build(), + ) + }, + Ok(None) => (None, ValidTransactionBuilder::default().build()), + Err(e) => (None, Err(e)), + } } fn post_dispatch( @@ -107,7 +122,11 @@ where { type ToPostDispatch = (); fn validate(_who: &T::AccountId, call: &T::RuntimeCall) -> ((), TransactionValidity) { - ((), GrandpaCallSubType::::check_obsolete_submit_finality_proof(call, 0).1) + ( + (), + GrandpaCallSubType::::check_obsolete_submit_finality_proof(call) + .and_then(|_| ValidTransactionBuilder::default().build()), + ) } } diff --git a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs index 2f692222166c..486f378584f6 100644 --- a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs +++ b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs @@ -329,12 +329,7 @@ pub trait RefundSignedExtension: } // do additional checks - if !Self::check_call_result_ex( - &relayer, - &call_info, - &mut extra_weight, - &mut extra_size, - ) { + if !Self::check_call_result_ex(&relayer, &call_info, &mut extra_weight, &mut extra_size) { return slash_relayer_if_delivery_result } @@ -647,7 +642,7 @@ where fn check_obsolete_parsed_call( call: &CallOf, ) -> Result<&CallOf, TransactionValidityError> { - call.check_obsolete_submit_finality_proof(0).1?; + call.check_obsolete_submit_finality_proof()?; call.check_obsolete_submit_parachain_heads()?; call.check_obsolete_call()?; Ok(call) @@ -791,7 +786,7 @@ where fn check_obsolete_parsed_call( call: &CallOf, ) -> Result<&CallOf, TransactionValidityError> { - call.check_obsolete_submit_finality_proof(0).1?; + call.check_obsolete_submit_finality_proof()?; call.check_obsolete_call()?; Ok(call) } diff --git a/bridges/modules/grandpa/src/call_ext.rs b/bridges/modules/grandpa/src/call_ext.rs index d0d83281359c..bed261c1a81a 100644 --- a/bridges/modules/grandpa/src/call_ext.rs +++ b/bridges/modules/grandpa/src/call_ext.rs @@ -27,12 +27,9 @@ use codec::Encode; use frame_support::{dispatch::CallableCallFor, traits::IsSubType, weights::Weight}; use sp_consensus_grandpa::SetId; use sp_runtime::{ - traits::{CheckedSub, Header, One, UniqueSaturatedInto, Zero}, - transaction_validity::{ - InvalidTransaction, TransactionPriority, TransactionValidity, ValidTransaction, - ValidTransactionBuilder, - }, - RuntimeDebug, SaturatedConversion, Saturating, + traits::{CheckedSub, Header, Zero}, + transaction_validity::{InvalidTransaction, TransactionValidityError}, + RuntimeDebug, SaturatedConversion, }; /// Info about a `SubmitParachainHeads` call which tries to update a single parachain. @@ -60,6 +57,16 @@ pub struct SubmitFinalityProofInfo { pub extra_size: u32, } +/// Verified `SubmitFinalityProofInfo` +#[derive(Copy, Clone, PartialEq, RuntimeDebug)] +pub struct VerifiedSubmitFinalityProofInfo { + /// Base call information. + pub base: SubmitFinalityProofInfo, + /// A difference between bundled bridged header and best bridged header known to us + /// before the call. + pub improved_by: N, +} + impl SubmitFinalityProofInfo { /// Returns `true` if call size/weight is below our estimations for regular calls. pub fn fits_limits(&self) -> bool { @@ -225,45 +232,36 @@ pub trait CallSubType, I: 'static>: /// bridged chain headers. Without this validation, even honest relayers may lose their funds /// if there are multiple relays running and submitting the same information. /// - /// It also adds `priority_boost` for every missed header between best finalized header, known - /// to the pallet and bundled header, staring from the second header. So if - /// `BestFinalized` header is header number `100` and transaction brings header - /// `101` there's no priority boost. If transaction brings header `102`, then - /// priority is boosted by `priority_boost` and so on. - /// - /// If first item in the tuple is true, then the call is the `submit_finality_proof_info` - /// (or `submit_finality_proof_info_ex` call) of the associated pallet instance. + /// Returns `Ok(None)` if the call is not the `submit_finality_proof` call of our pallet. + /// Returns `Ok(Some(_))` if the call is the `submit_finality_proof` call of our pallet and + /// we believe the call brings header that improves the pallet state. + /// Returns `Err(_)` if the call is the `submit_finality_proof` call of our pallet and we + /// believe that the call will fail. fn check_obsolete_submit_finality_proof( &self, - priority_boost: TransactionPriority, - ) -> (Option>, TransactionValidity) + ) -> Result< + Option>>, + TransactionValidityError, + > where Self: Sized, { let call_info = match self.submit_finality_proof_info() { Some(finality_proof) => finality_proof, - _ => return (None, Ok(ValidTransaction::default())), + _ => return Ok(None), }; - let block_number = Some(call_info.block_number); if Pallet::::ensure_not_halted().is_err() { - return (block_number, InvalidTransaction::Call.into()) + return Err(InvalidTransaction::Call.into()) } let result = SubmitFinalityProofHelper::::check_obsolete_from_extension(&call_info); - ( - block_number, - match result { - Ok(improved_by) => { - let improved_by: TransactionPriority = - improved_by.saturating_sub(One::one()).unique_saturated_into(); - let total_priority_boost = improved_by.saturating_mul(priority_boost); - ValidTransactionBuilder::default().priority(total_priority_boost).build() - }, - Err(Error::::OldHeader) => InvalidTransaction::Stale.into(), - Err(_) => InvalidTransaction::Call.into(), - }, - ) + match result { + Ok(improved_by) => + Ok(Some(VerifiedSubmitFinalityProofInfo { base: call_info, improved_by })), + Err(Error::::OldHeader) => Err(InvalidTransaction::Stale.into()), + Err(_) => Err(InvalidTransaction::Call.into()), + } } } @@ -353,11 +351,9 @@ mod tests { current_set_id: 0, is_free_execution_expected: false, }; - RuntimeCall::check_obsolete_submit_finality_proof( - &RuntimeCall::Grandpa(bridge_grandpa_call), - 0, - ) - .1 + RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa( + bridge_grandpa_call, + )) .is_ok() } @@ -423,29 +419,23 @@ mod tests { // when we can accept free headers => Ok FreeHeadersRemaining::::put(2); - assert!(RuntimeCall::check_obsolete_submit_finality_proof( - &RuntimeCall::Grandpa(bridge_grandpa_call.clone(),), - 0 - ) - .1 + assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa( + bridge_grandpa_call.clone(), + ),) .is_ok()); // when we can NOT accept free headers => Err FreeHeadersRemaining::::put(0); - assert!(RuntimeCall::check_obsolete_submit_finality_proof( - &RuntimeCall::Grandpa(bridge_grandpa_call.clone(),), - 0 - ) - .1 + assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa( + bridge_grandpa_call.clone(), + ),) .is_err()); // when called outside of transaction => Ok FreeHeadersRemaining::::kill(); - assert!(RuntimeCall::check_obsolete_submit_finality_proof( - &RuntimeCall::Grandpa(bridge_grandpa_call,), - 0 - ) - .1 + assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa( + bridge_grandpa_call, + ),) .is_ok()); }) } @@ -571,7 +561,7 @@ mod tests { } #[test] - fn check_obsolete_submit_finality_proof_boosts_priority() { + fn check_obsolete_submit_finality_proof_returns_correct_improved_by() { run_test(|| { fn make_call(number: u64) -> RuntimeCall { RuntimeCall::Grandpa(crate::Call::::submit_finality_proof_ex { @@ -582,37 +572,24 @@ mod tests { }) } - // when priority boost is zero, total boost is also zero sync_to_header_10(); - let result = RuntimeCall::check_obsolete_submit_finality_proof(&make_call(15), 0); - assert_eq!(result.0, Some(15)); - assert_eq!(result.1.unwrap().priority, 0,); - // when the difference between headers is 1, no boost + // when the difference between headers is 1 assert_eq!( - RuntimeCall::check_obsolete_submit_finality_proof(&make_call(11), 100) - .1 + RuntimeCall::check_obsolete_submit_finality_proof(&make_call(11)) .unwrap() - .priority, - 0, - ); - - // when the difference between headers is 2 => boost - assert_eq!( - RuntimeCall::check_obsolete_submit_finality_proof(&make_call(12), 100) - .1 .unwrap() - .priority, - 100, + .improved_by, + 1, ); - // when the difference between headers is 3 => 2 * boost + // when the difference between headers is 2 assert_eq!( - RuntimeCall::check_obsolete_submit_finality_proof(&make_call(13), 100) - .1 + RuntimeCall::check_obsolete_submit_finality_proof(&make_call(12)) + .unwrap() .unwrap() - .priority, - 200, + .improved_by, + 2, ); }) } @@ -623,7 +600,7 @@ mod tests { let call = RuntimeCall::System(frame_system::Call::::remark { remark: vec![42] }); - assert_eq!(RuntimeCall::check_obsolete_submit_finality_proof(&call, 0).0, None); + assert_eq!(RuntimeCall::check_obsolete_submit_finality_proof(&call), Ok(None)); }) } } diff --git a/bridges/modules/parachains/src/lib.rs b/bridges/modules/parachains/src/lib.rs index 48256c15ab39..1833a98203e2 100644 --- a/bridges/modules/parachains/src/lib.rs +++ b/bridges/modules/parachains/src/lib.rs @@ -195,14 +195,14 @@ pub mod pallet { /// The GRANDPA pallet instance must be configured to import headers of relay chain that /// we're interested in. /// - /// The associated GRANDPA pallet is also used to configure free parachain heads submissions. - /// The parachain head submission will be free if: + /// The associated GRANDPA pallet is also used to configure free parachain heads + /// submissions. The parachain head submission will be free if: /// /// 1) the submission contains exactly one parachain head update that succeeds; /// - /// 2) the difference between relay chain block numbers, used to prove new parachain head and - /// previous best parachain head is larger than the `FreeHeadersInterval`, configured at - /// the associated GRANDPA pallet; + /// 2) the difference between relay chain block numbers, used to prove new parachain head + /// and previous best parachain head is larger than the `FreeHeadersInterval`, configured + /// at the associated GRANDPA pallet; /// /// 3) there are slots for free submissions, remaining at the block. This is also configured /// at the associated GRANDPA pallet using `MaxFreeHeadersPerBlock` parameter. @@ -523,9 +523,9 @@ pub mod pallet { ParasInfo::::try_mutate(parachain, |stored_best_head| { let is_free = match stored_best_head { Some(ref best_head) - if at_relay_block.0.saturating_sub(best_head - .best_head_hash - .at_relay_block_number) >= free_headers_interval => + if at_relay_block.0.saturating_sub( + best_head.best_head_hash.at_relay_block_number, + ) >= free_headers_interval => true, Some(_) => false, None => true, @@ -538,7 +538,8 @@ pub mod pallet { parachain_head_hash, )?; - // TODO: do we want to refund submissions of large heads as we do for GRANDPA? + // TODO: do we want to refund submissions of large heads as we do for + // GRANDPA? is_updated_something = true; if is_free { @@ -576,7 +577,7 @@ pub mod pallet { })?; // check if we allow this submission for free -log::trace!("=== {} {} {}", total_parachains == 1, free_parachain_heads == total_parachains, SubmitFinalityProofHelper::::can_import_anything_for_free()); + log::trace!("=== {} {} {}", total_parachains == 1, free_parachain_heads == total_parachains, SubmitFinalityProofHelper::::can_import_anything_for_free()); let is_free = total_parachains == 1 && free_parachain_heads == total_parachains && SubmitFinalityProofHelper::::can_import_anything_for_free(); @@ -817,9 +818,9 @@ pub fn initialize_for_benchmarks, I: 'static, PC: Parachain Pays::Yes - let (state_root, proof, parachains) = - prepare_parachain_heads_proof::(vec![(2, head_data(2, 50))]); + let (state_root, proof, parachains) = prepare_parachain_heads_proof::< + RegularParachainHeader, + >(vec![(2, head_data(2, 50))]); let relay_block_number = FreeHeadersInterval::get() - 1; proceed(relay_block_number, state_root); let result = Pallet::::submit_parachain_heads( @@ -1795,8 +1797,9 @@ pub(crate) mod tests { ); assert_eq!(result.unwrap().pays_fee, Pays::Yes); // then we submit new head, proved after `FreeHeadersInterval` => Pays::No - let (state_root, proof, parachains) = - prepare_parachain_heads_proof::(vec![(2, head_data(2, 100))]); + let (state_root, proof, parachains) = prepare_parachain_heads_proof::< + RegularParachainHeader, + >(vec![(2, head_data(2, 100))]); let relay_block_number = relay_block_number + FreeHeadersInterval::get(); proceed(relay_block_number, state_root); let result = Pallet::::submit_parachain_heads( From 3ca9d7eb600e42a14cdb048de2f64993a9fe79ef Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 22 Mar 2024 16:39:43 +0300 Subject: [PATCH 25/70] lost changes --- .../extensions/refund_relayer_extension.rs | 21 ++++++++++++------- bridges/modules/grandpa/src/lib.rs | 2 +- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs index 486f378584f6..fe758f7d5e12 100644 --- a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs +++ b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs @@ -242,7 +242,7 @@ pub trait RefundSignedExtension: /// Called from post-dispatch and shall perform additional checks (apart from messages /// transaction success) of given call result. - fn check_call_result_ex( + fn additional_call_result_check( relayer: &AccountIdOf, call_info: &CallInfo, extra_weight: &mut Weight, @@ -329,7 +329,12 @@ pub trait RefundSignedExtension: } // do additional checks - if !Self::check_call_result_ex(&relayer, &call_info, &mut extra_weight, &mut extra_size) { + if !Self::additional_call_result_check( + &relayer, + &call_info, + &mut extra_weight, + &mut extra_size, + ) { return slash_relayer_if_delivery_result } @@ -648,14 +653,14 @@ where Ok(call) } - fn check_call_result_ex( + fn additional_call_result_check( relayer: &Runtime::AccountId, call_info: &CallInfo, extra_weight: &mut Weight, extra_size: &mut u32, ) -> bool { // check if relay chain state has been updated - let is_grandpa_call_successful = + let is_granda_call_succeeded = RefundBridgedGrandpaMessages::< Runtime, Runtime::BridgesGrandpaPalletInstance, @@ -663,8 +668,8 @@ where Refund, Priority, Id, - >::check_call_result_ex(relayer, call_info, extra_weight, extra_size); - if !is_grandpa_call_successful { + >::additional_call_result_check(relayer, call_info, extra_weight, extra_size); + if !is_granda_call_succeeded { return false } @@ -791,7 +796,7 @@ where Ok(call) } - fn check_call_result_ex( + fn additional_call_result_check( relayer: &Runtime::AccountId, call_info: &CallInfo, extra_weight: &mut Weight, @@ -897,7 +902,7 @@ where Ok(call) } - fn check_call_result_ex( + fn additional_call_result_check( _relayer: &Runtime::AccountId, _call_info: &CallInfo, _extra_weight: &mut Weight, diff --git a/bridges/modules/grandpa/src/lib.rs b/bridges/modules/grandpa/src/lib.rs index 8a36617120e3..a57d99c27a01 100644 --- a/bridges/modules/grandpa/src/lib.rs +++ b/bridges/modules/grandpa/src/lib.rs @@ -341,7 +341,7 @@ pub mod pallet { target: LOG_TARGET, "Successfully imported finalized header with hash {:?}! Free: {}", hash, - if may_refund_call_fee { "Yes" } else { "No" }, + if may_refund_call_fee { "No" } else { "Yes" }, ); // the proof size component of the call weight assumes that there are From 7f1375bc16c0a7d92e404dbaddd17468ca786d80 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 22 Mar 2024 17:17:24 +0300 Subject: [PATCH 26/70] dec counter when importing new para header --- bridges/modules/grandpa/src/lib.rs | 17 +++++++++++------ bridges/modules/parachains/src/lib.rs | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/bridges/modules/grandpa/src/lib.rs b/bridges/modules/grandpa/src/lib.rs index a57d99c27a01..abda4eb38bda 100644 --- a/bridges/modules/grandpa/src/lib.rs +++ b/bridges/modules/grandpa/src/lib.rs @@ -318,12 +318,7 @@ pub mod pallet { improved_by, ); if may_refund_call_fee { - FreeHeadersRemaining::::mutate(|count| { - *count = match *count { - Some(count) if count > 1 => Some(count - 1), - _ => None, - } - }); + on_free_header_imported::(); } insert_header::(*finality_target, hash); @@ -510,6 +505,16 @@ pub mod pallet { FreeHeadersLimitExceded, } + /// Called when new free header is imported. + pub fn on_free_header_imported, I: 'static>() { + FreeHeadersRemaining::::mutate(|count| { + *count = match *count { + Some(count) => Some(count.saturating_sub(1)), + None => None, + } + }); + } + /// Return true if we may refund transaction cost to the submitter. In other words, /// this transaction is considered as common good deed w.r.t to pallet configuration. fn may_refund_call_fee, I: 'static>( diff --git a/bridges/modules/parachains/src/lib.rs b/bridges/modules/parachains/src/lib.rs index 1833a98203e2..566893acaab2 100644 --- a/bridges/modules/parachains/src/lib.rs +++ b/bridges/modules/parachains/src/lib.rs @@ -577,12 +577,12 @@ pub mod pallet { })?; // check if we allow this submission for free - log::trace!("=== {} {} {}", total_parachains == 1, free_parachain_heads == total_parachains, SubmitFinalityProofHelper::::can_import_anything_for_free()); let is_free = total_parachains == 1 && free_parachain_heads == total_parachains && SubmitFinalityProofHelper::::can_import_anything_for_free(); let pays_fee = if is_free { log::trace!(target: LOG_TARGET, "Parachain heads update transaction is free"); + pallet_bridge_grandpa::on_free_header_imported::(); Pays::No } else { log::trace!(target: LOG_TARGET, "Parachain heads update transaction is paid"); From 5772858241792ea94c1c417941176e633fdb8f04 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 12 Apr 2024 15:40:10 +0300 Subject: [PATCH 27/70] cherry pick https://github.com/paritytech/parity-bridges-common/pull/2873/commits/bb4ad4b357acdb1fa8b43c60adb5e63192265068 --- bridges/bin/runtime-common/src/mock.rs | 1 + .../chain-bridge-hub-cumulus/src/lib.rs | 3 ++ .../chains/chain-bridge-hub-kusama/src/lib.rs | 1 + .../chain-bridge-hub-polkadot/src/lib.rs | 1 + .../chains/chain-bridge-hub-rococo/src/lib.rs | 1 + .../chain-bridge-hub-westend/src/lib.rs | 1 + bridges/modules/parachains/src/lib.rs | 41 ++++++++++++++----- bridges/modules/parachains/src/mock.rs | 4 ++ bridges/primitives/parachains/src/lib.rs | 19 +++++++++ bridges/primitives/runtime/src/chain.rs | 8 ++++ .../relays/client-substrate/src/test_chain.rs | 1 + 11 files changed, 70 insertions(+), 11 deletions(-) diff --git a/bridges/bin/runtime-common/src/mock.rs b/bridges/bin/runtime-common/src/mock.rs index 3beb52bbe7a7..e323f1edfc71 100644 --- a/bridges/bin/runtime-common/src/mock.rs +++ b/bridges/bin/runtime-common/src/mock.rs @@ -407,6 +407,7 @@ impl Chain for BridgedUnderlyingParachain { impl Parachain for BridgedUnderlyingParachain { const PARACHAIN_ID: u32 = 42; + const MAX_HEADER_SIZE: u32 = 1_024; } /// The other, bridged chain, used in tests. diff --git a/bridges/chains/chain-bridge-hub-cumulus/src/lib.rs b/bridges/chains/chain-bridge-hub-cumulus/src/lib.rs index c49aa4b85639..a5c90ceba111 100644 --- a/bridges/chains/chain-bridge-hub-cumulus/src/lib.rs +++ b/bridges/chains/chain-bridge-hub-cumulus/src/lib.rs @@ -39,6 +39,9 @@ use frame_support::{ use frame_system::limits; use sp_std::time::Duration; +/// Maximal bridge hub header size. +pub const MAX_BRIDGE_HUB_HEADER_SIZE: u32 = 4_096; + /// Average block interval in Cumulus-based parachains. /// /// Corresponds to the `MILLISECS_PER_BLOCK` from `parachains_common` crate. diff --git a/bridges/chains/chain-bridge-hub-kusama/src/lib.rs b/bridges/chains/chain-bridge-hub-kusama/src/lib.rs index 576e3dbee80d..ef3ef4ab7b7a 100644 --- a/bridges/chains/chain-bridge-hub-kusama/src/lib.rs +++ b/bridges/chains/chain-bridge-hub-kusama/src/lib.rs @@ -62,6 +62,7 @@ impl Chain for BridgeHubKusama { impl Parachain for BridgeHubKusama { const PARACHAIN_ID: u32 = BRIDGE_HUB_KUSAMA_PARACHAIN_ID; + const MAX_HEADER_SIZE: u32 = MAX_BRIDGE_HUB_HEADER_SIZE; } impl ChainWithMessages for BridgeHubKusama { diff --git a/bridges/chains/chain-bridge-hub-polkadot/src/lib.rs b/bridges/chains/chain-bridge-hub-polkadot/src/lib.rs index 6db389c92994..9db71af928e5 100644 --- a/bridges/chains/chain-bridge-hub-polkadot/src/lib.rs +++ b/bridges/chains/chain-bridge-hub-polkadot/src/lib.rs @@ -59,6 +59,7 @@ impl Chain for BridgeHubPolkadot { impl Parachain for BridgeHubPolkadot { const PARACHAIN_ID: u32 = BRIDGE_HUB_POLKADOT_PARACHAIN_ID; + const MAX_HEADER_SIZE: u32 = MAX_BRIDGE_HUB_HEADER_SIZE; } impl ChainWithMessages for BridgeHubPolkadot { diff --git a/bridges/chains/chain-bridge-hub-rococo/src/lib.rs b/bridges/chains/chain-bridge-hub-rococo/src/lib.rs index abce872d7ba3..c504620d97b5 100644 --- a/bridges/chains/chain-bridge-hub-rococo/src/lib.rs +++ b/bridges/chains/chain-bridge-hub-rococo/src/lib.rs @@ -59,6 +59,7 @@ impl Chain for BridgeHubRococo { impl Parachain for BridgeHubRococo { const PARACHAIN_ID: u32 = BRIDGE_HUB_ROCOCO_PARACHAIN_ID; + const MAX_HEADER_SIZE: u32 = MAX_BRIDGE_HUB_HEADER_SIZE; } impl ChainWithMessages for BridgeHubRococo { diff --git a/bridges/chains/chain-bridge-hub-westend/src/lib.rs b/bridges/chains/chain-bridge-hub-westend/src/lib.rs index 4af895cc6d32..8c4e03db025f 100644 --- a/bridges/chains/chain-bridge-hub-westend/src/lib.rs +++ b/bridges/chains/chain-bridge-hub-westend/src/lib.rs @@ -58,6 +58,7 @@ impl Chain for BridgeHubWestend { impl Parachain for BridgeHubWestend { const PARACHAIN_ID: u32 = BRIDGE_HUB_WESTEND_PARACHAIN_ID; + const MAX_HEADER_SIZE: u32 = MAX_BRIDGE_HUB_HEADER_SIZE; } impl ChainWithMessages for BridgeHubWestend { diff --git a/bridges/modules/parachains/src/lib.rs b/bridges/modules/parachains/src/lib.rs index 566893acaab2..cc1e7bd63257 100644 --- a/bridges/modules/parachains/src/lib.rs +++ b/bridges/modules/parachains/src/lib.rs @@ -505,6 +505,7 @@ pub mod pallet { } // convert from parachain head into stored parachain head data + let parachain_head_size = parachain_head.0.len(); let parachain_head_data = match T::ParaStoredHeaderDataBuilder::try_build(parachain, ¶chain_head) { Some(parachain_head_data) => parachain_head_data, @@ -521,15 +522,17 @@ pub mod pallet { let update_result: Result<_, ()> = ParasInfo::::try_mutate(parachain, |stored_best_head| { - let is_free = match stored_best_head { - Some(ref best_head) - if at_relay_block.0.saturating_sub( - best_head.best_head_hash.at_relay_block_number, - ) >= free_headers_interval => - true, - Some(_) => false, - None => true, - }; + let is_free = parachain_head_size < + T::ParaStoredHeaderDataBuilder::max_free_head_size() as usize && + match stored_best_head { + Some(ref best_head) + if at_relay_block.0.saturating_sub( + best_head.best_head_hash.at_relay_block_number, + ) >= free_headers_interval => + true, + Some(_) => false, + None => true, + }; let artifacts = Pallet::::update_parachain_head( parachain, stored_best_head.take(), @@ -582,7 +585,8 @@ pub mod pallet { && SubmitFinalityProofHelper::::can_import_anything_for_free(); let pays_fee = if is_free { log::trace!(target: LOG_TARGET, "Parachain heads update transaction is free"); - pallet_bridge_grandpa::on_free_header_imported::(); + pallet_bridge_grandpa::on_free_header_imported::( + ); Pays::No } else { log::trace!(target: LOG_TARGET, "Parachain heads update transaction is paid"); @@ -818,7 +822,7 @@ pub fn initialize_for_benchmarks, I: 'static, PC: Parachain Pays::Yes + // then we submit new head, proved after `FreeHeadersInterval` => Pays::No + let mut large_head = head_data(2, 100); + large_head.0.extend(&[42u8; BigParachain::MAX_HEADER_SIZE as _]); + let (state_root, proof, parachains) = + prepare_parachain_heads_proof::(vec![(2, large_head)]); + let relay_block_number = relay_block_number + FreeHeadersInterval::get(); + proceed(relay_block_number, state_root); + let result = Pallet::::submit_parachain_heads( + RuntimeOrigin::signed(1), + (relay_block_number, test_relay_header(relay_block_number, state_root).hash()), + parachains, + proof, + ); + assert_eq!(result.unwrap().pays_fee, Pays::Yes); }) } } diff --git a/bridges/modules/parachains/src/mock.rs b/bridges/modules/parachains/src/mock.rs index 2c5de95b14dc..dbb62845392d 100644 --- a/bridges/modules/parachains/src/mock.rs +++ b/bridges/modules/parachains/src/mock.rs @@ -70,6 +70,7 @@ impl Chain for Parachain1 { impl Parachain for Parachain1 { const PARACHAIN_ID: u32 = 1; + const MAX_HEADER_SIZE: u32 = 1_024; } pub struct Parachain2; @@ -96,6 +97,7 @@ impl Chain for Parachain2 { impl Parachain for Parachain2 { const PARACHAIN_ID: u32 = 2; + const MAX_HEADER_SIZE: u32 = 1_024; } pub struct Parachain3; @@ -122,6 +124,7 @@ impl Chain for Parachain3 { impl Parachain for Parachain3 { const PARACHAIN_ID: u32 = 3; + const MAX_HEADER_SIZE: u32 = 1_024; } // this parachain is using u128 as block number and stored head data size exceeds limit @@ -149,6 +152,7 @@ impl Chain for BigParachain { impl Parachain for BigParachain { const PARACHAIN_ID: u32 = 4; + const MAX_HEADER_SIZE: u32 = 2_048; } construct_runtime! { diff --git a/bridges/primitives/parachains/src/lib.rs b/bridges/primitives/parachains/src/lib.rs index 692bbd99ecef..142c6e9b0892 100644 --- a/bridges/primitives/parachains/src/lib.rs +++ b/bridges/primitives/parachains/src/lib.rs @@ -116,6 +116,10 @@ impl ParaStoredHeaderData { /// Stored parachain head data builder. pub trait ParaStoredHeaderDataBuilder { + /// Maximal parachain head size that we may accept for free. All heads above + /// this limit are submitted for a regular fee. + fn max_free_head_size() -> u32; + /// Return number of parachains that are supported by this builder. fn supported_parachains() -> u32; @@ -127,6 +131,10 @@ pub trait ParaStoredHeaderDataBuilder { pub struct SingleParaStoredHeaderDataBuilder(PhantomData); impl ParaStoredHeaderDataBuilder for SingleParaStoredHeaderDataBuilder { + fn max_free_head_size() -> u32 { + C::MAX_HEADER_SIZE + } + fn supported_parachains() -> u32 { 1 } @@ -147,6 +155,17 @@ impl ParaStoredHeaderDataBuilder for SingleParaStoredHeaderDataBui #[impl_trait_for_tuples::impl_for_tuples(1, 30)] #[tuple_types_custom_trait_bound(Parachain)] impl ParaStoredHeaderDataBuilder for C { + fn max_free_head_size() -> u32 { + let mut result = 0_u32; + for_tuples!( #( + result = sp_std::cmp::max( + result, + SingleParaStoredHeaderDataBuilder::::max_free_head_size(), + ); + )* ); + result + } + fn supported_parachains() -> u32 { let mut result = 0; for_tuples!( #( diff --git a/bridges/primitives/runtime/src/chain.rs b/bridges/primitives/runtime/src/chain.rs index 4ec5a001a99e..f232c7726a91 100644 --- a/bridges/primitives/runtime/src/chain.rs +++ b/bridges/primitives/runtime/src/chain.rs @@ -236,6 +236,12 @@ where pub trait Parachain: Chain { /// Parachain identifier. const PARACHAIN_ID: u32; + /// Maximal size of the parachain header. + /// + /// This isn't a strict limit. The relayer may submit larger headers and the + /// pallet will accept the call. The limit is only used to compute whether + /// the refund can be made. + const MAX_HEADER_SIZE: u32; } impl Parachain for T @@ -244,6 +250,8 @@ where ::Chain: Parachain, { const PARACHAIN_ID: u32 = <::Chain as Parachain>::PARACHAIN_ID; + const MAX_HEADER_SIZE: u32 = + <::Chain as Parachain>::MAX_HEADER_SIZE; } /// Adapter for `Get` to access `PARACHAIN_ID` from `trait Parachain` diff --git a/bridges/relays/client-substrate/src/test_chain.rs b/bridges/relays/client-substrate/src/test_chain.rs index 77240d15884f..d1203a2c58ea 100644 --- a/bridges/relays/client-substrate/src/test_chain.rs +++ b/bridges/relays/client-substrate/src/test_chain.rs @@ -110,6 +110,7 @@ impl bp_runtime::Chain for TestParachainBase { impl bp_runtime::Parachain for TestParachainBase { const PARACHAIN_ID: u32 = 1000; + const MAX_HEADER_SIZE: u32 = 1_024; } /// Parachain that may be used in tests. From 61c7ff5d45b2633efb009577d56f2fcc236153b2 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 25 Mar 2024 11:42:59 +0300 Subject: [PATCH 28/70] added grandpa_and_parachain_pallets_share_free_headers_counter test --- bridges/modules/parachains/src/lib.rs | 78 +++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 5 deletions(-) diff --git a/bridges/modules/parachains/src/lib.rs b/bridges/modules/parachains/src/lib.rs index cc1e7bd63257..c4c67e1cd0cc 100644 --- a/bridges/modules/parachains/src/lib.rs +++ b/bridges/modules/parachains/src/lib.rs @@ -846,7 +846,7 @@ pub(crate) mod tests { dispatch::DispatchResultWithPostInfo, pallet_prelude::Pays, storage::generator::{StorageDoubleMap, StorageMap}, - traits::{Get, OnInitialize}, + traits::Get, weights::Weight, }; use frame_system::{EventRecord, Pallet as System, Phase}; @@ -880,10 +880,6 @@ pub(crate) mod tests { num: RelayBlockNumber, state_root: RelayBlockHash, ) -> (ParaHash, GrandpaJustification) { - pallet_bridge_grandpa::Pallet::::on_initialize( - 0, - ); - let header = test_relay_header(num, state_root); let hash = header.hash(); let justification = make_default_justification(&header); @@ -1830,4 +1826,76 @@ pub(crate) mod tests { assert_eq!(result.unwrap().pays_fee, Pays::Yes); }) } + + #[test] + fn grandpa_and_parachain_pallets_share_free_headers_counter() { + run_test(|| { + initialize(Default::default()); + // set free headers limit to `4` + let mut free_headers_remaining = 4; + pallet_bridge_grandpa::FreeHeadersRemaining::::set( + Some(free_headers_remaining), + ); + // import free GRANDPA and parachain headers + let mut relay_block_number = 0; + for i in 0..2 { + // import free GRANDPA header + let (state_root, proof, parachains) = prepare_parachain_heads_proof::< + RegularParachainHeader, + >(vec![(2, head_data(2, 5 + i))]); + relay_block_number = relay_block_number + FreeHeadersInterval::get(); + proceed(relay_block_number, state_root); + assert_eq!( + pallet_bridge_grandpa::FreeHeadersRemaining::< + TestRuntime, + BridgesGrandpaPalletInstance, + >::get(), + Some(free_headers_remaining - 1), + ); + free_headers_remaining = free_headers_remaining - 1; + // import free parachain header + assert_ok!(Pallet::::submit_parachain_heads( + RuntimeOrigin::signed(1), + (relay_block_number, test_relay_header(relay_block_number, state_root).hash()), + parachains, + proof, + ),); + assert_eq!( + pallet_bridge_grandpa::FreeHeadersRemaining::< + TestRuntime, + BridgesGrandpaPalletInstance, + >::get(), + Some(free_headers_remaining - 1), + ); + free_headers_remaining = free_headers_remaining - 1; + } + // try to import free GRANDPA header => non-free execution + let (state_root, proof, parachains) = + prepare_parachain_heads_proof::(vec![(2, head_data(2, 7))]); + relay_block_number = relay_block_number + FreeHeadersInterval::get(); + let result = pallet_bridge_grandpa::Pallet::::submit_finality_proof_ex( + RuntimeOrigin::signed(1), + Box::new(test_relay_header(relay_block_number, state_root)), + make_default_justification(&test_relay_header(relay_block_number, state_root)), + TEST_GRANDPA_SET_ID, + false, + ); + assert_eq!(result.unwrap().pays_fee, Pays::Yes); + // try to import free parachain header => non-free execution + let result = Pallet::::submit_parachain_heads( + RuntimeOrigin::signed(1), + (relay_block_number, test_relay_header(relay_block_number, state_root).hash()), + parachains, + proof, + ); + assert_eq!(result.unwrap().pays_fee, Pays::Yes); + assert_eq!( + pallet_bridge_grandpa::FreeHeadersRemaining::< + TestRuntime, + BridgesGrandpaPalletInstance, + >::get(), + Some(0), + ); + }); + } } From e0817e97ef37e2e04b2e0ab4910c484d2dc4d0f2 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 25 Mar 2024 12:28:52 +0300 Subject: [PATCH 29/70] with free intervals (instead of fixed free headers) we need to check `improved_by` from extension code --- bridges/modules/grandpa/src/call_ext.rs | 87 +++++++++++++++++++--- bridges/modules/grandpa/src/lib.rs | 3 + bridges/modules/parachains/src/call_ext.rs | 68 ++++++++++++++--- 3 files changed, 138 insertions(+), 20 deletions(-) diff --git a/bridges/modules/grandpa/src/call_ext.rs b/bridges/modules/grandpa/src/call_ext.rs index bed261c1a81a..3bd6877122f5 100644 --- a/bridges/modules/grandpa/src/call_ext.rs +++ b/bridges/modules/grandpa/src/call_ext.rs @@ -15,8 +15,8 @@ // along with Parity Bridges Common. If not, see . use crate::{ - weights::WeightInfo, BridgedBlockNumber, BridgedHeader, Config, CurrentAuthoritySet, Error, - FreeHeadersRemaining, Pallet, + weights::WeightInfo, BestFinalized, BridgedBlockNumber, BridgedHeader, Config, + CurrentAuthoritySet, Error, FreeHeadersRemaining, Pallet, }; use bp_header_chain::{ justification::GrandpaJustification, max_expected_submit_finality_proof_arguments_size, @@ -24,7 +24,11 @@ use bp_header_chain::{ }; use bp_runtime::{BlockNumberOf, Chain, OwnedBridgeModule}; use codec::Encode; -use frame_support::{dispatch::CallableCallFor, traits::IsSubType, weights::Weight}; +use frame_support::{ + dispatch::CallableCallFor, + traits::{Get, IsSubType}, + weights::Weight, +}; use sp_consensus_grandpa::SetId; use sp_runtime::{ traits::{CheckedSub, Header, Zero}, @@ -127,6 +131,22 @@ impl, I: 'static> SubmitFinalityProofHelper { return Err(Error::::FreeHeadersLimitExceded); } + // ensure that the `improved_by` is larger than the configured free interval + if let Some(free_headers_interval) = T::FreeHeadersInterval::get() { + if improved_by < free_headers_interval.into() { + log::trace!( + target: crate::LOG_TARGET, + "Cannot accept free {:?} header {:?}. Too small difference between submitted headers: {} vs {}", + T::BridgedChain::ID, + call_info.block_number, + improved_by, + free_headers_interval, + ); + + return Err(Error::::BelowFreeHeaderInterval); + } + } + // we do not check whether the header matches free submission criteria here - it is the // relayer responsibility to check that @@ -144,7 +164,7 @@ impl, I: 'static> SubmitFinalityProofHelper { finality_target: BlockNumberOf, current_set_id: Option, ) -> Result, Error> { - let best_finalized = crate::BestFinalized::::get().ok_or_else(|| { + let best_finalized = BestFinalized::::get().ok_or_else(|| { log::trace!( target: crate::LOG_TARGET, "Cannot finalize header {:?} because pallet is not yet initialized", @@ -186,7 +206,7 @@ impl, I: 'static> SubmitFinalityProofHelper { /// Check if the `SubmitFinalityProof` was successfully executed. pub fn was_successful(finality_target: BlockNumberOf) -> bool { - match crate::BestFinalized::::get() { + match BestFinalized::::get() { Some(best_finalized) => best_finalized.number() == finality_target, None => false, } @@ -330,7 +350,10 @@ pub(crate) fn submit_finality_proof_info_from_args, I: 'static>( mod tests { use crate::{ call_ext::CallSubType, - mock::{run_test, test_header, RuntimeCall, TestBridgedChain, TestNumber, TestRuntime}, + mock::{ + run_test, test_header, FreeHeadersInterval, RuntimeCall, TestBridgedChain, TestNumber, + TestRuntime, + }, BestFinalized, Config, CurrentAuthoritySet, FreeHeadersRemaining, PalletOperatingMode, StoredAuthoritySet, SubmitFinalityProofInfo, WeightInfo, }; @@ -410,8 +433,10 @@ mod tests { ) { run_test(|| { let bridge_grandpa_call = crate::Call::::submit_finality_proof_ex { - finality_target: Box::new(test_header(15)), - justification: make_default_justification(&test_header(15)), + finality_target: Box::new(test_header(10 + FreeHeadersInterval::get() as u64)), + justification: make_default_justification(&test_header( + 10 + FreeHeadersInterval::get() as u64, + )), current_set_id: 0, is_free_execution_expected: true, }; @@ -440,6 +465,50 @@ mod tests { }) } + #[test] + fn extension_rejects_new_header_if_free_execution_is_requested_and_improved_by_is_below_expected( + ) { + run_test(|| { + let bridge_grandpa_call = crate::Call::::submit_finality_proof_ex { + finality_target: Box::new(test_header(100)), + justification: make_default_justification(&test_header(100)), + current_set_id: 0, + is_free_execution_expected: true, + }; + sync_to_header_10(); + + // when `improved_by` is less than the free interval + BestFinalized::::put(HeaderId( + 100 - FreeHeadersInterval::get() as u64 + 1, + sp_core::H256::default(), + )); + assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa( + bridge_grandpa_call.clone(), + ),) + .is_err()); + + // when `improved_by` is equal to the free interval + BestFinalized::::put(HeaderId( + 100 - FreeHeadersInterval::get() as u64, + sp_core::H256::default(), + )); + assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa( + bridge_grandpa_call.clone(), + ),) + .is_ok()); + + // when `improved_by` is larger than the free interval + BestFinalized::::put(HeaderId( + 100 - FreeHeadersInterval::get() as u64 - 1, + sp_core::H256::default(), + )); + assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa( + bridge_grandpa_call.clone(), + ),) + .is_ok()); + }) + } + #[test] fn extension_accepts_new_header() { run_test(|| { @@ -568,7 +637,7 @@ mod tests { finality_target: Box::new(test_header(number)), justification: make_default_justification(&test_header(number)), current_set_id: 0, - is_free_execution_expected: true, + is_free_execution_expected: false, }) } diff --git a/bridges/modules/grandpa/src/lib.rs b/bridges/modules/grandpa/src/lib.rs index abda4eb38bda..aee709904c84 100644 --- a/bridges/modules/grandpa/src/lib.rs +++ b/bridges/modules/grandpa/src/lib.rs @@ -503,6 +503,9 @@ pub mod pallet { /// The submitter wanted free execution, but we can't fit more free transactions /// to the block. FreeHeadersLimitExceded, + /// The submitter wanted free execution, but the difference between best known and + /// bundled header numbers is below the `FreeHeadersInterval`. + BelowFreeHeaderInterval, } /// Called when new free header is imported. diff --git a/bridges/modules/parachains/src/call_ext.rs b/bridges/modules/parachains/src/call_ext.rs index 1bb46d24d5c6..987fd1741a48 100644 --- a/bridges/modules/parachains/src/call_ext.rs +++ b/bridges/modules/parachains/src/call_ext.rs @@ -94,15 +94,8 @@ impl, I: 'static> SubmitParachainHeadsHelper { reject } else { // free headers interval is not configured and call is expected to execute - // for free => reject it - log::trace!( - target: crate::LOG_TARGET, - "The free parachain {:?} head can't be updated: free interval is not \ - configured in the runtime.", - update.para_id, - ); - - true + // for free => it is a relayer error, it should've been able to detect that + false } } @@ -268,7 +261,7 @@ where #[cfg(test)] mod tests { use crate::{ - mock::{run_test, RuntimeCall, TestRuntime}, + mock::{run_test, FreeHeadersInterval, RuntimeCall, TestRuntime}, CallSubType, PalletOperatingMode, ParaInfo, ParasInfo, RelayBlockHash, RelayBlockNumber, }; use bp_header_chain::StoredHeaderData; @@ -280,10 +273,25 @@ mod tests { num: RelayBlockNumber, parachains: Vec<(ParaId, ParaHash)>, ) -> bool { - RuntimeCall::Parachains(crate::Call::::submit_parachain_heads { + RuntimeCall::Parachains(crate::Call::::submit_parachain_heads_ex { at_relay_block: (num, [num as u8; 32].into()), parachains, parachain_heads_proof: ParaHeadsProof { storage_proof: Vec::new() }, + is_free_execution_expected: false, + }) + .check_obsolete_submit_parachain_heads() + .is_ok() + } + + fn validate_free_submit_parachain_heads( + num: RelayBlockNumber, + parachains: Vec<(ParaId, ParaHash)>, + ) -> bool { + RuntimeCall::Parachains(crate::Call::::submit_parachain_heads_ex { + at_relay_block: (num, [num as u8; 32].into()), + parachains, + parachain_heads_proof: ParaHeadsProof { storage_proof: Vec::new() }, + is_free_execution_expected: true, }) .check_obsolete_submit_parachain_heads() .is_ok() @@ -396,4 +404,42 @@ mod tests { assert!(validate_submit_parachain_heads(15, vec![(ParaId(2), [15u8; 32].into())])); }); } + + #[test] + fn extension_rejects_free_parachain_head_if_no_free_slots_remaining() { + run_test(|| { + // when current best finalized is #10 and we're trying to import header#15 => tx should + // be accepted + sync_to_relay_header_10(); + insert_relay_block(15); + // ... but since we have specified `is_free_execution_expected = true`, it'll be + // rejected + assert!(!validate_free_submit_parachain_heads(15, vec![(ParaId(1), [2u8; 32].into())])); + // ... if we have specify `is_free_execution_expected = false`, it'll be accepted + assert!(validate_submit_parachain_heads(15, vec![(ParaId(1), [2u8; 32].into())])); + }); + } + + #[test] + fn extension_rejects_free_parachain_head_if_improves_by_is_below_expected() { + run_test(|| { + // when current best finalized is #10 and we're trying to import header#15 => tx should + // be accepted + sync_to_relay_header_10(); + insert_relay_block(10 + FreeHeadersInterval::get() - 1); + insert_relay_block(10 + FreeHeadersInterval::get()); + // try to submit at 10 + FreeHeadersInterval::get() - 1 => failure + let relay_header = 10 + FreeHeadersInterval::get() - 1; + assert!(!validate_free_submit_parachain_heads( + relay_header, + vec![(ParaId(1), [2u8; 32].into())] + )); + // ... if we have specify `is_free_execution_expected = false`, it'll be accepted + let relay_header = 10 + FreeHeadersInterval::get(); + assert!(validate_free_submit_parachain_heads( + relay_header, + vec![(ParaId(1), [2u8; 32].into())] + )); + }); + } } From 380fe97a42ecca074ab8bcf3ec3423f237609950 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 25 Mar 2024 12:38:19 +0300 Subject: [PATCH 30/70] fix test name --- .../runtime-common/src/extensions/check_obsolete_extension.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs b/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs index 3f937b5c66f6..754c1ace6d85 100644 --- a/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs +++ b/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs @@ -388,7 +388,7 @@ mod tests { } #[test] - fn grandpa_wrapper_boosts_extensions_for_unregistered_relayer() { + fn grandpa_wrapper_boosts_extensions_for_registered_relayer() { run_test(|| { initialize_environment(100, 100, 100); BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000) From 779bf9d13dc06502b42e53dcdf1d586152b8abfb Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 25 Mar 2024 14:08:25 +0300 Subject: [PATCH 31/70] added CheckAndBoostBridgeParachainsTransactions to boost parachain transactions priority --- .../extensions/check_obsolete_extension.rs | 206 ++++++++++++++++-- .../extensions/refund_relayer_extension.rs | 16 +- bridges/modules/grandpa/src/call_ext.rs | 2 +- bridges/modules/parachains/src/call_ext.rs | 61 +++--- bridges/modules/parachains/src/lib.rs | 2 +- 5 files changed, 244 insertions(+), 43 deletions(-) diff --git a/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs b/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs index 754c1ace6d85..34584211fe9e 100644 --- a/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs +++ b/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs @@ -23,12 +23,13 @@ use bp_relayers::ExplicitOrAccountParams; use pallet_bridge_grandpa::{ BridgedBlockNumber, CallSubType as GrandpaCallSubType, SubmitFinalityProofHelper, }; -use pallet_bridge_parachains::CallSubType as ParachainsCallSubtype; +use pallet_bridge_parachains::{ + CallSubType as ParachainsCallSubtype, SubmitParachainHeadsHelper, SubmitParachainHeadsInfo, +}; use pallet_bridge_relayers::Pallet as RelayersPallet; use sp_runtime::{ - traits::{Get, One, PhantomData, UniqueSaturatedInto}, + traits::{Get, PhantomData, UniqueSaturatedInto}, transaction_validity::{TransactionPriority, TransactionValidity, ValidTransactionBuilder}, - Saturating, }; /// A duplication of the `FilterCall` trait. @@ -70,19 +71,13 @@ where who: &T::AccountId, call: &T::RuntimeCall, ) -> (Self::ToPostDispatch, TransactionValidity) { - // we only boost priority if relayer has staked required balance - let is_relayer_registration_active = RelayersPallet::::is_registration_active(who); - match GrandpaCallSubType::::check_obsolete_submit_finality_proof(call) { Ok(Some(our_tx)) => { - let block_number = Some(our_tx.base.block_number); - let improved_by: TransactionPriority = - our_tx.improved_by.saturating_sub(One::one()).unique_saturated_into(); - let boost_per_header = - if is_relayer_registration_active { Priority::get() } else { 0 }; - let total_priority_boost = improved_by.saturating_mul(boost_per_header); + let to_post_dispatch = Some(our_tx.base.block_number); + let total_priority_boost = + compute_priority_boost::(who, our_tx.improved_by); ( - block_number, + to_post_dispatch, ValidTransactionBuilder::default().priority(total_priority_boost).build(), ) }, @@ -114,6 +109,63 @@ where } } +/// Wrapper for the bridge parachains pallet that checks calls for obsolete submissions +/// and also boosts transaction priority if it has submitted by registered relayer. +/// The boost is computed as +/// `(BundledHeaderNumber - 1 - BestKnownHeaderNumber) * Priority::get()`. +/// The boost is only applied if submitter has active registration in the relayers +/// pallet. +pub struct CheckAndBoostBridgeParachainsTransactions( + PhantomData<(T, I, Priority, SlashAccount)>, +); + +impl, SlashAccount: Get> + BridgeRuntimeFilterCall + for CheckAndBoostBridgeParachainsTransactions +where + T: pallet_bridge_relayers::Config + pallet_bridge_parachains::Config, + T::RuntimeCall: ParachainsCallSubtype, +{ + // bridged header number, bundled in transaction + type ToPostDispatch = Option; + + fn validate( + who: &T::AccountId, + call: &T::RuntimeCall, + ) -> (Self::ToPostDispatch, TransactionValidity) { + match ParachainsCallSubtype::::check_obsolete_submit_parachain_heads(call) { + Ok(Some(our_tx)) => { + let to_post_dispatch = Some(our_tx.base); + let total_priority_boost = + compute_priority_boost::(&who, our_tx.improved_by); + ( + to_post_dispatch, + ValidTransactionBuilder::default().priority(total_priority_boost).build(), + ) + }, + Ok(None) => (None, ValidTransactionBuilder::default().build()), + Err(e) => (None, Err(e)), + } + } + + fn post_dispatch(relayer: &T::AccountId, has_failed: bool, maybe_update: Self::ToPostDispatch) { + // we are only interested in associated pallet submissions + let Some(update) = maybe_update else { return }; + // we are only interested in failed or unneeded transactions + let has_failed = has_failed || !SubmitParachainHeadsHelper::::was_successful(&update); + + if !has_failed { + return + } + + // let's slash registered relayer + RelayersPallet::::slash_and_deregister( + relayer, + ExplicitOrAccountParams::Explicit(SlashAccount::get()), + ); + } +} + impl BridgeRuntimeFilterCall for pallet_bridge_grandpa::Pallet where @@ -138,7 +190,11 @@ where { type ToPostDispatch = (); fn validate(_who: &T::AccountId, call: &T::RuntimeCall) -> ((), TransactionValidity) { - ((), ParachainsCallSubtype::::check_obsolete_submit_parachain_heads(call)) + ( + (), + ParachainsCallSubtype::::check_obsolete_submit_parachain_heads(call) + .and_then(|_| ValidTransactionBuilder::default().build()), + ) } } @@ -157,6 +213,25 @@ where } } +/// Computes priority boost that improved known header by `improved_by` +fn compute_priority_boost( + relayer: &T::AccountId, + improved_by: N, +) -> TransactionPriority +where + T: pallet_bridge_relayers::Config, + N: UniqueSaturatedInto, + Priority: Get, +{ + // we only boost priority if relayer has staked required balance + let is_relayer_registration_active = RelayersPallet::::is_registration_active(relayer); + // if tx improves by just one, there's no need to bump its priority + let improved_by: TransactionPriority = improved_by.unique_saturated_into().saturating_sub(1); + // if relayer is registered, for every skipped header we improve by `Priority` + let boost_per_header = if is_relayer_registration_active { Priority::get() } else { 0 }; + improved_by.saturating_mul(boost_per_header) +} + /// Declares a runtime-specific `BridgeRejectObsoleteHeadersAndMessages` signed extension. /// /// ## Example @@ -277,10 +352,13 @@ mod tests { use super::*; use crate::{ extensions::refund_relayer_extension::tests::{ - initialize_environment, relayer_account_at_this_chain, submit_relay_header_call_ex, + initialize_environment, relayer_account_at_this_chain, submit_parachain_head_call_ex, + submit_relay_header_call_ex, TestParachain, }, mock::*, }; + use bp_polkadot_core::parachains::ParaId; + use codec::Encode; use frame_support::assert_err; use sp_runtime::{ traits::{ConstU64, SignedExtension}, @@ -417,4 +495,102 @@ mod tests { assert!(!BridgeRelayers::is_registration_active(&relayer_account_at_this_chain())); }) } + + #[test] + fn grandpa_wrapper_does_not_slash_registered_relayer_if_transaction_succeeds() { + run_test(|| { + initialize_environment(100, 100, 100); + BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000) + .unwrap(); + + assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain())); + BridgeGrandpaWrapper::post_dispatch(&relayer_account_at_this_chain(), false, Some(100)); + assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain())); + }) + } + + type BridgeParachainsWrapper = CheckAndBoostBridgeParachainsTransactions< + TestRuntime, + (), + ConstU64<1_000>, + SlashDestination, + >; + + #[test] + fn parachains_wrapper_does_not_boost_extensions_for_unregistered_relayer() { + run_test(|| { + initialize_environment(100, 100, 100); + + let priority_boost = BridgeParachainsWrapper::validate( + &relayer_account_at_this_chain(), + &submit_parachain_head_call_ex(200), + ) + .1 + .unwrap() + .priority; + assert_eq!(priority_boost, 0); + }) + } + + #[test] + fn parachains_wrapper_boosts_extensions_for_registered_relayer() { + run_test(|| { + initialize_environment(100, 100, 100); + BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000) + .unwrap(); + + let priority_boost = BridgeParachainsWrapper::validate( + &relayer_account_at_this_chain(), + &submit_parachain_head_call_ex(200), + ) + .1 + .unwrap() + .priority; + assert_eq!(priority_boost, 99_000); + }) + } + + #[test] + fn parachains_wrapper_slashes_registered_relayer_if_transaction_fails() { + run_test(|| { + initialize_environment(100, 100, 100); + BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000) + .unwrap(); + + assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain())); + BridgeParachainsWrapper::post_dispatch( + &relayer_account_at_this_chain(), + true, + Some(SubmitParachainHeadsInfo { + at_relay_block: (150, Default::default()), + para_id: ParaId(TestParachain::get()), + para_head_hash: [150u8; 32].into(), + is_free_execution_expected: false, + }), + ); + assert!(!BridgeRelayers::is_registration_active(&relayer_account_at_this_chain())); + }) + } + + #[test] + fn parachains_wrapper_does_not_slash_registered_relayer_if_transaction_succeeds() { + run_test(|| { + initialize_environment(100, 100, 100); + BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000) + .unwrap(); + + assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain())); + BridgeParachainsWrapper::post_dispatch( + &relayer_account_at_this_chain(), + false, + Some(SubmitParachainHeadsInfo { + at_relay_block: (100, Default::default()), + para_id: ParaId(TestParachain::get()), + para_head_hash: [100u8; 32].into(), + is_free_execution_expected: false, + }), + ); + assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain())); + }) + } } diff --git a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs index fe758f7d5e12..5574633fdab7 100644 --- a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs +++ b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs @@ -953,7 +953,7 @@ pub(crate) mod tests { }; parameter_types! { - TestParachain: u32 = 1000; + pub TestParachain: u32 = 1000; pub TestLaneId: LaneId = TEST_LANE_ID; pub MsgProofsRewardsAccount: RewardsAccountParams = RewardsAccountParams::new( TEST_LANE_ID, @@ -1111,6 +1111,20 @@ pub(crate) mod tests { }) } + pub fn submit_parachain_head_call_ex( + parachain_head_at_relay_header_number: RelayBlockNumber, + ) -> RuntimeCall { + RuntimeCall::BridgeParachains(ParachainsCall::submit_parachain_heads_ex { + at_relay_block: (parachain_head_at_relay_header_number, RelayBlockHash::default()), + parachains: vec![( + ParaId(TestParachain::get()), + [parachain_head_at_relay_header_number as u8; 32].into(), + )], + parachain_heads_proof: ParaHeadsProof { storage_proof: vec![] }, + is_free_execution_expected: false, + }) + } + fn message_delivery_call(best_message: MessageNonce) -> RuntimeCall { RuntimeCall::BridgeMessages(MessagesCall::receive_messages_proof { relayer_id_at_bridged_chain: relayer_account_at_bridged_chain(), diff --git a/bridges/modules/grandpa/src/call_ext.rs b/bridges/modules/grandpa/src/call_ext.rs index 3bd6877122f5..bed8ea38442a 100644 --- a/bridges/modules/grandpa/src/call_ext.rs +++ b/bridges/modules/grandpa/src/call_ext.rs @@ -61,7 +61,7 @@ pub struct SubmitFinalityProofInfo { pub extra_size: u32, } -/// Verified `SubmitFinalityProofInfo` +/// Verified `SubmitFinalityProofInfo`. #[derive(Copy, Clone, PartialEq, RuntimeDebug)] pub struct VerifiedSubmitFinalityProofInfo { /// Base call information. diff --git a/bridges/modules/parachains/src/call_ext.rs b/bridges/modules/parachains/src/call_ext.rs index 987fd1741a48..5fd12ee2e984 100644 --- a/bridges/modules/parachains/src/call_ext.rs +++ b/bridges/modules/parachains/src/call_ext.rs @@ -25,7 +25,7 @@ use frame_support::{ }; use pallet_bridge_grandpa::SubmitFinalityProofHelper; use sp_runtime::{ - transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction}, + transaction_validity::{InvalidTransaction, TransactionValidityError}, RuntimeDebug, }; @@ -44,6 +44,16 @@ pub struct SubmitParachainHeadsInfo { pub is_free_execution_expected: bool, } +/// Verified `SubmitParachainHeadsInfo`. +#[derive(PartialEq, RuntimeDebug)] +pub struct VerifiedSubmitParachainHeadsInfo { + /// Base call information. + pub base: SubmitParachainHeadsInfo, + /// A difference between bundled bridged relay chain header and relay chain header number + /// used to prove best bridged parachain header, known to us before the call. + pub improved_by: RelayBlockNumber, +} + /// Helper struct that provides methods for working with the `SubmitParachainHeads` call. pub struct SubmitParachainHeadsHelper, I: 'static> { _phantom_data: sp_std::marker::PhantomData<(T, I)>, @@ -52,16 +62,15 @@ pub struct SubmitParachainHeadsHelper, I: 'static> { impl, I: 'static> SubmitParachainHeadsHelper { /// Check that is called from signed extension and takes the `is_free_execution_expected` /// into account. - pub fn is_obsolete_from_extension(update: &SubmitParachainHeadsInfo) -> bool { + pub fn check_obsolete_from_extension( + update: &SubmitParachainHeadsInfo, + ) -> Result { // first do all base checks - let (is_obsolete, improved_by) = Self::is_obsolete(update); - if is_obsolete { - return true; - } + let improved_by = Self::check_obsolete(update)?; // if we don't expect free execution - no more checks if !update.is_free_execution_expected { - return false; + return Ok(improved_by); } // reject if no more free slots remaining in the block @@ -73,7 +82,7 @@ impl, I: 'static> SubmitParachainHeadsHelper { update.para_id, ); - return true; + return Err(InvalidTransaction::Call.into()); } // reject if we are importing parachain headers too often @@ -89,19 +98,22 @@ impl, I: 'static> SubmitParachainHeadsHelper { improved_by, free_headers_interval, ); - } - reject + return Err(InvalidTransaction::Stale.into()); + } } else { // free headers interval is not configured and call is expected to execute // for free => it is a relayer error, it should've been able to detect that - false } + + Ok(improved_by) } /// Check if the para head provided by the `SubmitParachainHeads` is better than the best one /// we know. - pub fn is_obsolete(update: &SubmitParachainHeadsInfo) -> (bool, RelayBlockNumber) { + pub fn check_obsolete( + update: &SubmitParachainHeadsInfo, + ) -> Result { // check if we know better parachain head already let improved_by = match crate::ParasInfo::::get(update.para_id) { Some(stored_best_head) => { @@ -120,7 +132,7 @@ impl, I: 'static> SubmitParachainHeadsHelper { stored_best_head.best_head_hash.at_relay_block_number, update.at_relay_block.0 ); - return (true, 0) + return Err(InvalidTransaction::Stale.into()) }, }; @@ -134,7 +146,7 @@ impl, I: 'static> SubmitParachainHeadsHelper { stored_best_head.best_head_hash.at_relay_block_number, update.at_relay_block.0 ); - return (true, 0) + return Err(InvalidTransaction::Stale.into()) } improved_by @@ -154,10 +166,10 @@ impl, I: 'static> SubmitParachainHeadsHelper { update.at_relay_block.1, ); - return (true, 0) + return Err(InvalidTransaction::Call.into()) } - (false, improved_by) + Ok(improved_by) } /// Check if the `SubmitParachainHeads` was successfully executed. @@ -230,24 +242,23 @@ pub trait CallSubType, I: 'static>: /// block production, or "eat" significant portion of block production time literally /// for nothing. In addition, the single-parachain-head-per-transaction is how the /// pallet will be used in our environment. - fn check_obsolete_submit_parachain_heads(&self) -> TransactionValidity + fn check_obsolete_submit_parachain_heads( + &self, + ) -> Result, TransactionValidityError> where Self: Sized, { let update = match self.one_entry_submit_parachain_heads_info() { Some(update) => update, - None => return Ok(ValidTransaction::default()), + None => return Ok(None), }; if Pallet::::ensure_not_halted().is_err() { - return InvalidTransaction::Call.into() + return Err(InvalidTransaction::Call.into()) } - if SubmitParachainHeadsHelper::::is_obsolete_from_extension(&update) { - return InvalidTransaction::Stale.into() - } - - Ok(ValidTransaction::default()) + SubmitParachainHeadsHelper::::check_obsolete_from_extension(&update) + .map(|improved_by| Some(VerifiedSubmitParachainHeadsInfo { base: update, improved_by })) } } @@ -434,7 +445,7 @@ mod tests { relay_header, vec![(ParaId(1), [2u8; 32].into())] )); - // ... if we have specify `is_free_execution_expected = false`, it'll be accepted + // try to submit at 10 + FreeHeadersInterval::get() => ok let relay_header = 10 + FreeHeadersInterval::get(); assert!(validate_free_submit_parachain_heads( relay_header, diff --git a/bridges/modules/parachains/src/lib.rs b/bridges/modules/parachains/src/lib.rs index c4c67e1cd0cc..f73da3e25255 100644 --- a/bridges/modules/parachains/src/lib.rs +++ b/bridges/modules/parachains/src/lib.rs @@ -663,7 +663,7 @@ pub mod pallet { // don't actually matter here is_free_execution_expected: false, }; - if SubmitParachainHeadsHelper::::is_obsolete(&update).0 { + if SubmitParachainHeadsHelper::::check_obsolete(&update).is_err() { Self::deposit_event(Event::RejectedObsoleteParachainHead { parachain, parachain_head_hash: new_head_hash, From 19fb7661a3cbc4bb3231ca1915a4a52f2e457a58 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 25 Mar 2024 14:29:35 +0300 Subject: [PATCH 32/70] check parachain id in CheckAndBoostBridgeParachainsTransactions --- .../extensions/check_obsolete_extension.rs | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs b/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs index 34584211fe9e..abe0ecad6e9d 100644 --- a/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs +++ b/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs @@ -18,7 +18,7 @@ //! obsolete (duplicated) data or do not pass some additional pallet-specific //! checks. -use crate::messages_call_ext::MessagesCallSubType; +use crate::{messages_call_ext::MessagesCallSubType, RefundableParachainId}; use bp_relayers::ExplicitOrAccountParams; use pallet_bridge_grandpa::{ BridgedBlockNumber, CallSubType as GrandpaCallSubType, SubmitFinalityProofHelper, @@ -115,16 +115,17 @@ where /// `(BundledHeaderNumber - 1 - BestKnownHeaderNumber) * Priority::get()`. /// The boost is only applied if submitter has active registration in the relayers /// pallet. -pub struct CheckAndBoostBridgeParachainsTransactions( - PhantomData<(T, I, Priority, SlashAccount)>, +pub struct CheckAndBoostBridgeParachainsTransactions( + PhantomData<(T, RefPara, Priority, SlashAccount)>, ); -impl, SlashAccount: Get> +impl, SlashAccount: Get> BridgeRuntimeFilterCall - for CheckAndBoostBridgeParachainsTransactions + for CheckAndBoostBridgeParachainsTransactions where - T: pallet_bridge_relayers::Config + pallet_bridge_parachains::Config, - T::RuntimeCall: ParachainsCallSubtype, + T: pallet_bridge_relayers::Config + pallet_bridge_parachains::Config, + RefPara: RefundableParachainId, + T::RuntimeCall: ParachainsCallSubtype, { // bridged header number, bundled in transaction type ToPostDispatch = Option; @@ -133,8 +134,10 @@ where who: &T::AccountId, call: &T::RuntimeCall, ) -> (Self::ToPostDispatch, TransactionValidity) { - match ParachainsCallSubtype::::check_obsolete_submit_parachain_heads(call) { - Ok(Some(our_tx)) => { + match ParachainsCallSubtype::::check_obsolete_submit_parachain_heads( + call, + ) { + Ok(Some(our_tx)) if our_tx.base.para_id.0 == RefPara::Id::get() => { let to_post_dispatch = Some(our_tx.base); let total_priority_boost = compute_priority_boost::(&who, our_tx.improved_by); @@ -143,7 +146,7 @@ where ValidTransactionBuilder::default().priority(total_priority_boost).build(), ) }, - Ok(None) => (None, ValidTransactionBuilder::default().build()), + Ok(_) => (None, ValidTransactionBuilder::default().build()), Err(e) => (None, Err(e)), } } @@ -152,7 +155,8 @@ where // we are only interested in associated pallet submissions let Some(update) = maybe_update else { return }; // we are only interested in failed or unneeded transactions - let has_failed = has_failed || !SubmitParachainHeadsHelper::::was_successful(&update); + let has_failed = has_failed || + !SubmitParachainHeadsHelper::::was_successful(&update); if !has_failed { return @@ -356,6 +360,7 @@ mod tests { submit_relay_header_call_ex, TestParachain, }, mock::*, + DefaultRefundableParachainId, }; use bp_polkadot_core::parachains::ParaId; use codec::Encode; @@ -511,7 +516,7 @@ mod tests { type BridgeParachainsWrapper = CheckAndBoostBridgeParachainsTransactions< TestRuntime, - (), + DefaultRefundableParachainId<(), TestParachain>, ConstU64<1_000>, SlashDestination, >; From 7a89e1637203626913de56ac08b82e5b752876c9 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 25 Mar 2024 14:31:44 +0300 Subject: [PATCH 33/70] use submit_parachain_head_call_ex in tests --- .../runtime-common/src/extensions/refund_relayer_extension.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs index 5574633fdab7..932be09c49a0 100644 --- a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs +++ b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs @@ -1252,7 +1252,7 @@ pub(crate) mod tests { RuntimeCall::Utility(UtilityCall::batch_all { calls: vec![ submit_relay_header_call_ex(relay_header_number), - submit_parachain_head_call(parachain_head_at_relay_header_number), + submit_parachain_head_call_ex(parachain_head_at_relay_header_number), message_delivery_call(best_message), ], }) @@ -1280,7 +1280,7 @@ pub(crate) mod tests { RuntimeCall::Utility(UtilityCall::batch_all { calls: vec![ submit_relay_header_call_ex(relay_header_number), - submit_parachain_head_call(parachain_head_at_relay_header_number), + submit_parachain_head_call_ex(parachain_head_at_relay_header_number), message_confirmation_call(best_message), ], }) From b98420462a2646a45373f64a695c43fcad67753e Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 25 Mar 2024 14:34:14 +0300 Subject: [PATCH 34/70] removed commented code --- .../runtime-common/src/extensions/check_obsolete_extension.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs b/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs index abe0ecad6e9d..abca4d6e8a08 100644 --- a/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs +++ b/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs @@ -363,7 +363,6 @@ mod tests { DefaultRefundableParachainId, }; use bp_polkadot_core::parachains::ParaId; - use codec::Encode; use frame_support::assert_err; use sp_runtime::{ traits::{ConstU64, SignedExtension}, From d4533e27ba78a52ef76bf5e579a9eea44c147459 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 25 Mar 2024 14:36:15 +0300 Subject: [PATCH 35/70] changed condition for consistency --- bridges/modules/parachains/src/call_ext.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/modules/parachains/src/call_ext.rs b/bridges/modules/parachains/src/call_ext.rs index 5fd12ee2e984..8a9581332447 100644 --- a/bridges/modules/parachains/src/call_ext.rs +++ b/bridges/modules/parachains/src/call_ext.rs @@ -122,7 +122,7 @@ impl, I: 'static> SubmitParachainHeadsHelper { .0 .checked_sub(stored_best_head.best_head_hash.at_relay_block_number) { - Some(improved_by) if improved_by != 0 => improved_by, + Some(improved_by) if improved_by > Zero::zero() => improved_by, _ => { log::trace!( target: crate::LOG_TARGET, From 3c9acee16ec53fdcba4bfad6176326436c8533ff Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 25 Mar 2024 14:43:09 +0300 Subject: [PATCH 36/70] include overweight from runtime signed extensions into submit_parachain_heads call weight --- bridges/modules/parachains/src/call_ext.rs | 1 + bridges/modules/parachains/src/weights_ext.rs | 27 ++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/bridges/modules/parachains/src/call_ext.rs b/bridges/modules/parachains/src/call_ext.rs index 8a9581332447..d54256a87dfa 100644 --- a/bridges/modules/parachains/src/call_ext.rs +++ b/bridges/modules/parachains/src/call_ext.rs @@ -25,6 +25,7 @@ use frame_support::{ }; use pallet_bridge_grandpa::SubmitFinalityProofHelper; use sp_runtime::{ + traits::Zero, transaction_validity::{InvalidTransaction, TransactionValidityError}, RuntimeDebug, }; diff --git a/bridges/modules/parachains/src/weights_ext.rs b/bridges/modules/parachains/src/weights_ext.rs index 393086a85690..de3450fa29cf 100644 --- a/bridges/modules/parachains/src/weights_ext.rs +++ b/bridges/modules/parachains/src/weights_ext.rs @@ -36,6 +36,20 @@ pub const EXTRA_STORAGE_PROOF_SIZE: u32 = 1024; /// Extended weight info. pub trait WeightInfoExt: WeightInfo { + // Our configuration assumes that the runtime has special signed extensions used to: + // + // 1) boost priority of `submit_finality_proof` transactions; + // + // 2) slash relayer if he submits an invalid transaction. + // + // We read and update storage values of other pallets (`pallet-bridge-relayers` and + // balances/assets pallet). So we need to add this weight to the weight of our call. + // Hence two following methods. + + /// Extra weight that is added to the `submit_finality_proof` call weight by signed extensions + /// that are declared at runtime level. + fn submit_parachain_heads_overhead_from_runtime() -> Weight; + /// Storage proof overhead, that is included in every storage proof. /// /// The relayer would pay some extra fee for additional proof bytes, since they mean @@ -65,7 +79,10 @@ pub trait WeightInfoExt: WeightInfo { let pruning_weight = Self::parachain_head_pruning_weight(db_weight).saturating_mul(parachains_count as u64); - base_weight.saturating_add(proof_size_overhead).saturating_add(pruning_weight) + base_weight + .saturating_add(proof_size_overhead) + .saturating_add(pruning_weight) + .saturating_add(Self::submit_parachain_heads_overhead_from_runtime()) } /// Returns weight of single parachain head storage update. @@ -95,12 +112,20 @@ pub trait WeightInfoExt: WeightInfo { } impl WeightInfoExt for () { + fn submit_parachain_heads_overhead_from_runtime() -> Weight { + Weight::zero() + } + fn expected_extra_storage_proof_size() -> u32 { EXTRA_STORAGE_PROOF_SIZE } } impl WeightInfoExt for BridgeWeight { + fn submit_parachain_heads_overhead_from_runtime() -> Weight { + Weight::zero() + } + fn expected_extra_storage_proof_size() -> u32 { EXTRA_STORAGE_PROOF_SIZE } From 75b4bc0288142133f979263b1d6f1049a6e0ded5 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 25 Mar 2024 14:48:46 +0300 Subject: [PATCH 37/70] fixed comment --- bridges/modules/parachains/src/weights_ext.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/modules/parachains/src/weights_ext.rs b/bridges/modules/parachains/src/weights_ext.rs index de3450fa29cf..64dad625de08 100644 --- a/bridges/modules/parachains/src/weights_ext.rs +++ b/bridges/modules/parachains/src/weights_ext.rs @@ -38,7 +38,7 @@ pub const EXTRA_STORAGE_PROOF_SIZE: u32 = 1024; pub trait WeightInfoExt: WeightInfo { // Our configuration assumes that the runtime has special signed extensions used to: // - // 1) boost priority of `submit_finality_proof` transactions; + // 1) boost priority of `submit_parachain_heads` transactions; // // 2) slash relayer if he submits an invalid transaction. // From 1effc3d99fe13bc25a0affd94694087f821c1e90 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 25 Mar 2024 14:49:59 +0300 Subject: [PATCH 38/70] removed obsolete code comment --- bridges/modules/parachains/src/lib.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/bridges/modules/parachains/src/lib.rs b/bridges/modules/parachains/src/lib.rs index f73da3e25255..f56df053ee11 100644 --- a/bridges/modules/parachains/src/lib.rs +++ b/bridges/modules/parachains/src/lib.rs @@ -541,9 +541,6 @@ pub mod pallet { parachain_head_hash, )?; - // TODO: do we want to refund submissions of large heads as we do for - // GRANDPA? - is_updated_something = true; if is_free { free_parachain_heads = free_parachain_heads + 1; From 3a7e5aab42f8c048988e3848858432ee368f2248 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 25 Mar 2024 14:58:04 +0300 Subject: [PATCH 39/70] added important comment + test --- bridges/modules/grandpa/src/lib.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/bridges/modules/grandpa/src/lib.rs b/bridges/modules/grandpa/src/lib.rs index aee709904c84..58b7d157dd6f 100644 --- a/bridges/modules/grandpa/src/lib.rs +++ b/bridges/modules/grandpa/src/lib.rs @@ -512,6 +512,9 @@ pub mod pallet { pub fn on_free_header_imported, I: 'static>() { FreeHeadersRemaining::::mutate(|count| { *count = match *count { + // never set to `None` here - the signed extension assumes that it is `None` + // outside of block execution - i.e. when transaction is validatied from + // the transaction pool Some(count) => Some(count.saturating_sub(1)), None => None, } @@ -1684,4 +1687,17 @@ mod tests { ); }) } + + #[test] + fn on_free_header_imported_never_sets_to_none() { + run_test(|| { + FreeHeadersRemaining::::set(Some(2)); + on_free_header_imported::(); + assert_eq!(FreeHeadersRemaining::::get(), Some(1)); + on_free_header_imported::(); + assert_eq!(FreeHeadersRemaining::::get(), Some(0)); + on_free_header_imported::(); + assert_eq!(FreeHeadersRemaining::::get(), Some(0)); + }) + } } From d1eaba0b054f77ba8ee88b8cb346bdc6ed48efa1 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 25 Mar 2024 15:52:25 +0300 Subject: [PATCH 40/70] fix no-std compile --- bridges/modules/grandpa/src/call_ext.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/modules/grandpa/src/call_ext.rs b/bridges/modules/grandpa/src/call_ext.rs index bed8ea38442a..8568e75dfa13 100644 --- a/bridges/modules/grandpa/src/call_ext.rs +++ b/bridges/modules/grandpa/src/call_ext.rs @@ -136,7 +136,7 @@ impl, I: 'static> SubmitFinalityProofHelper { if improved_by < free_headers_interval.into() { log::trace!( target: crate::LOG_TARGET, - "Cannot accept free {:?} header {:?}. Too small difference between submitted headers: {} vs {}", + "Cannot accept free {:?} header {:?}. Too small difference between submitted headers: {:?} vs {}", T::BridgedChain::ID, call_info.block_number, improved_by, From 50a44eeeb20b1c47be2601726fe3fb10244c6625 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 25 Mar 2024 16:17:38 +0300 Subject: [PATCH 41/70] again fix lost change --- bridges/modules/grandpa/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/modules/grandpa/src/lib.rs b/bridges/modules/grandpa/src/lib.rs index 58b7d157dd6f..da7a25e5fceb 100644 --- a/bridges/modules/grandpa/src/lib.rs +++ b/bridges/modules/grandpa/src/lib.rs @@ -336,7 +336,7 @@ pub mod pallet { target: LOG_TARGET, "Successfully imported finalized header with hash {:?}! Free: {}", hash, - if may_refund_call_fee { "No" } else { "Yes" }, + if may_refund_call_fee { "Yes" } else { "No" }, ); // the proof size component of the call weight assumes that there are From 346cb7cece93846837495bc4bc6219b98633b42f Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 25 Mar 2024 17:01:26 +0300 Subject: [PATCH 42/70] fix for mandatory + interval check in ext --- .../extensions/refund_relayer_extension.rs | 4 ++ bridges/modules/grandpa/src/call_ext.rs | 67 +++++++++++++++---- 2 files changed, 59 insertions(+), 12 deletions(-) diff --git a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs index 932be09c49a0..ffd3f9104d8e 100644 --- a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs +++ b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs @@ -1295,6 +1295,7 @@ pub(crate) mod tests { current_set_id: None, extra_weight: Weight::zero(), extra_size: 0, + is_mandatory: false, is_free_execution_expected: false, }, SubmitParachainHeadsInfo { @@ -1334,6 +1335,7 @@ pub(crate) mod tests { current_set_id: None, extra_weight: Weight::zero(), extra_size: 0, + is_mandatory: false, is_free_execution_expected: false, }, SubmitParachainHeadsInfo { @@ -1369,6 +1371,7 @@ pub(crate) mod tests { current_set_id: None, extra_weight: Weight::zero(), extra_size: 0, + is_mandatory: false, is_free_execution_expected: false, }, MessagesCallInfo::ReceiveMessagesProof(ReceiveMessagesProofInfo { @@ -1402,6 +1405,7 @@ pub(crate) mod tests { current_set_id: None, extra_weight: Weight::zero(), extra_size: 0, + is_mandatory: false, is_free_execution_expected: false, }, MessagesCallInfo::ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo( diff --git a/bridges/modules/grandpa/src/call_ext.rs b/bridges/modules/grandpa/src/call_ext.rs index 8568e75dfa13..e8994797498b 100644 --- a/bridges/modules/grandpa/src/call_ext.rs +++ b/bridges/modules/grandpa/src/call_ext.rs @@ -44,6 +44,8 @@ pub struct SubmitFinalityProofInfo { /// An identifier of the validators set that has signed the submitted justification. /// It might be `None` if deprecated version of the `submit_finality_proof` is used. pub current_set_id: Option, + /// If `true`, then the call proves new **mandatory** header. + pub is_mandatory: bool, /// If `true`, then the call must be free (assuming that everything else is valid) to /// be treated as valid. pub is_free_execution_expected: bool, @@ -132,18 +134,21 @@ impl, I: 'static> SubmitFinalityProofHelper { } // ensure that the `improved_by` is larger than the configured free interval - if let Some(free_headers_interval) = T::FreeHeadersInterval::get() { - if improved_by < free_headers_interval.into() { - log::trace!( - target: crate::LOG_TARGET, - "Cannot accept free {:?} header {:?}. Too small difference between submitted headers: {:?} vs {}", - T::BridgedChain::ID, - call_info.block_number, - improved_by, - free_headers_interval, - ); - - return Err(Error::::BelowFreeHeaderInterval); + if !call_info.is_mandatory { + if let Some(free_headers_interval) = T::FreeHeadersInterval::get() { + if improved_by < free_headers_interval.into() { + log::trace!( + target: crate::LOG_TARGET, + "Cannot accept free {:?} header {:?}. Too small difference \ + between submitted headers: {:?} vs {}", + T::BridgedChain::ID, + call_info.block_number, + improved_by, + free_headers_interval, + ); + + return Err(Error::::BelowFreeHeaderInterval); + } } } @@ -340,6 +345,7 @@ pub(crate) fn submit_finality_proof_info_from_args, I: 'static>( SubmitFinalityProofInfo { block_number, current_set_id, + is_mandatory: is_mandatory_finality_target, is_free_execution_expected, extra_weight, extra_size, @@ -363,6 +369,7 @@ mod tests { make_default_justification, make_justification_for_header, JustificationGeneratorParams, TEST_GRANDPA_SET_ID, }; + use codec::Encode; use frame_support::weights::Weight; use sp_runtime::{testing::DigestItem, traits::Header as _, SaturatedConversion}; @@ -506,6 +513,40 @@ mod tests { bridge_grandpa_call.clone(), ),) .is_ok()); + + // when `improved_by` is less than the free interval BUT it is a mandatory header + let mut mandatory_header = test_header(100); + let consensus_log = sp_consensus_grandpa::ConsensusLog::::ScheduledChange( + sp_consensus_grandpa::ScheduledChange { + next_authorities: bp_test_utils::authority_list(), + delay: 0, + }, + ); + mandatory_header.digest = sp_runtime::Digest { + logs: vec![DigestItem::Consensus( + sp_consensus_grandpa::GRANDPA_ENGINE_ID, + consensus_log.encode(), + )], + }; + let justification = make_justification_for_header(JustificationGeneratorParams { + header: mandatory_header.clone(), + set_id: 1, + ..Default::default() + }); + let bridge_grandpa_call = crate::Call::::submit_finality_proof_ex { + finality_target: Box::new(mandatory_header), + justification, + current_set_id: 0, + is_free_execution_expected: true, + }; + BestFinalized::::put(HeaderId( + 100 - FreeHeadersInterval::get() as u64 + 1, + sp_core::H256::default(), + )); + assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa( + bridge_grandpa_call.clone(), + ),) + .is_ok()); }) } @@ -534,6 +575,7 @@ mod tests { current_set_id: None, extra_weight: Weight::zero(), extra_size: 0, + is_mandatory: false, is_free_execution_expected: false, }) ); @@ -553,6 +595,7 @@ mod tests { current_set_id: Some(777), extra_weight: Weight::zero(), extra_size: 0, + is_mandatory: false, is_free_execution_expected: false, }) ); From aea38c07e6da17546d44af59c74bd973bfaafd12 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 11 Apr 2024 14:43:06 +0300 Subject: [PATCH 43/70] apply review suggestions --- .../src/extensions/check_obsolete_extension.rs | 5 +++-- .../src/extensions/refund_relayer_extension.rs | 12 ++++++------ bridges/modules/grandpa/src/call_ext.rs | 12 ++++-------- bridges/modules/grandpa/src/lib.rs | 6 ++---- bridges/modules/parachains/src/call_ext.rs | 11 ++++++----- bridges/modules/parachains/src/lib.rs | 10 +++++----- 6 files changed, 26 insertions(+), 30 deletions(-) diff --git a/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs b/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs index abca4d6e8a08..f4852ebbb491 100644 --- a/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs +++ b/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs @@ -363,6 +363,7 @@ mod tests { DefaultRefundableParachainId, }; use bp_polkadot_core::parachains::ParaId; + use bp_runtime::HeaderId; use frame_support::assert_err; use sp_runtime::{ traits::{ConstU64, SignedExtension}, @@ -566,7 +567,7 @@ mod tests { &relayer_account_at_this_chain(), true, Some(SubmitParachainHeadsInfo { - at_relay_block: (150, Default::default()), + at_relay_block: HeaderId(150, Default::default()), para_id: ParaId(TestParachain::get()), para_head_hash: [150u8; 32].into(), is_free_execution_expected: false, @@ -588,7 +589,7 @@ mod tests { &relayer_account_at_this_chain(), false, Some(SubmitParachainHeadsInfo { - at_relay_block: (100, Default::default()), + at_relay_block: HeaderId(100, Default::default()), para_id: ParaId(TestParachain::get()), para_head_hash: [100u8; 32].into(), is_free_execution_expected: false, diff --git a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs index ffd3f9104d8e..4fc1d588e26e 100644 --- a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs +++ b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs @@ -660,7 +660,7 @@ where extra_size: &mut u32, ) -> bool { // check if relay chain state has been updated - let is_granda_call_succeeded = + let is_grandpa_call_successful = RefundBridgedGrandpaMessages::< Runtime, Runtime::BridgesGrandpaPalletInstance, @@ -669,7 +669,7 @@ where Priority, Id, >::additional_call_result_check(relayer, call_info, extra_weight, extra_size); - if !is_granda_call_succeeded { + if !is_grandpa_call_successful { return false } @@ -1299,7 +1299,7 @@ pub(crate) mod tests { is_free_execution_expected: false, }, SubmitParachainHeadsInfo { - at_relay_block: (200, [0u8; 32].into()), + at_relay_block: HeaderId(200, [0u8; 32].into()), para_id: ParaId(TestParachain::get()), para_head_hash: [200u8; 32].into(), is_free_execution_expected: false, @@ -1339,7 +1339,7 @@ pub(crate) mod tests { is_free_execution_expected: false, }, SubmitParachainHeadsInfo { - at_relay_block: (200, [0u8; 32].into()), + at_relay_block: HeaderId(200, [0u8; 32].into()), para_id: ParaId(TestParachain::get()), para_head_hash: [200u8; 32].into(), is_free_execution_expected: false, @@ -1431,7 +1431,7 @@ pub(crate) mod tests { relayer: relayer_account_at_this_chain(), call_info: CallInfo::ParachainFinalityAndMsgs( SubmitParachainHeadsInfo { - at_relay_block: (200, [0u8; 32].into()), + at_relay_block: HeaderId(200, [0u8; 32].into()), para_id: ParaId(TestParachain::get()), para_head_hash: [200u8; 32].into(), is_free_execution_expected: false, @@ -1456,7 +1456,7 @@ pub(crate) mod tests { relayer: relayer_account_at_this_chain(), call_info: CallInfo::ParachainFinalityAndMsgs( SubmitParachainHeadsInfo { - at_relay_block: (200, [0u8; 32].into()), + at_relay_block: HeaderId(200, [0u8; 32].into()), para_id: ParaId(TestParachain::get()), para_head_hash: [200u8; 32].into(), is_free_execution_expected: false, diff --git a/bridges/modules/grandpa/src/call_ext.rs b/bridges/modules/grandpa/src/call_ext.rs index e8994797498b..6fa62ec0cff4 100644 --- a/bridges/modules/grandpa/src/call_ext.rs +++ b/bridges/modules/grandpa/src/call_ext.rs @@ -89,18 +89,14 @@ impl, I: 'static> SubmitFinalityProofHelper { /// Returns `true` if we may fit more free headers into the current block. If `false` is /// returned, the call will be paid even if `is_free_execution_expected` has been set /// to `true`. - pub fn can_import_anything_for_free() -> bool { + pub fn has_free_header_slots() -> bool { // `unwrap_or(u32::MAX)` means that if `FreeHeadersRemaining` is `None`, we may accept - // this header for free. That is a small cheat - is is `None` if executed outside of + // this header for free. That is a small cheat - it is `None` if executed outside of // transaction (e.g. during block initialization). Normal relayer would never submit // such calls, but if he did, that is not our problem. During normal transactions, // the `FreeHeadersRemaining` is always `Some(_)`. let free_headers_remaining = FreeHeadersRemaining::::get().unwrap_or(u32::MAX); - if free_headers_remaining == 0 { - return false - } - - true + free_headers_remaining > 0 } /// Check that the: (1) GRANDPA head provided by the `SubmitFinalityProof` is better than the @@ -122,7 +118,7 @@ impl, I: 'static> SubmitFinalityProofHelper { } // else - if we can not accept more free headers, "reject" the transaction - if !Self::can_import_anything_for_free() { + if !Self::has_free_header_slots() { log::trace!( target: crate::LOG_TARGET, "Cannot accept free {:?} header {:?}. No more free slots remaining", diff --git a/bridges/modules/grandpa/src/lib.rs b/bridges/modules/grandpa/src/lib.rs index da7a25e5fceb..7599e7c5bffc 100644 --- a/bridges/modules/grandpa/src/lib.rs +++ b/bridges/modules/grandpa/src/lib.rs @@ -125,7 +125,7 @@ pub mod pallet { /// The distance between bridged chain headers, that may be submitted for free. The /// first free header is header number zero, the next one is header number /// `FreeHeadersInterval::get()` or any of its descendant if that header has not - /// bee submitted. In other words, interval between free headers should be at least + /// been submitted. In other words, interval between free headers should be at least /// `FreeHeadersInterval`. #[pallet::constant] type FreeHeadersInterval: Get>; @@ -311,7 +311,6 @@ pub mod pallet { let maybe_new_authority_set = try_enact_authority_change::(&finality_target, set_id)?; let may_refund_call_fee = may_refund_call_fee::( - maybe_new_authority_set.is_some(), &finality_target, &justification, current_set_id, @@ -524,7 +523,6 @@ pub mod pallet { /// Return true if we may refund transaction cost to the submitter. In other words, /// this transaction is considered as common good deed w.r.t to pallet configuration. fn may_refund_call_fee, I: 'static>( - is_mandatory_header: bool, finality_target: &BridgedHeader, justification: &GrandpaJustification>, current_set_id: SetId, @@ -550,7 +548,7 @@ pub mod pallet { } // if that's a mandatory header => refund - if is_mandatory_header { + if call_info.is_mandatory { return true; } diff --git a/bridges/modules/parachains/src/call_ext.rs b/bridges/modules/parachains/src/call_ext.rs index d54256a87dfa..d5f5056aa335 100644 --- a/bridges/modules/parachains/src/call_ext.rs +++ b/bridges/modules/parachains/src/call_ext.rs @@ -18,7 +18,7 @@ use crate::{Config, GrandpaPalletOf, Pallet, RelayBlockHash, RelayBlockNumber}; use bp_header_chain::HeaderChain; use bp_parachains::BestParaHeadHash; use bp_polkadot_core::parachains::{ParaHash, ParaId}; -use bp_runtime::OwnedBridgeModule; +use bp_runtime::{HeaderId, OwnedBridgeModule}; use frame_support::{ dispatch::CallableCallFor, traits::{Get, IsSubType}, @@ -35,7 +35,7 @@ use sp_runtime::{ pub struct SubmitParachainHeadsInfo { /// Number and hash of the finalized relay block that has been used to prove parachain /// finality. - pub at_relay_block: (RelayBlockNumber, RelayBlockHash), + pub at_relay_block: HeaderId, /// Parachain identifier. pub para_id: ParaId, /// Hash of the bundled parachain head. @@ -75,7 +75,8 @@ impl, I: 'static> SubmitParachainHeadsHelper { } // reject if no more free slots remaining in the block - if !SubmitFinalityProofHelper::::can_import_anything_for_free() { + if !SubmitFinalityProofHelper::::has_free_header_slots() + { log::trace!( target: crate::LOG_TARGET, "The free parachain {:?} head can't be updated: no more free slots \ @@ -201,7 +202,7 @@ pub trait CallSubType, I: 'static>: .. }) => match ¶chains[..] { &[(para_id, para_head_hash)] => Some(SubmitParachainHeadsInfo { - at_relay_block: *at_relay_block, + at_relay_block: HeaderId(at_relay_block.0, at_relay_block.1), para_id, para_head_hash, is_free_execution_expected: false, @@ -215,7 +216,7 @@ pub trait CallSubType, I: 'static>: .. }) => match ¶chains[..] { &[(para_id, para_head_hash)] => Some(SubmitParachainHeadsInfo { - at_relay_block: *at_relay_block, + at_relay_block: HeaderId(at_relay_block.0, at_relay_block.1), para_id, para_head_hash, is_free_execution_expected: *is_free_execution_expected, diff --git a/bridges/modules/parachains/src/lib.rs b/bridges/modules/parachains/src/lib.rs index f56df053ee11..308bc97008b1 100644 --- a/bridges/modules/parachains/src/lib.rs +++ b/bridges/modules/parachains/src/lib.rs @@ -536,7 +536,7 @@ pub mod pallet { let artifacts = Pallet::::update_parachain_head( parachain, stored_best_head.take(), - (relay_block_number, relay_block_hash), + HeaderId(relay_block_number, relay_block_hash), parachain_head_data, parachain_head_hash, )?; @@ -579,7 +579,7 @@ pub mod pallet { // check if we allow this submission for free let is_free = total_parachains == 1 && free_parachain_heads == total_parachains - && SubmitFinalityProofHelper::::can_import_anything_for_free(); + && SubmitFinalityProofHelper::::has_free_header_slots(); let pays_fee = if is_free { log::trace!(target: LOG_TARGET, "Parachain heads update transaction is free"); pallet_bridge_grandpa::on_free_header_imported::( @@ -647,7 +647,7 @@ pub mod pallet { pub(super) fn update_parachain_head( parachain: ParaId, stored_best_head: Option, - new_at_relay_block: (RelayBlockNumber, RelayBlockHash), + new_at_relay_block: HeaderId, new_head_data: ParaStoredHeaderData, new_head_hash: ParaHash, ) -> Result { @@ -657,7 +657,7 @@ pub mod pallet { at_relay_block: new_at_relay_block, para_id: parachain, para_head_hash: new_head_hash, - // don't actually matter here + // doesn't actually matter here is_free_execution_expected: false, }; if SubmitParachainHeadsHelper::::check_obsolete(&update).is_err() { @@ -808,7 +808,7 @@ pub fn initialize_for_benchmarks, I: 'static, PC: Parachain::update_parachain_head( parachain, None, - (0, Default::default()), + HeaderId(0, Default::default()), updated_head_data, parachain_head.hash(), ) From 0b1fc990b1b4f7cfc8e77fb8cfa03e58d128a26e Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 11 Apr 2024 15:04:22 +0300 Subject: [PATCH 44/70] Update modules/grandpa/src/lib.rs Co-authored-by: Adrian Catangiu --- bridges/modules/grandpa/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bridges/modules/grandpa/src/lib.rs b/bridges/modules/grandpa/src/lib.rs index 7599e7c5bffc..bdd186bceb70 100644 --- a/bridges/modules/grandpa/src/lib.rs +++ b/bridges/modules/grandpa/src/lib.rs @@ -511,11 +511,11 @@ pub mod pallet { pub fn on_free_header_imported, I: 'static>() { FreeHeadersRemaining::::mutate(|count| { *count = match *count { - // never set to `None` here - the signed extension assumes that it is `None` - // outside of block execution - i.e. when transaction is validatied from - // the transaction pool - Some(count) => Some(count.saturating_sub(1)), None => None, + // the signed extension expects that `None` means outside of block + // execution - i.e. when transaction is validated from the transaction pool, + // so use `saturating_sub` and don't go from `Some(0)`->`None` + Some(count) => Some(count.saturating_sub(1)), } }); } From 66d1d96b000df9b24ceda8ad7a73cdfdba809390 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 12 Apr 2024 13:46:36 +0300 Subject: [PATCH 45/70] remove unused getter --- bridges/modules/grandpa/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/bridges/modules/grandpa/src/lib.rs b/bridges/modules/grandpa/src/lib.rs index bdd186bceb70..cb536eb07ff6 100644 --- a/bridges/modules/grandpa/src/lib.rs +++ b/bridges/modules/grandpa/src/lib.rs @@ -372,7 +372,6 @@ pub mod pallet { /// finalization. So it never ends up in the storage trie. #[pallet::storage] #[pallet::whitelist_storage] - #[pallet::getter(fn free_mandatory_headers_remaining)] pub type FreeHeadersRemaining, I: 'static = ()> = StorageValue<_, u32, OptionQuery>; From bea7677952902de88403715ccf02b182ebc9aad0 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 15 Apr 2024 11:10:43 +0300 Subject: [PATCH 46/70] fixed TODOs and added some more tests --- .../extensions/check_obsolete_extension.rs | 127 ++++++++++++++---- 1 file changed, 100 insertions(+), 27 deletions(-) diff --git a/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs b/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs index 5139b02788a6..7e8d8a49de72 100644 --- a/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs +++ b/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs @@ -40,14 +40,15 @@ use sp_runtime::{ /// We need this trait in order to be able to implement it for the messages pallet, /// since the implementation is done outside of the pallet crate. pub trait BridgeRuntimeFilterCall { - /// Data that may be passed from the validate to `on_failure`. + /// Data that may be passed from the validate to `post_dispatch`. type ToPostDispatch; /// Called during validation. Needs to checks whether a runtime call, submitted /// by the `who` is valid. `who` may be `None` if transaction is not signed /// by a regular account. fn validate(who: &AccountId, call: &Call) -> (Self::ToPostDispatch, TransactionValidity); /// Called after transaction is dispatched. - fn post_dispatch(_who: &AccountId, _has_failed: bool, _to_on_failure: Self::ToPostDispatch) {} + fn post_dispatch(_who: &AccountId, _has_failed: bool, _to_post_dispatch: Self::ToPostDispatch) { + } } /// Wrapper for the bridge GRANDPA pallet that checks calls for obsolete submissions @@ -334,11 +335,9 @@ macro_rules! generate_bridge_reject_obsolete_headers_and_messages { len: usize, result: &sp_runtime::DispatchResult, ) -> Result<(), sp_runtime::transaction_validity::TransactionValidityError> { - // TODO: check me: removed if result.is_ok() { return Ok(()); } use tuplex::PopFront; - let has_failed = result.is_err(); - // TODO: check me: return if `to_post_dispatch` is `None` let Some((relayer, to_post_dispatch)) = to_post_dispatch else { return Ok(()) }; + let has_failed = result.is_err(); $( let (item, to_post_dispatch) = to_post_dispatch.pop_front(); < @@ -369,10 +368,11 @@ mod tests { }; use bp_polkadot_core::parachains::ParaId; use bp_runtime::HeaderId; - use frame_support::assert_err; + use frame_support::{assert_err, assert_ok}; use sp_runtime::{ traits::{ConstU64, SignedExtension}, transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction}, + DispatchError, }; pub struct MockCall { @@ -394,6 +394,16 @@ mod tests { } pub struct FirstFilterCall; + impl FirstFilterCall { + fn post_dispatch_called_with(success: bool) { + frame_support::storage::unhashed::put(&[1], &success); + } + + fn verify_post_dispatch_called_with(success: bool) { + assert_eq!(frame_support::storage::unhashed::get::(&[1]), Some(success)); + } + } + impl BridgeRuntimeFilterCall for FirstFilterCall { type ToPostDispatch = u64; fn validate(_who: &u64, call: &MockCall) -> (u64, TransactionValidity) { @@ -403,9 +413,25 @@ mod tests { (1, Ok(ValidTransaction { priority: 1, ..Default::default() })) } + + fn post_dispatch(_who: &u64, has_failed: bool, to_post_dispatch: Self::ToPostDispatch) { + Self::post_dispatch_called_with(!has_failed); + assert_eq!(to_post_dispatch, 1); + } } pub struct SecondFilterCall; + + impl SecondFilterCall { + fn post_dispatch_called_with(success: bool) { + frame_support::storage::unhashed::put(&[2], &success); + } + + fn verify_post_dispatch_called_with(success: bool) { + assert_eq!(frame_support::storage::unhashed::get::(&[2]), Some(success)); + } + } + impl BridgeRuntimeFilterCall for SecondFilterCall { type ToPostDispatch = u64; fn validate(_who: &u64, call: &MockCall) -> (u64, TransactionValidity) { @@ -415,6 +441,11 @@ mod tests { (2, Ok(ValidTransaction { priority: 2, ..Default::default() })) } + + fn post_dispatch(_who: &u64, has_failed: bool, to_post_dispatch: Self::ToPostDispatch) { + Self::post_dispatch_called_with(!has_failed); + assert_eq!(to_post_dispatch, 2); + } } #[test] @@ -426,30 +457,72 @@ mod tests { SecondFilterCall ); - // TODO: add tests for both validate and pre_dispatch here? + run_test(|| { + assert_err!( + BridgeRejectObsoleteHeadersAndMessages.validate(&42, &MockCall { data: 1 }, &(), 0), + InvalidTransaction::Custom(1) + ); + assert_err!( + BridgeRejectObsoleteHeadersAndMessages.pre_dispatch( + &42, + &MockCall { data: 1 }, + &(), + 0 + ), + InvalidTransaction::Custom(1) + ); - assert_err!( - BridgeRejectObsoleteHeadersAndMessages.validate(&42, &MockCall { data: 1 }, &(), 0), - InvalidTransaction::Custom(1) - ); + assert_err!( + BridgeRejectObsoleteHeadersAndMessages.validate(&42, &MockCall { data: 2 }, &(), 0), + InvalidTransaction::Custom(2) + ); + assert_err!( + BridgeRejectObsoleteHeadersAndMessages.pre_dispatch( + &42, + &MockCall { data: 2 }, + &(), + 0 + ), + InvalidTransaction::Custom(2) + ); - assert_err!( - BridgeRejectObsoleteHeadersAndMessages.validate(&42, &MockCall { data: 2 }, &(), 0), - InvalidTransaction::Custom(2) - ); + assert_eq!( + BridgeRejectObsoleteHeadersAndMessages + .validate(&42, &MockCall { data: 3 }, &(), 0) + .unwrap(), + ValidTransaction { priority: 3, ..Default::default() }, + ); + assert_eq!( + BridgeRejectObsoleteHeadersAndMessages + .pre_dispatch(&42, &MockCall { data: 3 }, &(), 0) + .unwrap(), + (42, (1, 2)), + ); - assert_eq!( - BridgeRejectObsoleteHeadersAndMessages - .validate(&42, &MockCall { data: 3 }, &(), 0) - .unwrap(), - ValidTransaction { priority: 3, ..Default::default() }, - ); - assert_eq!( - BridgeRejectObsoleteHeadersAndMessages - .pre_dispatch(&42, &MockCall { data: 3 }, &(), 0) - .unwrap(), - (42, (1, 2)), - ); + // when post_dispatch is called with `Ok(())`, it is propagated to all "nested" + // extensions + assert_ok!(BridgeRejectObsoleteHeadersAndMessages::post_dispatch( + Some((0, (1, 2))), + &(), + &(), + 0, + &Ok(()) + )); + FirstFilterCall::verify_post_dispatch_called_with(true); + SecondFilterCall::verify_post_dispatch_called_with(true); + + // when post_dispatch is called with `Err(())`, it is propagated to all "nested" + // extensions + assert_ok!(BridgeRejectObsoleteHeadersAndMessages::post_dispatch( + Some((0, (1, 2))), + &(), + &(), + 0, + &Err(DispatchError::BadOrigin) + )); + FirstFilterCall::verify_post_dispatch_called_with(false); + SecondFilterCall::verify_post_dispatch_called_with(false); + }); } frame_support::parameter_types! { From c04908262dba33d5091d0c76d6ffb74526f631e1 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 15 Apr 2024 12:05:34 +0300 Subject: [PATCH 47/70] apply new extensions and configurations to bridge-hub-rococo-runtime and bridge-hub-westend-runtime: - it compiles - tests are failing - we better need to have a tooling for PriorityBoostPerHeader (TODO left) --- Cargo.lock | 2 ++ .../bridge-hubs/bridge-hub-rococo/Cargo.toml | 2 ++ .../src/bridge_common_config.rs | 6 ++-- .../src/bridge_to_bulletin_config.rs | 15 +++++----- .../src/bridge_to_westend_config.rs | 12 ++++---- .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 30 +++++++++++++++++-- .../bridge-hub-rococo/src/weights/mod.rs | 20 +++++++++++++ .../bridge-hubs/bridge-hub-westend/Cargo.toml | 2 ++ .../src/bridge_to_rococo_config.rs | 12 ++++---- .../bridge-hubs/bridge-hub-westend/src/lib.rs | 23 ++++++++++++-- .../bridge-hub-westend/src/weights/mod.rs | 20 +++++++++++++ 11 files changed, 117 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 11839c995053..1a90cd7e8b53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2146,6 +2146,7 @@ dependencies = [ "static_assertions", "substrate-wasm-builder", "testnet-parachains-constants", + "tuplex", ] [[package]] @@ -2305,6 +2306,7 @@ dependencies = [ "static_assertions", "substrate-wasm-builder", "testnet-parachains-constants", + "tuplex", "westend-runtime-constants", ] diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml index f5a75aa03acd..574406ab305f 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -22,6 +22,7 @@ scale-info = { version = "2.11.1", default-features = false, features = [ "derive", ] } serde = { optional = true, features = ["derive"], workspace = true, default-features = true } +tuplex = { version = "0.1", default-features = false } # Substrate frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } @@ -218,6 +219,7 @@ std = [ "sp-version/std", "substrate-wasm-builder", "testnet-parachains-constants/std", + "tuplex/std", "xcm-builder/std", "xcm-executor/std", "xcm/std", diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs index 93ef9470363c..5551b05e2025 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs @@ -49,7 +49,8 @@ pub type BridgeGrandpaWestendInstance = pallet_bridge_grandpa::Instance3; impl pallet_bridge_grandpa::Config for Runtime { type RuntimeEvent = RuntimeEvent; type BridgedChain = bp_westend::Westend; - type MaxFreeMandatoryHeadersPerBlock = ConstU32<4>; + type MaxFreeHeadersPerBlock = ConstU32<4>; + type FreeHeadersInterval = ConstU32<5>; type HeadersToKeep = RelayChainHeadersToKeep; type WeightInfo = weights::pallet_bridge_grandpa::WeightInfo; } @@ -89,7 +90,8 @@ pub type BridgeGrandpaRococoBulletinInstance = pallet_bridge_grandpa::Instance4; impl pallet_bridge_grandpa::Config for Runtime { type RuntimeEvent = RuntimeEvent; type BridgedChain = bp_polkadot_bulletin::PolkadotBulletin; - type MaxFreeMandatoryHeadersPerBlock = ConstU32<4>; + type MaxFreeHeadersPerBlock = ConstU32<4>; + type FreeHeadersInterval = ConstU32<5>; type HeadersToKeep = RelayChainHeadersToKeep; // Technically this is incorrect - we have two pallet instances and ideally we shall // benchmark every instance separately. But the benchmarking engine has a flaw - it diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs index 8845f0538b5c..2325559b0a75 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs @@ -20,17 +20,15 @@ //! are reusing Polkadot Bulletin chain primitives everywhere here. use crate::{ - bridge_common_config::{BridgeGrandpaRococoBulletinInstance, BridgeHubRococo}, - weights, - xcm_config::UniversalLocation, - AccountId, BridgeRococoBulletinGrandpa, BridgeRococoBulletinMessages, PolkadotXcm, Runtime, - RuntimeEvent, XcmOverRococoBulletin, XcmRouter, + bridge_common_config::BridgeHubRococo, weights, xcm_config::UniversalLocation, AccountId, + BridgeRococoBulletinGrandpa, BridgeRococoBulletinMessages, PolkadotXcm, Runtime, RuntimeEvent, + XcmOverRococoBulletin, XcmRouter, }; use bp_messages::LaneId; use bp_runtime::Chain; use bridge_runtime_common::{ extensions::refund_relayer_extension::{ - ActualFeeRefund, RefundBridgedGrandpaMessages, RefundSignedExtensionAdapter, + ActualFeeRefund, RefundBridgedMessages, RefundSignedExtensionAdapter, RefundableMessagesLane, }, messages, @@ -90,6 +88,8 @@ parameter_types! { /// meaning of this value. pub PriorityBoostPerMessage: u64 = 182_044_444_444_444; + pub PriorityBoostPerHeader: u64 = PriorityBoostPerMessage::get() / 1_000_000; // TODO + /// Identifier of the sibling Rococo People parachain. pub RococoPeopleParaId: cumulus_primitives_core::ParaId = rococo_runtime_constants::system_parachain::PEOPLE_ID.into(); /// A route (XCM location and bridge lane) that the Rococo People Chain -> Rococo Bulletin Chain @@ -169,9 +169,8 @@ impl messages::BridgedChainWithMessages for RococoBulletin {} /// Signed extension that refunds relayers that are delivering messages from the Rococo Bulletin /// chain. pub type OnBridgeHubRococoRefundRococoBulletinMessages = RefundSignedExtensionAdapter< - RefundBridgedGrandpaMessages< + RefundBridgedMessages< Runtime, - BridgeGrandpaRococoBulletinInstance, RefundableMessagesLane< WithRococoBulletinMessagesInstance, RococoPeopleToRococoBulletinMessagesLane, diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs index e5a00073407f..3e224b2d7078 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs @@ -29,8 +29,8 @@ use bp_messages::LaneId; use bp_runtime::Chain; use bridge_runtime_common::{ extensions::refund_relayer_extension::{ - ActualFeeRefund, RefundBridgedParachainMessages, RefundSignedExtensionAdapter, - RefundableMessagesLane, RefundableParachain, + ActualFeeRefund, RefundBridgedMessages, RefundSignedExtensionAdapter, + RefundableMessagesLane, }, messages, messages::{ @@ -68,6 +68,8 @@ parameter_types! { // see the `FEE_BOOST_PER_MESSAGE` constant to get the meaning of this value pub PriorityBoostPerMessage: u64 = 182_044_444_444_444; + pub PriorityBoostPerHeader: u64 = PriorityBoostPerMessage::get() / 1_000_000; // TODO + pub AssetHubRococoParaId: cumulus_primitives_core::ParaId = bp_asset_hub_rococo::ASSET_HUB_ROCOCO_PARACHAIN_ID.into(); pub AssetHubWestendParaId: cumulus_primitives_core::ParaId = bp_asset_hub_westend::ASSET_HUB_WESTEND_PARACHAIN_ID.into(); @@ -174,12 +176,8 @@ impl messages::BridgedChainWithMessages for BridgeHubWestend {} /// Signed extension that refunds relayers that are delivering messages from the Westend parachain. pub type OnBridgeHubRococoRefundBridgeHubWestendMessages = RefundSignedExtensionAdapter< - RefundBridgedParachainMessages< + RefundBridgedMessages< Runtime, - RefundableParachain< - BridgeParachainWestendInstance, - bp_bridge_hub_westend::BridgeHubWestend, - >, RefundableMessagesLane< WithBridgeHubWestendMessagesInstance, AssetHubRococoToAssetHubWestendMessagesLane, diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index 1eac813b10ce..e9967dd93708 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -35,6 +35,12 @@ pub mod bridge_to_westend_config; mod weights; pub mod xcm_config; +use bridge_runtime_common::extensions::{ + check_obsolete_extension::{ + CheckAndBoostBridgeGrandpaTransactions, CheckAndBoostBridgeParachainsTransactions, + }, + refund_relayer_extension::RefundableParachain, +}; use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use snowbridge_beacon_primitives::{Fork, ForkVersions}; use snowbridge_core::{ @@ -740,10 +746,28 @@ pub type XcmOverRococoBulletin = XcmOverPolkadotBulletin; bridge_runtime_common::generate_bridge_reject_obsolete_headers_and_messages! { RuntimeCall, AccountId, // Grandpa - BridgeWestendGrandpa, - BridgeRococoBulletinGrandpa, + CheckAndBoostBridgeGrandpaTransactions< + Runtime, + bridge_common_config::BridgeGrandpaWestendInstance, + bridge_to_westend_config::PriorityBoostPerHeader, + xcm_config::TreasuryAccount, + >, + CheckAndBoostBridgeGrandpaTransactions< + Runtime, + bridge_common_config::BridgeGrandpaRococoBulletinInstance, + bridge_to_bulletin_config::PriorityBoostPerHeader, + xcm_config::TreasuryAccount, + >, // Parachains - BridgeWestendParachains, + CheckAndBoostBridgeParachainsTransactions< + Runtime, + RefundableParachain< + bridge_common_config::BridgeParachainWestendInstance, + bp_bridge_hub_westend::BridgeHubWestend, + >, + bridge_to_westend_config::PriorityBoostPerHeader, + xcm_config::TreasuryAccount, + >, // Messages BridgeWestendMessages, BridgeRococoBulletinMessages diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs index aac39a4564fb..942f243141da 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs @@ -17,8 +17,10 @@ //! Expose the auto generated weight files. +use ::pallet_bridge_grandpa::WeightInfoExt as GrandpaWeightInfoExt; use ::pallet_bridge_messages::WeightInfoExt as MessagesWeightInfoExt; use ::pallet_bridge_parachains::WeightInfoExt as ParachainsWeightInfoExt; +use ::pallet_bridge_relayers::WeightInfo as _; pub mod block_weights; pub mod cumulus_pallet_parachain_system; @@ -56,6 +58,16 @@ use frame_support::weights::Weight; // import trait from dependency module use ::pallet_bridge_relayers::WeightInfoExt as _; +impl GrandpaWeightInfoExt for pallet_bridge_grandpa::WeightInfo { + fn submit_finality_proof_overhead_from_runtime() -> Weight { + // our signed extension: + // 1) checks whether relayer registration is active from validate/pre_dispatch; + // 2) may slash and deregister relayer from post_dispatch + // (2) includes (1), so (2) is the worst case + pallet_bridge_relayers::WeightInfo::::slash_and_deregister() + } +} + impl MessagesWeightInfoExt for pallet_bridge_messages_rococo_to_rococo_bulletin::WeightInfo { @@ -94,4 +106,12 @@ impl ParachainsWeightInfoExt for pallet_bridge_parachains::WeightInfo u32 { bp_bridge_hub_westend::EXTRA_STORAGE_PROOF_SIZE } + + fn submit_parachain_heads_overhead_from_runtime() -> Weight { + // our signed extension: + // 1) checks whether relayer registration is active from validate/pre_dispatch; + // 2) may slash and deregister relayer from post_dispatch + // (2) includes (1), so (2) is the worst case + pallet_bridge_relayers::WeightInfo::::slash_and_deregister() + } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml index 86560caca99c..a7241cc6d10c 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml @@ -18,6 +18,7 @@ hex-literal = { version = "0.4.1" } log = { workspace = true } scale-info = { version = "2.11.1", default-features = false, features = ["derive"] } serde = { optional = true, features = ["derive"], workspace = true, default-features = true } +tuplex = { version = "0.1", default-features = false } # Substrate frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } @@ -180,6 +181,7 @@ std = [ "sp-version/std", "substrate-wasm-builder", "testnet-parachains-constants/std", + "tuplex/std", "westend-runtime-constants/std", "xcm-builder/std", "xcm-executor/std", diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs index d5da41cce286..cbe134fd4974 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs @@ -26,8 +26,8 @@ use bp_parachains::SingleParaStoredHeaderDataBuilder; use bp_runtime::Chain; use bridge_runtime_common::{ extensions::refund_relayer_extension::{ - ActualFeeRefund, RefundBridgedParachainMessages, RefundSignedExtensionAdapter, - RefundableMessagesLane, RefundableParachain, + ActualFeeRefund, RefundBridgedMessages, RefundSignedExtensionAdapter, + RefundableMessagesLane, }, messages, messages::{ @@ -73,6 +73,8 @@ parameter_types! { // see the `FEE_BOOST_PER_MESSAGE` constant to get the meaning of this value pub PriorityBoostPerMessage: u64 = 182_044_444_444_444; + pub PriorityBoostPerHeader: u64 = PriorityBoostPerMessage::get() / 1_000_000; // TODO + pub AssetHubWestendParaId: cumulus_primitives_core::ParaId = bp_asset_hub_westend::ASSET_HUB_WESTEND_PARACHAIN_ID.into(); pub AssetHubRococoParaId: cumulus_primitives_core::ParaId = bp_asset_hub_rococo::ASSET_HUB_ROCOCO_PARACHAIN_ID.into(); @@ -191,9 +193,8 @@ impl ThisChainWithMessages for BridgeHubWestend { /// Signed extension that refunds relayers that are delivering messages from the Rococo parachain. pub type OnBridgeHubWestendRefundBridgeHubRococoMessages = RefundSignedExtensionAdapter< - RefundBridgedParachainMessages< + RefundBridgedMessages< Runtime, - RefundableParachain, RefundableMessagesLane< WithBridgeHubRococoMessagesInstance, AssetHubWestendToAssetHubRococoMessagesLane, @@ -210,7 +211,8 @@ pub type BridgeGrandpaRococoInstance = pallet_bridge_grandpa::Instance1; impl pallet_bridge_grandpa::Config for Runtime { type RuntimeEvent = RuntimeEvent; type BridgedChain = bp_rococo::Rococo; - type MaxFreeMandatoryHeadersPerBlock = ConstU32<4>; + type MaxFreeHeadersPerBlock = ConstU32<4>; + type FreeHeadersInterval = ConstU32<5>; type HeadersToKeep = RelayChainHeadersToKeep; type WeightInfo = weights::pallet_bridge_grandpa::WeightInfo; } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index b4ea2c79f64f..6989c28d03cc 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -32,6 +32,12 @@ pub mod bridge_to_rococo_config; mod weights; pub mod xcm_config; +use bridge_runtime_common::extensions::{ + check_obsolete_extension::{ + CheckAndBoostBridgeGrandpaTransactions, CheckAndBoostBridgeParachainsTransactions, + }, + refund_relayer_extension::RefundableParachain, +}; use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use cumulus_primitives_core::ParaId; use sp_api::impl_runtime_apis; @@ -502,9 +508,22 @@ construct_runtime!( bridge_runtime_common::generate_bridge_reject_obsolete_headers_and_messages! { RuntimeCall, AccountId, // Grandpa - BridgeRococoGrandpa, + CheckAndBoostBridgeGrandpaTransactions< + Runtime, + bridge_to_rococo_config::BridgeGrandpaRococoInstance, + bridge_to_rococo_config::PriorityBoostPerHeader, + xcm_config::TreasuryAccount, + >, // Parachains - BridgeRococoParachains, + CheckAndBoostBridgeParachainsTransactions< + Runtime, + RefundableParachain< + bridge_to_rococo_config::BridgeParachainRococoInstance, + bp_bridge_hub_rococo::BridgeHubRococo, + >, + bridge_to_rococo_config::PriorityBoostPerHeader, + xcm_config::TreasuryAccount, + >, // Messages BridgeRococoMessages } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs index a65ee31d3e55..245daaf8ed91 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs @@ -17,8 +17,10 @@ //! Expose the auto generated weight files. +use ::pallet_bridge_grandpa::WeightInfoExt as GrandpaWeightInfoExt; use ::pallet_bridge_messages::WeightInfoExt as MessagesWeightInfoExt; use ::pallet_bridge_parachains::WeightInfoExt as ParachainsWeightInfoExt; +use ::pallet_bridge_relayers::WeightInfo as _; pub mod block_weights; pub mod cumulus_pallet_parachain_system; @@ -51,6 +53,16 @@ use frame_support::weights::Weight; // import trait from dependency module use ::pallet_bridge_relayers::WeightInfoExt as _; +impl GrandpaWeightInfoExt for pallet_bridge_grandpa::WeightInfo { + fn submit_finality_proof_overhead_from_runtime() -> Weight { + // our signed extension: + // 1) checks whether relayer registration is active from validate/pre_dispatch; + // 2) may slash and deregister relayer from post_dispatch + // (2) includes (1), so (2) is the worst case + pallet_bridge_relayers::WeightInfo::::slash_and_deregister() + } +} + impl MessagesWeightInfoExt for pallet_bridge_messages::WeightInfo { fn expected_extra_storage_proof_size() -> u32 { bp_bridge_hub_rococo::EXTRA_STORAGE_PROOF_SIZE @@ -70,4 +82,12 @@ impl ParachainsWeightInfoExt for pallet_bridge_parachains::WeightInfo u32 { bp_bridge_hub_rococo::EXTRA_STORAGE_PROOF_SIZE } + + fn submit_parachain_heads_overhead_from_runtime() -> Weight { + // our signed extension: + // 1) checks whether relayer registration is active from validate/pre_dispatch; + // 2) may slash and deregister relayer from post_dispatch + // (2) includes (1), so (2) is the worst case + pallet_bridge_relayers::WeightInfo::::slash_and_deregister() + } } From cbf08d0ebfd633f036c023946e72c787161b6410 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 15 Apr 2024 15:49:27 +0300 Subject: [PATCH 48/70] added tooling to compute `PriorityBoostPerRelayHeader` and `PriorityBoostPerParachainHeader` --- .../extensions/check_obsolete_extension.rs | 3 +- .../src/extensions/priority_calculator.rs | 433 +++++++++++++----- .../extensions/refund_relayer_extension.rs | 22 +- .../src/bridge_to_bulletin_config.rs | 16 +- .../src/bridge_to_westend_config.rs | 26 +- .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 6 +- .../src/bridge_to_rococo_config.rs | 26 +- .../bridge-hubs/bridge-hub-westend/src/lib.rs | 4 +- 8 files changed, 390 insertions(+), 146 deletions(-) diff --git a/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs b/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs index 7e8d8a49de72..a17b02564c1b 100644 --- a/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs +++ b/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs @@ -23,6 +23,7 @@ use crate::{ messages_call_ext::MessagesCallSubType, }; use bp_relayers::ExplicitOrAccountParams; +use bp_runtime::Parachain; use pallet_bridge_grandpa::{ BridgedBlockNumber, CallSubType as GrandpaCallSubType, SubmitFinalityProofHelper, }; @@ -141,7 +142,7 @@ where match ParachainsCallSubtype::::check_obsolete_submit_parachain_heads( call, ) { - Ok(Some(our_tx)) if our_tx.base.para_id.0 == RefPara::Id::get() => { + Ok(Some(our_tx)) if our_tx.base.para_id.0 == RefPara::BridgedChain::PARACHAIN_ID => { let to_post_dispatch = Some(our_tx.base); let total_priority_boost = compute_priority_boost::(&who, our_tx.improved_by); diff --git a/bridges/bin/runtime-common/src/extensions/priority_calculator.rs b/bridges/bin/runtime-common/src/extensions/priority_calculator.rs index 5035553f508d..92810290f95e 100644 --- a/bridges/bin/runtime-common/src/extensions/priority_calculator.rs +++ b/bridges/bin/runtime-common/src/extensions/priority_calculator.rs @@ -22,7 +22,6 @@ //! single message with nonce `N`, then the transaction with nonces `N..=N+100` will //! be rejected. This can lower bridge throughput down to one message per block. -use bp_messages::MessageNonce; use frame_support::traits::Get; use sp_runtime::transaction_validity::TransactionPriority; @@ -30,16 +29,19 @@ use sp_runtime::transaction_validity::TransactionPriority; #[allow(unused_imports)] pub use integrity_tests::*; -/// Compute priority boost for message delivery transaction that delivers -/// given number of messages. -pub fn compute_priority_boost( - messages: MessageNonce, -) -> TransactionPriority +/// We'll deal with different bridge items here - messages, headers, ... +/// To avoid being too verbose with generic code, let's just define a separate alias. +pub type ItemCount = u64; + +/// Compute priority boost for transaction that brings given number of bridge +/// items (messages, headers, ...), when every additional item adds `PriorityBoostPerItem` +/// to transaction priority. +pub fn compute_priority_boost(n_items: ItemCount) -> TransactionPriority where - PriorityBoostPerMessage: Get, + PriorityBoostPerItem: Get, { - // we don't want any boost for transaction with single message => minus one - PriorityBoostPerMessage::get().saturating_mul(messages.saturating_sub(1)) + // we don't want any boost for transaction with single (additional) item => minus one + PriorityBoostPerItem::get().saturating_mul(n_items.saturating_sub(1)) } #[cfg(not(feature = "integrity-test"))] @@ -47,7 +49,8 @@ mod integrity_tests {} #[cfg(feature = "integrity-test")] mod integrity_tests { - use super::compute_priority_boost; + use super::{compute_priority_boost, ItemCount}; + use crate::extensions::refund_relayer_extension::RefundableParachainId; use bp_messages::MessageNonce; use bp_runtime::PreComputedSize; @@ -55,7 +58,6 @@ mod integrity_tests { dispatch::{DispatchClass, DispatchInfo, Pays, PostDispatchInfo}, traits::Get, }; - use pallet_bridge_messages::WeightInfoExt; use pallet_transaction_payment::OnChargeTransaction; use sp_runtime::{ traits::{Dispatchable, UniqueSaturatedInto, Zero}, @@ -68,37 +70,33 @@ mod integrity_tests { T, >>::Balance; - /// Ensures that the value of `PriorityBoostPerMessage` matches the value of - /// `tip_boost_per_message`. + /// Ensures that the value of `PriorityBoostPerItem` matches the value of + /// `tip_boost_per_item`. /// - /// We want two transactions, `TX1` with `N` messages and `TX2` with `N+1` messages, have almost - /// the same priority if we'll add `tip_boost_per_message` tip to the `TX1`. We want to be sure - /// that if we add plain `PriorityBoostPerMessage` priority to `TX1`, the priority will be close + /// We want two transactions, `TX1` with `N` items and `TX2` with `N+1` items, have almost + /// the same priority if we'll add `tip_boost_per_item` tip to the `TX1`. We want to be sure + /// that if we add plain `PriorityBoostPerItem` priority to `TX1`, the priority will be close /// to `TX2` as well. - pub fn ensure_priority_boost_is_sane( - tip_boost_per_message: BalanceOf, + fn ensure_priority_boost_is_sane( + param_name: &str, + max_items: ItemCount, + tip_boost_per_item: Balance, + estimate_priority: impl Fn(ItemCount, Balance) -> TransactionPriority, ) where - Runtime: - pallet_transaction_payment::Config + pallet_bridge_messages::Config, - MessagesInstance: 'static, - PriorityBoostPerMessage: Get, - Runtime::RuntimeCall: Dispatchable, - BalanceOf: Send + Sync + FixedPointOperand, + PriorityBoostPerItem: Get, + ItemCount: UniqueSaturatedInto, + Balance: FixedPointOperand + Zero, { - let priority_boost_per_message = PriorityBoostPerMessage::get(); - let maximal_messages_in_delivery_transaction = - Runtime::MaxUnconfirmedMessagesAtInboundLane::get(); - for messages in 1..=maximal_messages_in_delivery_transaction { - let base_priority = estimate_message_delivery_transaction_priority::< - Runtime, - MessagesInstance, - >(messages, Zero::zero()); - let priority_boost = compute_priority_boost::(messages); - let priority_with_boost = base_priority + priority_boost; - - let tip = tip_boost_per_message.saturating_mul((messages - 1).unique_saturated_into()); - let priority_with_tip = - estimate_message_delivery_transaction_priority::(1, tip); + let priority_boost_per_item = PriorityBoostPerItem::get(); + for n_items in 1..=max_items { + let base_priority = estimate_priority(n_items, Zero::zero()); + let priority_boost = compute_priority_boost::(n_items); + let priority_with_boost = base_priority + .checked_add(priority_boost) + .expect("priority overflow: try lowering `max_items` or `tip_boost_per_item`?"); + + let tip = tip_boost_per_item.saturating_mul((n_items - 1).unique_saturated_into()); + let priority_with_tip = estimate_priority(1, tip); const ERROR_MARGIN: TransactionPriority = 5; // 5% if priority_with_boost.abs_diff(priority_with_tip).saturating_mul(100) / @@ -106,97 +104,304 @@ mod integrity_tests { ERROR_MARGIN { panic!( - "The PriorityBoostPerMessage value ({}) must be fixed to: {}", - priority_boost_per_message, - compute_priority_boost_per_message::( - tip_boost_per_message + "The {param_name} value ({}) must be fixed to: {}", + priority_boost_per_item, + compute_priority_boost_per_item( + max_items, + tip_boost_per_item, + estimate_priority ), ); } } } - /// Compute priority boost that we give to message delivery transaction for additional message. + /// Compute priority boost that we give to bridge transaction for every + /// additional bridge item. #[cfg(feature = "integrity-test")] - fn compute_priority_boost_per_message( - tip_boost_per_message: BalanceOf, + fn compute_priority_boost_per_item( + max_items: ItemCount, + tip_boost_per_item: Balance, + estimate_priority: impl Fn(ItemCount, Balance) -> TransactionPriority, ) -> TransactionPriority where - Runtime: - pallet_transaction_payment::Config + pallet_bridge_messages::Config, - MessagesInstance: 'static, - Runtime::RuntimeCall: Dispatchable, - BalanceOf: Send + Sync + FixedPointOperand, + ItemCount: UniqueSaturatedInto, + Balance: FixedPointOperand + Zero, { - // estimate priority of transaction that delivers one message and has large tip - let maximal_messages_in_delivery_transaction = - Runtime::MaxUnconfirmedMessagesAtInboundLane::get(); + // estimate priority of transaction that delivers one item and has large tip let small_with_tip_priority = - estimate_message_delivery_transaction_priority::( - 1, - tip_boost_per_message - .saturating_mul(maximal_messages_in_delivery_transaction.saturated_into()), - ); - // estimate priority of transaction that delivers maximal number of messages, but has no tip - let large_without_tip_priority = estimate_message_delivery_transaction_priority::< - Runtime, - MessagesInstance, - >(maximal_messages_in_delivery_transaction, Zero::zero()); + estimate_priority(1, tip_boost_per_item.saturating_mul(max_items.saturated_into())); + // estimate priority of transaction that delivers maximal number of items, but has no tip + let large_without_tip_priority = estimate_priority(max_items, Zero::zero()); small_with_tip_priority .saturating_sub(large_without_tip_priority) - .saturating_div(maximal_messages_in_delivery_transaction - 1) + .saturating_div(max_items - 1) } - /// Estimate message delivery transaction priority. - #[cfg(feature = "integrity-test")] - fn estimate_message_delivery_transaction_priority( - messages: MessageNonce, - tip: BalanceOf, - ) -> TransactionPriority - where - Runtime: - pallet_transaction_payment::Config + pallet_bridge_messages::Config, - MessagesInstance: 'static, - Runtime::RuntimeCall: Dispatchable, - BalanceOf: Send + Sync + FixedPointOperand, - { - // just an estimation of extra transaction bytes that are added to every transaction - // (including signature, signed extensions extra and etc + in our case it includes - // all call arguments except the proof itself) - let base_tx_size = 512; - // let's say we are relaying similar small messages and for every message we add more trie - // nodes to the proof (x0.5 because we expect some nodes to be reused) - let estimated_message_size = 512; - // let's say all our messages have the same dispatch weight - let estimated_message_dispatch_weight = - Runtime::WeightInfo::message_dispatch_weight(estimated_message_size); - // messages proof argument size is (for every message) messages size + some additional - // trie nodes. Some of them are reused by different messages, so let's take 2/3 of default - // "overhead" constant - let messages_proof_size = Runtime::WeightInfo::expected_extra_storage_proof_size() - .saturating_mul(2) - .saturating_div(3) - .saturating_add(estimated_message_size) - .saturating_mul(messages as _); - - // finally we are able to estimate transaction size and weight - let transaction_size = base_tx_size.saturating_add(messages_proof_size); - let transaction_weight = Runtime::WeightInfo::receive_messages_proof_weight( - &PreComputedSize(transaction_size as _), - messages as _, - estimated_message_dispatch_weight.saturating_mul(messages), - ); - - pallet_transaction_payment::ChargeTransactionPayment::::get_priority( - &DispatchInfo { - weight: transaction_weight, - class: DispatchClass::Normal, - pays_fee: Pays::Yes, - }, - transaction_size as _, - tip, - Zero::zero(), - ) + /// Computations, specific to bridge relay chains transactions. + pub mod per_relay_header { + use super::*; + + use bp_header_chain::{ + max_expected_submit_finality_proof_arguments_size, ChainWithGrandpa, + }; + use pallet_bridge_grandpa::WeightInfoExt; + + /// Ensures that the value of `PriorityBoostPerHeader` matches the value of + /// `tip_boost_per_header`. + /// + /// We want two transactions, `TX1` with `N` headers and `TX2` with `N+1` headers, have + /// almost the same priority if we'll add `tip_boost_per_header` tip to the `TX1`. We want + /// to be sure that if we add plain `PriorityBoostPerHeader` priority to `TX1`, the priority + /// will be close to `TX2` as well. + pub fn ensure_priority_boost_is_sane( + tip_boost_per_header: BalanceOf, + ) where + Runtime: + pallet_transaction_payment::Config + pallet_bridge_grandpa::Config, + GrandpaInstance: 'static, + PriorityBoostPerHeader: Get, + Runtime::RuntimeCall: Dispatchable, + BalanceOf: Send + Sync + FixedPointOperand, + { + // the meaning of `max_items` here is different when comparing with message + // transactions - with messages we have a strict limit on maximal number of + // messages we can fit into a single transaction. With headers, current best + // header may be improved by any "number of items". But this number is only + // used to verify priority boost, so it should be fine to select this arbitrary + // value - it SHALL NOT affect any value, it just adds more tests for the value. + let maximal_improved_by = 4_096; + super::ensure_priority_boost_is_sane::>( + "PriorityBoostPerRelayHeader", + maximal_improved_by, + tip_boost_per_header, + |_n_headers, tip| { + estimate_relay_header_submit_transaction_priority::( + tip, + ) + }, + ); + } + + /// Estimate relay header delivery transaction priority. + #[cfg(feature = "integrity-test")] + fn estimate_relay_header_submit_transaction_priority( + tip: BalanceOf, + ) -> TransactionPriority + where + Runtime: + pallet_transaction_payment::Config + pallet_bridge_grandpa::Config, + GrandpaInstance: 'static, + Runtime::RuntimeCall: Dispatchable, + BalanceOf: Send + Sync + FixedPointOperand, + { + // just an estimation of extra transaction bytes that are added to every transaction + // (including signature, signed extensions extra and etc + in our case it includes + // all call arguments except the proof itself) + let base_tx_size = 512; + // let's say we are relaying largest relay chain headers + let tx_call_size = max_expected_submit_finality_proof_arguments_size::< + Runtime::BridgedChain, + >(true, Runtime::BridgedChain::MAX_AUTHORITIES_COUNT * 2 / 3 + 1); + + // finally we are able to estimate transaction size and weight + let transaction_size = base_tx_size.saturating_add(tx_call_size); + let transaction_weight = Runtime::WeightInfo::submit_finality_proof_weight( + Runtime::BridgedChain::MAX_AUTHORITIES_COUNT * 2 / 3 + 1, + Runtime::BridgedChain::REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY, + ); + + pallet_transaction_payment::ChargeTransactionPayment::::get_priority( + &DispatchInfo { + weight: transaction_weight, + class: DispatchClass::Normal, + pays_fee: Pays::Yes, + }, + transaction_size as _, + tip, + Zero::zero(), + ) + } + } + + /// Computations, specific to bridge parachains transactions. + pub mod per_parachain_header { + use super::*; + + use bp_runtime::Parachain; + use pallet_bridge_parachains::WeightInfoExt; + + /// Ensures that the value of `PriorityBoostPerHeader` matches the value of + /// `tip_boost_per_header`. + /// + /// We want two transactions, `TX1` with `N` headers and `TX2` with `N+1` headers, have + /// almost the same priority if we'll add `tip_boost_per_header` tip to the `TX1`. We want + /// to be sure that if we add plain `PriorityBoostPerHeader` priority to `TX1`, the priority + /// will be close to `TX2` as well. + pub fn ensure_priority_boost_is_sane( + tip_boost_per_header: BalanceOf, + ) where + Runtime: pallet_transaction_payment::Config + + pallet_bridge_parachains::Config, + RefundableParachain: RefundableParachainId, + PriorityBoostPerHeader: Get, + Runtime::RuntimeCall: Dispatchable, + BalanceOf: Send + Sync + FixedPointOperand, + { + // the meaning of `max_items` here is different when comparing with message + // transactions - with messages we have a strict limit on maximal number of + // messages we can fit into a single transaction. With headers, current best + // header may be improved by any "number of items". But this number is only + // used to verify priority boost, so it should be fine to select this arbitrary + // value - it SHALL NOT affect any value, it just adds more tests for the value. + let maximal_improved_by = 4_096; + super::ensure_priority_boost_is_sane::>( + "PriorityBoostPerParachainHeader", + maximal_improved_by, + tip_boost_per_header, + |_n_headers, tip| { + estimate_parachain_header_submit_transaction_priority::< + Runtime, + RefundableParachain, + >(tip) + }, + ); + } + + /// Estimate parachain header delivery transaction priority. + #[cfg(feature = "integrity-test")] + fn estimate_parachain_header_submit_transaction_priority( + tip: BalanceOf, + ) -> TransactionPriority + where + Runtime: pallet_transaction_payment::Config + + pallet_bridge_parachains::Config, + RefundableParachain: RefundableParachainId, + Runtime::RuntimeCall: Dispatchable, + BalanceOf: Send + Sync + FixedPointOperand, + { + // just an estimation of extra transaction bytes that are added to every transaction + // (including signature, signed extensions extra and etc + in our case it includes + // all call arguments except the proof itself) + let base_tx_size = 512; + // let's say we are relaying largest parachain headers and proof takes some more bytes + let tx_call_size = >::WeightInfo::expected_extra_storage_proof_size() + .saturating_add(RefundableParachain::BridgedChain::MAX_HEADER_SIZE); + + // finally we are able to estimate transaction size and weight + let transaction_size = base_tx_size.saturating_add(tx_call_size); + let transaction_weight = >::WeightInfo::submit_parachain_heads_weight( + Runtime::DbWeight::get(), + &PreComputedSize(transaction_size as _), + // just one parachain - all other submissions won't receive any boost + 1, + ); + + pallet_transaction_payment::ChargeTransactionPayment::::get_priority( + &DispatchInfo { + weight: transaction_weight, + class: DispatchClass::Normal, + pays_fee: Pays::Yes, + }, + transaction_size as _, + tip, + Zero::zero(), + ) + } + } + + /// Computations, specific to bridge messages transactions. + pub mod per_message { + use super::*; + + use pallet_bridge_messages::WeightInfoExt; + + /// Ensures that the value of `PriorityBoostPerMessage` matches the value of + /// `tip_boost_per_message`. + /// + /// We want two transactions, `TX1` with `N` messages and `TX2` with `N+1` messages, have + /// almost the same priority if we'll add `tip_boost_per_message` tip to the `TX1`. We want + /// to be sure that if we add plain `PriorityBoostPerMessage` priority to `TX1`, the + /// priority will be close to `TX2` as well. + pub fn ensure_priority_boost_is_sane( + tip_boost_per_message: BalanceOf, + ) where + Runtime: pallet_transaction_payment::Config + + pallet_bridge_messages::Config, + MessagesInstance: 'static, + PriorityBoostPerMessage: Get, + Runtime::RuntimeCall: Dispatchable, + BalanceOf: Send + Sync + FixedPointOperand, + { + let maximal_messages_in_delivery_transaction = + Runtime::MaxUnconfirmedMessagesAtInboundLane::get(); + super::ensure_priority_boost_is_sane::>( + "PriorityBoostPerMessage", + maximal_messages_in_delivery_transaction, + tip_boost_per_message, + |n_messages, tip| { + estimate_message_delivery_transaction_priority::( + n_messages, tip, + ) + }, + ); + } + + /// Estimate message delivery transaction priority. + #[cfg(feature = "integrity-test")] + fn estimate_message_delivery_transaction_priority( + messages: MessageNonce, + tip: BalanceOf, + ) -> TransactionPriority + where + Runtime: pallet_transaction_payment::Config + + pallet_bridge_messages::Config, + MessagesInstance: 'static, + Runtime::RuntimeCall: Dispatchable, + BalanceOf: Send + Sync + FixedPointOperand, + { + // just an estimation of extra transaction bytes that are added to every transaction + // (including signature, signed extensions extra and etc + in our case it includes + // all call arguments except the proof itself) + let base_tx_size = 512; + // let's say we are relaying similar small messages and for every message we add more + // trie nodes to the proof (x0.5 because we expect some nodes to be reused) + let estimated_message_size = 512; + // let's say all our messages have the same dispatch weight + let estimated_message_dispatch_weight = + Runtime::WeightInfo::message_dispatch_weight(estimated_message_size); + // messages proof argument size is (for every message) messages size + some additional + // trie nodes. Some of them are reused by different messages, so let's take 2/3 of + // default "overhead" constant + let messages_proof_size = Runtime::WeightInfo::expected_extra_storage_proof_size() + .saturating_mul(2) + .saturating_div(3) + .saturating_add(estimated_message_size) + .saturating_mul(messages as _); + + // finally we are able to estimate transaction size and weight + let transaction_size = base_tx_size.saturating_add(messages_proof_size); + let transaction_weight = Runtime::WeightInfo::receive_messages_proof_weight( + &PreComputedSize(transaction_size as _), + messages as _, + estimated_message_dispatch_weight.saturating_mul(messages), + ); + + pallet_transaction_payment::ChargeTransactionPayment::::get_priority( + &DispatchInfo { + weight: transaction_weight, + class: DispatchClass::Normal, + pays_fee: Pays::Yes, + }, + transaction_size as _, + tip, + Zero::zero(), + ) + } } } diff --git a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs index 05674214b997..b2d911455f5b 100644 --- a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs +++ b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs @@ -24,7 +24,7 @@ use crate::messages_call_ext::{ }; use bp_messages::{LaneId, MessageNonce}; use bp_relayers::{ExplicitOrAccountParams, RewardsAccountOwner, RewardsAccountParams}; -use bp_runtime::{Parachain, ParachainIdOf, RangeInclusiveExt, StaticStrProvider}; +use bp_runtime::{Parachain, RangeInclusiveExt, StaticStrProvider}; use codec::{Codec, Decode, Encode}; use frame_support::{ dispatch::{CallableCallFor, DispatchInfo, PostDispatchInfo}, @@ -67,19 +67,7 @@ pub trait RefundableParachainId { /// The instance of the bridge parachains pallet. type Instance: 'static; /// The parachain Id. - type Id: Get; -} - -/// Default implementation of `RefundableParachainId`. -pub struct DefaultRefundableParachainId(PhantomData<(Instance, Id)>); - -impl RefundableParachainId for DefaultRefundableParachainId -where - Instance: 'static, - Id: Get, -{ - type Instance = Instance; - type Id = Id; + type BridgedChain: Parachain; } /// Implementation of `RefundableParachainId` for `trait Parachain`. @@ -91,7 +79,7 @@ where Para: Parachain, { type Instance = Instance; - type Id = ParachainIdOf; + type BridgedChain = Para; } /// Trait identifying a bridged messages lane. A relayer might be refunded for delivering messages @@ -659,7 +647,7 @@ where let para_finality_call = calls .next() .transpose()? - .and_then(|c| c.submit_parachain_heads_info_for(Para::Id::get())); + .and_then(|c| c.submit_parachain_heads_info_for(Para::BridgedChain::PARACHAIN_ID)); let relay_finality_call = calls.next().transpose()?.and_then(|c| c.submit_finality_proof_info()); @@ -713,7 +701,7 @@ where target: "runtime::bridge", "{} from parachain {} via {:?}: relayer {:?} has submitted invalid parachain finality proof", Id::STR, - Para::Id::get(), + Para::BridgedChain::PARACHAIN_ID, Msgs::Id::get(), relayer, ); diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs index 2325559b0a75..94b936889b77 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs @@ -81,6 +81,9 @@ parameter_types! { pub const RococoPeopleToRococoBulletinMessagesLane: bp_messages::LaneId = XCM_LANE_FOR_ROCOCO_PEOPLE_TO_ROCOCO_BULLETIN; + // see the `FEE_BOOST_PER_RELAY_HEADER` constant get the meaning of this value + pub PriorityBoostPerRelayHeader: u64 = 58_014_163_614_163; + /// Priority boost that the registered relayer receives for every additional message in the message /// delivery transaction. /// @@ -88,8 +91,6 @@ parameter_types! { /// meaning of this value. pub PriorityBoostPerMessage: u64 = 182_044_444_444_444; - pub PriorityBoostPerHeader: u64 = PriorityBoostPerMessage::get() / 1_000_000; // TODO - /// Identifier of the sibling Rococo People parachain. pub RococoPeopleParaId: cumulus_primitives_core::ParaId = rococo_runtime_constants::system_parachain::PEOPLE_ID.into(); /// A route (XCM location and bridge lane) that the Rococo People Chain -> Rococo Bulletin Chain @@ -243,6 +244,9 @@ mod tests { /// operational costs and a faster bridge), so this value should be significant. const FEE_BOOST_PER_MESSAGE: Balance = 2 * rococo::currency::UNITS; + // see `FEE_BOOST_PER_MESSAGE` comment + const FEE_BOOST_PER_RELAY_HEADER: Balance = 2 * rococo::currency::UNITS; + #[test] fn ensure_bridge_hub_rococo_message_lane_weights_are_correct() { check_message_lane_weights::< @@ -272,7 +276,13 @@ mod tests { // Bulletin chain - it has the same (almost) runtime for Polkadot Bulletin and Rococo // Bulletin, so we have to adhere Polkadot names here - bridge_runtime_common::extensions::priority_calculator::ensure_priority_boost_is_sane::< + bridge_runtime_common::extensions::priority_calculator::per_relay_header::ensure_priority_boost_is_sane::< + Runtime, + BridgeGrandpaRococoBulletinInstance, + PriorityBoostPerRelayHeader, + >(FEE_BOOST_PER_RELAY_HEADER); + + bridge_runtime_common::extensions::priority_calculator::per_message::ensure_priority_boost_is_sane::< Runtime, WithRococoBulletinMessagesInstance, PriorityBoostPerMessage, diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs index 3e224b2d7078..1681ac7f4687 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs @@ -65,11 +65,13 @@ parameter_types! { 2, [GlobalConsensus(WestendGlobalConsensusNetwork::get())] ); + // see the `FEE_BOOST_PER_RELAY_HEADER` constant get the meaning of this value + pub PriorityBoostPerRelayHeader: u64 = 32_007_814_407_814; + // see the `FEE_BOOST_PER_PARACHAIN_HEADER` constant get the meaning of this value + pub PriorityBoostPerParachainHeader: u64 = 1_396_340_903_540_903; // see the `FEE_BOOST_PER_MESSAGE` constant to get the meaning of this value pub PriorityBoostPerMessage: u64 = 182_044_444_444_444; - pub PriorityBoostPerHeader: u64 = PriorityBoostPerMessage::get() / 1_000_000; // TODO - pub AssetHubRococoParaId: cumulus_primitives_core::ParaId = bp_asset_hub_rococo::ASSET_HUB_ROCOCO_PARACHAIN_ID.into(); pub AssetHubWestendParaId: cumulus_primitives_core::ParaId = bp_asset_hub_westend::ASSET_HUB_WESTEND_PARACHAIN_ID.into(); @@ -244,6 +246,7 @@ mod tests { use crate::bridge_common_config::BridgeGrandpaWestendInstance; use bridge_runtime_common::{ assert_complete_bridge_types, + extensions::refund_relayer_extension::RefundableParachain, integrity::{ assert_complete_bridge_constants, check_message_lane_weights, AssertBridgeMessagesPalletConstants, AssertBridgePalletNames, AssertChainConstants, @@ -264,6 +267,11 @@ mod tests { /// operational costs and a faster bridge), so this value should be significant. const FEE_BOOST_PER_MESSAGE: Balance = 2 * rococo::currency::UNITS; + // see `FEE_BOOST_PER_MESSAGE` comment + const FEE_BOOST_PER_RELAY_HEADER: Balance = 2 * rococo::currency::UNITS; + // see `FEE_BOOST_PER_MESSAGE` comment + const FEE_BOOST_PER_PARACHAIN_HEADER: Balance = 2 * rococo::currency::UNITS; + #[test] fn ensure_bridge_hub_rococo_message_lane_weights_are_correct() { check_message_lane_weights::< @@ -316,7 +324,19 @@ mod tests { }, }); - bridge_runtime_common::extensions::priority_calculator::ensure_priority_boost_is_sane::< + bridge_runtime_common::extensions::priority_calculator::per_relay_header::ensure_priority_boost_is_sane::< + Runtime, + BridgeGrandpaWestendInstance, + PriorityBoostPerRelayHeader, + >(FEE_BOOST_PER_RELAY_HEADER); + + bridge_runtime_common::extensions::priority_calculator::per_parachain_header::ensure_priority_boost_is_sane::< + Runtime, + RefundableParachain, + PriorityBoostPerParachainHeader, + >(FEE_BOOST_PER_PARACHAIN_HEADER); + + bridge_runtime_common::extensions::priority_calculator::per_message::ensure_priority_boost_is_sane::< Runtime, WithBridgeHubWestendMessagesInstance, PriorityBoostPerMessage, diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index e9967dd93708..da4f8c6e9dd6 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -749,13 +749,13 @@ bridge_runtime_common::generate_bridge_reject_obsolete_headers_and_messages! { CheckAndBoostBridgeGrandpaTransactions< Runtime, bridge_common_config::BridgeGrandpaWestendInstance, - bridge_to_westend_config::PriorityBoostPerHeader, + bridge_to_westend_config::PriorityBoostPerRelayHeader, xcm_config::TreasuryAccount, >, CheckAndBoostBridgeGrandpaTransactions< Runtime, bridge_common_config::BridgeGrandpaRococoBulletinInstance, - bridge_to_bulletin_config::PriorityBoostPerHeader, + bridge_to_bulletin_config::PriorityBoostPerRelayHeader, xcm_config::TreasuryAccount, >, // Parachains @@ -765,7 +765,7 @@ bridge_runtime_common::generate_bridge_reject_obsolete_headers_and_messages! { bridge_common_config::BridgeParachainWestendInstance, bp_bridge_hub_westend::BridgeHubWestend, >, - bridge_to_westend_config::PriorityBoostPerHeader, + bridge_to_westend_config::PriorityBoostPerParachainHeader, xcm_config::TreasuryAccount, >, // Messages diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs index cbe134fd4974..425b53da30fc 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs @@ -70,11 +70,13 @@ parameter_types! { 2, [GlobalConsensus(RococoGlobalConsensusNetwork::get())] ); + // see the `FEE_BOOST_PER_RELAY_HEADER` constant get the meaning of this value + pub PriorityBoostPerRelayHeader: u64 = 32_007_814_407_814; + // see the `FEE_BOOST_PER_PARACHAIN_HEADER` constant get the meaning of this value + pub PriorityBoostPerParachainHeader: u64 = 1_396_340_903_540_903; // see the `FEE_BOOST_PER_MESSAGE` constant to get the meaning of this value pub PriorityBoostPerMessage: u64 = 182_044_444_444_444; - pub PriorityBoostPerHeader: u64 = PriorityBoostPerMessage::get() / 1_000_000; // TODO - pub AssetHubWestendParaId: cumulus_primitives_core::ParaId = bp_asset_hub_westend::ASSET_HUB_WESTEND_PARACHAIN_ID.into(); pub AssetHubRococoParaId: cumulus_primitives_core::ParaId = bp_asset_hub_rococo::ASSET_HUB_ROCOCO_PARACHAIN_ID.into(); @@ -283,6 +285,7 @@ mod tests { use super::*; use bridge_runtime_common::{ assert_complete_bridge_types, + extensions::refund_relayer_extension::RefundableParachain, integrity::{ assert_complete_bridge_constants, check_message_lane_weights, AssertBridgeMessagesPalletConstants, AssertBridgePalletNames, AssertChainConstants, @@ -303,6 +306,11 @@ mod tests { /// operational costs and a faster bridge), so this value should be significant. const FEE_BOOST_PER_MESSAGE: Balance = 2 * westend::currency::UNITS; + // see `FEE_BOOST_PER_MESSAGE` comment + const FEE_BOOST_PER_RELAY_HEADER: Balance = 2 * westend::currency::UNITS; + // see `FEE_BOOST_PER_MESSAGE` comment + const FEE_BOOST_PER_PARACHAIN_HEADER: Balance = 2 * westend::currency::UNITS; + #[test] fn ensure_bridge_hub_westend_message_lane_weights_are_correct() { check_message_lane_weights::< @@ -354,7 +362,19 @@ mod tests { }, }); - bridge_runtime_common::extensions::priority_calculator::ensure_priority_boost_is_sane::< + bridge_runtime_common::extensions::priority_calculator::per_relay_header::ensure_priority_boost_is_sane::< + Runtime, + BridgeGrandpaRococoInstance, + PriorityBoostPerRelayHeader, + >(FEE_BOOST_PER_RELAY_HEADER); + + bridge_runtime_common::extensions::priority_calculator::per_parachain_header::ensure_priority_boost_is_sane::< + Runtime, + RefundableParachain, + PriorityBoostPerParachainHeader, + >(FEE_BOOST_PER_PARACHAIN_HEADER); + + bridge_runtime_common::extensions::priority_calculator::per_message::ensure_priority_boost_is_sane::< Runtime, WithBridgeHubRococoMessagesInstance, PriorityBoostPerMessage, diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index 6989c28d03cc..1475be5bc125 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -511,7 +511,7 @@ bridge_runtime_common::generate_bridge_reject_obsolete_headers_and_messages! { CheckAndBoostBridgeGrandpaTransactions< Runtime, bridge_to_rococo_config::BridgeGrandpaRococoInstance, - bridge_to_rococo_config::PriorityBoostPerHeader, + bridge_to_rococo_config::PriorityBoostPerRelayHeader, xcm_config::TreasuryAccount, >, // Parachains @@ -521,7 +521,7 @@ bridge_runtime_common::generate_bridge_reject_obsolete_headers_and_messages! { bridge_to_rococo_config::BridgeParachainRococoInstance, bp_bridge_hub_rococo::BridgeHubRococo, >, - bridge_to_rococo_config::PriorityBoostPerHeader, + bridge_to_rococo_config::PriorityBoostPerParachainHeader, xcm_config::TreasuryAccount, >, // Messages From 7aba80987fe712751276487f85270ceb06305330 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 15 Apr 2024 17:10:05 +0300 Subject: [PATCH 49/70] fixed tests in BH runtimes --- .../chains/chain-bridge-hub-rococo/src/lib.rs | 4 +- .../chain-bridge-hub-westend/src/lib.rs | 8 +- .../bridge-hub-rococo/tests/tests.rs | 58 ++------- .../bridge-hub-westend/tests/tests.rs | 33 ++--- .../src/test_cases/from_grandpa_chain.rs | 105 +++++++++++++++ .../src/test_cases/from_parachain.rs | 122 ++++++++++++++++++ .../src/test_data/from_grandpa_chain.rs | 54 ++++++++ .../src/test_data/from_parachain.rs | 46 +++++++ 8 files changed, 353 insertions(+), 77 deletions(-) diff --git a/bridges/chains/chain-bridge-hub-rococo/src/lib.rs b/bridges/chains/chain-bridge-hub-rococo/src/lib.rs index c504620d97b5..d7097f01c531 100644 --- a/bridges/chains/chain-bridge-hub-rococo/src/lib.rs +++ b/bridges/chains/chain-bridge-hub-rococo/src/lib.rs @@ -104,9 +104,9 @@ frame_support::parameter_types! { /// Transaction fee that is paid at the Rococo BridgeHub for delivering single inbound message. /// (initially was calculated by test `BridgeHubRococo::can_calculate_fee_for_complex_message_delivery_transaction` + `33%`) - pub const BridgeHubRococoBaseDeliveryFeeInRocs: u128 = 5_651_581_649; + pub const BridgeHubRococoBaseDeliveryFeeInRocs: u128 = 314_037_860; /// Transaction fee that is paid at the Rococo BridgeHub for delivering single outbound message confirmation. /// (initially was calculated by test `BridgeHubRococo::can_calculate_fee_for_complex_message_confirmation_transaction` + `33%`) - pub const BridgeHubRococoBaseConfirmationFeeInRocs: u128 = 5_380_901_781; + pub const BridgeHubRococoBaseConfirmationFeeInRocs: u128 = 57_414_813; } diff --git a/bridges/chains/chain-bridge-hub-westend/src/lib.rs b/bridges/chains/chain-bridge-hub-westend/src/lib.rs index 8c4e03db025f..800f290d7bfa 100644 --- a/bridges/chains/chain-bridge-hub-westend/src/lib.rs +++ b/bridges/chains/chain-bridge-hub-westend/src/lib.rs @@ -94,10 +94,10 @@ frame_support::parameter_types! { pub const BridgeHubWestendBaseXcmFeeInWnds: u128 = 17_756_830_000; /// Transaction fee that is paid at the Westend BridgeHub for delivering single inbound message. - /// (initially was calculated by test `BridgeHubWestend::can_calculate_fee_for_complex_message_delivery_transaction` + `33%`) - pub const BridgeHubWestendBaseDeliveryFeeInWnds: u128 = 1_695_489_961_344; + /// (initially was calculated by test `BridgeHubWestend::can_calculate_fee_for_standalone_message_delivery_transaction` + `33%`) + pub const BridgeHubWestendBaseDeliveryFeeInWnds: u128 = 94_211_536_452; /// Transaction fee that is paid at the Westend BridgeHub for delivering single outbound message confirmation. - /// (initially was calculated by test `BridgeHubWestend::can_calculate_fee_for_complex_message_confirmation_transaction` + `33%`) - pub const BridgeHubWestendBaseConfirmationFeeInWnds: u128 = 1_618_309_961_344; + /// (initially was calculated by test `BridgeHubWestend::can_calculate_fee_for_standalone_message_confirmation_transaction` + `33%`) + pub const BridgeHubWestendBaseConfirmationFeeInWnds: u128 = 17_224_486_452; } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs index 776c505fa640..c81b0ff5509a 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs @@ -80,11 +80,10 @@ fn construct_and_apply_extrinsic( r.unwrap() } -fn construct_and_estimate_extrinsic_fee(batch: pallet_utility::Call) -> Balance { - let batch_call = RuntimeCall::Utility(batch); - let batch_info = batch_call.get_dispatch_info(); - let xt = construct_extrinsic(Alice, batch_call); - TransactionPayment::compute_fee(xt.encoded_size() as _, &batch_info, 0) +fn construct_and_estimate_extrinsic_fee(call: RuntimeCall) -> Balance { + let info = call.get_dispatch_info(); + let xt = construct_extrinsic(Alice, call); + TransactionPayment::compute_fee(xt.encoded_size() as _, &info, 0) } fn collator_session_keys() -> bridge_hub_test_utils::CollatorSessionKeys { @@ -375,23 +374,6 @@ mod bridge_hub_westend_tests { ) } - #[test] - pub fn complex_relay_extrinsic_works() { - // for Westend - from_parachain::complex_relay_extrinsic_works::( - collator_session_keys(), - slot_durations(), - bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID, - bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID, - SIBLING_PARACHAIN_ID, - BridgeHubWestendChainId::get(), - Rococo, - XCM_LANE_FOR_ASSET_HUB_ROCOCO_TO_ASSET_HUB_WESTEND, - || (), - construct_and_apply_extrinsic, - ); - } - #[test] pub fn can_calculate_weight_for_paid_export_message_with_reserve_transfer() { bridge_hub_test_utils::check_sane_fees_values( @@ -414,12 +396,12 @@ mod bridge_hub_westend_tests { } #[test] - pub fn can_calculate_fee_for_complex_message_delivery_transaction() { + fn can_calculate_fee_for_standalone_message_delivery_transaction() { bridge_hub_test_utils::check_sane_fees_values( "bp_bridge_hub_rococo::BridgeHubRococoBaseDeliveryFeeInRocs", bp_bridge_hub_rococo::BridgeHubRococoBaseDeliveryFeeInRocs::get(), || { - from_parachain::can_calculate_fee_for_complex_message_delivery_transaction::< + from_parachain::can_calculate_fee_for_standalone_message_delivery_transaction::< RuntimeTestsAdapter, >(collator_session_keys(), construct_and_estimate_extrinsic_fee) }, @@ -433,12 +415,12 @@ mod bridge_hub_westend_tests { } #[test] - pub fn can_calculate_fee_for_complex_message_confirmation_transaction() { + fn can_calculate_fee_for_standalone_message_confirmation_transaction() { bridge_hub_test_utils::check_sane_fees_values( "bp_bridge_hub_rococo::BridgeHubRococoBaseConfirmationFeeInRocs", bp_bridge_hub_rococo::BridgeHubRococoBaseConfirmationFeeInRocs::get(), || { - from_parachain::can_calculate_fee_for_complex_message_confirmation_transaction::< + from_parachain::can_calculate_fee_for_standalone_message_confirmation_transaction::< RuntimeTestsAdapter, >(collator_session_keys(), construct_and_estimate_extrinsic_fee) }, @@ -581,28 +563,12 @@ mod bridge_hub_bulletin_tests { } #[test] - pub fn complex_relay_extrinsic_works() { - // for Bulletin - from_grandpa_chain::complex_relay_extrinsic_works::( - collator_session_keys(), - slot_durations(), - bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID, - SIBLING_PARACHAIN_ID, - RococoBulletinChainId::get(), - Rococo, - XCM_LANE_FOR_ROCOCO_PEOPLE_TO_ROCOCO_BULLETIN, - || (), - construct_and_apply_extrinsic, - ); - } - - #[test] - pub fn can_calculate_fee_for_complex_message_delivery_transaction() { + pub fn can_calculate_fee_for_standalone_message_delivery_transaction() { bridge_hub_test_utils::check_sane_fees_values( "bp_bridge_hub_rococo::BridgeHubRococoBaseDeliveryFeeInRocs", bp_bridge_hub_rococo::BridgeHubRococoBaseDeliveryFeeInRocs::get(), || { - from_grandpa_chain::can_calculate_fee_for_complex_message_delivery_transaction::< + from_grandpa_chain::can_calculate_fee_for_standalone_message_delivery_transaction::< RuntimeTestsAdapter, >(collator_session_keys(), construct_and_estimate_extrinsic_fee) }, @@ -617,12 +583,12 @@ mod bridge_hub_bulletin_tests { } #[test] - pub fn can_calculate_fee_for_complex_message_confirmation_transaction() { + pub fn can_calculate_fee_for_standalone_message_confirmation_transaction() { bridge_hub_test_utils::check_sane_fees_values( "bp_bridge_hub_rococo::BridgeHubRococoBaseConfirmationFeeInRocs", bp_bridge_hub_rococo::BridgeHubRococoBaseConfirmationFeeInRocs::get(), || { - from_grandpa_chain::can_calculate_fee_for_complex_message_confirmation_transaction::< + from_grandpa_chain::can_calculate_fee_for_standalone_message_confirmation_transaction::< RuntimeTestsAdapter, >(collator_session_keys(), construct_and_estimate_extrinsic_fee) }, diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs index 988b10e1e2d8..836594140b23 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs @@ -94,11 +94,10 @@ fn construct_and_apply_extrinsic( r.unwrap() } -fn construct_and_estimate_extrinsic_fee(batch: pallet_utility::Call) -> Balance { - let batch_call = RuntimeCall::Utility(batch); - let batch_info = batch_call.get_dispatch_info(); - let xt = construct_extrinsic(Alice, batch_call); - TransactionPayment::compute_fee(xt.encoded_size() as _, &batch_info, 0) +fn construct_and_estimate_extrinsic_fee(call: RuntimeCall) -> Balance { + let info = call.get_dispatch_info(); + let xt = construct_extrinsic(Alice, call); + TransactionPayment::compute_fee(xt.encoded_size() as _, &info, 0) } fn collator_session_keys() -> bridge_hub_test_utils::CollatorSessionKeys { @@ -271,22 +270,6 @@ fn relayed_incoming_message_works() { ) } -#[test] -pub fn complex_relay_extrinsic_works() { - from_parachain::complex_relay_extrinsic_works::( - collator_session_keys(), - slot_durations(), - bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID, - bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID, - SIBLING_PARACHAIN_ID, - BridgeHubRococoChainId::get(), - Westend, - XCM_LANE_FOR_ASSET_HUB_WESTEND_TO_ASSET_HUB_ROCOCO, - || (), - construct_and_apply_extrinsic, - ); -} - #[test] pub fn can_calculate_weight_for_paid_export_message_with_reserve_transfer() { bridge_hub_test_utils::check_sane_fees_values( @@ -309,12 +292,12 @@ pub fn can_calculate_weight_for_paid_export_message_with_reserve_transfer() { } #[test] -pub fn can_calculate_fee_for_complex_message_delivery_transaction() { +pub fn can_calculate_fee_for_standalone_message_delivery_transaction() { bridge_hub_test_utils::check_sane_fees_values( "bp_bridge_hub_westend::BridgeHubWestendBaseDeliveryFeeInWnds", bp_bridge_hub_westend::BridgeHubWestendBaseDeliveryFeeInWnds::get(), || { - from_parachain::can_calculate_fee_for_complex_message_delivery_transaction::< + from_parachain::can_calculate_fee_for_standalone_message_delivery_transaction::< RuntimeTestsAdapter, >(collator_session_keys(), construct_and_estimate_extrinsic_fee) }, @@ -328,12 +311,12 @@ pub fn can_calculate_fee_for_complex_message_delivery_transaction() { } #[test] -pub fn can_calculate_fee_for_complex_message_confirmation_transaction() { +pub fn can_calculate_fee_for_standalone_message_confirmation_transaction() { bridge_hub_test_utils::check_sane_fees_values( "bp_bridge_hub_westend::BridgeHubWestendBaseConfirmationFeeInWnds", bp_bridge_hub_westend::BridgeHubWestendBaseConfirmationFeeInWnds::get(), || { - from_parachain::can_calculate_fee_for_complex_message_confirmation_transaction::< + from_parachain::can_calculate_fee_for_standalone_message_confirmation_transaction::< RuntimeTestsAdapter, >(collator_session_keys(), construct_and_estimate_extrinsic_fee) }, diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_grandpa_chain.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_grandpa_chain.rs index 8aaaa4f59d78..2e2f9a50d4cb 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_grandpa_chain.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_grandpa_chain.rs @@ -423,3 +423,108 @@ where compute_extrinsic_fee(batch) }) } + +/// Estimates transaction fee for default message delivery transaction from bridged GRANDPA chain. +pub fn can_calculate_fee_for_standalone_message_delivery_transaction( + collator_session_key: CollatorSessionKeys, + compute_extrinsic_fee: fn( + ::RuntimeCall, + ) -> u128, +) -> u128 +where + RuntimeHelper: WithRemoteGrandpaChainHelper, + RuntimeCallOf: + From>, + UnderlyingChainOf>: ChainWithGrandpa, + >::SourceHeaderChain: + SourceHeaderChain< + MessagesProof = FromBridgedChainMessagesProof< + HashOf>, + >, + >, +{ + run_test::(collator_session_key, 1000, vec![], || { + // generate bridged relay chain finality, parachain heads and message proofs, + // to be submitted by relayer to this chain. + // + // we don't care about parameter values here, apart from the XCM message size. But we + // do not need to have a large message here, because we're charging for every byte of + // the message additionally + let (_, _, message_proof) = + test_data::from_grandpa_chain::make_complex_relayer_delivery_proofs::< + RuntimeHelper::MB, + (), + >( + LaneId::default(), + vec![Instruction::<()>::ClearOrigin; 1_024].into(), + 1, + [GlobalConsensus(Polkadot), Parachain(1_000)].into(), + 1u32.into(), + ); + + let call = test_data::from_grandpa_chain::make_standalone_relayer_delivery_call::< + RuntimeHelper::Runtime, + RuntimeHelper::GPI, + RuntimeHelper::MPI, + >( + message_proof, + helpers::relayer_id_at_bridged_chain::(), + ); + + compute_extrinsic_fee(call) + }) +} + +/// Estimates transaction fee for default message confirmation transaction (batched with required +/// proofs) from bridged parachain. +pub fn can_calculate_fee_for_standalone_message_confirmation_transaction( + collator_session_key: CollatorSessionKeys, + compute_extrinsic_fee: fn( + ::RuntimeCall, + ) -> u128, +) -> u128 +where + RuntimeHelper: WithRemoteGrandpaChainHelper, + AccountIdOf: From, + MessageThisChain: + bp_runtime::Chain>, + RuntimeCallOf: + From>, + UnderlyingChainOf>: ChainWithGrandpa, + >::TargetHeaderChain: + TargetHeaderChain< + XcmAsPlainPayload, + AccountIdOf, + MessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof< + HashOf>>, + >, + >, +{ + run_test::(collator_session_key, 1000, vec![], || { + // generate bridged relay chain finality, parachain heads and message proofs, + // to be submitted by relayer to this chain. + let unrewarded_relayers = UnrewardedRelayersState { + unrewarded_relayer_entries: 1, + total_messages: 1, + ..Default::default() + }; + let (_, _, message_delivery_proof) = + test_data::from_grandpa_chain::make_complex_relayer_confirmation_proofs::< + RuntimeHelper::MB, + (), + >( + LaneId::default(), + 1u32.into(), + AccountId32::from(Alice.public()).into(), + unrewarded_relayers.clone(), + ); + + let call = test_data::from_grandpa_chain::make_standalone_relayer_confirmation_call::< + RuntimeHelper::Runtime, + RuntimeHelper::GPI, + RuntimeHelper::MPI, + >(message_delivery_proof, unrewarded_relayers); + + compute_extrinsic_fee(call) + }) +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_parachain.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_parachain.rs index 72ec0718acf7..7d88da2a3380 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_parachain.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_parachain.rs @@ -527,3 +527,125 @@ where compute_extrinsic_fee(batch) }) } + +/// Estimates transaction fee for default message delivery transaction from bridged parachain. +pub fn can_calculate_fee_for_standalone_message_delivery_transaction( + collator_session_key: CollatorSessionKeys, + compute_extrinsic_fee: fn( + ::RuntimeCall, + ) -> u128, +) -> u128 +where + RuntimeHelper: WithRemoteParachainHelper, + RuntimeCallOf: + From>, + UnderlyingChainOf>: + bp_runtime::Chain + Parachain, + >::BridgedChain: + bp_runtime::Chain + ChainWithGrandpa, + >::SourceHeaderChain: + SourceHeaderChain< + MessagesProof = FromBridgedChainMessagesProof< + HashOf>, + >, + >, +{ + run_test::(collator_session_key, 1000, vec![], || { + // generate bridged relay chain finality, parachain heads and message proofs, + // to be submitted by relayer to this chain. + // + // we don't care about parameter values here, apart from the XCM message size. But we + // do not need to have a large message here, because we're charging for every byte of + // the message additionally + let ( + _, + _, + _, + _, + _, + message_proof, + ) = test_data::from_parachain::make_complex_relayer_delivery_proofs::< + >::BridgedChain, + RuntimeHelper::MB, + (), + >( + LaneId::default(), + vec![Instruction::<()>::ClearOrigin; 1_024].into(), + 1, + [GlobalConsensus(Polkadot), Parachain(1_000)].into(), + 1, + 5, + 1_000, + ); + + let call = test_data::from_parachain::make_standalone_relayer_delivery_call::< + RuntimeHelper::Runtime, + RuntimeHelper::MPI, + _, + >( + message_proof, + helpers::relayer_id_at_bridged_chain::(), + ); + + compute_extrinsic_fee(call) + }) +} + +/// Estimates transaction fee for default message confirmation transaction (batched with required +/// proofs) from bridged parachain. +pub fn can_calculate_fee_for_standalone_message_confirmation_transaction( + collator_session_key: CollatorSessionKeys, + compute_extrinsic_fee: fn( + ::RuntimeCall, + ) -> u128, +) -> u128 +where + RuntimeHelper: WithRemoteParachainHelper, + AccountIdOf: From, + MessageThisChain: + bp_runtime::Chain>, + RuntimeCallOf: + From>, + UnderlyingChainOf>: + bp_runtime::Chain + Parachain, + >::BridgedChain: + bp_runtime::Chain + ChainWithGrandpa, + >::TargetHeaderChain: + TargetHeaderChain< + XcmAsPlainPayload, + AccountIdOf, + MessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof< + HashOf>>, + >, + >, +{ + run_test::(collator_session_key, 1000, vec![], || { + // generate bridged relay chain finality, parachain heads and message proofs, + // to be submitted by relayer to this chain. + let unrewarded_relayers = UnrewardedRelayersState { + unrewarded_relayer_entries: 1, + total_messages: 1, + ..Default::default() + }; + let (_, _, _, _, _, message_delivery_proof) = + test_data::from_parachain::make_complex_relayer_confirmation_proofs::< + >::BridgedChain, + RuntimeHelper::MB, + (), + >( + LaneId::default(), + 1, + 5, + 1_000, + AccountId32::from(Alice.public()).into(), + unrewarded_relayers.clone(), + ); + + let call = test_data::from_parachain::make_standalone_relayer_confirmation_call::< + RuntimeHelper::Runtime, + RuntimeHelper::MPI, + >(message_delivery_proof, unrewarded_relayers); + + compute_extrinsic_fee(call) + }) +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_grandpa_chain.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_grandpa_chain.rs index 017ec0fd5405..b18265726e89 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_grandpa_chain.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_grandpa_chain.rs @@ -121,6 +121,60 @@ where } } +/// Prepare a call with message proof. +pub fn make_standalone_relayer_delivery_call( + message_proof: FromBridgedChainMessagesProof>>, + relayer_id_at_bridged_chain: AccountIdOf>, +) -> Runtime::RuntimeCall +where + Runtime: pallet_bridge_grandpa::Config + + pallet_bridge_messages::Config< + MPI, + InboundPayload = XcmAsPlainPayload, + InboundRelayer = AccountIdOf>, + >, + MPI: 'static, + >::SourceHeaderChain: SourceHeaderChain< + MessagesProof = FromBridgedChainMessagesProof>>, + >, + Runtime::RuntimeCall: From>, +{ + pallet_bridge_messages::Call::::receive_messages_proof { + relayer_id_at_bridged_chain, + proof: message_proof, + messages_count: 1, + dispatch_weight: Weight::from_parts(1000000000, 0), + } + .into() +} + +/// Prepare a call with message delivery proof. +pub fn make_standalone_relayer_confirmation_call( + message_delivery_proof: FromBridgedChainMessagesDeliveryProof< + HashOf>, + >, + relayers_state: UnrewardedRelayersState, +) -> Runtime::RuntimeCall +where + Runtime: pallet_bridge_grandpa::Config + + pallet_bridge_messages::Config, + MPI: 'static, + >::TargetHeaderChain: TargetHeaderChain< + XcmAsPlainPayload, + Runtime::AccountId, + MessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof< + HashOf>, + >, + >, + Runtime::RuntimeCall: From>, +{ + pallet_bridge_messages::Call::::receive_messages_delivery_proof { + proof: message_delivery_proof, + relayers_state, + } + .into() +} + /// Prepare storage proofs of messages, stored at the (bridged) source GRANDPA chain. pub fn make_complex_relayer_delivery_proofs( lane_id: LaneId, diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_parachain.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_parachain.rs index 932ba2312399..5d308599065d 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_parachain.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_parachain.rs @@ -159,6 +159,52 @@ where } } +/// Prepare a call with message proof. +pub fn make_standalone_relayer_delivery_call( + message_proof: FromBridgedChainMessagesProof, + relayer_id_at_bridged_chain: InboundRelayer, +) -> Runtime::RuntimeCall where + Runtime: pallet_bridge_messages::Config< + MPI, + InboundPayload = XcmAsPlainPayload, + InboundRelayer = InboundRelayer, + >, + MPI: 'static, + Runtime::RuntimeCall: From>, + <>::SourceHeaderChain as SourceHeaderChain>::MessagesProof: + From>, +{ + pallet_bridge_messages::Call::::receive_messages_proof { + relayer_id_at_bridged_chain: relayer_id_at_bridged_chain.into(), + proof: message_proof.into(), + messages_count: 1, + dispatch_weight: Weight::from_parts(1000000000, 0), + } + .into() +} + +/// Prepare a call with message delivery proof. +pub fn make_standalone_relayer_confirmation_call( + message_delivery_proof: FromBridgedChainMessagesDeliveryProof, + relayers_state: UnrewardedRelayersState, +) -> Runtime::RuntimeCall +where + Runtime: pallet_bridge_messages::Config, + MPI: 'static, + Runtime::RuntimeCall: From>, + >::TargetHeaderChain: TargetHeaderChain< + XcmAsPlainPayload, + Runtime::AccountId, + MessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof, + >, +{ + pallet_bridge_messages::Call::::receive_messages_delivery_proof { + proof: message_delivery_proof, + relayers_state, + } + .into() +} + /// Prepare storage proofs of messages, stored at the source chain. pub fn make_complex_relayer_delivery_proofs( lane_id: LaneId, From 21962bd8edfb490c0c71fc7292fdf69581ee5924 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 16 Apr 2024 08:56:17 +0300 Subject: [PATCH 50/70] port FinalityApi::free_headers_interval from https://github.com/paritytech/parity-bridges-common/pull/2884 --- bridges/primitives/runtime/src/chain.rs | 12 ++++++++++++ .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 17 ++++++++++++++++- .../bridge-hubs/bridge-hub-westend/src/lib.rs | 11 ++++++++++- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/bridges/primitives/runtime/src/chain.rs b/bridges/primitives/runtime/src/chain.rs index f232c7726a91..1b1c623104f9 100644 --- a/bridges/primitives/runtime/src/chain.rs +++ b/bridges/primitives/runtime/src/chain.rs @@ -314,6 +314,11 @@ macro_rules! decl_bridge_finality_runtime_apis { pub const []: &str = stringify!([<$chain:camel FinalityApi_best_finalized>]); + /// Name of the `FinalityApi::free_headers_interval` runtime method. + pub const []: &str = + stringify!([<$chain:camel FinalityApi_free_headers_interval>]); + + $( /// Name of the `FinalityApi::accepted__finality_proofs` /// runtime method. @@ -330,6 +335,13 @@ macro_rules! decl_bridge_finality_runtime_apis { /// Returns number and hash of the best finalized header known to the bridge module. fn best_finalized() -> Option>; + /// Returns free headers interval, if it is configured in the runtime. + /// The caller expects that if his transaction improves best known header + /// at least by the free_headers_interval`, it will be fee-free. + /// + /// See [`pallet_bridge_grandpa::Config::FreeHeadersInterval`] for details. + fn free_headers_interval() -> Option; + $( /// Returns the justifications accepted in the current block. fn []( diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index da4f8c6e9dd6..cc59e71ea25f 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -69,7 +69,7 @@ use frame_support::{ dispatch::DispatchClass, genesis_builder_helper::{build_state, get_preset}, parameter_types, - traits::{ConstBool, ConstU32, ConstU64, ConstU8, TransformOrigin}, + traits::{ConstBool, ConstU32, ConstU64, ConstU8, Get, TransformOrigin}, weights::{ConstantMultiplier, Weight}, PalletId, }; @@ -962,6 +962,11 @@ impl_runtime_apis! { fn best_finalized() -> Option> { BridgeWestendGrandpa::best_finalized() } + fn free_headers_interval() -> Option { + >::FreeHeadersInterval::get() + } fn synced_headers_grandpa_info( ) -> Vec> { BridgeWestendGrandpa::synced_headers_grandpa_info() @@ -974,6 +979,10 @@ impl_runtime_apis! { bp_bridge_hub_westend::BridgeHubWestend >().unwrap_or(None) } + fn free_headers_interval() -> Option { + // "free interval" is not currently used for parachains + None + } } // This is exposed by BridgeHubRococo @@ -1008,6 +1017,12 @@ impl_runtime_apis! { BridgePolkadotBulletinGrandpa::best_finalized() } + fn free_headers_interval() -> Option { + >::FreeHeadersInterval::get() + } + fn synced_headers_grandpa_info( ) -> Vec> { BridgePolkadotBulletinGrandpa::synced_headers_grandpa_info() diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index 1475be5bc125..5dea30436a83 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -63,7 +63,7 @@ use frame_support::{ dispatch::DispatchClass, genesis_builder_helper::{build_state, get_preset}, parameter_types, - traits::{ConstBool, ConstU32, ConstU64, ConstU8, TransformOrigin}, + traits::{ConstBool, ConstU32, ConstU64, ConstU8, Get, TransformOrigin}, weights::{ConstantMultiplier, Weight}, PalletId, }; @@ -711,6 +711,11 @@ impl_runtime_apis! { fn best_finalized() -> Option> { BridgeRococoGrandpa::best_finalized() } + fn free_headers_interval() -> Option { + >::FreeHeadersInterval::get() + } fn synced_headers_grandpa_info( ) -> Vec> { BridgeRococoGrandpa::synced_headers_grandpa_info() @@ -723,6 +728,10 @@ impl_runtime_apis! { bp_bridge_hub_rococo::BridgeHubRococo >().unwrap_or(None) } + fn free_headers_interval() -> Option { + // "free interval" is not currently used for parachains + None + } } impl bp_bridge_hub_rococo::FromBridgeHubRococoInboundLaneApi for Runtime { From aa890401bd762fc6ce8b5baef02d481118bbee4c Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 16 Apr 2024 09:11:45 +0300 Subject: [PATCH 51/70] adapted 0001-asset-transfer test for new fee scheme --- .../bridge_hub_rococo_local_network.toml | 8 +- .../bridge_hub_westend_local_network.toml | 8 +- .../rococo-westend/bridges_rococo_westend.sh | 98 +++++++++++++++++++ .../environments/rococo-westend/explorers.sh | 11 +++ .../environments/rococo-westend/helper.sh | 8 +- .../environments/rococo-westend/spawn.sh | 4 +- .../rococo-westend/start_relayer.sh | 26 +++-- .../js-helpers/native-asset-balance.js | 12 +++ .../roc-reaches-westend.zndsl | 6 +- .../roc-relayer-balance-does-not-change.zndsl | 11 +++ .../testing/tests/0001-asset-transfer/run.sh | 6 ++ .../wnd-reaches-rococo.zndsl | 6 +- .../wnd-relayer-balance-does-not-change.zndsl | 11 +++ 13 files changed, 192 insertions(+), 23 deletions(-) create mode 100755 bridges/testing/environments/rococo-westend/explorers.sh create mode 100644 bridges/testing/framework/js-helpers/native-asset-balance.js create mode 100644 bridges/testing/tests/0001-asset-transfer/roc-relayer-balance-does-not-change.zndsl create mode 100644 bridges/testing/tests/0001-asset-transfer/wnd-relayer-balance-does-not-change.zndsl diff --git a/bridges/testing/environments/rococo-westend/bridge_hub_rococo_local_network.toml b/bridges/testing/environments/rococo-westend/bridge_hub_rococo_local_network.toml index 52271f944213..f59f689bf6b5 100644 --- a/bridges/testing/environments/rococo-westend/bridge_hub_rococo_local_network.toml +++ b/bridges/testing/environments/rococo-westend/bridge_hub_rococo_local_network.toml @@ -40,7 +40,7 @@ cumulus_based = true rpc_port = 8933 ws_port = 8943 args = [ - "-lparachain=debug,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace" + "-lparachain=debug,runtime::bridge=trace,xcm=trace,txpool=trace" ] # run bob as parachain collator @@ -51,7 +51,7 @@ cumulus_based = true rpc_port = 8934 ws_port = 8944 args = [ - "-lparachain=trace,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace" + "-lparachain=debug,runtime::bridge=trace,xcm=trace,txpool=trace" ] [[parachains]] @@ -65,14 +65,14 @@ cumulus_based = true ws_port = 9910 command = "{{POLKADOT_PARACHAIN_BINARY}}" args = [ - "-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace" + "-lparachain=debug,xcm=trace,runtime::bridge=trace,txpool=trace" ] [[parachains.collators]] name = "asset-hub-rococo-collator2" command = "{{POLKADOT_PARACHAIN_BINARY}}" args = [ - "-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace" + "-lparachain=debug,xcm=trace,runtime::bridge=trace,txpool=trace" ] #[[hrmp_channels]] diff --git a/bridges/testing/environments/rococo-westend/bridge_hub_westend_local_network.toml b/bridges/testing/environments/rococo-westend/bridge_hub_westend_local_network.toml index f2550bcc9959..6ab03ad5fe2c 100644 --- a/bridges/testing/environments/rococo-westend/bridge_hub_westend_local_network.toml +++ b/bridges/testing/environments/rococo-westend/bridge_hub_westend_local_network.toml @@ -40,7 +40,7 @@ cumulus_based = true rpc_port = 8935 ws_port = 8945 args = [ - "-lparachain=debug,runtime::mmr=info,substrate=info,runtime=info,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace" + "-lparachain=debug,runtime::bridge=trace,xcm=trace,txpool=trace" ] # run bob as parachain collator @@ -51,7 +51,7 @@ cumulus_based = true rpc_port = 8936 ws_port = 8946 args = [ - "-lparachain=trace,runtime::mmr=info,substrate=info,runtime=info,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace" + "-lparachain=debug,runtime::bridge=trace,xcm=trace,txpool=trace" ] [[parachains]] @@ -65,14 +65,14 @@ cumulus_based = true ws_port = 9010 command = "{{POLKADOT_PARACHAIN_BINARY}}" args = [ - "-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace" + "-lparachain=debug,xcm=trace,runtime::bridge=trace,txpool=trace" ] [[parachains.collators]] name = "asset-hub-westend-collator2" command = "{{POLKADOT_PARACHAIN_BINARY}}" args = [ - "-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace" + "-lparachain=debug,xcm=trace,runtime::bridge=trace,txpool=trace" ] #[[hrmp_channels]] diff --git a/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh b/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh index 41aa862be576..2f11692d97b9 100755 --- a/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh +++ b/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh @@ -169,12 +169,107 @@ function run_relay() { --lane "${LANE_ID}" } +function run_finality_relay() { + local relayer_path=$(ensure_relayer) + + RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ + $relayer_path relay-headers rococo-to-bridge-hub-westend \ + --only-free-headers \ + --source-host localhost \ + --source-port 9942 \ + --target-host localhost \ + --target-port 8945 \ + --target-version-mode Auto \ + --target-signer //Charlie \ + --target-transactions-mortality 4& + + RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ + $relayer_path relay-headers westend-to-bridge-hub-rococo \ + --only-free-headers \ + --source-host localhost \ + --source-port 9945 \ + --target-host localhost \ + --target-port 8943 \ + --target-version-mode Auto \ + --target-signer //Charlie \ + --target-transactions-mortality 4 +} + +function run_parachains_relay() { + local relayer_path=$(ensure_relayer) + + RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ + $relayer_path relay-parachains rococo-to-bridge-hub-westend \ + --only-free-headers \ + --source-host localhost \ + --source-port 9942 \ + --target-host localhost \ + --target-port 8945 \ + --target-version-mode Auto \ + --target-signer //Dave \ + --target-transactions-mortality 4& + + RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ + $relayer_path relay-parachains westend-to-bridge-hub-rococo \ + --only-free-headers \ + --source-host localhost \ + --source-port 9945 \ + --target-host localhost \ + --target-port 8943 \ + --target-version-mode Auto \ + --target-signer //Dave \ + --target-transactions-mortality 4 +} + +function run_messages_relay() { + local relayer_path=$(ensure_relayer) + + RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ + $relayer_path relay-messages bridge-hub-rococo-to-bridge-hub-westend \ + --source-host localhost \ + --source-port 8943 \ + --source-version-mode Auto \ + --source-signer //Eve \ + --source-transactions-mortality 4 \ + --target-host localhost \ + --target-port 8945 \ + --target-version-mode Auto \ + --target-signer //Eve \ + --target-transactions-mortality 4 \ + --lane $LANE_ID& + + RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ + $relayer_path relay-messages bridge-hub-westend-to-bridge-hub-rococo \ + --source-host localhost \ + --source-port 8945 \ + --source-version-mode Auto \ + --source-signer //Ferdie \ + --source-transactions-mortality 4 \ + --target-host localhost \ + --target-port 8943 \ + --target-version-mode Auto \ + --target-signer //Ferdie \ + --target-transactions-mortality 4 \ + --lane $LANE_ID +} + case "$1" in run-relay) init_wnd_ro init_ro_wnd run_relay ;; + run-finality-relay) + init_wnd_ro + init_ro_wnd + run_finality_relay + ;; + run-parachains-relay) + run_parachains_relay + ;; + run-messages-relay) + run_messages_relay + ;; init-asset-hub-rococo-local) ensure_polkadot_js_api # create foreign assets for native Westend token (governance call on Rococo) @@ -386,6 +481,9 @@ case "$1" in echo "A command is require. Supported commands for: Local (zombienet) run: - run-relay + - run-finality-relay + - run-parachains-relay + - run-messages-relay - init-asset-hub-rococo-local - init-bridge-hub-rococo-local - init-asset-hub-westend-local diff --git a/bridges/testing/environments/rococo-westend/explorers.sh b/bridges/testing/environments/rococo-westend/explorers.sh new file mode 100755 index 000000000000..fb137726c93c --- /dev/null +++ b/bridges/testing/environments/rococo-westend/explorers.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# Rococo AH +xdg-open https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:9910#/explorer& +# Rococo BH +xdg-open https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:8943#/explorer& + +# Westend BH +xdg-open https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:8945#/explorer& +# Westend AH +xdg-open https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:9010#/explorer& diff --git a/bridges/testing/environments/rococo-westend/helper.sh b/bridges/testing/environments/rococo-westend/helper.sh index 0a13ded213f5..571c78fea584 100755 --- a/bridges/testing/environments/rococo-westend/helper.sh +++ b/bridges/testing/environments/rococo-westend/helper.sh @@ -1,3 +1,9 @@ #!/bin/bash -$ENV_PATH/bridges_rococo_westend.sh "$@" +if [ $1 == "auto-log" ]; then + shift # ignore "auto-log" + log_name=$1 + $ENV_PATH/bridges_rococo_westend.sh "$@" >$TEST_DIR/logs/$log_name.log +else + $ENV_PATH/bridges_rococo_westend.sh "$@" +fi diff --git a/bridges/testing/environments/rococo-westend/spawn.sh b/bridges/testing/environments/rococo-westend/spawn.sh index cbd0b1bc623a..a0ab00be1444 100755 --- a/bridges/testing/environments/rococo-westend/spawn.sh +++ b/bridges/testing/environments/rococo-westend/spawn.sh @@ -59,12 +59,12 @@ if [[ $init -eq 1 ]]; then fi if [[ $start_relayer -eq 1 ]]; then - ${BASH_SOURCE%/*}/start_relayer.sh $rococo_dir $westend_dir relayer_pid + ${BASH_SOURCE%/*}/start_relayer.sh $rococo_dir $westend_dir finality_relayer_pid parachains_relayer_pid messages_relayer_pid fi echo $rococo_dir > $TEST_DIR/rococo.env echo $westend_dir > $TEST_DIR/westend.env echo -wait -n $rococo_pid $westend_pid $relayer_pid +wait -n $rococo_pid $westend_pid $finality_relayer_pid $parachains_relayer_pid $messages_relayer_pid kill -9 -$$ diff --git a/bridges/testing/environments/rococo-westend/start_relayer.sh b/bridges/testing/environments/rococo-westend/start_relayer.sh index 7ddd312d395a..9c57e4a6ab6e 100755 --- a/bridges/testing/environments/rococo-westend/start_relayer.sh +++ b/bridges/testing/environments/rococo-westend/start_relayer.sh @@ -7,17 +7,31 @@ source "$FRAMEWORK_PATH/utils/zombienet.sh" rococo_dir=$1 westend_dir=$2 -__relayer_pid=$3 +__finality_relayer_pid=$3 +__parachains_relayer_pid=$4 +__messages_relayer_pid=$5 logs_dir=$TEST_DIR/logs helper_script="${BASH_SOURCE%/*}/helper.sh" -relayer_log=$logs_dir/relayer.log -echo -e "Starting rococo-westend relayer. Logs available at: $relayer_log\n" -start_background_process "$helper_script run-relay" $relayer_log relayer_pid +# start finality relayer +finality_relayer_log=$logs_dir/relayer_finality.log +echo -e "Starting rococo-westend finality relayer. Logs available at: $finality_relayer_log\n" +start_background_process "$helper_script run-finality-relay" $finality_relayer_log finality_relayer_pid + +# start parachains relayer +parachains_relayer_log=$logs_dir/relayer_parachains.log +echo -e "Starting rococo-westend parachains relayer. Logs available at: $parachains_relayer_log\n" +start_background_process "$helper_script run-parachains-relay" $parachains_relayer_log parachains_relayer_pid + +# start messages relayer +messages_relayer_log=$logs_dir/relayer_messages.log +echo -e "Starting rococo-westend messages relayer. Logs available at: $messages_relayer_log\n" +start_background_process "$helper_script run-messages-relay" $messages_relayer_log messages_relayer_pid run_zndsl ${BASH_SOURCE%/*}/rococo.zndsl $rococo_dir run_zndsl ${BASH_SOURCE%/*}/westend.zndsl $westend_dir -eval $__relayer_pid="'$relayer_pid'" - +eval $__finality_relayer_pid="'$finality_relayer_pid'" +eval $__parachains_relayer_pid="'$parachains_relayer_pid'" +eval $__messages_relayer_pid="'$messages_relayer_pid'" diff --git a/bridges/testing/framework/js-helpers/native-asset-balance.js b/bridges/testing/framework/js-helpers/native-asset-balance.js new file mode 100644 index 000000000000..4869eba35d8d --- /dev/null +++ b/bridges/testing/framework/js-helpers/native-asset-balance.js @@ -0,0 +1,12 @@ +async function run(nodeName, networkInfo, args) { + const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName]; + const api = await zombie.connect(wsUri, userDefinedTypes); + + const accountAddress = args[0]; + const accountData = await api.query.system.account(accountAddress); + const accountBalance = accountData.data['free']; + console.log("Balance of " + accountAddress + ": " + accountBalance); + return accountBalance; +} + +module.exports = {run} diff --git a/bridges/testing/tests/0001-asset-transfer/roc-reaches-westend.zndsl b/bridges/testing/tests/0001-asset-transfer/roc-reaches-westend.zndsl index cdb7d28e940c..6e26632fd9f9 100644 --- a/bridges/testing/tests/0001-asset-transfer/roc-reaches-westend.zndsl +++ b/bridges/testing/tests/0001-asset-transfer/roc-reaches-westend.zndsl @@ -3,10 +3,10 @@ Network: {{ENV_PATH}}/bridge_hub_westend_local_network.toml Creds: config # send 5 ROC to //Alice from Rococo AH to Westend AH -asset-hub-westend-collator1: run {{ENV_PATH}}/helper.sh with "reserve-transfer-assets-from-asset-hub-rococo-local 5000000000000" within 120 seconds +asset-hub-westend-collator1: run {{ENV_PATH}}/helper.sh with "auto-log reserve-transfer-assets-from-asset-hub-rococo-local 5000000000000" within 120 seconds # check that //Alice received at least 4.8 ROC on Westend AH asset-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/wrapped-assets-balance.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,4800000000000,Rococo" within 600 seconds -# check that the relayer //Charlie is rewarded by Westend AH -bridge-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x6268726F,ThisChain,0" within 30 seconds +# relayer //Ferdie is rewarded for delivering messages from Rococo BH +bridge-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/relayer-rewards.js with "5HGjWAeFDfFCWPsjFQdVV2Msvz2XtMktvgocEZcCj68kUMaw,0x00000002,0x6268726F,ThisChain,0" within 300 seconds diff --git a/bridges/testing/tests/0001-asset-transfer/roc-relayer-balance-does-not-change.zndsl b/bridges/testing/tests/0001-asset-transfer/roc-relayer-balance-does-not-change.zndsl new file mode 100644 index 000000000000..4839c19c0ff2 --- /dev/null +++ b/bridges/testing/tests/0001-asset-transfer/roc-relayer-balance-does-not-change.zndsl @@ -0,0 +1,11 @@ +Description: Finality and parachain relays should have the constant balance, because their transactions are free +Network: {{ENV_PATH}}/bridge_hub_rococo_local_network.toml +Creds: config + +# local chain spec gives `1u64 << 60` tokens to every endowed account: if it'll ever +# change, it'd need to be fixed here as well + +# //Charlie only submits free and mandatory relay chain headers, so the balance should stay the same +bridge-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/native-asset-balance.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y" return is 1152921504606846976 within 30 seconds +# //Dave only submits free parachain headers, so the balance should stay the same +bridge-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/native-asset-balance.js with "5DAAnrj7VHTznn2AWBemMuyBwZWs6FNFjdyVXUeYum3PTXFy" return is 1152921504606846976 within 30 seconds diff --git a/bridges/testing/tests/0001-asset-transfer/run.sh b/bridges/testing/tests/0001-asset-transfer/run.sh index a7bb122919b4..227069932f2d 100755 --- a/bridges/testing/tests/0001-asset-transfer/run.sh +++ b/bridges/testing/tests/0001-asset-transfer/run.sh @@ -18,8 +18,14 @@ ensure_process_file $env_pid $TEST_DIR/westend.env 300 westend_dir=`cat $TEST_DIR/westend.env` echo +run_zndsl ${BASH_SOURCE%/*}/roc-relayer-balance-does-not-change.zndsl $rococo_dir +run_zndsl ${BASH_SOURCE%/*}/wnd-relayer-balance-does-not-change.zndsl $westend_dir + run_zndsl ${BASH_SOURCE%/*}/roc-reaches-westend.zndsl $westend_dir run_zndsl ${BASH_SOURCE%/*}/wnd-reaches-rococo.zndsl $rococo_dir run_zndsl ${BASH_SOURCE%/*}/wroc-reaches-rococo.zndsl $rococo_dir run_zndsl ${BASH_SOURCE%/*}/wwnd-reaches-westend.zndsl $westend_dir + +run_zndsl ${BASH_SOURCE%/*}/roc-relayer-balance-does-not-change.zndsl $rococo_dir +run_zndsl ${BASH_SOURCE%/*}/wnd-relayer-balance-does-not-change.zndsl $westend_dir diff --git a/bridges/testing/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl b/bridges/testing/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl index dbc03864e2b6..5a8d6dabc20e 100644 --- a/bridges/testing/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl +++ b/bridges/testing/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl @@ -3,10 +3,10 @@ Network: {{ENV_PATH}}/bridge_hub_rococo_local_network.toml Creds: config # send 5 WND to //Alice from Westend AH to Rococo AH -asset-hub-rococo-collator1: run {{ENV_PATH}}/helper.sh with "reserve-transfer-assets-from-asset-hub-westend-local 5000000000000" within 120 seconds +asset-hub-rococo-collator1: run {{ENV_PATH}}/helper.sh with "auto-log reserve-transfer-assets-from-asset-hub-westend-local 5000000000000" within 120 seconds # check that //Alice received at least 4.8 WND on Rococo AH asset-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/wrapped-assets-balance.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,4800000000000,Westend" within 600 seconds -# check that the relayer //Charlie is rewarded by Rococo AH -bridge-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x62687764,ThisChain,0" within 30 seconds +# relayer //Eve is rewarded for delivering messages from Westend BH +bridge-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/relayer-rewards.js with "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL,0x00000002,0x62687764,ThisChain,0" within 300 seconds diff --git a/bridges/testing/tests/0001-asset-transfer/wnd-relayer-balance-does-not-change.zndsl b/bridges/testing/tests/0001-asset-transfer/wnd-relayer-balance-does-not-change.zndsl new file mode 100644 index 000000000000..d2563e180786 --- /dev/null +++ b/bridges/testing/tests/0001-asset-transfer/wnd-relayer-balance-does-not-change.zndsl @@ -0,0 +1,11 @@ +Description: Finality and parachain relays should have the constant balance, because their transactions are free +Network: {{ENV_PATH}}/bridge_hub_westend_local_network.toml +Creds: config + +# local chain spec gives `1u64 << 60` tokens to every endowed account: if it'll ever +# change, it'd need to be fixed here as well + +# //Charlie only submits free and mandatory relay chain headers, so the balance should stay the same +bridge-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/native-asset-balance.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y" return is 1152921504606846976 within 30 seconds +# //Dave only submits free parachain headers, so the balance should stay the same +bridge-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/native-asset-balance.js with "5DAAnrj7VHTznn2AWBemMuyBwZWs6FNFjdyVXUeYum3PTXFy" return is 1152921504606846976 within 30 seconds From 51e51d7b880f8472ddd4e34b57fb66251fa66b0f Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 16 Apr 2024 09:24:11 +0300 Subject: [PATCH 52/70] fix `bridge-runtime-common` compilation after recent refactoring --- .../extensions/check_obsolete_extension.rs | 10 ++++---- .../extensions/refund_relayer_extension.rs | 24 ++++++++++--------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs b/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs index a17b02564c1b..2c152aef6822 100644 --- a/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs +++ b/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs @@ -361,9 +361,9 @@ mod tests { extensions::refund_relayer_extension::{ tests::{ initialize_environment, relayer_account_at_this_chain, - submit_parachain_head_call_ex, submit_relay_header_call_ex, TestParachain, + submit_parachain_head_call_ex, submit_relay_header_call_ex, }, - DefaultRefundableParachainId, + RefundableParachain, }, mock::*, }; @@ -595,7 +595,7 @@ mod tests { type BridgeParachainsWrapper = CheckAndBoostBridgeParachainsTransactions< TestRuntime, - DefaultRefundableParachainId<(), TestParachain>, + RefundableParachain<(), BridgedUnderlyingParachain>, ConstU64<1_000>, SlashDestination, >; @@ -647,7 +647,7 @@ mod tests { true, Some(SubmitParachainHeadsInfo { at_relay_block: HeaderId(150, Default::default()), - para_id: ParaId(TestParachain::get()), + para_id: ParaId(BridgedUnderlyingParachain::PARACHAIN_ID), para_head_hash: [150u8; 32].into(), is_free_execution_expected: false, }), @@ -669,7 +669,7 @@ mod tests { false, Some(SubmitParachainHeadsInfo { at_relay_block: HeaderId(100, Default::default()), - para_id: ParaId(TestParachain::get()), + para_id: ParaId(BridgedUnderlyingParachain::PARACHAIN_ID), para_head_hash: [100u8; 32].into(), is_free_execution_expected: false, }), diff --git a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs index b2d911455f5b..5aa7f1c095d5 100644 --- a/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs +++ b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs @@ -970,7 +970,6 @@ pub(crate) mod tests { }; parameter_types! { - pub TestParachain: u32 = 1000; pub TestLaneId: LaneId = TEST_LANE_ID; pub MsgProofsRewardsAccount: RewardsAccountParams = RewardsAccountParams::new( TEST_LANE_ID, @@ -1005,7 +1004,7 @@ pub(crate) mod tests { type TestGrandpaExtension = RefundSignedExtensionAdapter; type TestExtensionProvider = RefundBridgedParachainMessages< TestRuntime, - DefaultRefundableParachainId<(), TestParachain>, + RefundableParachain<(), BridgedUnderlyingParachain>, RefundableMessagesLane<(), TestLaneId>, ActualFeeRefund, ConstU64<1>, @@ -1053,7 +1052,7 @@ pub(crate) mod tests { bp_test_utils::test_header::(0).build(), ); - let para_id = ParaId(TestParachain::get()); + let para_id = ParaId(BridgedUnderlyingParachain::PARACHAIN_ID); let para_info = ParaInfo { best_head_hash: BestParaHeadHash { at_relay_block_number: parachain_head_at_relay_header_number, @@ -1121,7 +1120,7 @@ pub(crate) mod tests { RuntimeCall::BridgeParachains(ParachainsCall::submit_parachain_heads { at_relay_block: (parachain_head_at_relay_header_number, RelayBlockHash::default()), parachains: vec![( - ParaId(TestParachain::get()), + ParaId(BridgedUnderlyingParachain::PARACHAIN_ID), [parachain_head_at_relay_header_number as u8; 32].into(), )], parachain_heads_proof: ParaHeadsProof { storage_proof: vec![] }, @@ -1134,7 +1133,7 @@ pub(crate) mod tests { RuntimeCall::BridgeParachains(ParachainsCall::submit_parachain_heads_ex { at_relay_block: (parachain_head_at_relay_header_number, RelayBlockHash::default()), parachains: vec![( - ParaId(TestParachain::get()), + ParaId(BridgedUnderlyingParachain::PARACHAIN_ID), [parachain_head_at_relay_header_number as u8; 32].into(), )], parachain_heads_proof: ParaHeadsProof { storage_proof: vec![] }, @@ -1317,7 +1316,7 @@ pub(crate) mod tests { }, SubmitParachainHeadsInfo { at_relay_block: HeaderId(200, [0u8; 32].into()), - para_id: ParaId(TestParachain::get()), + para_id: ParaId(BridgedUnderlyingParachain::PARACHAIN_ID), para_head_hash: [200u8; 32].into(), is_free_execution_expected: false, }, @@ -1357,7 +1356,7 @@ pub(crate) mod tests { }, SubmitParachainHeadsInfo { at_relay_block: HeaderId(200, [0u8; 32].into()), - para_id: ParaId(TestParachain::get()), + para_id: ParaId(BridgedUnderlyingParachain::PARACHAIN_ID), para_head_hash: [200u8; 32].into(), is_free_execution_expected: false, }, @@ -1449,7 +1448,7 @@ pub(crate) mod tests { call_info: CallInfo::ParachainFinalityAndMsgs( SubmitParachainHeadsInfo { at_relay_block: HeaderId(200, [0u8; 32].into()), - para_id: ParaId(TestParachain::get()), + para_id: ParaId(BridgedUnderlyingParachain::PARACHAIN_ID), para_head_hash: [200u8; 32].into(), is_free_execution_expected: false, }, @@ -1474,7 +1473,7 @@ pub(crate) mod tests { call_info: CallInfo::ParachainFinalityAndMsgs( SubmitParachainHeadsInfo { at_relay_block: HeaderId(200, [0u8; 32].into()), - para_id: ParaId(TestParachain::get()), + para_id: ParaId(BridgedUnderlyingParachain::PARACHAIN_ID), para_head_hash: [200u8; 32].into(), is_free_execution_expected: false, }, @@ -2098,8 +2097,11 @@ pub(crate) mod tests { RuntimeCall::BridgeParachains(ParachainsCall::submit_parachain_heads { at_relay_block: (100, RelayBlockHash::default()), parachains: vec![ - (ParaId(TestParachain::get()), [1u8; 32].into()), - (ParaId(TestParachain::get() + 1), [1u8; 32].into()), + (ParaId(BridgedUnderlyingParachain::PARACHAIN_ID), [1u8; 32].into()), + ( + ParaId(BridgedUnderlyingParachain::PARACHAIN_ID + 1), + [1u8; 32].into(), + ), ], parachain_heads_proof: ParaHeadsProof { storage_proof: vec![] }, }), From f07eddbdddca18b6785be8c844fe3967b22090ac Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 16 Apr 2024 09:46:27 +0300 Subject: [PATCH 53/70] added prdoc --- prdoc/pr_4102.prdoc | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 prdoc/pr_4102.prdoc diff --git a/prdoc/pr_4102.prdoc b/prdoc/pr_4102.prdoc new file mode 100644 index 000000000000..50c1ec23b2ac --- /dev/null +++ b/prdoc/pr_4102.prdoc @@ -0,0 +1,43 @@ +title: "Bridge: make some headers submissions free" + +doc: + - audience: Runtime Dev + description: | + Adds `FreeHeadersInterval` configuration constant to the `pallet_bridge_grandpa`. + Transactions that improve best known header by at least `FreeHeadersInterval` headers + are now free for the submitter. Additionally, we allow single free parachain header + update per every free relay chain header. Bridge signed extensions are adjusted + to support that new scheme. Bridge runtime APIs are extended to support that new + scheme. Bridge fees are decreased by ~98% because now they do not include cost of + finality submissions - we assume relayers will be submitting finality transactions + for free. + +crates: + - name: bridge-runtime-common + bump: major + - name: bp-bridge-hub-cumulus + bump: patch + - name: bp-bridge-hub-kusama + bump: major + - name: bp-bridge-hub-polkadot + bump: major + - name: bp-bridge-hub-rococo + bump: major + - name: bp-bridge-hub-westend + bump: major + - name: pallet-bridge-grandpa + bump: major + - name: pallet-bridge-parachains + bump: major + - name: bp-parachains + bump: major + - name: bp-runtime + bump: major + - name: relay-substrate-client + bump: major + - name: bridge-hub-rococo-runtime + bump: major + - name: bridge-hub-westend-runtime + bump: major + - name: bridge-hub-test-utils + bump: minor From c5471c9fc0c99fb247514164bd5a2f520309c11b Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 16 Apr 2024 11:12:48 +0300 Subject: [PATCH 54/70] fixed messages pallet benchmarks --- bridges/modules/parachains/src/lib.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/bridges/modules/parachains/src/lib.rs b/bridges/modules/parachains/src/lib.rs index 308bc97008b1..61e04aed3770 100644 --- a/bridges/modules/parachains/src/lib.rs +++ b/bridges/modules/parachains/src/lib.rs @@ -801,14 +801,28 @@ impl, I: 'static, C: Parachain> HeaderChain pub fn initialize_for_benchmarks, I: 'static, PC: Parachain>( header: HeaderOf, ) { + use bp_runtime::HeaderIdProvider; + use sp_runtime::traits::Header; + + let relay_head = + pallet_bridge_grandpa::BridgedHeader::::new( + 0, + Default::default(), + Default::default(), + Default::default(), + Default::default(), + ); let parachain = ParaId(PC::PARACHAIN_ID); let parachain_head = ParaHead(header.encode()); let updated_head_data = T::ParaStoredHeaderDataBuilder::try_build(parachain, ¶chain_head) .expect("failed to build stored parachain head in benchmarks"); + pallet_bridge_grandpa::initialize_for_benchmarks::( + relay_head.clone(), + ); Pallet::::update_parachain_head( parachain, None, - HeaderId(0, Default::default()), + relay_head.id(), updated_head_data, parachain_head.hash(), ) From 360614c89f96e2376036d36da3572a3dd59db684 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 15 Mar 2024 12:52:37 +0300 Subject: [PATCH 55/70] added --only-free-headers CLI parameter (not used yet) to finality and complex relay --- bridges/relays/finality/README.md | 4 ++- bridges/relays/finality/src/finality_loop.rs | 31 +++++++++++++------ bridges/relays/finality/src/lib.rs | 4 ++- .../src/cli/relay_headers.rs | 18 ++++++++++- .../src/cli/relay_headers_and_messages/mod.rs | 17 +++++++++- .../parachain_to_parachain.rs | 4 +-- .../relay_to_parachain.rs | 4 +-- .../relay_to_relay.rs | 4 +-- .../lib-substrate-relay/src/finality/mod.rs | 9 +++--- bridges/relays/lib-substrate-relay/src/lib.rs | 3 ++ .../src/on_demand/headers.rs | 14 ++++----- 11 files changed, 81 insertions(+), 31 deletions(-) diff --git a/bridges/relays/finality/README.md b/bridges/relays/finality/README.md index 92e765cea0e5..89b9d1399584 100644 --- a/bridges/relays/finality/README.md +++ b/bridges/relays/finality/README.md @@ -33,7 +33,9 @@ node. The transaction is then tracked by the relay until it is mined and finaliz The main entrypoint for the crate is the [`run` function](./src/finality_loop.rs), which takes source and target clients and [`FinalitySyncParams`](./src/finality_loop.rs) parameters. The most important parameter is the `only_mandatory_headers` - it is set to `true`, the relay will only submit mandatory headers. Since transactions -with mandatory headers are fee-free, the cost of running such relay is zero (in terms of fees). +with mandatory headers are fee-free, the cost of running such relay is zero (in terms of fees). If a similar, +`only_free_headers` parameter, is set to `true`, then free headers (if configured in the runtime) are also +relayed. ## Finality Relay Metrics diff --git a/bridges/relays/finality/src/finality_loop.rs b/bridges/relays/finality/src/finality_loop.rs index e31d8a708122..8af6de0db807 100644 --- a/bridges/relays/finality/src/finality_loop.rs +++ b/bridges/relays/finality/src/finality_loop.rs @@ -39,6 +39,17 @@ use std::{ time::{Duration, Instant}, }; +/// Type of headers that we relay. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum HeadersToRelay { + /// Relay all headers. + All, + /// Relay only mandatory headers. + Mandatory, + /// Relay only free (including mandatory) headers. + Free, +} + /// Finality proof synchronization loop parameters. #[derive(Debug, Clone)] pub struct FinalitySyncParams { @@ -63,7 +74,7 @@ pub struct FinalitySyncParams { /// 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, + pub headers_to_relay: HeadersToRelay, } /// Source client used in finality synchronization loop. @@ -304,7 +315,7 @@ impl, TC: TargetClient

> Finality // read missing headers let selector = JustifiedHeaderSelector::new::(&self.source_client, info).await?; // if we see that the header schedules GRANDPA change, we need to submit it - if self.sync_params.only_mandatory_headers { + if self.sync_params.headers_to_relay == HeadersToRelay::Mandatory { return Ok(selector.select_mandatory()) } @@ -509,7 +520,7 @@ mod tests { tick: Duration::from_secs(0), recent_finality_proofs_limit: 1024, stall_timeout: Duration::from_secs(1), - only_mandatory_headers: false, + headers_to_relay: HeadersToRelay::All, } } @@ -593,8 +604,8 @@ mod tests { ); } - fn run_only_mandatory_headers_mode_test( - only_mandatory_headers: bool, + fn run_headers_to_relay_mode_test( + headers_to_relay: HeadersToRelay, has_mandatory_headers: bool, ) -> Option> { let (exit_sender, _) = futures::channel::mpsc::unbounded(); @@ -619,7 +630,7 @@ mod tests { tick: Duration::from_secs(0), recent_finality_proofs_limit: 0, stall_timeout: Duration::from_secs(0), - only_mandatory_headers, + headers_to_relay, }, None, ); @@ -635,9 +646,9 @@ mod tests { #[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_headers_to_relay_mode_test(HeadersToRelay::Mandatory, false), None); assert_eq!( - run_only_mandatory_headers_mode_test(false, false), + run_headers_to_relay_mode_test(HeadersToRelay::All, false), Some(JustifiedHeader { header: TestSourceHeader(false, 10, 10), proof: TestFinalityProof(10) @@ -649,14 +660,14 @@ mod tests { fn select_header_to_submit_selects_mandatory_headers_when_only_mandatory_headers_are_required() { assert_eq!( - run_only_mandatory_headers_mode_test(true, true), + run_headers_to_relay_mode_test(HeadersToRelay::Mandatory, true), Some(JustifiedHeader { header: TestSourceHeader(true, 8, 8), proof: TestFinalityProof(8) }), ); assert_eq!( - run_only_mandatory_headers_mode_test(false, true), + run_headers_to_relay_mode_test(HeadersToRelay::All, true), Some(JustifiedHeader { header: TestSourceHeader(true, 8, 8), proof: TestFinalityProof(8) diff --git a/bridges/relays/finality/src/lib.rs b/bridges/relays/finality/src/lib.rs index 3579e68e1ef9..4346f96674b4 100644 --- a/bridges/relays/finality/src/lib.rs +++ b/bridges/relays/finality/src/lib.rs @@ -21,7 +21,9 @@ pub use crate::{ base::{FinalityPipeline, SourceClientBase}, - finality_loop::{metrics_prefix, run, FinalitySyncParams, SourceClient, TargetClient}, + finality_loop::{ + metrics_prefix, run, FinalitySyncParams, HeadersToRelay, SourceClient, TargetClient, + }, finality_proofs::{FinalityProofsBuf, FinalityProofsStream}, sync_loop_metrics::SyncLoopMetrics, }; diff --git a/bridges/relays/lib-substrate-relay/src/cli/relay_headers.rs b/bridges/relays/lib-substrate-relay/src/cli/relay_headers.rs index 90558ed46138..cf1957c7323b 100644 --- a/bridges/relays/lib-substrate-relay/src/cli/relay_headers.rs +++ b/bridges/relays/lib-substrate-relay/src/cli/relay_headers.rs @@ -24,6 +24,7 @@ use relay_utils::metrics::{GlobalMetrics, StandaloneMetric}; use crate::{ cli::{bridge::*, chain_schema::*, PrometheusParams}, finality::SubstrateFinalitySyncPipeline, + HeadersToRelay, }; /// Chain headers relaying params. @@ -33,6 +34,10 @@ pub struct RelayHeadersParams { /// are relayed. #[structopt(long)] only_mandatory_headers: bool, + /// If passed, only free headers (mandatory and every Nth header, if configured in runtime) + /// are relayed. Overrides `only_mandatory_headers`. + #[structopt(long)] + only_free_headers: bool, #[structopt(flatten)] source: SourceConnectionParams, #[structopt(flatten)] @@ -43,11 +48,22 @@ pub struct RelayHeadersParams { prometheus_params: PrometheusParams, } +impl RelayHeadersParams { + fn headers_to_relay(&self) -> HeadersToRelay { + match (self.only_mandatory_headers, self.only_free_headers) { + (_, true) => HeadersToRelay::Free, + (true, false) => HeadersToRelay::Mandatory, + _ => HeadersToRelay::All, + } + } +} + /// Trait used for relaying headers between 2 chains. #[async_trait] pub trait HeadersRelayer: RelayToRelayHeadersCliBridge { /// Relay headers. async fn relay_headers(data: RelayHeadersParams) -> anyhow::Result<()> { + let headers_to_relay = data.headers_to_relay(); let source_client = data.source.into_client::().await?; let target_client = data.target.into_client::().await?; let target_transactions_mortality = data.target_sign.target_transactions_mortality; @@ -67,7 +83,7 @@ pub trait HeadersRelayer: RelayToRelayHeadersCliBridge { crate::finality::run::( source_client, target_client, - data.only_mandatory_headers, + headers_to_relay, target_transactions_params, metrics_params, ) diff --git a/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/mod.rs b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/mod.rs index 27e9f1c21ba0..a796df6721b8 100644 --- a/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/mod.rs +++ b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/mod.rs @@ -40,7 +40,7 @@ use crate::{ cli::{bridge::MessagesCliBridge, HexLaneId, PrometheusParams}, messages_lane::{MessagesRelayLimits, MessagesRelayParams}, on_demand::OnDemandRelay, - TaggedAccount, TransactionParams, + HeadersToRelay, TaggedAccount, TransactionParams, }; use bp_messages::LaneId; use bp_runtime::BalanceOf; @@ -61,11 +61,25 @@ pub struct HeadersAndMessagesSharedParams { /// are relayed. #[structopt(long)] pub only_mandatory_headers: bool, + /// If passed, only free headers (mandatory and every Nth header, if configured in runtime) + /// are relayed. Overrides `only_mandatory_headers`. + #[structopt(long)] + pub only_free_headers: bool, #[structopt(flatten)] /// Prometheus metrics params. pub prometheus_params: PrometheusParams, } +impl HeadersAndMessagesSharedParams { + fn headers_to_relay(&self) -> HeadersToRelay { + match (self.only_mandatory_headers, self.only_free_headers) { + (_, true) => HeadersToRelay::Free, + (true, false) => HeadersToRelay::Mandatory, + _ => HeadersToRelay::All, + } + } +} + /// Bridge parameters, shared by all bridge types. pub struct Full2WayBridgeCommonParams< Left: ChainWithTransactions + ChainWithRuntimeVersion, @@ -418,6 +432,7 @@ mod tests { shared: HeadersAndMessagesSharedParams { lane: vec![HexLaneId([0x00, 0x00, 0x00, 0x00])], only_mandatory_headers: false, + only_free_headers: false, prometheus_params: PrometheusParams { no_prometheus: false, prometheus_host: "0.0.0.0".into(), diff --git a/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/parachain_to_parachain.rs b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/parachain_to_parachain.rs index 76accfa29050..7f6f40777823 100644 --- a/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/parachain_to_parachain.rs +++ b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/parachain_to_parachain.rs @@ -180,7 +180,7 @@ where self.left_relay.clone(), self.common.right.client.clone(), self.common.right.tx_params.clone(), - self.common.shared.only_mandatory_headers, + self.common.shared.headers_to_relay(), Some(self.common.metrics_params.clone()), ); let right_relay_to_left_on_demand_headers = @@ -188,7 +188,7 @@ where self.right_relay.clone(), self.common.left.client.clone(), self.common.left.tx_params.clone(), - self.common.shared.only_mandatory_headers, + self.common.shared.headers_to_relay(), Some(self.common.metrics_params.clone()), ); diff --git a/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/relay_to_parachain.rs b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/relay_to_parachain.rs index b75ac3e60c26..5911fe49df4a 100644 --- a/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/relay_to_parachain.rs +++ b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/relay_to_parachain.rs @@ -171,7 +171,7 @@ where self.common.left.client.clone(), self.common.right.client.clone(), self.common.right.tx_params.clone(), - self.common.shared.only_mandatory_headers, + self.common.shared.headers_to_relay(), None, ); let right_relay_to_left_on_demand_headers = @@ -179,7 +179,7 @@ where self.right_relay.clone(), self.common.left.client.clone(), self.common.left.tx_params.clone(), - self.common.shared.only_mandatory_headers, + self.common.shared.headers_to_relay(), Some(self.common.metrics_params.clone()), ); let right_to_left_on_demand_parachains = OnDemandParachainsRelay::< diff --git a/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/relay_to_relay.rs b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/relay_to_relay.rs index b397ff50a20a..832df4ae4003 100644 --- a/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/relay_to_relay.rs +++ b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/relay_to_relay.rs @@ -152,7 +152,7 @@ where self.common.left.client.clone(), self.common.right.client.clone(), self.common.right.tx_params.clone(), - self.common.shared.only_mandatory_headers, + self.common.shared.headers_to_relay(), None, ); let right_to_left_on_demand_headers = @@ -160,7 +160,7 @@ where self.common.right.client.clone(), self.common.left.client.clone(), self.common.left.tx_params.clone(), - self.common.shared.only_mandatory_headers, + self.common.shared.headers_to_relay(), None, ); diff --git a/bridges/relays/lib-substrate-relay/src/finality/mod.rs b/bridges/relays/lib-substrate-relay/src/finality/mod.rs index 206f628b143b..a94a88db7381 100644 --- a/bridges/relays/lib-substrate-relay/src/finality/mod.rs +++ b/bridges/relays/lib-substrate-relay/src/finality/mod.rs @@ -25,7 +25,7 @@ use crate::{ use async_trait::async_trait; use bp_header_chain::justification::{GrandpaJustification, JustificationVerificationContext}; -use finality_relay::{FinalityPipeline, FinalitySyncPipeline}; +use finality_relay::{FinalityPipeline, FinalitySyncPipeline, HeadersToRelay}; use pallet_bridge_grandpa::{Call as BridgeGrandpaCall, Config as BridgeGrandpaConfig}; use relay_substrate_client::{ transaction_stall_timeout, AccountIdOf, AccountKeyPairOf, BlockNumberOf, CallOf, Chain, @@ -235,15 +235,16 @@ macro_rules! generate_submit_finality_proof_ex_call_builder { pub async fn run( source_client: Client, target_client: Client, - only_mandatory_headers: bool, + headers_to_relay: HeadersToRelay, transaction_params: TransactionParams>, metrics_params: MetricsParams, ) -> anyhow::Result<()> { log::info!( target: "bridge", - "Starting {} -> {} finality proof relay", + "Starting {} -> {} finality proof relay: relaying {:?} headers", P::SourceChain::NAME, P::TargetChain::NAME, + headers_to_relay, ); finality_relay::run( @@ -260,7 +261,7 @@ pub async fn run( P::TargetChain::AVERAGE_BLOCK_INTERVAL, relay_utils::STALL_TIMEOUT, ), - only_mandatory_headers, + headers_to_relay, }, metrics_params, futures::future::pending(), diff --git a/bridges/relays/lib-substrate-relay/src/lib.rs b/bridges/relays/lib-substrate-relay/src/lib.rs index b90453ae0db2..b3e8e7ed9a20 100644 --- a/bridges/relays/lib-substrate-relay/src/lib.rs +++ b/bridges/relays/lib-substrate-relay/src/lib.rs @@ -22,6 +22,9 @@ use relay_substrate_client::{Chain, ChainWithUtilityPallet, UtilityPallet}; use std::marker::PhantomData; +// to avoid `finality_relay` dependency in other crates +pub use finality_relay::HeadersToRelay; + pub mod cli; pub mod equivocation; pub mod error; diff --git a/bridges/relays/lib-substrate-relay/src/on_demand/headers.rs b/bridges/relays/lib-substrate-relay/src/on_demand/headers.rs index e8a2a3c6c58a..d751b79c47e5 100644 --- a/bridges/relays/lib-substrate-relay/src/on_demand/headers.rs +++ b/bridges/relays/lib-substrate-relay/src/on_demand/headers.rs @@ -28,7 +28,7 @@ use futures::{select, FutureExt}; use num_traits::{One, Saturating, Zero}; use sp_runtime::traits::Header; -use finality_relay::{FinalitySyncParams, TargetClient as FinalityTargetClient}; +use finality_relay::{FinalitySyncParams, HeadersToRelay, TargetClient as FinalityTargetClient}; use relay_substrate_client::{ AccountIdOf, AccountKeyPairOf, BlockNumberOf, CallOf, Chain, Client, Error as SubstrateError, HeaderIdOf, @@ -75,7 +75,7 @@ impl OnDemandHeadersRelay

{ source_client: Client, target_client: Client, target_transaction_params: TransactionParams>, - only_mandatory_headers: bool, + headers_to_relay: HeadersToRelay, metrics_params: Option, ) -> Self where @@ -94,7 +94,7 @@ impl OnDemandHeadersRelay

{ source_client, target_client, target_transaction_params, - only_mandatory_headers, + headers_to_relay, required_header_number, metrics_params, ) @@ -204,7 +204,7 @@ async fn background_task( source_client: Client, target_client: Client, target_transaction_params: TransactionParams>, - only_mandatory_headers: bool, + headers_to_relay: HeadersToRelay, required_header_number: RequiredHeaderNumberRef, metrics_params: Option, ) where @@ -346,11 +346,11 @@ async fn background_task( log::info!( target: "bridge", "[{}] Starting on-demand headers relay task\n\t\ - Only mandatory headers: {}\n\t\ + Headers to relay: {:?}\n\t\ Tx mortality: {:?} (~{}m)\n\t\ Stall timeout: {:?}", relay_task_name, - only_mandatory_headers, + headers_to_relay, target_transactions_mortality, stall_timeout.as_secs_f64() / 60.0f64, stall_timeout, @@ -367,7 +367,7 @@ async fn background_task( ), recent_finality_proofs_limit: RECENT_FINALITY_PROOFS_LIMIT, stall_timeout, - only_mandatory_headers, + headers_to_relay, }, metrics_params.clone().unwrap_or_else(MetricsParams::disabled), futures::future::pending(), From f096726040d9bde4e054a3b19e2b201bb0429669 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 15 Mar 2024 15:19:55 +0300 Subject: [PATCH 56/70] actually use HeadersToRelay::Free in finality-relay --- bridges/relays/client-substrate/src/chain.rs | 6 + .../relays/client-substrate/src/test_chain.rs | 2 + bridges/relays/finality/src/finality_loop.rs | 57 +++++++- bridges/relays/finality/src/headers.rs | 127 ++++++++++++++++-- bridges/relays/finality/src/mock.rs | 4 + .../src/finality/target.rs | 15 ++- bridges/relays/utils/src/lib.rs | 2 + 7 files changed, 195 insertions(+), 18 deletions(-) diff --git a/bridges/relays/client-substrate/src/chain.rs b/bridges/relays/client-substrate/src/chain.rs index 2aba5f5674d9..05009c718c5b 100644 --- a/bridges/relays/client-substrate/src/chain.rs +++ b/bridges/relays/client-substrate/src/chain.rs @@ -46,6 +46,12 @@ pub trait Chain: ChainBase + Clone { /// Keep in mind that this method is normally provided by the other chain, which is /// bridged with this chain. const BEST_FINALIZED_HEADER_ID_METHOD: &'static str; + /// Name of the runtime API method that is returning interval between source chain + /// headers that may be submitted for free to the target chain. + /// + /// Keep in mind that this method is normally provided by the other chain, which is + /// bridged with this chain. + const FREE_HEADERS_INTERVAL_METHOD: &'static str; /// Average block interval. /// diff --git a/bridges/relays/client-substrate/src/test_chain.rs b/bridges/relays/client-substrate/src/test_chain.rs index d1203a2c58ea..cfd241c022a2 100644 --- a/bridges/relays/client-substrate/src/test_chain.rs +++ b/bridges/relays/client-substrate/src/test_chain.rs @@ -56,6 +56,7 @@ impl bp_runtime::Chain for TestChain { impl Chain for TestChain { const NAME: &'static str = "Test"; const BEST_FINALIZED_HEADER_ID_METHOD: &'static str = "TestMethod"; + const FREE_HEADERS_INTERVAL_METHOD: &'static str = "TestMethod"; const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_millis(0); type SignedBlock = sp_runtime::generic::SignedBlock< @@ -124,6 +125,7 @@ impl bp_runtime::UnderlyingChainProvider for TestParachain { impl Chain for TestParachain { const NAME: &'static str = "TestParachain"; const BEST_FINALIZED_HEADER_ID_METHOD: &'static str = "TestParachainMethod"; + const FREE_HEADERS_INTERVAL_METHOD: &'static str = "TestParachainMethod"; const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_millis(0); type SignedBlock = sp_runtime::generic::SignedBlock< diff --git a/bridges/relays/finality/src/finality_loop.rs b/bridges/relays/finality/src/finality_loop.rs index 8af6de0db807..ab4b21122e06 100644 --- a/bridges/relays/finality/src/finality_loop.rs +++ b/bridges/relays/finality/src/finality_loop.rs @@ -101,6 +101,10 @@ pub trait TargetClient: RelayClient { &self, ) -> Result, Self::Error>; + /// Get free source headers submission interval, if it is configured in the + /// target runtime. + async fn free_source_headers_interval(&self) -> Result, Self::Error>; + /// Submit header finality proof. async fn submit_finality_proof( &self, @@ -115,10 +119,19 @@ pub fn metrics_prefix() -> String { format!("{}_to_{}_Sync", P::SOURCE_NAME, P::TARGET_NAME) } +/// Finality sync information. pub struct SyncInfo { + /// Best finalized header at the source client. pub best_number_at_source: P::Number, + /// Best source header, known to the target client. pub best_number_at_target: P::Number, + /// Whether the target client follows the same fork as the source client do. pub is_using_same_fork: bool, + /// Free headers interval. We assume that the submission of header `N`, divisible + /// by `free_headers_interval` will be free for submitter. May be `None` if runtime + /// is configured to not allow free headers. If it is `Some(_)`, it is guaranteed + /// not to be zero. + pub free_headers_interval: Option, } impl SyncInfo

{ @@ -157,11 +170,22 @@ impl SyncInfo

{ target_client.best_finalized_source_block_id().await.map_err(Error::Target)?; let best_number_at_target = best_id_at_target.0; + let mut free_headers_interval = + target_client.free_source_headers_interval().await.map_err(Error::Target)?; + if free_headers_interval == Some(0.into()) { + free_headers_interval = None; + } + let is_using_same_fork = Self::is_on_same_fork(source_client, &best_id_at_target) .await .map_err(Error::Source)?; - Ok(Self { best_number_at_source, best_number_at_target, is_using_same_fork }) + Ok(Self { + best_number_at_source, + best_number_at_target, + free_headers_interval, + is_using_same_fork, + }) } fn update_metrics(&self, metrics_sync: &Option) { @@ -313,7 +337,12 @@ impl, TC: TargetClient

> Finality ); // read missing headers - let selector = JustifiedHeaderSelector::new::(&self.source_client, info).await?; + let selector = JustifiedHeaderSelector::new::( + &self.source_client, + info, + self.sync_params.headers_to_relay, + ) + .await?; // if we see that the header schedules GRANDPA change, we need to submit it if self.sync_params.headers_to_relay == HeadersToRelay::Mandatory { return Ok(selector.select_mandatory()) @@ -323,7 +352,8 @@ impl, TC: TargetClient

> Finality // => even if we have already selected some header and its persistent finality proof, // we may try to select better header by reading non-persistent proofs from the stream self.finality_proofs_buf.fill(&mut self.finality_proofs_stream); - let maybe_justified_header = selector.select(&self.finality_proofs_buf); + let maybe_justified_header = + selector.select(info, self.sync_params.headers_to_relay, &self.finality_proofs_buf); // remove obsolete 'recent' finality proofs + keep its size under certain limit let oldest_finality_proof_to_keep = maybe_justified_header @@ -637,6 +667,7 @@ mod tests { let info = SyncInfo { best_number_at_source: 10, best_number_at_target: 5, + free_headers_interval: Some(3), is_using_same_fork: true, }; finality_loop.select_header_to_submit(&info).await.unwrap() @@ -644,9 +675,15 @@ mod tests { } #[test] - fn select_header_to_submit_skips_non_mandatory_headers_when_only_mandatory_headers_are_required( - ) { + fn select_header_to_submit_may_select_non_mandatory_header() { assert_eq!(run_headers_to_relay_mode_test(HeadersToRelay::Mandatory, false), None); + assert_eq!( + run_headers_to_relay_mode_test(HeadersToRelay::Free, false), + Some(JustifiedHeader { + header: TestSourceHeader(false, 9, 9), + proof: TestFinalityProof(9) + }), + ); assert_eq!( run_headers_to_relay_mode_test(HeadersToRelay::All, false), Some(JustifiedHeader { @@ -657,8 +694,7 @@ mod tests { } #[test] - fn select_header_to_submit_selects_mandatory_headers_when_only_mandatory_headers_are_required() - { + fn select_header_to_submit_may_select_mandatory_header() { assert_eq!( run_headers_to_relay_mode_test(HeadersToRelay::Mandatory, true), Some(JustifiedHeader { @@ -666,6 +702,13 @@ mod tests { proof: TestFinalityProof(8) }), ); + assert_eq!( + run_headers_to_relay_mode_test(HeadersToRelay::Free, true), + Some(JustifiedHeader { + header: TestSourceHeader(true, 8, 8), + proof: TestFinalityProof(8) + }), + ); assert_eq!( run_headers_to_relay_mode_test(HeadersToRelay::All, true), Some(JustifiedHeader { diff --git a/bridges/relays/finality/src/headers.rs b/bridges/relays/finality/src/headers.rs index 91f7cd0378ec..119d04be7d6b 100644 --- a/bridges/relays/finality/src/headers.rs +++ b/bridges/relays/finality/src/headers.rs @@ -16,10 +16,11 @@ use crate::{ finality_loop::SyncInfo, finality_proofs::FinalityProofsBuf, Error, FinalitySyncPipeline, - SourceClient, SourceHeader, TargetClient, + HeadersToRelay, SourceClient, SourceHeader, TargetClient, }; use bp_header_chain::FinalityProof; +use num_traits::Zero; use std::cmp::Ordering; /// Unjustified headers container. Ordered by header number. @@ -50,9 +51,12 @@ pub enum JustifiedHeaderSelector { } impl JustifiedHeaderSelector

{ + /// Selects last header with persitent justification, missing from the target and matching + /// the `headers_to_relay` criteria. pub(crate) async fn new, TC: TargetClient

>( source_client: &SC, info: &SyncInfo

, + headers_to_relay: HeadersToRelay, ) -> Result> { let mut unjustified_headers = Vec::new(); let mut maybe_justified_header = None; @@ -70,12 +74,12 @@ impl JustifiedHeaderSelector

{ return Ok(Self::Mandatory(JustifiedHeader { header, proof })) }, (true, None) => return Err(Error::MissingMandatoryFinalityProof(header.number())), - (false, Some(proof)) => { + (false, Some(proof)) if need_to_relay(info, headers_to_relay, &header) => { log::trace!(target: "bridge", "Header {:?} has persistent finality proof", header_number); unjustified_headers.clear(); maybe_justified_header = Some(JustifiedHeader { header, proof }); }, - (false, None) => { + _ => { unjustified_headers.push(header); }, } @@ -97,6 +101,7 @@ impl JustifiedHeaderSelector

{ }) } + /// Returns selected mandatory header if we have seen one. Otherwise returns `None`. pub fn select_mandatory(self) -> Option> { match self { JustifiedHeaderSelector::Mandatory(header) => Some(header), @@ -104,7 +109,14 @@ impl JustifiedHeaderSelector

{ } } - pub fn select(self, buf: &FinalityProofsBuf

) -> Option> { + /// Tries to improve previously selected header using ephemeral + /// justifications stream. + pub fn select( + self, + info: &SyncInfo

, + headers_to_relay: HeadersToRelay, + buf: &FinalityProofsBuf

, + ) -> Option> { let (unjustified_headers, maybe_justified_header) = match self { JustifiedHeaderSelector::Mandatory(justified_header) => return Some(justified_header), JustifiedHeaderSelector::Regular(unjustified_headers, justified_header) => @@ -122,7 +134,7 @@ impl JustifiedHeaderSelector

{ (maybe_finality_proof, maybe_unjustified_header) { match finality_proof.target_header_number().cmp(&unjustified_header.number()) { - Ordering::Equal => { + Ordering::Equal if need_to_relay(info, headers_to_relay, &unjustified_header) => { log::trace!( target: "bridge", "Managed to improve selected {} finality proof {:?} to {:?}.", @@ -135,6 +147,10 @@ impl JustifiedHeaderSelector

{ proof: finality_proof.clone(), }) }, + Ordering::Equal => { + maybe_finality_proof = finality_proofs_iter.next(); + maybe_unjustified_header = unjustified_headers_iter.next(); + }, Ordering::Less => maybe_unjustified_header = unjustified_headers_iter.next(), Ordering::Greater => { maybe_finality_proof = finality_proofs_iter.next(); @@ -152,6 +168,26 @@ impl JustifiedHeaderSelector

{ } } +/// Returns true if we want to relay header `header_number`. +fn need_to_relay( + info: &SyncInfo

, + headers_to_relay: HeadersToRelay, + header: &P::Header, +) -> bool { + match headers_to_relay { + HeadersToRelay::All => true, + HeadersToRelay::Mandatory => header.is_mandatory(), + HeadersToRelay::Free => + header.is_mandatory() || + info.free_headers_interval + .map(|free_headers_interval| { + !header.number().is_zero() && + (header.number() % free_headers_interval).is_zero() + }) + .unwrap_or(false), + } +} + #[cfg(test)] mod tests { use super::*; @@ -159,13 +195,23 @@ mod tests { #[test] fn select_better_recent_finality_proof_works() { + let mut info = SyncInfo { + best_number_at_source: 10, + best_number_at_target: 5, + free_headers_interval: None, + is_using_same_fork: true, + }; + // if there are no unjustified headers, nothing is changed let finality_proofs_buf = FinalityProofsBuf::::new(vec![TestFinalityProof(5)]); let justified_header = JustifiedHeader { header: TestSourceHeader(false, 2, 2), proof: TestFinalityProof(2) }; let selector = JustifiedHeaderSelector::Regular(vec![], justified_header.clone()); - assert_eq!(selector.select(&finality_proofs_buf), Some(justified_header)); + assert_eq!( + selector.select(&info, HeadersToRelay::All, &finality_proofs_buf), + Some(justified_header) + ); // if there are no buffered finality proofs, nothing is changed let finality_proofs_buf = FinalityProofsBuf::::new(vec![]); @@ -175,7 +221,10 @@ mod tests { vec![TestSourceHeader(false, 5, 5)], justified_header.clone(), ); - assert_eq!(selector.select(&finality_proofs_buf), Some(justified_header)); + assert_eq!( + selector.select(&info, HeadersToRelay::All, &finality_proofs_buf), + Some(justified_header) + ); // if there's no intersection between recent finality proofs and unjustified headers, // nothing is changed @@ -189,7 +238,10 @@ mod tests { vec![TestSourceHeader(false, 9, 9), TestSourceHeader(false, 10, 10)], justified_header.clone(), ); - assert_eq!(selector.select(&finality_proofs_buf), Some(justified_header)); + assert_eq!( + selector.select(&info, HeadersToRelay::All, &finality_proofs_buf), + Some(justified_header) + ); // if there's intersection between recent finality proofs and unjustified headers, but there // are no proofs in this intersection, nothing is changed @@ -207,7 +259,10 @@ mod tests { ], justified_header.clone(), ); - assert_eq!(selector.select(&finality_proofs_buf), Some(justified_header)); + assert_eq!( + selector.select(&info, HeadersToRelay::All, &finality_proofs_buf), + Some(justified_header) + ); // if there's intersection between recent finality proofs and unjustified headers and // there's a proof in this intersection: @@ -228,11 +283,63 @@ mod tests { justified_header, ); assert_eq!( - selector.select(&finality_proofs_buf), + selector.select(&info, HeadersToRelay::All, &finality_proofs_buf), Some(JustifiedHeader { header: TestSourceHeader(false, 9, 9), proof: TestFinalityProof(9) }) ); + + // when only free headers needs to be relayed and there are no free headers + info.free_headers_interval = Some(7); + let finality_proofs_buf = FinalityProofsBuf::::new(vec![ + TestFinalityProof(7), + TestFinalityProof(9), + ]); + let selector = JustifiedHeaderSelector::None(vec![ + TestSourceHeader(false, 8, 8), + TestSourceHeader(false, 9, 9), + TestSourceHeader(false, 10, 10), + ]); + assert_eq!(selector.select(&info, HeadersToRelay::Free, &finality_proofs_buf), None,); + + // when only free headers needs to be relayed, mandatory header may be selected + info.free_headers_interval = Some(7); + let finality_proofs_buf = FinalityProofsBuf::::new(vec![ + TestFinalityProof(6), + TestFinalityProof(9), + ]); + let selector = JustifiedHeaderSelector::None(vec![ + TestSourceHeader(false, 8, 8), + TestSourceHeader(true, 9, 9), + TestSourceHeader(false, 10, 10), + ]); + assert_eq!( + selector.select(&info, HeadersToRelay::Free, &finality_proofs_buf), + Some(JustifiedHeader { + header: TestSourceHeader(true, 9, 9), + proof: TestFinalityProof(9) + }) + ); + + // when only free headers needs to be relayed and there is free header + info.free_headers_interval = Some(7); + let finality_proofs_buf = FinalityProofsBuf::::new(vec![ + TestFinalityProof(7), + TestFinalityProof(9), + TestFinalityProof(14), + ]); + let selector = JustifiedHeaderSelector::None(vec![ + TestSourceHeader(false, 7, 7), + TestSourceHeader(false, 10, 10), + TestSourceHeader(false, 14, 14), + ]); + assert_eq!( + selector.select(&info, HeadersToRelay::Free, &finality_proofs_buf), + Some(JustifiedHeader { + header: TestSourceHeader(false, 14, 14), + proof: TestFinalityProof(14) + }) + ); } } diff --git a/bridges/relays/finality/src/mock.rs b/bridges/relays/finality/src/mock.rs index e3ec4e4d0d47..726b31ef9b41 100644 --- a/bridges/relays/finality/src/mock.rs +++ b/bridges/relays/finality/src/mock.rs @@ -198,6 +198,10 @@ impl TargetClient for TestTargetClient { Ok(data.target_best_block_id) } + async fn free_source_headers_interval(&self) -> Result, TestError> { + Ok(Some(3)) + } + async fn submit_finality_proof( &self, header: TestSourceHeader, diff --git a/bridges/relays/lib-substrate-relay/src/finality/target.rs b/bridges/relays/lib-substrate-relay/src/finality/target.rs index 18464d523f4f..293f1f63167b 100644 --- a/bridges/relays/lib-substrate-relay/src/finality/target.rs +++ b/bridges/relays/lib-substrate-relay/src/finality/target.rs @@ -25,9 +25,10 @@ use crate::{ }; use async_trait::async_trait; +use bp_runtime::BlockNumberOf; use finality_relay::TargetClient; use relay_substrate_client::{ - AccountKeyPairOf, Client, Error, HeaderIdOf, HeaderOf, SyncHeader, TransactionEra, + AccountKeyPairOf, Chain, Client, Error, HeaderIdOf, HeaderOf, SyncHeader, TransactionEra, TransactionTracker, UnsignedTransaction, }; use relay_utils::relay_loop::Client as RelayClient; @@ -103,6 +104,18 @@ impl TargetClient Result>, Self::Error> { + self.client + .typed_state_call( + P::SourceChain::FREE_HEADERS_INTERVAL_METHOD.into(), + (), + Some(self.client.best_header().await?.hash()), + ) + .await + } + async fn submit_finality_proof( &self, header: SyncHeader>, diff --git a/bridges/relays/utils/src/lib.rs b/bridges/relays/utils/src/lib.rs index 2776620be359..1df6e9718f15 100644 --- a/bridges/relays/utils/src/lib.rs +++ b/bridges/relays/utils/src/lib.rs @@ -64,6 +64,7 @@ pub trait BlockNumberBase: + std::fmt::Display + std::hash::Hash + std::ops::Add + + std::ops::Rem + std::ops::Sub + num_traits::CheckedSub + num_traits::Saturating @@ -86,6 +87,7 @@ impl BlockNumberBase for T where + std::fmt::Display + std::hash::Hash + std::ops::Add + + std::ops::Rem + std::ops::Sub + num_traits::CheckedSub + num_traits::Saturating From 6375100ad518c90281eddf19078a0c7b75218932 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 18 Mar 2024 11:56:52 +0300 Subject: [PATCH 57/70] actually support only-free-headers mode in parachains relay --- bridges/chains/chain-kusama/src/lib.rs | 2 + bridges/chains/chain-polkadot/src/lib.rs | 2 + bridges/chains/chain-rococo/src/lib.rs | 2 + bridges/chains/chain-westend/src/lib.rs | 2 + bridges/relays/client-substrate/src/chain.rs | 3 + .../src/cli/relay_parachains.rs | 10 +- .../src/on_demand/parachains.rs | 12 +- .../src/parachains/source.rs | 9 +- .../src/parachains/target.rs | 109 ++++++-- .../relays/parachains/src/parachains_loop.rs | 260 +++++++++++++++--- 10 files changed, 339 insertions(+), 72 deletions(-) diff --git a/bridges/chains/chain-kusama/src/lib.rs b/bridges/chains/chain-kusama/src/lib.rs index a81004afe812..fd7172c5869d 100644 --- a/bridges/chains/chain-kusama/src/lib.rs +++ b/bridges/chains/chain-kusama/src/lib.rs @@ -67,6 +67,8 @@ pub const PARAS_PALLET_NAME: &str = "Paras"; /// Name of the With-Kusama GRANDPA pallet instance that is deployed at bridged chains. pub const WITH_KUSAMA_GRANDPA_PALLET_NAME: &str = "BridgeKusamaGrandpa"; +/// Name of the With-Kusama parachains pallet instance that is deployed at bridged chains. +pub const WITH_KUSAMA_BRIDGE_PARACHAINS_PALLET_NAME: &str = "BridgeKusamaParachains"; /// Maximal size of encoded `bp_parachains::ParaStoredHeaderData` structure among all Polkadot /// parachains. diff --git a/bridges/chains/chain-polkadot/src/lib.rs b/bridges/chains/chain-polkadot/src/lib.rs index 00d35783a9b6..a8cac0467d57 100644 --- a/bridges/chains/chain-polkadot/src/lib.rs +++ b/bridges/chains/chain-polkadot/src/lib.rs @@ -69,6 +69,8 @@ pub const PARAS_PALLET_NAME: &str = "Paras"; /// Name of the With-Polkadot GRANDPA pallet instance that is deployed at bridged chains. pub const WITH_POLKADOT_GRANDPA_PALLET_NAME: &str = "BridgePolkadotGrandpa"; +/// Name of the With-Polkadot parachains pallet instance that is deployed at bridged chains. +pub const WITH_POLKADOT_BRIDGE_PARACHAINS_PALLET_NAME: &str = "BridgePolkadotParachains"; /// Maximal size of encoded `bp_parachains::ParaStoredHeaderData` structure among all Polkadot /// parachains. diff --git a/bridges/chains/chain-rococo/src/lib.rs b/bridges/chains/chain-rococo/src/lib.rs index 2385dd2cbb25..b290fe71c829 100644 --- a/bridges/chains/chain-rococo/src/lib.rs +++ b/bridges/chains/chain-rococo/src/lib.rs @@ -67,6 +67,8 @@ pub const PARAS_PALLET_NAME: &str = "Paras"; /// Name of the With-Rococo GRANDPA pallet instance that is deployed at bridged chains. pub const WITH_ROCOCO_GRANDPA_PALLET_NAME: &str = "BridgeRococoGrandpa"; +/// Name of the With-Rococo parachains pallet instance that is deployed at bridged chains. +pub const WITH_ROCOCO_BRIDGE_PARACHAINS_PALLET_NAME: &str = "BridgeRococoParachains"; /// Maximal size of encoded `bp_parachains::ParaStoredHeaderData` structure among all Rococo /// parachains. diff --git a/bridges/chains/chain-westend/src/lib.rs b/bridges/chains/chain-westend/src/lib.rs index b344b7f4bf93..ef451f7de0a9 100644 --- a/bridges/chains/chain-westend/src/lib.rs +++ b/bridges/chains/chain-westend/src/lib.rs @@ -67,6 +67,8 @@ pub const PARAS_PALLET_NAME: &str = "Paras"; /// Name of the With-Westend GRANDPA pallet instance that is deployed at bridged chains. pub const WITH_WESTEND_GRANDPA_PALLET_NAME: &str = "BridgeWestendGrandpa"; +/// Name of the With-Westend parachains pallet instance that is deployed at bridged chains. +pub const WITH_WESTEND_BRIDGE_PARACHAINS_PALLET_NAME: &str = "BridgeWestendParachains"; /// Maximal size of encoded `bp_parachains::ParaStoredHeaderData` structure among all Westend /// parachains. diff --git a/bridges/relays/client-substrate/src/chain.rs b/bridges/relays/client-substrate/src/chain.rs index 05009c718c5b..40269fe64c87 100644 --- a/bridges/relays/client-substrate/src/chain.rs +++ b/bridges/relays/client-substrate/src/chain.rs @@ -81,6 +81,9 @@ pub trait ChainWithRuntimeVersion: Chain { pub trait RelayChain: Chain { /// Name of the `runtime_parachains::paras` pallet in the runtime of this chain. const PARAS_PALLET_NAME: &'static str; + /// Name of the `pallet-bridge-parachains`, deployed at the **bridged** chain to sync + /// parachains of **this** chain. + const WITH_CHAIN_BRIDGE_PARACHAINS_PALLET_NAME: &'static str; } /// Substrate-based chain that is using direct GRANDPA finality from minimal relay-client point of diff --git a/bridges/relays/lib-substrate-relay/src/cli/relay_parachains.rs b/bridges/relays/lib-substrate-relay/src/cli/relay_parachains.rs index e5a52349469b..511e9d2b2eb6 100644 --- a/bridges/relays/lib-substrate-relay/src/cli/relay_parachains.rs +++ b/bridges/relays/lib-substrate-relay/src/cli/relay_parachains.rs @@ -59,9 +59,9 @@ where { /// Start relaying parachains finality. async fn relay_parachains(data: RelayParachainsParams) -> anyhow::Result<()> { - let source_client = data.source.into_client::().await?; + let source_chain_client = data.source.into_client::().await?; let source_client = ParachainsSource::::new( - source_client, + source_chain_client.clone(), Arc::new(Mutex::new(AvailableHeader::Missing)), ); @@ -69,9 +69,10 @@ where signer: data.target_sign.to_keypair::()?, mortality: data.target_sign.target_transactions_mortality, }; - let target_client = data.target.into_client::().await?; + let target_chain_client = data.target.into_client::().await?; let target_client = ParachainsTarget::::new( - target_client.clone(), + source_chain_client, + target_chain_client, target_transaction_params, ); @@ -83,6 +84,7 @@ where source_client, target_client, metrics_params, + false, // TODO futures::future::pending(), ) .await diff --git a/bridges/relays/lib-substrate-relay/src/on_demand/parachains.rs b/bridges/relays/lib-substrate-relay/src/on_demand/parachains.rs index f67c002bba7f..40852d396b3c 100644 --- a/bridges/relays/lib-substrate-relay/src/on_demand/parachains.rs +++ b/bridges/relays/lib-substrate-relay/src/on_demand/parachains.rs @@ -256,8 +256,11 @@ async fn background_task( let mut parachains_source = ParachainsSource::

::new(source_relay_client.clone(), required_para_header_ref.clone()); - let mut parachains_target = - ParachainsTarget::

::new(target_client.clone(), target_transaction_params.clone()); + let mut parachains_target = ParachainsTarget::

::new( + source_relay_client.clone(), + target_client.clone(), + target_transaction_params.clone(), + ); loop { select! { @@ -392,6 +395,7 @@ async fn background_task( parachains_source.clone(), parachains_target.clone(), MetricsParams::disabled(), + false, // TODO futures::future::pending(), ) .fuse(), @@ -481,7 +485,7 @@ where let para_header_at_target = best_finalized_peer_header_at_self::< P::TargetChain, P::SourceParachain, - >(target.client(), best_target_block_hash) + >(target.target_client(), best_target_block_hash) .await; // if there are no parachain heads at the target (`NoParachainHeadAtTarget`), we'll need to // submit at least one. Otherwise the pallet will be treated as uninitialized and messages @@ -504,7 +508,7 @@ where let relay_header_at_target = best_finalized_peer_header_at_self::< P::TargetChain, P::SourceRelayChain, - >(target.client(), best_target_block_hash) + >(target.target_client(), best_target_block_hash) .await .map_err(map_target_err)?; diff --git a/bridges/relays/lib-substrate-relay/src/parachains/source.rs b/bridges/relays/lib-substrate-relay/src/parachains/source.rs index 4cc512b9d9b4..3d28769df075 100644 --- a/bridges/relays/lib-substrate-relay/src/parachains/source.rs +++ b/bridges/relays/lib-substrate-relay/src/parachains/source.rs @@ -22,7 +22,7 @@ use async_std::sync::{Arc, Mutex}; use async_trait::async_trait; use bp_parachains::parachain_head_storage_key_at_source; use bp_polkadot_core::parachains::{ParaHash, ParaHead, ParaHeadsProof, ParaId}; -use bp_runtime::HeaderIdProvider; +use bp_runtime::{BlockNumberOf, HeaderIdProvider}; use codec::Decode; use parachains_relay::parachains_loop::{AvailableHeader, SourceClient}; use relay_substrate_client::{ @@ -98,6 +98,13 @@ where } } + async fn relay_header_id( + &self, + number: BlockNumberOf, + ) -> Result, Self::Error> { + self.client.header_by_number(number).await.map(|h| h.id()) + } + async fn parachain_head( &self, at_block: HeaderIdOf, diff --git a/bridges/relays/lib-substrate-relay/src/parachains/target.rs b/bridges/relays/lib-substrate-relay/src/parachains/target.rs index 6df7bc0a742a..e54d360532fd 100644 --- a/bridges/relays/lib-substrate-relay/src/parachains/target.rs +++ b/bridges/relays/lib-substrate-relay/src/parachains/target.rs @@ -24,42 +24,53 @@ use crate::{ }; use async_trait::async_trait; -use bp_polkadot_core::parachains::{ParaHash, ParaHeadsProof, ParaId}; -use bp_runtime::HeaderIdProvider; -use codec::Decode; +use bp_parachains::{ + ImportedParaHeadsKeyProvider, ParaInfo, ParaStoredHeaderData, ParasInfoKeyProvider, +}; +use bp_polkadot_core::{ + parachains::{ParaHash, ParaHeadsProof, ParaId}, + BlockNumber as RelayBlockNumber, +}; +use bp_runtime::{ + Chain as ChainBase, HeaderId, HeaderIdProvider, StorageDoubleMapKeyProvider, + StorageMapKeyProvider, +}; use parachains_relay::parachains_loop::TargetClient; use relay_substrate_client::{ - AccountIdOf, AccountKeyPairOf, Chain, Client, Error as SubstrateError, HeaderIdOf, - ParachainBase, TransactionEra, TransactionTracker, UnsignedTransaction, + AccountIdOf, AccountKeyPairOf, BlockNumberOf, Chain, Client, Error as SubstrateError, + HeaderIdOf, ParachainBase, RelayChain, TransactionEra, TransactionTracker, UnsignedTransaction, }; use relay_utils::relay_loop::Client as RelayClient; -use sp_core::{Bytes, Pair}; +use sp_core::Pair; /// Substrate client as parachain heads source. pub struct ParachainsTarget { - client: Client, + source_client: Client, + target_client: Client, transaction_params: TransactionParams>, } impl ParachainsTarget

{ /// Creates new parachains target client. pub fn new( - client: Client, + source_client: Client, + target_client: Client, transaction_params: TransactionParams>, ) -> Self { - ParachainsTarget { client, transaction_params } + ParachainsTarget { source_client, target_client, transaction_params } } /// Returns reference to the underlying RPC client. - pub fn client(&self) -> &Client { - &self.client + pub fn target_client(&self) -> &Client { + &self.target_client } } impl Clone for ParachainsTarget

{ fn clone(&self) -> Self { ParachainsTarget { - client: self.client.clone(), + source_client: self.source_client.clone(), + target_client: self.target_client.clone(), transaction_params: self.transaction_params.clone(), } } @@ -70,7 +81,9 @@ impl RelayClient for ParachainsTarget

{ type Error = SubstrateError; async fn reconnect(&mut self) -> Result<(), SubstrateError> { - self.client.reconnect().await + self.target_client.reconnect().await?; + self.source_client.reconnect().await?; + Ok(()) } } @@ -79,11 +92,13 @@ impl

TargetClient> for ParachainsTarget

where P: SubstrateParachainsPipeline, AccountIdOf: From< as Pair>::Public>, + P::SourceParachain: ChainBase, + P::SourceRelayChain: ChainBase, { type TransactionTracker = TransactionTracker>; async fn best_block(&self) -> Result, Self::Error> { - let best_header = self.client.best_header().await?; + let best_header = self.target_client.best_header().await?; let best_id = best_header.id(); Ok(best_id) @@ -93,7 +108,7 @@ where &self, at_block: &HeaderIdOf, ) -> Result, Self::Error> { - self.client + self.target_client .typed_state_call::<_, Option>>( P::SourceRelayChain::BEST_FINALIZED_HEADER_ID_METHOD.into(), (), @@ -104,23 +119,59 @@ where .unwrap_or(Err(SubstrateError::BridgePalletIsNotInitialized)) } + async fn free_source_relay_headers_interval( + &self, + ) -> Result>, Self::Error> { + // may be cached, because we assume that on upgrades (when this may change), relayer + // is also upgraded and restarted + self.target_client + .typed_state_call(P::SourceRelayChain::FREE_HEADERS_INTERVAL_METHOD.into(), (), None) + .await + } + async fn parachain_head( &self, at_block: HeaderIdOf, - ) -> Result>, Self::Error> { - let encoded_best_finalized_source_para_block = self - .client - .state_call( - P::SourceParachain::BEST_FINALIZED_HEADER_ID_METHOD.into(), - Bytes(Vec::new()), - Some(at_block.1), - ) - .await?; + ) -> Result< + Option<(HeaderIdOf, HeaderIdOf)>, + Self::Error, + > { + // read best parachain head from the target bridge-parachains pallet + let storage_key = ParasInfoKeyProvider::final_key( + P::SourceRelayChain::WITH_CHAIN_BRIDGE_PARACHAINS_PALLET_NAME, + &P::SourceParachain::PARACHAIN_ID.into(), + ); + let storage_value: Option = + self.target_client.storage_value(storage_key, Some(at_block.hash())).await?; + let para_info = match storage_value { + Some(para_info) => para_info, + None => return Ok(None), + }; + + // now we need to get full header ids. For source relay chain it is simple, because we + // are connected + let relay_header_id = self + .source_client + .header_by_number(para_info.best_head_hash.at_relay_block_number) + .await? + .id(); - Ok(Option::>::decode( - &mut &encoded_best_finalized_source_para_block.0[..], - ) - .map_err(SubstrateError::ResponseParseFailed)?) + // for parachain, we need to read from the target chain runtime storage + let storage_key = ImportedParaHeadsKeyProvider::final_key( + P::SourceRelayChain::WITH_CHAIN_BRIDGE_PARACHAINS_PALLET_NAME, + &P::SourceParachain::PARACHAIN_ID.into(), + ¶_info.best_head_hash.head_hash, + ); + let storage_value: Option = + self.target_client.storage_value(storage_key, Some(at_block.hash())).await?; + let para_head_number = match storage_value { + Some(para_head_data) => + para_head_data.decode_parachain_head_data::()?.number, + None => return Ok(None), + }; + + let para_head_id = HeaderId(para_head_number, para_info.best_head_hash.head_hash); + Ok(Some((relay_header_id, para_head_id))) } async fn submit_parachain_head_proof( @@ -135,7 +186,7 @@ where vec![(ParaId(P::SourceParachain::PARACHAIN_ID), updated_head_hash)], proof, ); - self.client + self.target_client .submit_and_watch_signed_extrinsic( &transaction_params.signer, move |best_block_id, transaction_nonce| { diff --git a/bridges/relays/parachains/src/parachains_loop.rs b/bridges/relays/parachains/src/parachains_loop.rs index 41ebbf5aaded..a20f7c3ecd72 100644 --- a/bridges/relays/parachains/src/parachains_loop.rs +++ b/bridges/relays/parachains/src/parachains_loop.rs @@ -25,7 +25,7 @@ use futures::{ future::{FutureExt, Shared}, poll, select_biased, }; -use relay_substrate_client::{Chain, HeaderIdOf, ParachainBase}; +use relay_substrate_client::{BlockNumberOf, Chain, HeaderIdOf, ParachainBase}; use relay_utils::{ metrics::MetricsParams, relay_loop::Client as RelayClient, FailedClient, TrackedTransactionStatus, TransactionTracker, @@ -74,6 +74,12 @@ pub trait SourceClient: RelayClient { /// Returns `Ok(true)` if client is in synced state. async fn ensure_synced(&self) -> Result; + /// Get finalized relay chain header id by its number. + async fn relay_header_id( + &self, + number: BlockNumberOf, + ) -> Result, Self::Error>; + /// Get parachain head id at given block. async fn parachain_head( &self, @@ -96,17 +102,27 @@ pub trait TargetClient: RelayClient { /// Get best block id. async fn best_block(&self) -> Result, Self::Error>; - /// Get best finalized source relay chain block id. + /// Get best finalized source relay chain block id. If `free_source_relay_headers_interval` + /// is `Some(_)`, the returned async fn best_finalized_source_relay_chain_block( &self, at_block: &HeaderIdOf, ) -> Result, Self::Error>; + /// Get free source **relay** headers submission interval, if it is configured in the + /// target runtime. We assume that the target chain will accept parachain header, proved + /// at such relay header for free. + async fn free_source_relay_headers_interval( + &self, + ) -> Result>, Self::Error>; /// Get parachain head id at given block. async fn parachain_head( &self, at_block: HeaderIdOf, - ) -> Result>, Self::Error>; + ) -> Result< + Option<(HeaderIdOf, HeaderIdOf)>, + Self::Error, + >; /// Submit parachain heads proof. async fn submit_parachain_head_proof( @@ -133,6 +149,7 @@ pub async fn run( source_client: impl SourceClient

, target_client: impl TargetClient

, metrics_params: MetricsParams, + only_free_headers: bool, exit_signal: impl Future + 'static + Send, ) -> Result<(), relay_utils::Error> where @@ -145,7 +162,13 @@ where .expose() .await? .run(metrics_prefix::

(), move |source_client, target_client, metrics| { - run_until_connection_lost(source_client, target_client, metrics, exit_signal.clone()) + run_until_connection_lost( + source_client, + target_client, + metrics, + only_free_headers, + exit_signal.clone(), + ) }) .await } @@ -155,6 +178,7 @@ async fn run_until_connection_lost( source_client: impl SourceClient

, target_client: impl TargetClient

, metrics: Option, + only_free_headers: bool, exit_signal: impl Future + Send, ) -> Result<(), FailedClient> where @@ -166,6 +190,29 @@ where P::TargetChain::AVERAGE_BLOCK_INTERVAL, ); + // free parachain header = header, available (proved) at free relay chain block. Let's + // read interval of free source relay chain blocks from target client + let free_source_relay_headers_interval = if only_free_headers { + let free_source_relay_headers_interval = target_client + .free_source_relay_headers_interval() + .await + .map_err(|e| { + log::warn!(target: "bridge", "Failed to read free {} headers interval at {}: {:?}", P::SourceRelayChain::NAME, P::TargetChain::NAME, e); + FailedClient::Target + })?; + match free_source_relay_headers_interval { + Some(free_source_relay_headers_interval) if free_source_relay_headers_interval != 0 => + free_source_relay_headers_interval, + _ => { + log::warn!(target: "bridge", "Invalid free {} headers interval at {}: {:?}", P::SourceRelayChain::NAME, P::TargetChain::NAME, free_source_relay_headers_interval); + return Err(FailedClient::Target) + }, + } + } else { + // ignore - we don't need it + 0 + }; + let mut submitted_heads_tracker: Option> = None; futures::pin_mut!(exit_signal); @@ -211,7 +258,7 @@ where log::warn!(target: "bridge", "Failed to read best {} block: {:?}", P::SourceRelayChain::NAME, e); FailedClient::Target })?; - let head_at_target = + let (relay_of_head_at_target, head_at_target) = read_head_at_target(&target_client, metrics.as_ref(), &best_target_block).await?; // check if our transaction has been mined @@ -238,9 +285,9 @@ where } } - // we have no active transaction and may need to update heads, but do we have something for - // update? - let best_finalized_relay_block = target_client + // in all-headers strategy we'll be submitting para head, available at + // `best_finalized_relay_block_at_target` + let best_finalized_relay_block_at_target = target_client .best_finalized_source_relay_chain_block(&best_target_block) .await .map_err(|e| { @@ -253,21 +300,61 @@ where ); FailedClient::Target })?; + + // ..but if we only need to submit free headers, we need to submit para + // head, available at best free source relay chain header, known to the + // target chain + let prove_at_relay_block = if only_free_headers { + let relay_of_head_at_target = match relay_of_head_at_target { + Some(relay_of_head_at_target) => relay_of_head_at_target, + None => { + // no relay headers available at target => wait + continue + }, + }; + + // find last free relay chain header in the range that we are interested in + let scan_range_begin = relay_of_head_at_target.number() + 1; + let scan_range_end = best_finalized_relay_block_at_target.number(); + let last_free_source_relay_header_number = (scan_range_end / + free_source_relay_headers_interval) * + free_source_relay_headers_interval; + if last_free_source_relay_header_number < scan_range_begin { + // there are no new **free** relay chain headers in the range + continue; + } + + // ok - we know the relay chain header number, now let's get its full id + source_client + .relay_header_id(last_free_source_relay_header_number) + .await + .map_err(|e| { + log::warn!( + target: "bridge", + "Failed to get full header id of {} block #{:?}: {:?}", + P::SourceRelayChain::NAME, + last_free_source_relay_header_number, + e, + ); + FailedClient::Source + })? + } else { + best_finalized_relay_block_at_target + }; + + // now let's check if we need to update parachain head at all let head_at_source = - read_head_at_source(&source_client, metrics.as_ref(), &best_finalized_relay_block) - .await?; + read_head_at_source(&source_client, metrics.as_ref(), &prove_at_relay_block).await?; let is_update_required = is_update_required::

( head_at_source, head_at_target, - best_finalized_relay_block, + prove_at_relay_block, best_target_block, ); if is_update_required { - let (head_proof, head_hash) = source_client - .prove_parachain_head(best_finalized_relay_block) - .await - .map_err(|e| { + let (head_proof, head_hash) = + source_client.prove_parachain_head(prove_at_relay_block).await.map_err(|e| { log::warn!( target: "bridge", "Failed to prove {} parachain ParaId({}) heads: {:?}", @@ -283,12 +370,12 @@ where P::SourceRelayChain::NAME, P::SourceParachain::PARACHAIN_ID, P::TargetChain::NAME, - best_finalized_relay_block, + prove_at_relay_block, head_hash, ); let transaction_tracker = target_client - .submit_parachain_head_proof(best_finalized_relay_block, head_hash, head_proof) + .submit_parachain_head_proof(prove_at_relay_block, head_hash, head_proof) .await .map_err(|e| { log::warn!( @@ -311,7 +398,7 @@ where fn is_update_required( head_at_source: AvailableHeader>, head_at_target: Option>, - best_finalized_relay_block_at_source: HeaderIdOf, + prove_at_relay_block: HeaderIdOf, best_target_block: HeaderIdOf, ) -> bool where @@ -326,7 +413,7 @@ where P::SourceParachain::PARACHAIN_ID, P::TargetChain::NAME, P::SourceRelayChain::NAME, - best_finalized_relay_block_at_source, + prove_at_relay_block, head_at_source, P::TargetChain::NAME, best_target_block, @@ -413,24 +500,28 @@ async fn read_head_at_source( } } -/// Reads parachain head from the target client. +/// Reads parachain head from the target client. Also returns source relay chain header +/// that has been used to prove that head. async fn read_head_at_target( target_client: &impl TargetClient

, metrics: Option<&ParachainsLoopMetrics>, at_block: &HeaderIdOf, -) -> Result>, FailedClient> { +) -> Result< + (Option>, Option>), + FailedClient, +> { let para_head_id = target_client.parachain_head(*at_block).await; match para_head_id { - Ok(Some(para_head_id)) => { + Ok(Some((relay_header_id, para_head_id))) => { if let Some(metrics) = metrics { metrics.update_best_parachain_block_at_target( ParaId(P::SourceParachain::PARACHAIN_ID), para_head_id.number(), ); } - Ok(Some(para_head_id)) + Ok((Some(relay_header_id), Some(para_head_id))) }, - Ok(None) => Ok(None), + Ok(None) => Ok((None, None)), Err(e) => { log::warn!( target: "bridge", @@ -543,6 +634,7 @@ mod tests { use relay_substrate_client::test_chain::{TestChain, TestParachain}; use relay_utils::{HeaderId, MaybeConnectionError}; use sp_core::H256; + use std::collections::HashMap; const PARA_10_HASH: ParaHash = H256([10u8; 32]); const PARA_20_HASH: ParaHash = H256([20u8; 32]); @@ -590,14 +682,20 @@ mod tests { #[derive(Clone, Debug)] struct TestClientData { source_sync_status: Result, - source_head: Result>, TestError>, + source_head: HashMap< + BlockNumberOf, + Result>, TestError>, + >, source_proof: Result<(), TestError>, + target_free_source_relay_headers_interval: + Result>, TestError>, target_best_block: Result, TestError>, target_best_finalized_source_block: Result, TestError>, - target_head: Result>, TestError>, + target_head: Result, HeaderIdOf)>, TestError>, target_submit_result: Result<(), TestError>, + submitted_proof_at_source_relay_block: Option>, exit_signal_sender: Option>>, } @@ -605,14 +703,18 @@ mod tests { pub fn minimal() -> Self { TestClientData { source_sync_status: Ok(true), - source_head: Ok(AvailableHeader::Available(HeaderId(0, PARA_20_HASH))), + source_head: vec![(0, Ok(AvailableHeader::Available(HeaderId(0, PARA_20_HASH))))] + .into_iter() + .collect(), source_proof: Ok(()), + target_free_source_relay_headers_interval: Ok(None), target_best_block: Ok(HeaderId(0, Default::default())), target_best_finalized_source_block: Ok(HeaderId(0, Default::default())), target_head: Ok(None), target_submit_result: Ok(()), + submitted_proof_at_source_relay_block: None, exit_signal_sender: None, } } @@ -647,18 +749,34 @@ mod tests { self.data.lock().await.source_sync_status.clone() } + async fn relay_header_id( + &self, + number: BlockNumberOf, + ) -> Result, Self::Error> { + let hash = number.using_encoded(sp_core::blake2_256); + Ok(HeaderId(number, hash.into())) + } + async fn parachain_head( &self, - _at_block: HeaderIdOf, + at_block: HeaderIdOf, ) -> Result>, TestError> { - self.data.lock().await.source_head.clone() + self.data + .lock() + .await + .source_head + .get(&at_block.0) + .expect(&format!("SourceClient::parachain_head({})", at_block.0)) + .clone() } async fn prove_parachain_head( &self, - _at_block: HeaderIdOf, + at_block: HeaderIdOf, ) -> Result<(ParaHeadsProof, ParaHash), TestError> { - let head = *self.data.lock().await.source_head.clone()?.as_available().unwrap(); + let head_result = + SourceClient::::parachain_head(self, at_block).await?; + let head = head_result.as_available().unwrap(); let storage_proof = vec![head.hash().encode()]; let proof = (ParaHeadsProof { storage_proof }, head.hash()); self.data.lock().await.source_proof.clone().map(|_| proof) @@ -680,21 +798,28 @@ mod tests { self.data.lock().await.target_best_finalized_source_block.clone() } + async fn free_source_relay_headers_interval( + &self, + ) -> Result>, TestError> { + self.data.lock().await.target_free_source_relay_headers_interval.clone() + } + async fn parachain_head( &self, _at_block: HeaderIdOf, - ) -> Result>, TestError> { + ) -> Result, HeaderIdOf)>, TestError> { self.data.lock().await.target_head.clone() } async fn submit_parachain_head_proof( &self, - _at_source_block: HeaderIdOf, + at_source_block: HeaderIdOf, _updated_parachain_head: ParaHash, _proof: ParaHeadsProof, ) -> Result { let mut data = self.data.lock().await; data.target_submit_result.clone()?; + data.submitted_proof_at_source_relay_block = Some(at_source_block); if let Some(mut exit_signal_sender) = data.exit_signal_sender.take() { exit_signal_sender.send(()).await.unwrap(); @@ -715,6 +840,7 @@ mod tests { TestClient::from(test_source_client), TestClient::from(TestClientData::minimal()), None, + false, futures::future::pending(), )), Err(FailedClient::Source), @@ -731,6 +857,7 @@ mod tests { TestClient::from(TestClientData::minimal()), TestClient::from(test_target_client), None, + false, futures::future::pending(), )), Err(FailedClient::Target), @@ -747,6 +874,7 @@ mod tests { TestClient::from(TestClientData::minimal()), TestClient::from(test_target_client), None, + false, futures::future::pending(), )), Err(FailedClient::Target), @@ -763,6 +891,7 @@ mod tests { TestClient::from(TestClientData::minimal()), TestClient::from(test_target_client), None, + false, futures::future::pending(), )), Err(FailedClient::Target), @@ -772,13 +901,14 @@ mod tests { #[test] fn when_source_client_fails_to_read_heads() { let mut test_source_client = TestClientData::minimal(); - test_source_client.source_head = Err(TestError::Error); + test_source_client.source_head.insert(0, Err(TestError::Error)); assert_eq!( async_std::task::block_on(run_until_connection_lost( TestClient::from(test_source_client), TestClient::from(TestClientData::minimal()), None, + false, futures::future::pending(), )), Err(FailedClient::Source), @@ -795,6 +925,7 @@ mod tests { TestClient::from(test_source_client), TestClient::from(TestClientData::minimal()), None, + false, futures::future::pending(), )), Err(FailedClient::Source), @@ -811,6 +942,7 @@ mod tests { TestClient::from(TestClientData::minimal()), TestClient::from(test_target_client), None, + false, futures::future::pending(), )), Err(FailedClient::Target), @@ -825,12 +957,72 @@ mod tests { TestClient::from(TestClientData::minimal()), TestClient::from(TestClientData::with_exit_signal_sender(exit_signal_sender)), None, + false, exit_signal.into_future().map(|(_, _)| ()), )), Ok(()), ); } + #[async_std::test] + async fn free_headers_are_relayed() { + // prepare following case: + // 1) best source relay at target: 95 + // 2) best source parachain at target: 5 at relay 50 + // 3) free headers interval: 10 + // 4) at source relay chain block 90 source parachain block is 9 + // + + // 5) best finalized source relay chain block is 95 + // 6) at source relay chain block 95 source parachain block is 42 + // => + // without free requirement, parachain block 42 would have been relayed + // with free requirement we relay parachain block 9 + let (exit_signal_sender, exit_signal) = futures::channel::mpsc::unbounded(); + let clients_data = TestClientData { + source_sync_status: Ok(true), + source_head: vec![ + (90, Ok(AvailableHeader::Available(HeaderId(9, [9u8; 32].into())))), + (95, Ok(AvailableHeader::Available(HeaderId(42, [42u8; 32].into())))), + ] + .into_iter() + .collect(), + source_proof: Ok(()), + + target_free_source_relay_headers_interval: Ok(Some(10)), + target_best_block: Ok(HeaderId(200, [200u8; 32].into())), + target_best_finalized_source_block: Ok(HeaderId(95, [95u8; 32].into())), + target_head: Ok(Some((HeaderId(50, [50u8; 32].into()), HeaderId(5, [5u8; 32].into())))), + target_submit_result: Ok(()), + + submitted_proof_at_source_relay_block: None, + exit_signal_sender: Some(Box::new(exit_signal_sender)), + }; + + let source_client = TestClient::from(clients_data.clone()); + let target_client = TestClient::from(clients_data); + assert_eq!( + run_until_connection_lost( + source_client.clone(), + target_client.clone(), + None, + true, + exit_signal.into_future().map(|(_, _)| ()), + ) + .await, + Ok(()), + ); + + assert_eq!( + target_client + .data + .lock() + .await + .submitted_proof_at_source_relay_block + .map(|id| id.0), + Some(90) + ); + } + fn test_tx_tracker() -> SubmittedHeadsTracker { SubmittedHeadsTracker::new( AvailableHeader::Available(HeaderId(20, PARA_20_HASH)), From 56eb7557ee9754e9af534a6b5eaececa6ba6f958 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 18 Mar 2024 11:58:30 +0300 Subject: [PATCH 58/70] removed inactual comment (free_source_headers_interval is cached at relay level) --- bridges/relays/lib-substrate-relay/src/parachains/target.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/bridges/relays/lib-substrate-relay/src/parachains/target.rs b/bridges/relays/lib-substrate-relay/src/parachains/target.rs index e54d360532fd..da8791c4b0f0 100644 --- a/bridges/relays/lib-substrate-relay/src/parachains/target.rs +++ b/bridges/relays/lib-substrate-relay/src/parachains/target.rs @@ -122,8 +122,6 @@ where async fn free_source_relay_headers_interval( &self, ) -> Result>, Self::Error> { - // may be cached, because we assume that on upgrades (when this may change), relayer - // is also upgraded and restarted self.target_client .typed_state_call(P::SourceRelayChain::FREE_HEADERS_INTERVAL_METHOD.into(), (), None) .await From 9e2a6c9e2e8425a74729b8cd6a5ab5776f2a46d8 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 18 Mar 2024 12:18:09 +0300 Subject: [PATCH 59/70] added comment re free-headers-only support in on-demand parachains relay --- bridges/relays/lib-substrate-relay/src/on_demand/parachains.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bridges/relays/lib-substrate-relay/src/on_demand/parachains.rs b/bridges/relays/lib-substrate-relay/src/on_demand/parachains.rs index 40852d396b3c..a58b26850966 100644 --- a/bridges/relays/lib-substrate-relay/src/on_demand/parachains.rs +++ b/bridges/relays/lib-substrate-relay/src/on_demand/parachains.rs @@ -395,7 +395,8 @@ async fn background_task( parachains_source.clone(), parachains_target.clone(), MetricsParams::disabled(), - false, // TODO + // we do not support free parachain headers relay in on-demand relays + false, futures::future::pending(), ) .fuse(), From 3eb1c1469b91709cd45214c660b3da25483bee38 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 18 Mar 2024 12:22:06 +0300 Subject: [PATCH 60/70] added --only-free-headers support to standalone parachains relay --- .../relays/lib-substrate-relay/src/cli/relay_parachains.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bridges/relays/lib-substrate-relay/src/cli/relay_parachains.rs b/bridges/relays/lib-substrate-relay/src/cli/relay_parachains.rs index 511e9d2b2eb6..1425233add1e 100644 --- a/bridges/relays/lib-substrate-relay/src/cli/relay_parachains.rs +++ b/bridges/relays/lib-substrate-relay/src/cli/relay_parachains.rs @@ -43,6 +43,10 @@ pub struct RelayParachainsParams { target: TargetConnectionParams, #[structopt(flatten)] target_sign: TargetSigningParams, + /// If passed, only free headers (those, available at "free" relay chain headers) + /// are relayed. + #[structopt(long)] + only_free_headers: bool, #[structopt(flatten)] prometheus_params: PrometheusParams, } @@ -84,7 +88,7 @@ where source_client, target_client, metrics_params, - false, // TODO + data.only_free_headers, futures::future::pending(), ) .await From 124a9ff48258a72de51815e953ddfaefedb21746 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 19 Mar 2024 10:40:06 +0300 Subject: [PATCH 61/70] update RBH and WBH runtimes + ask for free execution if we are running in HeadersToRelay::Free mode --- bridges/relays/finality/src/finality_loop.rs | 18 +++++++++++++----- bridges/relays/finality/src/mock.rs | 1 + .../lib-substrate-relay/src/finality/mod.rs | 7 ++++++- .../lib-substrate-relay/src/finality/target.rs | 6 +++++- .../src/on_demand/headers.rs | 2 +- 5 files changed, 26 insertions(+), 8 deletions(-) diff --git a/bridges/relays/finality/src/finality_loop.rs b/bridges/relays/finality/src/finality_loop.rs index ab4b21122e06..28443742eae1 100644 --- a/bridges/relays/finality/src/finality_loop.rs +++ b/bridges/relays/finality/src/finality_loop.rs @@ -110,6 +110,7 @@ pub trait TargetClient: RelayClient { &self, header: P::Header, proof: P::FinalityProof, + is_free_execution_expected: bool, ) -> Result; } @@ -218,6 +219,7 @@ impl Transaction Result { let header_number = header.number(); log::debug!( @@ -228,7 +230,9 @@ impl Transaction, TC: TargetClient

> Finality // submit new header if we have something new match self.select_header_to_submit(&info).await? { Some(header) => { - let transaction = - Transaction::submit(&self.target_client, header.header, header.proof) - .await - .map_err(Error::Target)?; + let transaction = Transaction::submit( + &self.target_client, + header.header, + header.proof, + self.sync_params.headers_to_relay == HeadersToRelay::Free, + ) + .await + .map_err(Error::Target)?; self.best_submitted_number = Some(transaction.header_number); Ok(Some(transaction)) }, diff --git a/bridges/relays/finality/src/mock.rs b/bridges/relays/finality/src/mock.rs index 726b31ef9b41..69357f71ce27 100644 --- a/bridges/relays/finality/src/mock.rs +++ b/bridges/relays/finality/src/mock.rs @@ -206,6 +206,7 @@ impl TargetClient for TestTargetClient { &self, header: TestSourceHeader, proof: TestFinalityProof, + _is_free_execution_expected: bool, ) -> Result { let mut data = self.data.lock(); (self.on_method_call)(&mut data); diff --git a/bridges/relays/lib-substrate-relay/src/finality/mod.rs b/bridges/relays/lib-substrate-relay/src/finality/mod.rs index a94a88db7381..a06857ae1d9b 100644 --- a/bridges/relays/lib-substrate-relay/src/finality/mod.rs +++ b/bridges/relays/lib-substrate-relay/src/finality/mod.rs @@ -115,6 +115,7 @@ pub trait SubmitFinalityProofCallBuilder { fn build_submit_finality_proof_call( header: SyncHeader>, proof: SubstrateFinalityProof

, + is_free_execution_expected: bool, context: <

::FinalityEngine as Engine>::FinalityVerificationContext, ) -> CallOf; } @@ -142,6 +143,7 @@ where fn build_submit_finality_proof_call( header: SyncHeader>, proof: GrandpaJustification>, + _is_free_execution_expected: bool, _context: JustificationVerificationContext, ) -> CallOf { BridgeGrandpaCall::::submit_finality_proof { @@ -176,6 +178,7 @@ macro_rules! generate_submit_finality_proof_call_builder { <$pipeline as $crate::finality_base::SubstrateFinalityPipeline>::SourceChain > >, + _is_free_execution_expected: bool, _context: bp_header_chain::justification::JustificationVerificationContext, ) -> relay_substrate_client::CallOf< <$pipeline as $crate::finality_base::SubstrateFinalityPipeline>::TargetChain @@ -215,6 +218,7 @@ macro_rules! generate_submit_finality_proof_ex_call_builder { <$pipeline as $crate::finality_base::SubstrateFinalityPipeline>::SourceChain > >, + is_free_execution_expected: bool, context: bp_header_chain::justification::JustificationVerificationContext, ) -> relay_substrate_client::CallOf< <$pipeline as $crate::finality_base::SubstrateFinalityPipeline>::TargetChain @@ -223,7 +227,8 @@ macro_rules! generate_submit_finality_proof_ex_call_builder { $bridge_grandpa($submit_finality_proof { finality_target: Box::new(header.into_inner()), justification: proof, - current_set_id: context.authority_set_id + current_set_id: context.authority_set_id, + is_free_execution_expected, }) } } diff --git a/bridges/relays/lib-substrate-relay/src/finality/target.rs b/bridges/relays/lib-substrate-relay/src/finality/target.rs index 293f1f63167b..adbcfe0096d5 100644 --- a/bridges/relays/lib-substrate-relay/src/finality/target.rs +++ b/bridges/relays/lib-substrate-relay/src/finality/target.rs @@ -120,6 +120,7 @@ impl TargetClient>, mut proof: SubstrateFinalityProof

, + is_free_execution_expected: bool, ) -> Result { // verify and runtime module at target chain may require optimized finality proof let context = @@ -128,7 +129,10 @@ impl TargetClient OnDemandRelay Date: Wed, 20 Mar 2024 14:38:55 +0300 Subject: [PATCH 62/70] add some traces to relay --- bridges/relays/finality/src/finality_loop.rs | 82 ++++++++++---- bridges/relays/finality/src/headers.rs | 19 +++- .../relays/parachains/src/parachains_loop.rs | 102 +++++++++++------- 3 files changed, 138 insertions(+), 65 deletions(-) diff --git a/bridges/relays/finality/src/finality_loop.rs b/bridges/relays/finality/src/finality_loop.rs index 28443742eae1..8752c2dd599e 100644 --- a/bridges/relays/finality/src/finality_loop.rs +++ b/bridges/relays/finality/src/finality_loop.rs @@ -29,7 +29,7 @@ use crate::{ use async_trait::async_trait; use backoff::{backoff::Backoff, ExponentialBackoff}; use futures::{future::Fuse, select, Future, FutureExt}; -use num_traits::Saturating; +use num_traits::{Saturating, Zero}; use relay_utils::{ metrics::MetricsParams, relay_loop::Client as RelayClient, retry_backoff, FailedClient, HeaderId, MaybeConnectionError, TrackedTransactionStatus, TransactionTracker, @@ -128,11 +128,6 @@ pub struct SyncInfo { pub best_number_at_target: P::Number, /// Whether the target client follows the same fork as the source client do. pub is_using_same_fork: bool, - /// Free headers interval. We assume that the submission of header `N`, divisible - /// by `free_headers_interval` will be free for submitter. May be `None` if runtime - /// is configured to not allow free headers. If it is `Some(_)`, it is guaranteed - /// not to be zero. - pub free_headers_interval: Option, } impl SyncInfo

{ @@ -171,22 +166,11 @@ impl SyncInfo

{ target_client.best_finalized_source_block_id().await.map_err(Error::Target)?; let best_number_at_target = best_id_at_target.0; - let mut free_headers_interval = - target_client.free_source_headers_interval().await.map_err(Error::Target)?; - if free_headers_interval == Some(0.into()) { - free_headers_interval = None; - } - let is_using_same_fork = Self::is_on_same_fork(source_client, &best_id_at_target) .await .map_err(Error::Source)?; - Ok(Self { - best_number_at_source, - best_number_at_target, - free_headers_interval, - is_using_same_fork, - }) + Ok(Self { best_number_at_source, best_number_at_target, is_using_same_fork }) } fn update_metrics(&self, metrics_sync: &Option) { @@ -331,6 +315,7 @@ impl, TC: TargetClient

> Finality pub async fn select_header_to_submit( &mut self, info: &SyncInfo

, + free_headers_interval: Option, ) -> Result>, Error> { // to see that the loop is progressing log::trace!( @@ -345,6 +330,7 @@ impl, TC: TargetClient

> Finality &self.source_client, info, self.sync_params.headers_to_relay, + free_headers_interval, ) .await?; // if we see that the header schedules GRANDPA change, we need to submit it @@ -356,8 +342,11 @@ impl, TC: TargetClient

> Finality // => even if we have already selected some header and its persistent finality proof, // we may try to select better header by reading non-persistent proofs from the stream self.finality_proofs_buf.fill(&mut self.finality_proofs_stream); - let maybe_justified_header = - selector.select(info, self.sync_params.headers_to_relay, &self.finality_proofs_buf); + let maybe_justified_header = selector.select( + self.sync_params.headers_to_relay, + free_headers_interval, + &self.finality_proofs_buf, + ); // remove obsolete 'recent' finality proofs + keep its size under certain limit let oldest_finality_proof_to_keep = maybe_justified_header @@ -374,6 +363,7 @@ impl, TC: TargetClient

> Finality pub async fn run_iteration( &mut self, + free_headers_interval: Option, ) -> Result< Option>, Error, @@ -390,7 +380,7 @@ impl, TC: TargetClient

> Finality } // submit new header if we have something new - match self.select_header_to_submit(&info).await? { + match self.select_header_to_submit(&info, free_headers_interval).await? { Some(header) => { let transaction = Transaction::submit( &self.target_client, @@ -427,9 +417,11 @@ impl, TC: TargetClient

> Finality let exit_signal = exit_signal.fuse(); futures::pin_mut!(exit_signal, proof_submission_tx_tracker); + let free_headers_interval = free_headers_interval(&self.target_client).await?; + loop { // run loop iteration - let next_tick = match self.run_iteration().await { + let next_tick = match self.run_iteration(free_headers_interval).await { Ok(Some(tx)) => { proof_submission_tx_tracker .set(tx.track::(self.target_client.clone()).fuse()); @@ -482,6 +474,52 @@ impl, TC: TargetClient

> Finality } } +async fn free_headers_interval( + target_client: &impl TargetClient

, +) -> Result, FailedClient> { + match target_client.free_source_headers_interval().await { + Ok(Some(free_headers_interval)) if !free_headers_interval.is_zero() => { + log::trace!( + target: "bridge", + "Free headers interval for {} headers at {} is: {:?}", + P::SOURCE_NAME, + P::TARGET_NAME, + free_headers_interval, + ); + Ok(Some(free_headers_interval)) + }, + Ok(Some(_free_headers_interval)) => { + log::trace!( + target: "bridge", + "Free headers interval for {} headers at {} is zero. Not submitting any free headers", + P::SOURCE_NAME, + P::TARGET_NAME, + ); + Ok(None) + }, + Ok(None) => { + log::trace!( + target: "bridge", + "Free headers interval for {} headers at {} is None. Not submitting any free headers", + P::SOURCE_NAME, + P::TARGET_NAME, + ); + + Ok(None) + }, + Err(e) => { + log::error!( + target: "bridge", + "Failed to read free headers interval for {} headers at {}: {:?}", + P::SOURCE_NAME, + P::TARGET_NAME, + e, + ); + Err(FailedClient::Target) + }, + } +} + /// Run finality proofs synchronization loop. pub async fn run( source_client: impl SourceClient

, diff --git a/bridges/relays/finality/src/headers.rs b/bridges/relays/finality/src/headers.rs index 119d04be7d6b..83028945803b 100644 --- a/bridges/relays/finality/src/headers.rs +++ b/bridges/relays/finality/src/headers.rs @@ -57,6 +57,7 @@ impl JustifiedHeaderSelector

{ source_client: &SC, info: &SyncInfo

, headers_to_relay: HeadersToRelay, + free_headers_interval: Option, ) -> Result> { let mut unjustified_headers = Vec::new(); let mut maybe_justified_header = None; @@ -74,7 +75,9 @@ impl JustifiedHeaderSelector

{ return Ok(Self::Mandatory(JustifiedHeader { header, proof })) }, (true, None) => return Err(Error::MissingMandatoryFinalityProof(header.number())), - (false, Some(proof)) if need_to_relay(info, headers_to_relay, &header) => { + (false, Some(proof)) + if need_to_relay::

(headers_to_relay, free_headers_interval, &header) => + { log::trace!(target: "bridge", "Header {:?} has persistent finality proof", header_number); unjustified_headers.clear(); maybe_justified_header = Some(JustifiedHeader { header, proof }); @@ -113,8 +116,8 @@ impl JustifiedHeaderSelector

{ /// justifications stream. pub fn select( self, - info: &SyncInfo

, headers_to_relay: HeadersToRelay, + free_headers_interval: Option, buf: &FinalityProofsBuf

, ) -> Option> { let (unjustified_headers, maybe_justified_header) = match self { @@ -134,7 +137,13 @@ impl JustifiedHeaderSelector

{ (maybe_finality_proof, maybe_unjustified_header) { match finality_proof.target_header_number().cmp(&unjustified_header.number()) { - Ordering::Equal if need_to_relay(info, headers_to_relay, &unjustified_header) => { + Ordering::Equal + if need_to_relay::

( + headers_to_relay, + free_headers_interval, + &unjustified_header, + ) => + { log::trace!( target: "bridge", "Managed to improve selected {} finality proof {:?} to {:?}.", @@ -170,8 +179,8 @@ impl JustifiedHeaderSelector

{ /// Returns true if we want to relay header `header_number`. fn need_to_relay( - info: &SyncInfo

, headers_to_relay: HeadersToRelay, + free_headers_interval: Option, header: &P::Header, ) -> bool { match headers_to_relay { @@ -179,7 +188,7 @@ fn need_to_relay( HeadersToRelay::Mandatory => header.is_mandatory(), HeadersToRelay::Free => header.is_mandatory() || - info.free_headers_interval + free_headers_interval .map(|free_headers_interval| { !header.number().is_zero() && (header.number() % free_headers_interval).is_zero() diff --git a/bridges/relays/parachains/src/parachains_loop.rs b/bridges/relays/parachains/src/parachains_loop.rs index a20f7c3ecd72..62363ab8f227 100644 --- a/bridges/relays/parachains/src/parachains_loop.rs +++ b/bridges/relays/parachains/src/parachains_loop.rs @@ -193,18 +193,36 @@ where // free parachain header = header, available (proved) at free relay chain block. Let's // read interval of free source relay chain blocks from target client let free_source_relay_headers_interval = if only_free_headers { - let free_source_relay_headers_interval = target_client - .free_source_relay_headers_interval() - .await - .map_err(|e| { - log::warn!(target: "bridge", "Failed to read free {} headers interval at {}: {:?}", P::SourceRelayChain::NAME, P::TargetChain::NAME, e); + let free_source_relay_headers_interval = + target_client.free_source_relay_headers_interval().await.map_err(|e| { + log::warn!( + target: "bridge", + "Failed to read free {} headers interval at {}: {:?}", + P::SourceRelayChain::NAME, + P::TargetChain::NAME, + e, + ); FailedClient::Target })?; match free_source_relay_headers_interval { - Some(free_source_relay_headers_interval) if free_source_relay_headers_interval != 0 => - free_source_relay_headers_interval, + Some(free_source_relay_headers_interval) if free_source_relay_headers_interval != 0 => { + log::trace!( + target: "bridge", + "Free {} headers interval at {}: {:?}", + P::SourceRelayChain::NAME, + P::TargetChain::NAME, + free_source_relay_headers_interval, + ); + free_source_relay_headers_interval + }, _ => { - log::warn!(target: "bridge", "Invalid free {} headers interval at {}: {:?}", P::SourceRelayChain::NAME, P::TargetChain::NAME, free_source_relay_headers_interval); + log::warn!( + target: "bridge", + "Invalid free {} headers interval at {}: {:?}", + P::SourceRelayChain::NAME, + P::TargetChain::NAME, + free_source_relay_headers_interval, + ); return Err(FailedClient::Target) }, } @@ -305,39 +323,47 @@ where // head, available at best free source relay chain header, known to the // target chain let prove_at_relay_block = if only_free_headers { - let relay_of_head_at_target = match relay_of_head_at_target { - Some(relay_of_head_at_target) => relay_of_head_at_target, + match relay_of_head_at_target { + Some(relay_of_head_at_target) => { + // find last free relay chain header in the range that we are interested in + let scan_range_begin = relay_of_head_at_target.number() + 1; + let scan_range_end = best_finalized_relay_block_at_target.number(); + let last_free_source_relay_header_number = (scan_range_end / + free_source_relay_headers_interval) * + free_source_relay_headers_interval; + if last_free_source_relay_header_number < scan_range_begin { + // there are no new **free** relay chain headers in the range + log::trace!( + target: "bridge", + "Waiting for new free {} headers at {}: scanned {:?}..={:?}", + P::SourceRelayChain::NAME, + P::TargetChain::NAME, + scan_range_begin, + scan_range_end, + ); + continue; + } + + // ok - we know the relay chain header number, now let's get its full id + source_client + .relay_header_id(last_free_source_relay_header_number) + .await + .map_err(|e| { + log::warn!( + target: "bridge", + "Failed to get full header id of {} block #{:?}: {:?}", + P::SourceRelayChain::NAME, + last_free_source_relay_header_number, + e, + ); + FailedClient::Source + })? + }, None => { - // no relay headers available at target => wait - continue + // no parachain head at target => let's submit first one + best_finalized_relay_block_at_target }, - }; - - // find last free relay chain header in the range that we are interested in - let scan_range_begin = relay_of_head_at_target.number() + 1; - let scan_range_end = best_finalized_relay_block_at_target.number(); - let last_free_source_relay_header_number = (scan_range_end / - free_source_relay_headers_interval) * - free_source_relay_headers_interval; - if last_free_source_relay_header_number < scan_range_begin { - // there are no new **free** relay chain headers in the range - continue; } - - // ok - we know the relay chain header number, now let's get its full id - source_client - .relay_header_id(last_free_source_relay_header_number) - .await - .map_err(|e| { - log::warn!( - target: "bridge", - "Failed to get full header id of {} block #{:?}: {:?}", - P::SourceRelayChain::NAME, - last_free_source_relay_header_number, - e, - ); - FailedClient::Source - })? } else { best_finalized_relay_block_at_target }; From 19689dcbac5f6efd30fec8ebb383558acab26346 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 22 Mar 2024 14:56:39 +0300 Subject: [PATCH 63/70] updated relays for new free headers criteria --- bridges/relays/finality/src/finality_loop.rs | 10 +-- bridges/relays/finality/src/headers.rs | 41 +++++---- .../src/parachains/source.rs | 9 +- .../relays/parachains/src/parachains_loop.rs | 83 ++++++++++--------- 4 files changed, 76 insertions(+), 67 deletions(-) diff --git a/bridges/relays/finality/src/finality_loop.rs b/bridges/relays/finality/src/finality_loop.rs index 8752c2dd599e..8b3def868a45 100644 --- a/bridges/relays/finality/src/finality_loop.rs +++ b/bridges/relays/finality/src/finality_loop.rs @@ -343,6 +343,7 @@ impl, TC: TargetClient

> Finality // we may try to select better header by reading non-persistent proofs from the stream self.finality_proofs_buf.fill(&mut self.finality_proofs_stream); let maybe_justified_header = selector.select( + info, self.sync_params.headers_to_relay, free_headers_interval, &self.finality_proofs_buf, @@ -713,10 +714,9 @@ mod tests { let info = SyncInfo { best_number_at_source: 10, best_number_at_target: 5, - free_headers_interval: Some(3), is_using_same_fork: true, }; - finality_loop.select_header_to_submit(&info).await.unwrap() + finality_loop.select_header_to_submit(&info, Some(3)).await.unwrap() }) } @@ -726,8 +726,8 @@ mod tests { assert_eq!( run_headers_to_relay_mode_test(HeadersToRelay::Free, false), Some(JustifiedHeader { - header: TestSourceHeader(false, 9, 9), - proof: TestFinalityProof(9) + header: TestSourceHeader(false, 10, 10), + proof: TestFinalityProof(10) }), ); assert_eq!( @@ -790,7 +790,7 @@ mod tests { test_sync_params(), Some(metrics_sync.clone()), ); - finality_loop.run_iteration().await.unwrap() + finality_loop.run_iteration(None).await.unwrap() }); assert!(!metrics_sync.is_using_same_fork()); diff --git a/bridges/relays/finality/src/headers.rs b/bridges/relays/finality/src/headers.rs index 83028945803b..9f76dab172f8 100644 --- a/bridges/relays/finality/src/headers.rs +++ b/bridges/relays/finality/src/headers.rs @@ -20,7 +20,7 @@ use crate::{ }; use bp_header_chain::FinalityProof; -use num_traits::Zero; +use num_traits::Saturating; use std::cmp::Ordering; /// Unjustified headers container. Ordered by header number. @@ -76,7 +76,12 @@ impl JustifiedHeaderSelector

{ }, (true, None) => return Err(Error::MissingMandatoryFinalityProof(header.number())), (false, Some(proof)) - if need_to_relay::

(headers_to_relay, free_headers_interval, &header) => + if need_to_relay::

( + info, + headers_to_relay, + free_headers_interval, + &header, + ) => { log::trace!(target: "bridge", "Header {:?} has persistent finality proof", header_number); unjustified_headers.clear(); @@ -116,6 +121,7 @@ impl JustifiedHeaderSelector

{ /// justifications stream. pub fn select( self, + info: &SyncInfo

, headers_to_relay: HeadersToRelay, free_headers_interval: Option, buf: &FinalityProofsBuf

, @@ -139,6 +145,7 @@ impl JustifiedHeaderSelector

{ match finality_proof.target_header_number().cmp(&unjustified_header.number()) { Ordering::Equal if need_to_relay::

( + info, headers_to_relay, free_headers_interval, &unjustified_header, @@ -179,6 +186,7 @@ impl JustifiedHeaderSelector

{ /// Returns true if we want to relay header `header_number`. fn need_to_relay( + info: &SyncInfo

, headers_to_relay: HeadersToRelay, free_headers_interval: Option, header: &P::Header, @@ -190,8 +198,8 @@ fn need_to_relay( header.is_mandatory() || free_headers_interval .map(|free_headers_interval| { - !header.number().is_zero() && - (header.number() % free_headers_interval).is_zero() + header.number().saturating_sub(info.best_number_at_target) >= + free_headers_interval }) .unwrap_or(false), } @@ -204,10 +212,9 @@ mod tests { #[test] fn select_better_recent_finality_proof_works() { - let mut info = SyncInfo { + let info = SyncInfo { best_number_at_source: 10, best_number_at_target: 5, - free_headers_interval: None, is_using_same_fork: true, }; @@ -218,7 +225,7 @@ mod tests { JustifiedHeader { header: TestSourceHeader(false, 2, 2), proof: TestFinalityProof(2) }; let selector = JustifiedHeaderSelector::Regular(vec![], justified_header.clone()); assert_eq!( - selector.select(&info, HeadersToRelay::All, &finality_proofs_buf), + selector.select(&info, HeadersToRelay::All, None, &finality_proofs_buf), Some(justified_header) ); @@ -231,7 +238,7 @@ mod tests { justified_header.clone(), ); assert_eq!( - selector.select(&info, HeadersToRelay::All, &finality_proofs_buf), + selector.select(&info, HeadersToRelay::All, None, &finality_proofs_buf), Some(justified_header) ); @@ -248,7 +255,7 @@ mod tests { justified_header.clone(), ); assert_eq!( - selector.select(&info, HeadersToRelay::All, &finality_proofs_buf), + selector.select(&info, HeadersToRelay::All, None, &finality_proofs_buf), Some(justified_header) ); @@ -269,7 +276,7 @@ mod tests { justified_header.clone(), ); assert_eq!( - selector.select(&info, HeadersToRelay::All, &finality_proofs_buf), + selector.select(&info, HeadersToRelay::All, None, &finality_proofs_buf), Some(justified_header) ); @@ -292,7 +299,7 @@ mod tests { justified_header, ); assert_eq!( - selector.select(&info, HeadersToRelay::All, &finality_proofs_buf), + selector.select(&info, HeadersToRelay::All, None, &finality_proofs_buf), Some(JustifiedHeader { header: TestSourceHeader(false, 9, 9), proof: TestFinalityProof(9) @@ -300,7 +307,6 @@ mod tests { ); // when only free headers needs to be relayed and there are no free headers - info.free_headers_interval = Some(7); let finality_proofs_buf = FinalityProofsBuf::::new(vec![ TestFinalityProof(7), TestFinalityProof(9), @@ -310,10 +316,12 @@ mod tests { TestSourceHeader(false, 9, 9), TestSourceHeader(false, 10, 10), ]); - assert_eq!(selector.select(&info, HeadersToRelay::Free, &finality_proofs_buf), None,); + assert_eq!( + selector.select(&info, HeadersToRelay::Free, Some(7), &finality_proofs_buf), + None, + ); // when only free headers needs to be relayed, mandatory header may be selected - info.free_headers_interval = Some(7); let finality_proofs_buf = FinalityProofsBuf::::new(vec![ TestFinalityProof(6), TestFinalityProof(9), @@ -324,7 +332,7 @@ mod tests { TestSourceHeader(false, 10, 10), ]); assert_eq!( - selector.select(&info, HeadersToRelay::Free, &finality_proofs_buf), + selector.select(&info, HeadersToRelay::Free, Some(7), &finality_proofs_buf), Some(JustifiedHeader { header: TestSourceHeader(true, 9, 9), proof: TestFinalityProof(9) @@ -332,7 +340,6 @@ mod tests { ); // when only free headers needs to be relayed and there is free header - info.free_headers_interval = Some(7); let finality_proofs_buf = FinalityProofsBuf::::new(vec![ TestFinalityProof(7), TestFinalityProof(9), @@ -344,7 +351,7 @@ mod tests { TestSourceHeader(false, 14, 14), ]); assert_eq!( - selector.select(&info, HeadersToRelay::Free, &finality_proofs_buf), + selector.select(&info, HeadersToRelay::Free, Some(7), &finality_proofs_buf), Some(JustifiedHeader { header: TestSourceHeader(false, 14, 14), proof: TestFinalityProof(14) diff --git a/bridges/relays/lib-substrate-relay/src/parachains/source.rs b/bridges/relays/lib-substrate-relay/src/parachains/source.rs index 3d28769df075..4cc512b9d9b4 100644 --- a/bridges/relays/lib-substrate-relay/src/parachains/source.rs +++ b/bridges/relays/lib-substrate-relay/src/parachains/source.rs @@ -22,7 +22,7 @@ use async_std::sync::{Arc, Mutex}; use async_trait::async_trait; use bp_parachains::parachain_head_storage_key_at_source; use bp_polkadot_core::parachains::{ParaHash, ParaHead, ParaHeadsProof, ParaId}; -use bp_runtime::{BlockNumberOf, HeaderIdProvider}; +use bp_runtime::HeaderIdProvider; use codec::Decode; use parachains_relay::parachains_loop::{AvailableHeader, SourceClient}; use relay_substrate_client::{ @@ -98,13 +98,6 @@ where } } - async fn relay_header_id( - &self, - number: BlockNumberOf, - ) -> Result, Self::Error> { - self.client.header_by_number(number).await.map(|h| h.id()) - } - async fn parachain_head( &self, at_block: HeaderIdOf, diff --git a/bridges/relays/parachains/src/parachains_loop.rs b/bridges/relays/parachains/src/parachains_loop.rs index 62363ab8f227..9f619757b6a6 100644 --- a/bridges/relays/parachains/src/parachains_loop.rs +++ b/bridges/relays/parachains/src/parachains_loop.rs @@ -74,12 +74,6 @@ pub trait SourceClient: RelayClient { /// Returns `Ok(true)` if client is in synced state. async fn ensure_synced(&self) -> Result; - /// Get finalized relay chain header id by its number. - async fn relay_header_id( - &self, - number: BlockNumberOf, - ) -> Result, Self::Error>; - /// Get parachain head id at given block. async fn parachain_head( &self, @@ -326,12 +320,11 @@ where match relay_of_head_at_target { Some(relay_of_head_at_target) => { // find last free relay chain header in the range that we are interested in - let scan_range_begin = relay_of_head_at_target.number() + 1; + let scan_range_begin = relay_of_head_at_target.number(); let scan_range_end = best_finalized_relay_block_at_target.number(); - let last_free_source_relay_header_number = (scan_range_end / - free_source_relay_headers_interval) * - free_source_relay_headers_interval; - if last_free_source_relay_header_number < scan_range_begin { + if scan_range_end.saturating_sub(scan_range_begin) < + free_source_relay_headers_interval + { // there are no new **free** relay chain headers in the range log::trace!( target: "bridge", @@ -344,20 +337,8 @@ where continue; } - // ok - we know the relay chain header number, now let's get its full id - source_client - .relay_header_id(last_free_source_relay_header_number) - .await - .map_err(|e| { - log::warn!( - target: "bridge", - "Failed to get full header id of {} block #{:?}: {:?}", - P::SourceRelayChain::NAME, - last_free_source_relay_header_number, - e, - ); - FailedClient::Source - })? + // we may submit new parachain head for free + best_finalized_relay_block_at_target }, None => { // no parachain head at target => let's submit first one @@ -775,14 +756,6 @@ mod tests { self.data.lock().await.source_sync_status.clone() } - async fn relay_header_id( - &self, - number: BlockNumberOf, - ) -> Result, Self::Error> { - let hash = number.using_encoded(sp_core::blake2_256); - Ok(HeaderId(number, hash.into())) - } - async fn parachain_head( &self, at_block: HeaderIdOf, @@ -1001,8 +974,7 @@ mod tests { // 5) best finalized source relay chain block is 95 // 6) at source relay chain block 95 source parachain block is 42 // => - // without free requirement, parachain block 42 would have been relayed - // with free requirement we relay parachain block 9 + // parachain block 42 would have been relayed, because 95 - 50 > 10 let (exit_signal_sender, exit_signal) = futures::channel::mpsc::unbounded(); let clients_data = TestClientData { source_sync_status: Ok(true), @@ -1028,7 +1000,7 @@ mod tests { let target_client = TestClient::from(clients_data); assert_eq!( run_until_connection_lost( - source_client.clone(), + source_client, target_client.clone(), None, true, @@ -1045,7 +1017,44 @@ mod tests { .await .submitted_proof_at_source_relay_block .map(|id| id.0), - Some(90) + Some(95) + ); + + // now source relay block chain 104 is mined with parachain head #84 + // => since 104 - 95 < 10, there are no free headers + // => nothing is submitted + let mut clients_data: TestClientData = target_client.data.lock().await.clone(); + clients_data + .source_head + .insert(104, Ok(AvailableHeader::Available(HeaderId(84, [84u8; 32].into())))); + clients_data.target_best_finalized_source_block = Ok(HeaderId(104, [104u8; 32].into())); + clients_data.target_head = + Ok(Some((HeaderId(95, [95u8; 32].into()), HeaderId(42, [42u8; 32].into())))); + clients_data.target_best_block = Ok(HeaderId(255, [255u8; 32].into())); + clients_data.exit_signal_sender = None; + + let source_client = TestClient::from(clients_data.clone()); + let target_client = TestClient::from(clients_data); + assert_eq!( + run_until_connection_lost( + source_client, + target_client.clone(), + None, + true, + async_std::task::sleep(std::time::Duration::from_millis(100)), + ) + .await, + Ok(()), + ); + + assert_eq!( + target_client + .data + .lock() + .await + .submitted_proof_at_source_relay_block + .map(|id| id.0), + Some(95) ); } From 874f0e230cd44c57c4305558b3ab2500f418e7a3 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 25 Mar 2024 15:07:28 +0300 Subject: [PATCH 64/70] some fixes --- bridges/relays/finality/src/headers.rs | 2 +- bridges/relays/parachains/src/parachains_loop.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/bridges/relays/finality/src/headers.rs b/bridges/relays/finality/src/headers.rs index 9f76dab172f8..5bba4a384562 100644 --- a/bridges/relays/finality/src/headers.rs +++ b/bridges/relays/finality/src/headers.rs @@ -51,7 +51,7 @@ pub enum JustifiedHeaderSelector { } impl JustifiedHeaderSelector

{ - /// Selects last header with persitent justification, missing from the target and matching + /// Selects last header with persistent justification, missing from the target and matching /// the `headers_to_relay` criteria. pub(crate) async fn new, TC: TargetClient

>( source_client: &SC, diff --git a/bridges/relays/parachains/src/parachains_loop.rs b/bridges/relays/parachains/src/parachains_loop.rs index 9f619757b6a6..e40c5bb00dfd 100644 --- a/bridges/relays/parachains/src/parachains_loop.rs +++ b/bridges/relays/parachains/src/parachains_loop.rs @@ -699,6 +699,7 @@ mod tests { Result>, TestError>, target_best_block: Result, TestError>, target_best_finalized_source_block: Result, TestError>, + #[allow(clippy::type_complexity)] target_head: Result, HeaderIdOf)>, TestError>, target_submit_result: Result<(), TestError>, From ad16bfa8270992c62ae488e5819186c1eee3f9d7 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 25 Mar 2024 16:27:42 +0300 Subject: [PATCH 65/70] use submit_parachain_heads_ex for Rococo<>Westend bridge --- .../lib-substrate-relay/src/on_demand/parachains.rs | 1 + bridges/relays/lib-substrate-relay/src/parachains/mod.rs | 2 ++ .../relays/lib-substrate-relay/src/parachains/target.rs | 2 ++ bridges/relays/parachains/src/parachains_loop.rs | 9 ++++++++- 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/bridges/relays/lib-substrate-relay/src/on_demand/parachains.rs b/bridges/relays/lib-substrate-relay/src/on_demand/parachains.rs index a58b26850966..966bdc310720 100644 --- a/bridges/relays/lib-substrate-relay/src/on_demand/parachains.rs +++ b/bridges/relays/lib-substrate-relay/src/on_demand/parachains.rs @@ -222,6 +222,7 @@ where proved_relay_block, vec![(para_id, para_hash)], para_proof, + false, )); Ok((proved_parachain_block, calls)) diff --git a/bridges/relays/lib-substrate-relay/src/parachains/mod.rs b/bridges/relays/lib-substrate-relay/src/parachains/mod.rs index 722f9b61f9f0..8b128bb770dd 100644 --- a/bridges/relays/lib-substrate-relay/src/parachains/mod.rs +++ b/bridges/relays/lib-substrate-relay/src/parachains/mod.rs @@ -71,6 +71,7 @@ pub trait SubmitParachainHeadsCallBuilder: at_relay_block: HeaderIdOf, parachains: Vec<(ParaId, ParaHash)>, parachain_heads_proof: ParaHeadsProof, + is_free_execution_expected: bool, ) -> CallOf; } @@ -97,6 +98,7 @@ where at_relay_block: HeaderIdOf, parachains: Vec<(ParaId, ParaHash)>, parachain_heads_proof: ParaHeadsProof, + _is_free_execution_expected: bool, ) -> CallOf { BridgeParachainsCall::::submit_parachain_heads { at_relay_block: (at_relay_block.0, at_relay_block.1), diff --git a/bridges/relays/lib-substrate-relay/src/parachains/target.rs b/bridges/relays/lib-substrate-relay/src/parachains/target.rs index da8791c4b0f0..e10d15b6edf6 100644 --- a/bridges/relays/lib-substrate-relay/src/parachains/target.rs +++ b/bridges/relays/lib-substrate-relay/src/parachains/target.rs @@ -177,12 +177,14 @@ where at_relay_block: HeaderIdOf, updated_head_hash: ParaHash, proof: ParaHeadsProof, + is_free_execution_expected: bool, ) -> Result { let transaction_params = self.transaction_params.clone(); let call = P::SubmitParachainHeadsCallBuilder::build_submit_parachain_heads_call( at_relay_block, vec![(ParaId(P::SourceParachain::PARACHAIN_ID), updated_head_hash)], proof, + is_free_execution_expected, ); self.target_client .submit_and_watch_signed_extrinsic( diff --git a/bridges/relays/parachains/src/parachains_loop.rs b/bridges/relays/parachains/src/parachains_loop.rs index e40c5bb00dfd..55f236eeac1d 100644 --- a/bridges/relays/parachains/src/parachains_loop.rs +++ b/bridges/relays/parachains/src/parachains_loop.rs @@ -124,6 +124,7 @@ pub trait TargetClient: RelayClient { at_source_block: HeaderIdOf, para_head_hash: ParaHash, proof: ParaHeadsProof, + is_free_execution_expected: bool, ) -> Result; } @@ -382,7 +383,12 @@ where ); let transaction_tracker = target_client - .submit_parachain_head_proof(prove_at_relay_block, head_hash, head_proof) + .submit_parachain_head_proof( + prove_at_relay_block, + head_hash, + head_proof, + only_free_headers, + ) .await .map_err(|e| { log::warn!( @@ -816,6 +822,7 @@ mod tests { at_source_block: HeaderIdOf, _updated_parachain_head: ParaHash, _proof: ParaHeadsProof, + _is_free_execution_expected: bool, ) -> Result { let mut data = self.data.lock().await; data.target_submit_result.clone()?; From 55f20955d17d4f5e42c67af54941a2a65a52ff62 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 25 Apr 2024 09:07:25 +0300 Subject: [PATCH 66/70] merge damage --- bridges/modules/parachains/src/call_ext.rs | 36 +++++++++++----------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/bridges/modules/parachains/src/call_ext.rs b/bridges/modules/parachains/src/call_ext.rs index d5f5056aa335..fe6b319205d4 100644 --- a/bridges/modules/parachains/src/call_ext.rs +++ b/bridges/modules/parachains/src/call_ext.rs @@ -87,25 +87,25 @@ impl, I: 'static> SubmitParachainHeadsHelper { return Err(InvalidTransaction::Call.into()); } + // if free headers interval is not configured and call is expected to execute + // for free => it is a relayer error, it should've been able to detect that. + let free_headers_interval = match T::FreeHeadersInterval::get() { + Some(free_headers_interval) => free_headers_interval, + None => return Ok(improved_by), + }; + // reject if we are importing parachain headers too often - if let Some(free_headers_interval) = T::FreeHeadersInterval::get() { - let reject = improved_by < free_headers_interval; - - if reject { - log::trace!( - target: crate::LOG_TARGET, - "The free parachain {:?} head can't be updated: it improves previous - best head by {} while at least {} is expected.", - update.para_id, - improved_by, - free_headers_interval, - ); - - return Err(InvalidTransaction::Stale.into()); - } - } else { - // free headers interval is not configured and call is expected to execute - // for free => it is a relayer error, it should've been able to detect that + if improved_by < free_headers_interval { + log::trace!( + target: crate::LOG_TARGET, + "The free parachain {:?} head can't be updated: it improves previous + best head by {} while at least {} is expected.", + update.para_id, + improved_by, + free_headers_interval, + ); + + return Err(InvalidTransaction::Stale.into()); } Ok(improved_by) From a4ca5024dd7eee176ef3463b56e6c9ae38cd9f4c Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 25 Apr 2024 09:12:43 +0300 Subject: [PATCH 67/70] revert unneeded change --- bridges/relays/utils/src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/bridges/relays/utils/src/lib.rs b/bridges/relays/utils/src/lib.rs index 1df6e9718f15..2776620be359 100644 --- a/bridges/relays/utils/src/lib.rs +++ b/bridges/relays/utils/src/lib.rs @@ -64,7 +64,6 @@ pub trait BlockNumberBase: + std::fmt::Display + std::hash::Hash + std::ops::Add - + std::ops::Rem + std::ops::Sub + num_traits::CheckedSub + num_traits::Saturating @@ -87,7 +86,6 @@ impl BlockNumberBase for T where + std::fmt::Display + std::hash::Hash + std::ops::Add - + std::ops::Rem + std::ops::Sub + num_traits::CheckedSub + num_traits::Saturating From d67b7ec3b1c40f43fe89e4489c8c2b86b0a20c64 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 25 Apr 2024 09:19:45 +0300 Subject: [PATCH 68/70] added prdoc --- prdoc/pr_4157.prdoc | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 prdoc/pr_4157.prdoc diff --git a/prdoc/pr_4157.prdoc b/prdoc/pr_4157.prdoc new file mode 100644 index 000000000000..7c481192ccee --- /dev/null +++ b/prdoc/pr_4157.prdoc @@ -0,0 +1,29 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "Bridge: added free headers submission support to the substrate-relay" + +doc: + - audience: Node Dev + description: | + Bridge finality and parachains relayer now supports mode, where it only submits some headers + for free. There's a setting in a runtime configuration, which introduces this "free header" + concept. Submitting such header is considered a common good deed, so it is free for relayers. + +crates: + - bp-bridge-hub-kusama + major + - bp-bridge-hub-polkadot + major + - bp-bridge-hub-rococo + major + - bp-bridge-hub-westend + major + - relay-substrate-client + major + - finality-relay + major + - substrate-relay-helper + major + - parachains-relay + major From 66e2070822f987164ba8ab4f169349cc7e3726e6 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 25 Apr 2024 09:23:30 +0300 Subject: [PATCH 69/70] fix prdoc --- prdoc/pr_4157.prdoc | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/prdoc/pr_4157.prdoc b/prdoc/pr_4157.prdoc index 7c481192ccee..acb2fc08f0b0 100644 --- a/prdoc/pr_4157.prdoc +++ b/prdoc/pr_4157.prdoc @@ -12,18 +12,18 @@ doc: crates: - bp-bridge-hub-kusama - major + bump: major - bp-bridge-hub-polkadot - major + bump: major - bp-bridge-hub-rococo - major + bump: major - bp-bridge-hub-westend - major + bump: major - relay-substrate-client - major + bump: major - finality-relay - major + bump: major - substrate-relay-helper - major + bump: major - parachains-relay - major + bump: major From 6ed7cb4165e547c0e8f6f12a9aaacd39e760ea01 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 25 Apr 2024 09:25:57 +0300 Subject: [PATCH 70/70] another fix --- prdoc/pr_4157.prdoc | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/prdoc/pr_4157.prdoc b/prdoc/pr_4157.prdoc index acb2fc08f0b0..783eaa2dd427 100644 --- a/prdoc/pr_4157.prdoc +++ b/prdoc/pr_4157.prdoc @@ -11,19 +11,19 @@ doc: concept. Submitting such header is considered a common good deed, so it is free for relayers. crates: - - bp-bridge-hub-kusama + - name: bp-bridge-hub-kusama bump: major - - bp-bridge-hub-polkadot + - name: bp-bridge-hub-polkadot bump: major - - bp-bridge-hub-rococo + - name: bp-bridge-hub-rococo bump: major - - bp-bridge-hub-westend + - name: bp-bridge-hub-westend bump: major - - relay-substrate-client + - name: relay-substrate-client bump: major - - finality-relay + - name: finality-relay bump: major - - substrate-relay-helper + - name: substrate-relay-helper bump: major - - parachains-relay + - name: parachains-relay bump: major