Introduce submit_finality_proof_ex call to bridges GRANDPA pallet (#3225)

backport of
https://github.com/paritytech/parity-bridges-common/pull/2821 (see
detailed description there)
This commit is contained in:
Svyatoslav Nikolsky
2024-02-06 16:11:27 +03:00
committed by GitHub
parent bc2e5e1fe2
commit a462207158
10 changed files with 580 additions and 133 deletions
+171 -107
View File
@@ -151,102 +151,31 @@ pub mod pallet {
#[pallet::call]
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Verify a target header is finalized according to the given finality proof.
///
/// It will use the underlying storage pallet to fetch information about the current
/// authorities and best finalized header in order to verify that the header is finalized.
///
/// If successful in verification, it will write the target header to the underlying storage
/// pallet.
///
/// The call fails if:
///
/// - the pallet is halted;
///
/// - the pallet knows better header than the `finality_target`;
///
/// - verification is not optimized or invalid;
///
/// - header contains forced authorities set change or change with non-zero delay.
/// This call is deprecated and will be removed around May 2024. Use the
/// `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(<T::WeightInfo as WeightInfo>::submit_finality_proof(
justification.commit.precommits.len().saturated_into(),
justification.votes_ancestries.len().saturated_into(),
))]
#[allow(deprecated)]
#[deprecated(
note = "`submit_finality_proof` will be removed in May 2024. Use `submit_finality_proof_ex` instead."
)]
pub fn submit_finality_proof(
origin: OriginFor<T>,
finality_target: Box<BridgedHeader<T, I>>,
justification: GrandpaJustification<BridgedHeader<T, I>>,
) -> DispatchResultWithPostInfo {
Self::ensure_not_halted().map_err(Error::<T, I>::BridgeModule)?;
ensure_signed(origin)?;
let (hash, number) = (finality_target.hash(), *finality_target.number());
log::trace!(
target: LOG_TARGET,
"Going to try and finalize header {:?}",
finality_target
);
SubmitFinalityProofHelper::<T, I>::check_obsolete(number)?;
let authority_set = <CurrentAuthoritySet<T, I>>::get();
let unused_proof_size = authority_set.unused_proof_size();
let set_id = authority_set.set_id;
let authority_set: AuthoritySet = authority_set.into();
verify_justification::<T, I>(&justification, hash, number, authority_set)?;
let maybe_new_authority_set =
try_enact_authority_change::<T, I>(&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::<T, I>(&finality_target, &justification)
.fits_limits();
if may_refund_call_fee {
FreeMandatoryHeadersRemaining::<T, I>::mutate(|count| {
*count = count.saturating_sub(1)
});
}
insert_header::<T, I>(*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.
//
// We don't want to charge extra costs for mandatory operations. So relayer is not
// paying fee for mandatory headers import transactions.
//
// If size/weight of the call is exceeds our estimated limits, the relayer still needs
// to pay for the transaction.
let pays_fee = if may_refund_call_fee { Pays::No } else { Pays::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`,
// 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));
Self::deposit_event(Event::UpdatedBestFinalizedHeader {
number,
hash,
grandpa_info: StoredHeaderGrandpaInfo {
finality_proof: justification,
new_verification_context: maybe_new_authority_set,
},
});
Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee })
Self::submit_finality_proof_ex(
origin,
finality_target,
justification,
// 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
<CurrentAuthoritySet<T, I>>::get().set_id,
)
}
/// Bootstrap the bridge pallet with an initial header and authority set from which to sync.
@@ -299,6 +228,111 @@ pub mod pallet {
) -> DispatchResult {
<Self as OwnedBridgeModule<_>>::set_operating_mode(origin, operating_mode)
}
/// Verify a target header is finalized according to the given finality proof. The proof
/// is assumed to be signed by GRANDPA authorities set with `current_set_id` id.
///
/// It will use the underlying storage pallet to fetch information about the current
/// authorities and best finalized header in order to verify that the header is finalized.
///
/// If successful in verification, it will write the target header to the underlying storage
/// pallet.
///
/// The call fails if:
///
/// - the pallet is halted;
///
/// - the pallet knows better header than the `finality_target`;
///
/// - the id of best GRANDPA authority set, known to the pallet is not equal to the
/// `current_set_id`;
///
/// - verification is not optimized or invalid;
///
/// - header contains forced authorities set change or change with non-zero delay.
#[pallet::call_index(4)]
#[pallet::weight(<T::WeightInfo as WeightInfo>::submit_finality_proof(
justification.commit.precommits.len().saturated_into(),
justification.votes_ancestries.len().saturated_into(),
))]
pub fn submit_finality_proof_ex(
origin: OriginFor<T>,
finality_target: Box<BridgedHeader<T, I>>,
justification: GrandpaJustification<BridgedHeader<T, I>>,
current_set_id: sp_consensus_grandpa::SetId,
) -> DispatchResultWithPostInfo {
Self::ensure_not_halted().map_err(Error::<T, I>::BridgeModule)?;
ensure_signed(origin)?;
let (hash, number) = (finality_target.hash(), *finality_target.number());
log::trace!(
target: LOG_TARGET,
"Going to try and finalize header {:?}",
finality_target
);
// 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::<T, I>::check_obsolete(number, Some(current_set_id))?;
let authority_set = <CurrentAuthoritySet<T, I>>::get();
let unused_proof_size = authority_set.unused_proof_size();
let set_id = authority_set.set_id;
let authority_set: AuthoritySet = authority_set.into();
verify_justification::<T, I>(&justification, hash, number, authority_set)?;
let maybe_new_authority_set =
try_enact_authority_change::<T, I>(&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::<T, I>(&finality_target, &justification, Some(current_set_id))
.fits_limits();
if may_refund_call_fee {
FreeMandatoryHeadersRemaining::<T, I>::mutate(|count| {
*count = count.saturating_sub(1)
});
}
insert_header::<T, I>(*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.
//
// We don't want to charge extra costs for mandatory operations. So relayer is not
// paying fee for mandatory headers import transactions.
//
// If size/weight of the call is exceeds our estimated limits, the relayer still needs
// to pay for the transaction.
let pays_fee = if may_refund_call_fee { Pays::No } else { Pays::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`,
// 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));
Self::deposit_event(Event::UpdatedBestFinalizedHeader {
number,
hash,
grandpa_info: StoredHeaderGrandpaInfo {
finality_proof: justification,
new_verification_context: maybe_new_authority_set,
},
});
Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee })
}
}
/// Number mandatory headers that we may accept in the current block for free (returning
@@ -436,6 +470,9 @@ pub mod pallet {
TooManyAuthoritiesInSet,
/// Error generated by the `OwnedBridgeModule` trait.
BridgeModule(bp_runtime::OwnedBridgeModuleError),
/// 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,
}
/// Check the given header for a GRANDPA scheduled authority set change. If a change
@@ -663,6 +700,7 @@ mod tests {
use bp_test_utils::{
authority_list, generate_owned_bridge_module_tests, make_default_justification,
make_justification_for_header, JustificationGeneratorParams, ALICE, BOB,
TEST_GRANDPA_SET_ID,
};
use codec::Encode;
use frame_support::{
@@ -693,7 +731,7 @@ mod tests {
let init_data = InitializationData {
header: Box::new(genesis),
authority_list: authority_list(),
set_id: 1,
set_id: TEST_GRANDPA_SET_ID,
operating_mode: BasicOperatingMode::Normal,
};
@@ -704,10 +742,11 @@ mod tests {
let header = test_header(header.into());
let justification = make_default_justification(&header);
Pallet::<TestRuntime>::submit_finality_proof(
Pallet::<TestRuntime>::submit_finality_proof_ex(
RuntimeOrigin::signed(1),
Box::new(header),
justification,
TEST_GRANDPA_SET_ID,
)
}
@@ -722,10 +761,11 @@ mod tests {
..Default::default()
});
Pallet::<TestRuntime>::submit_finality_proof(
Pallet::<TestRuntime>::submit_finality_proof_ex(
RuntimeOrigin::signed(1),
Box::new(header),
justification,
set_id,
)
}
@@ -749,10 +789,11 @@ mod tests {
..Default::default()
});
Pallet::<TestRuntime>::submit_finality_proof(
Pallet::<TestRuntime>::submit_finality_proof_ex(
RuntimeOrigin::signed(1),
Box::new(header),
justification,
set_id,
)
}
@@ -955,17 +996,30 @@ mod tests {
let header = test_header(1);
let params =
JustificationGeneratorParams::<TestHeader> { set_id: 2, ..Default::default() };
let next_set_id = 2;
let params = JustificationGeneratorParams::<TestHeader> {
set_id: next_set_id,
..Default::default()
};
let justification = make_justification_for_header(params);
assert_err!(
Pallet::<TestRuntime>::submit_finality_proof(
Pallet::<TestRuntime>::submit_finality_proof_ex(
RuntimeOrigin::signed(1),
Box::new(header.clone()),
justification.clone(),
TEST_GRANDPA_SET_ID,
),
<Error<TestRuntime>>::InvalidJustification
);
assert_err!(
Pallet::<TestRuntime>::submit_finality_proof_ex(
RuntimeOrigin::signed(1),
Box::new(header),
justification,
next_set_id,
),
<Error<TestRuntime>>::InvalidJustification
<Error<TestRuntime>>::InvalidAuthoritySetId
);
})
}
@@ -980,10 +1034,11 @@ mod tests {
justification.round = 42;
assert_err!(
Pallet::<TestRuntime>::submit_finality_proof(
Pallet::<TestRuntime>::submit_finality_proof_ex(
RuntimeOrigin::signed(1),
Box::new(header),
justification,
TEST_GRANDPA_SET_ID,
),
<Error<TestRuntime>>::InvalidJustification
);
@@ -1009,10 +1064,11 @@ mod tests {
let justification = make_default_justification(&header);
assert_err!(
Pallet::<TestRuntime>::submit_finality_proof(
Pallet::<TestRuntime>::submit_finality_proof_ex(
RuntimeOrigin::signed(1),
Box::new(header),
justification,
TEST_GRANDPA_SET_ID,
),
<Error<TestRuntime>>::InvalidAuthoritySet
);
@@ -1047,10 +1103,11 @@ mod tests {
let justification = make_default_justification(&header);
// Let's import our test header
let result = Pallet::<TestRuntime>::submit_finality_proof(
let result = Pallet::<TestRuntime>::submit_finality_proof_ex(
RuntimeOrigin::signed(1),
Box::new(header.clone()),
justification.clone(),
TEST_GRANDPA_SET_ID,
);
assert_ok!(result);
assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::No);
@@ -1109,10 +1166,11 @@ mod tests {
// without large digest item ^^^ the relayer would have paid zero transaction fee
// (`Pays::No`)
let result = Pallet::<TestRuntime>::submit_finality_proof(
let result = Pallet::<TestRuntime>::submit_finality_proof_ex(
RuntimeOrigin::signed(1),
Box::new(header.clone()),
justification,
TEST_GRANDPA_SET_ID,
);
assert_ok!(result);
assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::Yes);
@@ -1140,10 +1198,11 @@ mod tests {
// without many headers in votes ancestries ^^^ the relayer would have paid zero
// transaction fee (`Pays::No`)
let result = Pallet::<TestRuntime>::submit_finality_proof(
let result = Pallet::<TestRuntime>::submit_finality_proof_ex(
RuntimeOrigin::signed(1),
Box::new(header.clone()),
justification,
TEST_GRANDPA_SET_ID,
);
assert_ok!(result);
assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::Yes);
@@ -1169,10 +1228,11 @@ mod tests {
// Should not be allowed to import this header
assert_err!(
Pallet::<TestRuntime>::submit_finality_proof(
Pallet::<TestRuntime>::submit_finality_proof_ex(
RuntimeOrigin::signed(1),
Box::new(header),
justification
justification,
TEST_GRANDPA_SET_ID,
),
<Error<TestRuntime>>::UnsupportedScheduledChange
);
@@ -1194,10 +1254,11 @@ mod tests {
// Should not be allowed to import this header
assert_err!(
Pallet::<TestRuntime>::submit_finality_proof(
Pallet::<TestRuntime>::submit_finality_proof_ex(
RuntimeOrigin::signed(1),
Box::new(header),
justification
justification,
TEST_GRANDPA_SET_ID,
),
<Error<TestRuntime>>::UnsupportedScheduledChange
);
@@ -1219,10 +1280,11 @@ mod tests {
// Should not be allowed to import this header
assert_err!(
Pallet::<TestRuntime>::submit_finality_proof(
Pallet::<TestRuntime>::submit_finality_proof_ex(
RuntimeOrigin::signed(1),
Box::new(header),
justification
justification,
TEST_GRANDPA_SET_ID,
),
<Error<TestRuntime>>::TooManyAuthoritiesInSet
);
@@ -1283,10 +1345,11 @@ mod tests {
let mut invalid_justification = make_default_justification(&header);
invalid_justification.round = 42;
Pallet::<TestRuntime>::submit_finality_proof(
Pallet::<TestRuntime>::submit_finality_proof_ex(
RuntimeOrigin::signed(1),
Box::new(header),
invalid_justification,
TEST_GRANDPA_SET_ID,
)
};
@@ -1451,10 +1514,11 @@ mod tests {
let justification = make_default_justification(&header);
assert_noop!(
Pallet::<TestRuntime>::submit_finality_proof(
Pallet::<TestRuntime>::submit_finality_proof_ex(
RuntimeOrigin::root(),
Box::new(header),
justification,
TEST_GRANDPA_SET_ID,
),
DispatchError::BadOrigin,
);