Skip to content

Commit 662267a

Browse files
authored
"refund" proof size in GRANDPa pallet (#1863)
* "refund" proof size in GRANDPa pallet * clippy * extra_proof_size_bytes_works * use saturated_into * fix review comments
1 parent 4c4a7ea commit 662267a

File tree

3 files changed

+97
-8
lines changed

3 files changed

+97
-8
lines changed

modules/grandpa/src/lib.rs

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,12 @@ use bp_header_chain::{
4444
};
4545
use bp_runtime::{BlockNumberOf, Chain, HashOf, HasherOf, HeaderId, HeaderOf, OwnedBridgeModule};
4646
use finality_grandpa::voter_set::VoterSet;
47-
use frame_support::{ensure, fail};
47+
use frame_support::{dispatch::PostDispatchInfo, ensure, fail};
4848
use sp_finality_grandpa::{ConsensusLog, GRANDPA_ENGINE_ID};
49-
use sp_runtime::traits::{Header as HeaderT, Zero};
49+
use sp_runtime::{
50+
traits::{Header as HeaderT, Zero},
51+
SaturatedConversion,
52+
};
5053
use sp_std::{boxed::Box, convert::TryInto};
5154

5255
mod extension;
@@ -152,8 +155,8 @@ pub mod pallet {
152155
/// pallet.
153156
#[pallet::call_index(0)]
154157
#[pallet::weight(T::WeightInfo::submit_finality_proof(
155-
justification.commit.precommits.len().try_into().unwrap_or(u32::MAX),
156-
justification.votes_ancestries.len().try_into().unwrap_or(u32::MAX),
158+
justification.commit.precommits.len().saturated_into(),
159+
justification.votes_ancestries.len().saturated_into(),
157160
))]
158161
pub fn submit_finality_proof(
159162
_origin: OriginFor<T>,
@@ -189,6 +192,7 @@ pub mod pallet {
189192
ensure!(best_finalized_number < *number, <Error<T, I>>::OldHeader);
190193

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

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

213-
Ok(pays_fee.into())
217+
// the proof size component of the call weight assumes that there are
218+
// `MaxBridgedAuthorities` in the `CurrentAuthoritySet` (we use `MaxEncodedLen`
219+
// estimation). But if their number is lower, then we may "refund" some `proof_size`,
220+
// making proof smaller and leaving block space to other useful transactions
221+
let pre_dispatch_weight = T::WeightInfo::submit_finality_proof(
222+
justification.commit.precommits.len().saturated_into(),
223+
justification.votes_ancestries.len().saturated_into(),
224+
);
225+
let actual_weight = pre_dispatch_weight
226+
.set_proof_size(pre_dispatch_weight.proof_size().saturating_sub(unused_proof_size));
227+
228+
Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee })
214229
}
215230

