Skip to content

Commit

Permalink
"refund" proof size in GRANDPa pallet (paritytech#1863)
Browse files Browse the repository at this point in the history
* "refund" proof size in GRANDPa pallet

* clippy

* extra_proof_size_bytes_works

* use saturated_into

* fix review comments
  • Loading branch information
svyatonik authored and serban300 committed Apr 8, 2024
1 parent 342bfb6 commit bb87847
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 8 deletions.
45 changes: 39 additions & 6 deletions bridges/modules/grandpa/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,12 @@ use bp_header_chain::{
};
use bp_runtime::{BlockNumberOf, Chain, HashOf, HasherOf, HeaderId, HeaderOf, OwnedBridgeModule};
use finality_grandpa::voter_set::VoterSet;
use frame_support::{ensure, fail};
use frame_support::{dispatch::PostDispatchInfo, ensure, fail};
use sp_finality_grandpa::{ConsensusLog, GRANDPA_ENGINE_ID};
use sp_runtime::traits::{Header as HeaderT, Zero};
use sp_runtime::{
traits::{Header as HeaderT, Zero},
SaturatedConversion,
};
use sp_std::{boxed::Box, convert::TryInto};

mod extension;
Expand Down Expand Up @@ -152,8 +155,8 @@ pub mod pallet {
/// pallet.
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::submit_finality_proof(
justification.commit.precommits.len().try_into().unwrap_or(u32::MAX),
justification.votes_ancestries.len().try_into().unwrap_or(u32::MAX),
justification.commit.precommits.len().saturated_into(),
justification.votes_ancestries.len().saturated_into(),
))]
pub fn submit_finality_proof(
_origin: OriginFor<T>,
Expand Down Expand Up @@ -189,6 +192,7 @@ pub mod pallet {
ensure!(best_finalized_number < *number, <Error<T, I>>::OldHeader);

let authority_set = <CurrentAuthoritySet<T, I>>::get();
let unused_proof_size = authority_set.unused_proof_size();
let set_id = authority_set.set_id;
verify_justification::<T, I>(&justification, hash, *number, authority_set.into())?;

Expand All @@ -210,7 +214,18 @@ pub mod pallet {
let is_mandatory_header = is_authorities_change_enacted;
let pays_fee = if is_mandatory_header { Pays::No } else { Pays::Yes };

Ok(pays_fee.into())
// 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`,
// making proof smaller and leaving block space to other useful transactions
let pre_dispatch_weight = T::WeightInfo::submit_finality_proof(
justification.commit.precommits.len().saturated_into(),
justification.votes_ancestries.len().saturated_into(),
);
let actual_weight = pre_dispatch_weight
.set_proof_size(pre_dispatch_weight.proof_size().saturating_sub(unused_proof_size));

Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee })
}

