Skip to content

Commit

Permalink
Select header that will be fully refunded in on-demand batch finality…
Browse files Browse the repository at this point in the history
… relay (#2729)

* select header that will be fully refunded for submission in on-demand **batch** finality relay

* added the only possible test

* spelling

* nl

* updated comment
  • Loading branch information
svyatonik committed Dec 6, 2023
1 parent ce701dd commit 390e836
Show file tree
Hide file tree
Showing 16 changed files with 223 additions and 115 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions bin/runtime-common/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -376,9 +376,8 @@ impl ChainWithGrandpa for BridgedUnderlyingChain {
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = "";
const MAX_AUTHORITIES_COUNT: u32 = 16;
const REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY: u32 = 8;
const MAX_HEADER_SIZE: u32 = 256;
const AVERAGE_HEADER_SIZE_IN_JUSTIFICATION: u32 = 64;
const WORST_HEADER_SIZE_IN_JUSTIFICATION: u32 = 64;
const MAX_MANDATORY_HEADER_SIZE: u32 = 256;
const AVERAGE_HEADER_SIZE: u32 = 64;
}

impl Chain for BridgedUnderlyingParachain {
Expand Down
29 changes: 16 additions & 13 deletions modules/grandpa/src/call_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.

use crate::{weights::WeightInfo, BridgedBlockNumber, BridgedHeader, Config, Error, Pallet};
use bp_header_chain::{justification::GrandpaJustification, ChainWithGrandpa};
use bp_header_chain::{
justification::GrandpaJustification, max_expected_submit_finality_proof_arguments_size,
ChainWithGrandpa, GrandpaConsensusLogReader,
};
use bp_runtime::{BlockNumberOf, OwnedBridgeModule};
use codec::Encode;
use frame_support::{dispatch::CallableCallFor, traits::IsSubType, weights::Weight};
Expand Down Expand Up @@ -169,28 +172,28 @@ pub(crate) fn submit_finality_proof_info_from_args<T: Config<I>, I: 'static>(
Weight::zero()
};

// check if the `finality_target` is a mandatory header. If so, we are ready to refund larger
// size
let is_mandatory_finality_target =
GrandpaConsensusLogReader::<BridgedBlockNumber<T, I>>::find_scheduled_change(
finality_target.digest(),
)
.is_some();

// we can estimate extra call size easily, without any additional significant overhead
let actual_call_size: u32 = finality_target
.encoded_size()
.saturating_add(justification.encoded_size())
.saturated_into();
let max_expected_call_size = max_expected_call_size::<T, I>(required_precommits);
let max_expected_call_size = max_expected_submit_finality_proof_arguments_size::<T::BridgedChain>(
is_mandatory_finality_target,
required_precommits,
);
let extra_size = actual_call_size.saturating_sub(max_expected_call_size);

SubmitFinalityProofInfo { block_number, extra_weight, extra_size }
}

/// Returns maximal expected size of `submit_finality_proof` call arguments.
fn max_expected_call_size<T: Config<I>, I: 'static>(required_precommits: u32) -> u32 {
let max_expected_justification_size =
GrandpaJustification::<BridgedHeader<T, I>>::max_reasonable_size::<T::BridgedChain>(
required_precommits,
);

// call arguments are header and justification
T::BridgedChain::MAX_HEADER_SIZE.saturating_add(max_expected_justification_size)
}