216231
/// Bootstrap the bridge pallet with an initial header and authority set from which to sync.
@@ -819,9 +834,27 @@ mod tests {
819834
fn succesfully_imports_header_with_valid_finality() {
820835
run_test(|| {
821836
initialize_substrate_bridge();
822-
let result = submit_finality_proof(1);
837+
838+
let header_number = 1;
839+
let header = test_header(header_number.into());
840+
let justification = make_default_justification(&header);
841+
842+
let pre_dispatch_weight = <TestRuntime as Config>::WeightInfo::submit_finality_proof(
843+
justification.commit.precommits.len().try_into().unwrap_or(u32::MAX),
844+
justification.votes_ancestries.len().try_into().unwrap_or(u32::MAX),
845+
);
846+
847+
let result = submit_finality_proof(header_number);
823848
assert_ok!(result);
824849
assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::Yes);
850+
// our test config assumes 2048 max authorities and we are just using couple
851+
let pre_dispatch_proof_size = pre_dispatch_weight.proof_size();
852+
let actual_proof_size = result.unwrap().actual_weight.unwrap().proof_size();
853+
assert!(actual_proof_size > 0);
854+
assert!(
855+
actual_proof_size < pre_dispatch_proof_size,
856+
"Actual proof size {actual_proof_size} must be less than the pre-dispatch {pre_dispatch_proof_size}",
857+
);
825858

826859
let header = test_header(1);
827860
assert_eq!(<BestFinalized<TestRuntime>>::get().unwrap().1, header.hash());

modules/grandpa/src/mock.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ pub type TestNumber = crate::BridgedBlockNumber<TestRuntime, ()>;
3333
type Block = frame_system::mocking::MockBlock<TestRuntime>;
3434
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<TestRuntime>;
3535

36-
pub const MAX_BRIDGED_AUTHORITIES: u32 = 2048;
36+
pub const MAX_BRIDGED_AUTHORITIES: u32 = 5;
3737

3838
use crate as grandpa;
3939

modules/grandpa/src/storage_types.rs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use crate::Config;
2020

2121
use bp_header_chain::AuthoritySet;
2222
use codec::{Decode, Encode, MaxEncodedLen};
23-
use frame_support::{BoundedVec, RuntimeDebugNoBound};
23+
use frame_support::{traits::Get, BoundedVec, RuntimeDebugNoBound};
2424
use scale_info::TypeInfo;
2525
use sp_finality_grandpa::{AuthorityId, AuthorityList, AuthorityWeight, SetId};
2626

@@ -45,6 +45,24 @@ impl<T: Config<I>, I: 'static> StoredAuthoritySet<T, I> {
4545
pub fn try_new(authorities: AuthorityList, set_id: SetId) -> Result<Self, ()> {
4646
Ok(Self { authorities: TryFrom::try_from(authorities).map_err(drop)?, set_id })
4747
}
48+
49+
/// Returns number of bytes that may be subtracted from the PoV component of
50+
/// `submit_finality_proof` call, because the actual authorities set is smaller than the maximal
51+
/// configured.
52+
///
53+
/// Maximal authorities set size is configured by the `MaxBridgedAuthorities` constant from
54+
/// the pallet configuration. The PoV of the call includes the size of maximal authorities
55+
/// count. If the actual size is smaller, we may subtract extra bytes from this component.
56+
pub fn unused_proof_size(&self) -> u64 {
57+
// we can only safely estimate bytes that are occupied by the authority data itself. We have
58+
// no means here to compute PoV bytes, occupied by extra trie nodes or extra bytes in the
59+
// whole set encoding
60+
let single_authority_max_encoded_len =
61+
<(AuthorityId, AuthorityWeight)>::max_encoded_len() as u64;
62+
let extra_authorities =
63+
T::MaxBridgedAuthorities::get().saturating_sub(self.authorities.len() as _);
64+
single_authority_max_encoded_len.saturating_mul(extra_authorities as u64)
65+
}
4866
}
4967

5068
impl<T: Config<I>, I: 'static> PartialEq for StoredAuthoritySet<T, I> {
@@ -64,3 +82,41 @@ impl<T: Config<I>, I: 'static> From<StoredAuthoritySet<T, I>> for AuthoritySet {
6482
AuthoritySet { authorities: t.authorities.into(), set_id: t.set_id }
6583
}
6684
}
85+
86+
#[cfg(test)]
87+
mod tests {
88+
use crate::mock::{TestRuntime, MAX_BRIDGED_AUTHORITIES};
89+
use bp_test_utils::authority_list;
90+
91+
type StoredAuthoritySet = super::StoredAuthoritySet<TestRuntime, ()>;
92+
93+
#[test]
94+
fn unused_proof_size_works() {
95+
let authority_entry = authority_list().pop().unwrap();
96+
97+
// when we have exactly `MaxBridgedAuthorities` authorities
98+
assert_eq!(
99+
StoredAuthoritySet::try_new(
100+
vec![authority_entry.clone(); MAX_BRIDGED_AUTHORITIES as usize],
101+
0,
102+
)
103+
.unwrap()
104+
.unused_proof_size(),
105+
0,
106+
);
107+
108+
// when we have less than `MaxBridgedAuthorities` authorities
109+
assert_eq!(
110+
StoredAuthoritySet::try_new(
111+
vec![authority_entry; MAX_BRIDGED_AUTHORITIES as usize - 1],
112+
0,
113+
)
114+
.unwrap()
115+
.unused_proof_size(),
116+
40,
117+
);
118+
119+
// and we can't have more than `MaxBridgedAuthorities` authorities in the bounded vec, so
120+
// no test for this case
121+
}
122+
}

0 commit comments

Comments
 (0)