/// Bootstrap the bridge pallet with an initial header and authority set from which to sync.
Expand Down Expand Up @@ -819,9 +834,27 @@ mod tests {
fn succesfully_imports_header_with_valid_finality() {
run_test(|| {
initialize_substrate_bridge();
let result = submit_finality_proof(1);

let header_number = 1;
let header = test_header(header_number.into());
let justification = make_default_justification(&header);

let pre_dispatch_weight = <TestRuntime as Config>::WeightInfo::submit_finality_proof(
justification.commit.precommits.len().try_into().unwrap_or(u32::MAX),
justification.votes_ancestries.len().try_into().unwrap_or(u32::MAX),
);

let result = submit_finality_proof(header_number);
assert_ok!(result);
assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::Yes);
// our test config assumes 2048 max authorities and we are just using couple
let pre_dispatch_proof_size = pre_dispatch_weight.proof_size();
let actual_proof_size = result.unwrap().actual_weight.unwrap().proof_size();
assert!(actual_proof_size > 0);
assert!(
actual_proof_size < pre_dispatch_proof_size,
"Actual proof size {actual_proof_size} must be less than the pre-dispatch {pre_dispatch_proof_size}",
);

let header = test_header(1);
assert_eq!(<BestFinalized<TestRuntime>>::get().unwrap().1, header.hash());
Expand Down
2 changes: 1 addition & 1 deletion bridges/modules/grandpa/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pub type TestNumber = crate::BridgedBlockNumber<TestRuntime, ()>;
type Block = frame_system::mocking::MockBlock<TestRuntime>;
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<TestRuntime>;

pub const MAX_BRIDGED_AUTHORITIES: u32 = 2048;
pub const MAX_BRIDGED_AUTHORITIES: u32 = 5;

use crate as grandpa;

Expand Down
58 changes: 57 additions & 1 deletion bridges/modules/grandpa/src/storage_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::Config;

use bp_header_chain::AuthoritySet;
use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::{BoundedVec, RuntimeDebugNoBound};
use frame_support::{traits::Get, BoundedVec, RuntimeDebugNoBound};
use scale_info::TypeInfo;
use sp_finality_grandpa::{AuthorityId, AuthorityList, AuthorityWeight, SetId};

Expand All @@ -45,6 +45,24 @@ impl<T: Config<I>, I: 'static> StoredAuthoritySet<T, I> {
pub fn try_new(authorities: AuthorityList, set_id: SetId) -> Result<Self, ()> {
Ok(Self { authorities: TryFrom::try_from(authorities).map_err(drop)?, set_id })
}

/// Returns number of bytes that may be subtracted from the PoV component of
/// `submit_finality_proof` call, because the actual authorities set is smaller than the maximal
/// configured.
///
/// Maximal authorities set size is configured by the `MaxBridgedAuthorities` constant from
/// the pallet configuration. The PoV of the call includes the size of maximal authorities
/// count. If the actual size is smaller, we may subtract extra bytes from this component.
pub fn unused_proof_size(&self) -> u64 {
// we can only safely estimate bytes that are occupied by the authority data itself. We have
// no means here to compute PoV bytes, occupied by extra trie nodes or extra bytes in the
// whole set encoding
let single_authority_max_encoded_len =
<(AuthorityId, AuthorityWeight)>::max_encoded_len() as u64;
let extra_authorities =
T::MaxBridgedAuthorities::get().saturating_sub(self.authorities.len() as _);
single_authority_max_encoded_len.saturating_mul(extra_authorities as u64)
}
}

impl<T: Config<I>, I: 'static> PartialEq for StoredAuthoritySet<T, I> {
Expand All @@ -64,3 +82,41 @@ impl<T: Config<I>, I: 'static> From<StoredAuthoritySet<T, I>> for AuthoritySet {
AuthoritySet { authorities: t.authorities.into(), set_id: t.set_id }
}
}

#[cfg(test)]
mod tests {
use crate::mock::{TestRuntime, MAX_BRIDGED_AUTHORITIES};
use bp_test_utils::authority_list;

type StoredAuthoritySet = super::StoredAuthoritySet<TestRuntime, ()>;

#[test]
fn unused_proof_size_works() {
let authority_entry = authority_list().pop().unwrap();

// when we have exactly `MaxBridgedAuthorities` authorities
assert_eq!(
StoredAuthoritySet::try_new(
vec![authority_entry.clone(); MAX_BRIDGED_AUTHORITIES as usize],
0,
)
.unwrap()
.unused_proof_size(),
0,
);

// when we have less than `MaxBridgedAuthorities` authorities
assert_eq!(
StoredAuthoritySet::try_new(
vec![authority_entry; MAX_BRIDGED_AUTHORITIES as usize - 1],
0,
)
.unwrap()
.unused_proof_size(),
40,
);

// and we can't have more than `MaxBridgedAuthorities` authorities in the bounded vec, so
// no test for this case
}
}

0 comments on commit bb87847

Please sign in to comment.