#[cfg(test)]
mod tests {
use crate::{
Expand Down
5 changes: 2 additions & 3 deletions modules/grandpa/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,8 @@ impl ChainWithGrandpa for TestBridgedChain {
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = "";
const MAX_AUTHORITIES_COUNT: u32 = MAX_BRIDGED_AUTHORITIES;
const REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY: u32 = 8;
const MAX_HEADER_SIZE: u32 = 256;
const AVERAGE_HEADER_SIZE_IN_JUSTIFICATION: u32 = 64;
const WORST_HEADER_SIZE_IN_JUSTIFICATION: u32 = 64;
const MAX_MANDATORY_HEADER_SIZE: u32 = 256;
const AVERAGE_HEADER_SIZE: u32 = 64;
}

/// Return test externalities to use in tests.
Expand Down
10 changes: 4 additions & 6 deletions modules/parachains/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,9 +252,8 @@ impl ChainWithGrandpa for TestBridgedChain {
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = "";
const MAX_AUTHORITIES_COUNT: u32 = 16;
const REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY: u32 = 8;
const MAX_HEADER_SIZE: u32 = 256;
const AVERAGE_HEADER_SIZE_IN_JUSTIFICATION: u32 = 64;
const WORST_HEADER_SIZE_IN_JUSTIFICATION: u32 = 64;
const MAX_MANDATORY_HEADER_SIZE: u32 = 256;
const AVERAGE_HEADER_SIZE: u32 = 64;
}

#[derive(Debug)]
Expand Down Expand Up @@ -284,9 +283,8 @@ impl ChainWithGrandpa for OtherBridgedChain {
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = "";
const MAX_AUTHORITIES_COUNT: u32 = 16;
const REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY: u32 = 8;
const MAX_HEADER_SIZE: u32 = 256;
const AVERAGE_HEADER_SIZE_IN_JUSTIFICATION: u32 = 64;
const WORST_HEADER_SIZE_IN_JUSTIFICATION: u32 = 64;
const MAX_MANDATORY_HEADER_SIZE: u32 = 256;
const AVERAGE_HEADER_SIZE: u32 = 64;
}

/// Return test externalities to use in tests.
Expand Down
5 changes: 2 additions & 3 deletions primitives/chain-kusama/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,8 @@ impl ChainWithGrandpa for Kusama {
const MAX_AUTHORITIES_COUNT: u32 = MAX_AUTHORITIES_COUNT;
const REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY: u32 =
REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY;
const MAX_HEADER_SIZE: u32 = MAX_HEADER_SIZE;
const AVERAGE_HEADER_SIZE_IN_JUSTIFICATION: u32 = AVERAGE_HEADER_SIZE_IN_JUSTIFICATION;
const WORST_HEADER_SIZE_IN_JUSTIFICATION: u32 = WORST_HEADER_SIZE_IN_JUSTIFICATION;
const MAX_MANDATORY_HEADER_SIZE: u32 = MAX_MANDATORY_HEADER_SIZE;
const AVERAGE_HEADER_SIZE: u32 = AVERAGE_HEADER_SIZE;
}

// The SignedExtension used by Kusama.
Expand Down
10 changes: 4 additions & 6 deletions primitives/chain-polkadot-bulletin/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,8 @@ use sp_runtime::{traits::DispatchInfoOf, transaction_validity::TransactionValidi
// This chain reuses most of Polkadot primitives.
pub use bp_polkadot_core::{
AccountAddress, AccountId, Balance, Block, BlockNumber, Hash, Hasher, Header, Nonce, Signature,
SignedBlock, UncheckedExtrinsic, AVERAGE_HEADER_SIZE_IN_JUSTIFICATION,
EXTRA_STORAGE_PROOF_SIZE, MAX_HEADER_SIZE, REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY,
WORST_HEADER_SIZE_IN_JUSTIFICATION,
SignedBlock, UncheckedExtrinsic, AVERAGE_HEADER_SIZE, EXTRA_STORAGE_PROOF_SIZE,
MAX_MANDATORY_HEADER_SIZE, REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY,
};

/// Maximal number of GRANDPA authorities at Polkadot Bulletin chain.
Expand Down Expand Up @@ -208,9 +207,8 @@ impl ChainWithGrandpa for PolkadotBulletin {
const MAX_AUTHORITIES_COUNT: u32 = MAX_AUTHORITIES_COUNT;
const REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY: u32 =
REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY;
const MAX_HEADER_SIZE: u32 = MAX_HEADER_SIZE;
const AVERAGE_HEADER_SIZE_IN_JUSTIFICATION: u32 = AVERAGE_HEADER_SIZE_IN_JUSTIFICATION;
const WORST_HEADER_SIZE_IN_JUSTIFICATION: u32 = WORST_HEADER_SIZE_IN_JUSTIFICATION;
const MAX_MANDATORY_HEADER_SIZE: u32 = MAX_MANDATORY_HEADER_SIZE;
const AVERAGE_HEADER_SIZE: u32 = AVERAGE_HEADER_SIZE;
}

decl_bridge_finality_runtime_apis!(polkadot_bulletin, grandpa);
Expand Down
5 changes: 2 additions & 3 deletions primitives/chain-polkadot/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,8 @@ impl ChainWithGrandpa for Polkadot {
const MAX_AUTHORITIES_COUNT: u32 = MAX_AUTHORITIES_COUNT;
const REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY: u32 =
REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY;
const MAX_HEADER_SIZE: u32 = MAX_HEADER_SIZE;
const AVERAGE_HEADER_SIZE_IN_JUSTIFICATION: u32 = AVERAGE_HEADER_SIZE_IN_JUSTIFICATION;
const WORST_HEADER_SIZE_IN_JUSTIFICATION: u32 = WORST_HEADER_SIZE_IN_JUSTIFICATION;
const MAX_MANDATORY_HEADER_SIZE: u32 = MAX_MANDATORY_HEADER_SIZE;
const AVERAGE_HEADER_SIZE: u32 = AVERAGE_HEADER_SIZE;
}

/// The SignedExtension used by Polkadot.
Expand Down
5 changes: 2 additions & 3 deletions primitives/chain-rococo/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,8 @@ impl ChainWithGrandpa for Rococo {
const MAX_AUTHORITIES_COUNT: u32 = MAX_AUTHORITIES_COUNT;
const REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY: u32 =
REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY;
const MAX_HEADER_SIZE: u32 = MAX_HEADER_SIZE;
const AVERAGE_HEADER_SIZE_IN_JUSTIFICATION: u32 = AVERAGE_HEADER_SIZE_IN_JUSTIFICATION;
const WORST_HEADER_SIZE_IN_JUSTIFICATION: u32 = WORST_HEADER_SIZE_IN_JUSTIFICATION;
const MAX_MANDATORY_HEADER_SIZE: u32 = MAX_MANDATORY_HEADER_SIZE;
const AVERAGE_HEADER_SIZE: u32 = AVERAGE_HEADER_SIZE;
}

parameter_types! {
Expand Down
5 changes: 2 additions & 3 deletions primitives/chain-westend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,8 @@ impl ChainWithGrandpa for Westend {
const MAX_AUTHORITIES_COUNT: u32 = MAX_AUTHORITIES_COUNT;
const REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY: u32 =
REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY;
const MAX_HEADER_SIZE: u32 = MAX_HEADER_SIZE;
const AVERAGE_HEADER_SIZE_IN_JUSTIFICATION: u32 = AVERAGE_HEADER_SIZE_IN_JUSTIFICATION;
const WORST_HEADER_SIZE_IN_JUSTIFICATION: u32 = WORST_HEADER_SIZE_IN_JUSTIFICATION;
const MAX_MANDATORY_HEADER_SIZE: u32 = MAX_MANDATORY_HEADER_SIZE;
const AVERAGE_HEADER_SIZE: u32 = AVERAGE_HEADER_SIZE;
}

parameter_types! {
Expand Down
4 changes: 2 additions & 2 deletions primitives/header-chain/src/justification/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ impl<H: HeaderT> GrandpaJustification<H> {
.saturating_add(BlockNumberOf::<C>::max_encoded_len().saturated_into())
.saturating_add(HashOf::<C>::max_encoded_len().saturated_into());

let max_expected_votes_ancestries_size = C::REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY
.saturating_mul(C::AVERAGE_HEADER_SIZE_IN_JUSTIFICATION);
let max_expected_votes_ancestries_size =
C::REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY.saturating_mul(C::AVERAGE_HEADER_SIZE);

// justification is round number (u64=8b), a signed GRANDPA commit and the
// `votes_ancestries` vector
Expand Down
106 changes: 78 additions & 28 deletions primitives/header-chain/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,36 +266,28 @@ pub trait ChainWithGrandpa: Chain {
/// to submitter.
const REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY: u32;

/// Maximal size of the chain header. The header may be the header that enacts new GRANDPA
/// authorities set (so it has large digest inside).
/// Maximal size of the mandatory chain header. Mandatory header is the header that enacts new
/// GRANDPA authorities set (so it has large digest inside).
///
/// This isn't a strict limit. The relay may submit larger headers and the pallet will accept
/// the call. The limit is only used to compute maximal refund amount and doing calls which
/// exceed the limit, may be costly to submitter.
const MAX_HEADER_SIZE: u32;
const MAX_MANDATORY_HEADER_SIZE: u32;

/// Average size of the chain header from justification ancestry. We don't expect to see there
/// headers that change GRANDPA authorities set (GRANDPA will probably be able to finalize at
/// least one additional header per session on non test chains), so this is average size of
/// headers that aren't changing the set.
/// Average size of the chain header. We don't expect to see there headers that change GRANDPA
/// authorities set (GRANDPA will probably be able to finalize at least one additional header
/// per session on non test chains), so this is average size of headers that aren't changing the
/// set.
///
/// This isn't a strict limit. The relay may submit justifications with larger headers in its
/// ancestry and the pallet will accept the call. The limit is only used to compute fee, paid
/// by the user at the sending chain. It covers most of cases, but if the actual header,
/// submitted with the messages transaction will be larger than the
/// `AVERAGE_HEADER_SIZE_IN_JUSTIFICATION`, the difference (`WORST_HEADER_SIZE_IN_JUSTIFICATION`
/// - `AVERAGE_HEADER_SIZE_IN_JUSTIFICATION`) must be covered by the sending chain.
const AVERAGE_HEADER_SIZE_IN_JUSTIFICATION: u32;

/// Worst-case size of the chain header from justification ancestry. We don't expect to see
/// there headers that change GRANDPA authorities set (GRANDPA will probably be able to finalize
/// at least one additional header per session on non test chains), so this is the worst-case
/// size of headers that aren't changing the set.
/// This isn't a strict limit. The relay may submit justifications with larger headers and the
/// pallet will accept the call. However, if the total size of all `submit_finality_proof`
/// arguments exceeds the maximal size, computed using this average size, relayer will only get
/// partial refund.
///
/// This isn't a strict limit. The relay may submit justifications with larger headers in its
/// ancestry and the pallet will accept the call. The limit is only used to compute maximal
/// refund amount and doing calls which exceed the limit, may be costly to submitter.
const WORST_HEADER_SIZE_IN_JUSTIFICATION: u32;
/// We expect some headers on production chains that are above this size. But they are rare and
/// if rellayer cares about its profitability, we expect it'll select other headers for
/// submission.
const AVERAGE_HEADER_SIZE: u32;
}

impl<T> ChainWithGrandpa for T
Expand All @@ -308,9 +300,67 @@ where
const MAX_AUTHORITIES_COUNT: u32 = <T::Chain as ChainWithGrandpa>::MAX_AUTHORITIES_COUNT;
const REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY: u32 =
<T::Chain as ChainWithGrandpa>::REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY;
const MAX_HEADER_SIZE: u32 = <T::Chain as ChainWithGrandpa>::MAX_HEADER_SIZE;
const AVERAGE_HEADER_SIZE_IN_JUSTIFICATION: u32 =
<T::Chain as ChainWithGrandpa>::AVERAGE_HEADER_SIZE_IN_JUSTIFICATION;
const WORST_HEADER_SIZE_IN_JUSTIFICATION: u32 =
<T::Chain as ChainWithGrandpa>::WORST_HEADER_SIZE_IN_JUSTIFICATION;
const MAX_MANDATORY_HEADER_SIZE: u32 =
<T::Chain as ChainWithGrandpa>::MAX_MANDATORY_HEADER_SIZE;
const AVERAGE_HEADER_SIZE: u32 = <T::Chain as ChainWithGrandpa>::AVERAGE_HEADER_SIZE;
}

/// Returns maximal expected size of `submit_finality_proof` call arguments.
pub fn max_expected_submit_finality_proof_arguments_size<C: ChainWithGrandpa>(
is_mandatory_finality_target: bool,
precommits: u32,
) -> u32 {
let max_expected_justification_size =
GrandpaJustification::<HeaderOf<C>>::max_reasonable_size::<C>(precommits);

// call arguments are header and justification
let max_expected_finality_target_size = if is_mandatory_finality_target {
C::MAX_MANDATORY_HEADER_SIZE
} else {
C::AVERAGE_HEADER_SIZE
};
max_expected_finality_target_size.saturating_add(max_expected_justification_size)
}

#[cfg(test)]
mod tests {
use super::*;
use frame_support::weights::Weight;
use sp_runtime::{testing::H256, traits::BlakeTwo256, MultiSignature};

struct TestChain;

impl Chain for TestChain {
type BlockNumber = u32;
type Hash = H256;
type Hasher = BlakeTwo256;
type Header = sp_runtime::generic::Header<u32, BlakeTwo256>;
type AccountId = u64;
type Balance = u64;
type Nonce = u64;
type Signature = MultiSignature;

fn max_extrinsic_size() -> u32 {
0
}
fn max_extrinsic_weight() -> Weight {
Weight::zero()
}
}

impl ChainWithGrandpa for TestChain {
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = "Test";
const MAX_AUTHORITIES_COUNT: u32 = 128;
const REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY: u32 = 2;
const MAX_MANDATORY_HEADER_SIZE: u32 = 100_000;
const AVERAGE_HEADER_SIZE: u32 = 1_024;
}

#[test]
fn max_expected_submit_finality_proof_arguments_size_respects_mandatory_argument() {
assert!(
max_expected_submit_finality_proof_arguments_size::<TestChain>(true, 100) >
max_expected_submit_finality_proof_arguments_size::<TestChain>(false, 100),
);
}
}
25 changes: 7 additions & 18 deletions primitives/polkadot-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ pub const MAX_AUTHORITIES_COUNT: u32 = 1_256;
///
/// See [`bp-header-chain::ChainWithGrandpa`] for more details.
///
/// This value comes from recent (February, 2023) Kusama and Polkadot headers. There are no
/// This value comes from recent (December, 2023) Kusama and Polkadot headers. There are no
/// justifications with any additional headers in votes ancestry, so reasonable headers may
/// be set to zero. But we assume that there may be small GRANDPA lags, so we're leaving some
/// reserve here.
Expand All @@ -75,28 +75,17 @@ pub const REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY: u32 = 2;
///
/// See [`bp-header-chain::ChainWithGrandpa`] for more details.
///
/// This value comes from recent (February, 2023) Kusama headers. Average is `336` there, but let's
/// have some reserve and make it 1024.
pub const AVERAGE_HEADER_SIZE_IN_JUSTIFICATION: u32 = 1024;

/// Worst-case header size in `votes_ancestries` field of justification on Polkadot-like
/// chains.
///
/// See [`bp-header-chain::ChainWithGrandpa`] for more details.
///
/// This value comes from recent (February, 2023) Kusama headers. Average is `336` there, but some
/// non-mandatory headers has size `40kb` (they contain the BABE epoch descriptor with all
/// authorities - just like our mandatory header). Since we assume `2` headers in justification
/// votes ancestry, let's set average header to `40kb / 2`.
pub const WORST_HEADER_SIZE_IN_JUSTIFICATION: u32 = 20 * 1024;
/// This value comes from recent (December, 2023) Kusama headers. Most of headers are `327` bytes
/// there, but let's have some reserve and make it 1024.
pub const AVERAGE_HEADER_SIZE: u32 = 1024;

/// Approximate maximal header size on Polkadot-like chains.
///
/// See [`bp-header-chain::ChainWithGrandpa`] for more details.
///
/// This value comes from recent (February, 2023) Kusama headers. Maximal header is a mandatory
/// header. In its SCALE-encoded form it is `80348` bytes. Let's have some reserve here.
pub const MAX_HEADER_SIZE: u32 = 90_000;
/// This value comes from recent (December, 2023) Kusama headers. Maximal header is a mandatory
/// header. In its SCALE-encoded form it is `113407` bytes. Let's have some reserve here.
pub const MAX_MANDATORY_HEADER_SIZE: u32 = 120 * 1024;

/// Number of extra bytes (excluding size of storage value itself) of storage proof, built at
/// Polkadot-like chain. This mostly depends on number of entries in the storage trie.
Expand Down
1 change: 1 addition & 0 deletions relays/bin-substrate/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ anyhow = "1.0"
async-std = "1.9.0"
async-trait = "0.1"
codec = { package = "parity-scale-codec", version = "3.1.5" }
env_logger = "0.10"
futures = "0.3.29"
hex = "0.4"
log = "0.4.20"
Expand Down

0 comments on commit 390e836

Please sign in to comment.