mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-31 02:51:01 +00:00
Integrate Grandpa Proof Checker into Substrate Pallet (#375)
* Remove the Substrate primitives crate The types here were only used in one place, the pallet itself. If other components start using these types we can considering moving them back into a standalone crate. * Start trying to integrate justification module * Make Substrate blocks configurable in Pallet * WIP: Try and generalize justification test helpers * Fix tests which use "real" justifications * Put common test helpers alongside mock code * Use common helper for creating headers * Remove usage of UintAuthorityId This change favours the use of the Ed25519Keyring authorities in order to keep things consistent with the tests. * Add documentation around config trait types * Make test header, hash, and number types consistent * Update modules/substrate/src/verifier.rs Co-authored-by: Svyatoslav Nikolsky <svyatonik@gmail.com> * Update modules/substrate/src/lib.rs Co-authored-by: Tomasz Drwięga <tomusdrw@users.noreply.github.com> * Update modules/substrate/Cargo.toml Co-authored-by: Svyatoslav Nikolsky <svyatonik@gmail.com> * Derive `RuntimeDebug` instead of `Debug` * Add `Paramter` as a trait constraint on config types Since we use these types as part of the dispatchable functions we should explicitly require this. * Enforce that hasher output matches expected hash type * Accept headers over indexes when making test justifications * Check that authority sets are valid * Make Clippy happy * Apply correct Clippy fix * Move justification code into primitives module * Use new module in verifier code * Add primitives module for Substrate test helpers * WIP * Move justification generation into test_helpers * Revert commits which move `justification` into primitives This reverts commit 03a381f0bc4a8dbe4785c30d42ab252a06ba876c. Co-authored-by: Svyatoslav Nikolsky <svyatonik@gmail.com> Co-authored-by: Tomasz Drwięga <tomusdrw@users.noreply.github.com>
This commit is contained in:
committed by
Bastian Köcher
parent
52c1913fff
commit
f9db999a1a
@@ -22,17 +22,21 @@
|
||||
//! has been signed off by the correct Grandpa authorities, and also enact any authority set changes
|
||||
//! if required.
|
||||
|
||||
use crate::justification::verify_justification;
|
||||
use crate::storage::{AuthoritySet, ImportedHeader, ScheduledChange};
|
||||
use crate::BridgeStorage;
|
||||
use bp_substrate::{check_finality_proof, AuthoritySet, ImportedHeader, ScheduledChange};
|
||||
use finality_grandpa::voter_set::VoterSet;
|
||||
use sp_finality_grandpa::{ConsensusLog, GRANDPA_ENGINE_ID};
|
||||
use sp_runtime::generic::OpaqueDigestItemId;
|
||||
use sp_runtime::traits::{CheckedAdd, Header as HeaderT, One};
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use sp_std::{prelude::Vec, vec};
|
||||
|
||||
/// The finality proof used by the pallet.
|
||||
///
|
||||
/// For a Substrate based chain using Grandpa this will
|
||||
/// be an encoded Grandpa Justification.
|
||||
#[derive(RuntimeDebug)]
|
||||
pub struct FinalityProof(Vec<u8>);
|
||||
|
||||
impl From<&[u8]> for FinalityProof {
|
||||
@@ -48,7 +52,7 @@ impl From<Vec<u8>> for FinalityProof {
|
||||
}
|
||||
|
||||
/// Errors which can happen while importing a header.
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(RuntimeDebug, PartialEq)]
|
||||
pub enum ImportError {
|
||||
/// This header is older than our latest finalized block, thus not useful.
|
||||
OldHeader,
|
||||
@@ -60,25 +64,33 @@ pub enum ImportError {
|
||||
InvalidChildNumber,
|
||||
/// The height of the next authority set change overflowed.
|
||||
ScheduledHeightOverflow,
|
||||
/// Received an authority set which was invalid in some way, such as
|
||||
/// the authority weights being empty or overflowing the `AuthorityWeight`
|
||||
/// type.
|
||||
InvalidAuthoritySet,
|
||||
}
|
||||
|
||||
/// Errors which can happen while verifying a headers finality.
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(RuntimeDebug, PartialEq)]
|
||||
pub enum FinalizationError {
|
||||
/// This header has never been imported by the pallet.
|
||||
UnknownHeader,
|
||||
/// We were unable to prove finality for this header.
|
||||
UnfinalizedHeader,
|
||||
/// Trying to prematurely import a justification
|
||||
PrematureJustification,
|
||||
/// We failed to verify this header's ancestry.
|
||||
AncestryCheckFailed,
|
||||
/// This header is older than our latest finalized block, thus not useful.
|
||||
OldHeader,
|
||||
/// The given justification was not able to finalize the given header.
|
||||
///
|
||||
/// There are several reasons why this might happen, such as the justification being
|
||||
/// signed by the wrong authority set, being given alongside an unexpected header,
|
||||
/// or failing ancestry checks.
|
||||
InvalidJustification,
|
||||
}
|
||||
|
||||
/// Used to verify imported headers and their finality status.
|
||||
#[derive(Debug)]
|
||||
#[derive(RuntimeDebug)]
|
||||
pub struct Verifier<S> {
|
||||
pub storage: S,
|
||||
}
|
||||
@@ -87,6 +99,7 @@ impl<S, H> Verifier<S>
|
||||
where
|
||||
S: BridgeStorage<Header = H>,
|
||||
H: HeaderT,
|
||||
H::Number: finality_grandpa::BlockNumberOps,
|
||||
{
|
||||
/// Import a header to the pallet.
|
||||
///
|
||||
@@ -126,6 +139,21 @@ where
|
||||
// Since we don't currently have a pending authority set change let's check if the header
|
||||
// contains a log indicating when the next change should be.
|
||||
if let Some(change) = find_scheduled_change(&header) {
|
||||
let mut total_weight = 0u64;
|
||||
|
||||
// We need to make sure that we don't overflow the `AuthorityWeight` type.
|
||||
for (_id, weight) in &change.next_authorities {
|
||||
total_weight = total_weight
|
||||
.checked_add(*weight)
|
||||
.ok_or(ImportError::InvalidAuthoritySet)?;
|
||||
}
|
||||
|
||||
// If none of the authorities have a weight associated with them the
|
||||
// set is essentially empty. We don't want that.
|
||||
if total_weight == 0 {
|
||||
return Err(ImportError::InvalidAuthoritySet);
|
||||
}
|
||||
|
||||
let next_set = AuthoritySet {
|
||||
authorities: change.next_authorities,
|
||||
set_id: self.storage.current_authority_set().set_id + 1,
|
||||
@@ -176,10 +204,18 @@ where
|
||||
}
|
||||
|
||||
let current_authority_set = self.storage.current_authority_set();
|
||||
let is_finalized = check_finality_proof(&header, ¤t_authority_set, &proof.0);
|
||||
if !is_finalized {
|
||||
return Err(FinalizationError::UnfinalizedHeader);
|
||||
}
|
||||
let voter_set = VoterSet::new(current_authority_set.authorities).expect(
|
||||
"This only fails if we have an invalid list of authorities. Since we
|
||||
got this from storage it should always be valid, otherwise we have a bug.",
|
||||
);
|
||||
verify_justification::<H>(
|
||||
(hash, *header.number()),
|
||||
current_authority_set.set_id,
|
||||
voter_set,
|
||||
&proof.0,
|
||||
)
|
||||
.map_err(|_| FinalizationError::InvalidJustification)?;
|
||||
frame_support::debug::trace!(target: "sub-bridge", "Received valid justification for {:?}", header);
|
||||
|
||||
frame_support::debug::trace!(target: "sub-bridge", "Checking ancestry for headers between {:?} and {:?}", last_finalized, header);
|
||||
let mut finalized_headers =
|
||||
@@ -279,37 +315,29 @@ fn find_scheduled_change<H: HeaderT>(header: &H) -> Option<sp_finality_grandpa::
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::justification::tests::*;
|
||||
use crate::mock::helpers::*;
|
||||
use crate::mock::*;
|
||||
use crate::{BestFinalized, ImportedHeaders, PalletStorage};
|
||||
use codec::Encode;
|
||||
use frame_support::{assert_err, assert_ok};
|
||||
use frame_support::{StorageMap, StorageValue};
|
||||
use sp_finality_grandpa::{AuthorityId, AuthorityList};
|
||||
use sp_runtime::testing::UintAuthorityId;
|
||||
|
||||
type TestHeader = <TestRuntime as frame_system::Trait>::Header;
|
||||
type TestNumber = <TestHeader as HeaderT>::Number;
|
||||
use sp_finality_grandpa::{AuthorityId, SetId};
|
||||
|
||||
fn unfinalized_header(num: u64) -> ImportedHeader<TestHeader> {
|
||||
ImportedHeader {
|
||||
header: TestHeader::new_from_number(num),
|
||||
header: test_header(num),
|
||||
requires_justification: false,
|
||||
is_finalized: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_authorities(authorities: Vec<(u64, u64)>) -> AuthorityList {
|
||||
authorities
|
||||
.iter()
|
||||
.map(|(id, weight)| (UintAuthorityId(*id).to_public_key::<AuthorityId>(), *weight))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn schedule_next_change(
|
||||
authorities: Vec<(u64, u64)>,
|
||||
set_id: u64,
|
||||
authorities: Vec<AuthorityId>,
|
||||
set_id: SetId,
|
||||
height: TestNumber,
|
||||
) -> ScheduledChange<TestNumber> {
|
||||
let authorities = get_authorities(authorities);
|
||||
let authorities = authorities.into_iter().map(|id| (id, 1u64)).collect();
|
||||
let authority_set = AuthoritySet::new(authorities, set_id);
|
||||
ScheduledChange { authority_set, height }
|
||||
}
|
||||
@@ -321,7 +349,7 @@ mod tests {
|
||||
) -> Vec<ImportedHeader<TestHeader>> {
|
||||
let mut imported_headers = vec![];
|
||||
let genesis = ImportedHeader {
|
||||
header: TestHeader::new_from_number(0),
|
||||
header: test_header(0),
|
||||
requires_justification: false,
|
||||
is_finalized: true,
|
||||
};
|
||||
@@ -331,11 +359,8 @@ mod tests {
|
||||
imported_headers.push(genesis);
|
||||
|
||||
for (num, requires_justification, is_finalized) in headers {
|
||||
let mut h = TestHeader::new_from_number(num);
|
||||
h.parent_hash = imported_headers.last().unwrap().hash();
|
||||
|
||||
let header = ImportedHeader {
|
||||
header: h,
|
||||
header: test_header(num),
|
||||
requires_justification,
|
||||
is_finalized,
|
||||
};
|
||||
@@ -355,7 +380,7 @@ mod tests {
|
||||
storage.write_header(&parent);
|
||||
storage.update_best_finalized(parent.hash());
|
||||
|
||||
let header = TestHeader::new_from_number(1);
|
||||
let header = test_header(1);
|
||||
let mut verifier = Verifier { storage };
|
||||
assert_err!(verifier.import_header(header), ImportError::OldHeader);
|
||||
})
|
||||
@@ -381,7 +406,7 @@ mod tests {
|
||||
fn fails_to_import_header_twice() {
|
||||
run_test(|| {
|
||||
let storage = PalletStorage::<TestRuntime>::new();
|
||||
let header = TestHeader::new_from_number(1);
|
||||
let header = test_header(1);
|
||||
<BestFinalized<TestRuntime>>::put(header.hash());
|
||||
|
||||
let imported_header = ImportedHeader {
|
||||
@@ -400,7 +425,7 @@ mod tests {
|
||||
fn succesfully_imports_valid_but_unfinalized_header() {
|
||||
run_test(|| {
|
||||
let storage = PalletStorage::<TestRuntime>::new();
|
||||
let parent = TestHeader::new_from_number(1);
|
||||
let parent = test_header(1);
|
||||
let parent_hash = parent.hash();
|
||||
<BestFinalized<TestRuntime>>::put(parent.hash());
|
||||
|
||||
@@ -411,8 +436,7 @@ mod tests {
|
||||
};
|
||||
<ImportedHeaders<TestRuntime>>::insert(parent_hash, &imported_header);
|
||||
|
||||
let mut header = TestHeader::new_from_number(2);
|
||||
header.parent_hash = parent_hash;
|
||||
let header = test_header(2);
|
||||
let mut verifier = Verifier {
|
||||
storage: storage.clone(),
|
||||
};
|
||||
@@ -458,7 +482,7 @@ mod tests {
|
||||
|
||||
// Need to give it a different parent_hash or else it'll be
|
||||
// related to our test genesis header
|
||||
let mut bad_ancestor = TestHeader::new_from_number(0);
|
||||
let mut bad_ancestor = test_header(0);
|
||||
bad_ancestor.parent_hash = [1u8; 32].into();
|
||||
let bad_ancestor = ImportedHeader {
|
||||
header: bad_ancestor,
|
||||
@@ -484,7 +508,7 @@ mod tests {
|
||||
}
|
||||
|
||||
// What if we have an "ancestor" that's newer than child?
|
||||
let new_ancestor = TestHeader::new_from_number(5);
|
||||
let new_ancestor = test_header(5);
|
||||
let new_ancestor = ImportedHeader {
|
||||
header: new_ancestor,
|
||||
requires_justification: false,
|
||||
@@ -497,24 +521,62 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doesnt_import_header_which_schedules_change_with_invalid_authority_set() {
|
||||
use sp_runtime::{Digest, DigestItem};
|
||||
|
||||
run_test(|| {
|
||||
let mut storage = PalletStorage::<TestRuntime>::new();
|
||||
let headers = vec![(1, false, false)];
|
||||
let _imported_headers = write_headers(&mut storage, headers);
|
||||
let mut header = test_header(2);
|
||||
|
||||
// This is an *invalid* authority set because the combined weight of the
|
||||
// authorities is greater than `u64::MAX`
|
||||
let consensus_log = ConsensusLog::<TestNumber>::ScheduledChange(sp_finality_grandpa::ScheduledChange {
|
||||
next_authorities: vec![(alice(), u64::MAX), (bob(), u64::MAX)],
|
||||
delay: 0,
|
||||
});
|
||||
|
||||
header.digest = Digest::<TestHash> {
|
||||
logs: vec![DigestItem::Consensus(GRANDPA_ENGINE_ID, consensus_log.encode())],
|
||||
};
|
||||
|
||||
let mut verifier = Verifier { storage };
|
||||
|
||||
assert_eq!(
|
||||
verifier.import_header(header).unwrap_err(),
|
||||
ImportError::InvalidAuthoritySet
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finalizes_header_which_doesnt_enact_or_schedule_a_new_authority_set() {
|
||||
run_test(|| {
|
||||
let mut storage = PalletStorage::<TestRuntime>::new();
|
||||
let headers = vec![(1, false, false)];
|
||||
let imported_headers = write_headers(&mut storage, headers);
|
||||
let _imported_headers = write_headers(&mut storage, headers);
|
||||
|
||||
// Nothing special about this header, yet Grandpa may have created a justification
|
||||
// for it since it does that periodically
|
||||
let mut header = TestHeader::new_from_number(2);
|
||||
header.parent_hash = imported_headers[1].hash();
|
||||
let header = test_header(2);
|
||||
|
||||
let set_id = 1;
|
||||
let authorities = authority_list();
|
||||
let authority_set = AuthoritySet::new(authorities.clone(), set_id);
|
||||
storage.update_current_authority_set(authority_set);
|
||||
|
||||
// We'll need this justification to finalize the header
|
||||
let grandpa_round = 1;
|
||||
let justification = make_justification_for_header(&header, grandpa_round, set_id, &authorities).encode();
|
||||
|
||||
let mut verifier = Verifier {
|
||||
storage: storage.clone(),
|
||||
};
|
||||
|
||||
assert_ok!(verifier.import_header(header.clone()));
|
||||
assert_ok!(verifier.import_finality_proof(header.hash(), vec![4, 2].into()));
|
||||
assert_ok!(verifier.import_finality_proof(header.hash(), justification.into()));
|
||||
assert_eq!(storage.best_finalized_header().header, header);
|
||||
})
|
||||
}
|
||||
@@ -525,15 +587,26 @@ mod tests {
|
||||
let mut storage = PalletStorage::<TestRuntime>::new();
|
||||
let headers = vec![(1, false, false), (2, false, false)];
|
||||
let imported_headers = write_headers(&mut storage, headers);
|
||||
let header = test_header(3);
|
||||
|
||||
let mut header = TestHeader::new_from_number(3);
|
||||
header.parent_hash = imported_headers[2].hash();
|
||||
let set_id = 1;
|
||||
let authorities = authority_list();
|
||||
let authority_set = AuthoritySet {
|
||||
authorities: authorities.clone(),
|
||||
set_id,
|
||||
};
|
||||
storage.update_current_authority_set(authority_set);
|
||||
|
||||
let grandpa_round = 1;
|
||||
let justification = make_justification_for_header(&header, grandpa_round, set_id, &authorities).encode();
|
||||
|
||||
let mut verifier = Verifier {
|
||||
storage: storage.clone(),
|
||||
};
|
||||
assert!(verifier.import_header(header.clone()).is_ok());
|
||||
assert!(verifier.import_finality_proof(header.hash(), vec![4, 2].into()).is_ok());
|
||||
assert!(verifier
|
||||
.import_finality_proof(header.hash(), justification.into())
|
||||
.is_ok());
|
||||
|
||||
// Make sure we marked the our headers as finalized
|
||||
assert!(storage.header_by_hash(imported_headers[1].hash()).unwrap().is_finalized);
|
||||
@@ -550,21 +623,23 @@ mod tests {
|
||||
run_test(|| {
|
||||
let mut storage = PalletStorage::<TestRuntime>::new();
|
||||
let headers = vec![(1, false, false)];
|
||||
let imported_headers = write_headers(&mut storage, headers);
|
||||
let _imported_headers = write_headers(&mut storage, headers);
|
||||
|
||||
let set_id = 0;
|
||||
let authorities = get_authorities(vec![(1, 1)]);
|
||||
let initial_authority_set = AuthoritySet::new(authorities, set_id);
|
||||
let set_id = 1;
|
||||
let authorities = authority_list();
|
||||
let initial_authority_set = AuthoritySet::new(authorities.clone(), set_id);
|
||||
storage.update_current_authority_set(initial_authority_set);
|
||||
|
||||
// This header enacts an authority set change upon finalization
|
||||
let mut header = TestHeader::new_from_number(2);
|
||||
header.parent_hash = imported_headers[1].hash();
|
||||
let header = test_header(2);
|
||||
|
||||
let grandpa_round = 1;
|
||||
let justification = make_justification_for_header(&header, grandpa_round, set_id, &authorities).encode();
|
||||
|
||||
// Schedule a change at the height of our header
|
||||
let set_id = 1;
|
||||
let set_id = 2;
|
||||
let height = *header.number();
|
||||
let authorities = vec![(2, 1)];
|
||||
let authorities = vec![alice()];
|
||||
let change = schedule_next_change(authorities, set_id, height);
|
||||
storage.schedule_next_set_change(change.clone());
|
||||
|
||||
@@ -573,7 +648,7 @@ mod tests {
|
||||
};
|
||||
|
||||
assert_ok!(verifier.import_header(header.clone()));
|
||||
assert_ok!(verifier.import_finality_proof(header.hash(), vec![4, 2].into()));
|
||||
assert_ok!(verifier.import_finality_proof(header.hash(), justification.into()));
|
||||
assert_eq!(storage.best_finalized_header().header, header);
|
||||
|
||||
// Make sure that we have updated the set now that we've finalized our header
|
||||
@@ -585,7 +660,7 @@ mod tests {
|
||||
fn importing_finality_proof_for_already_finalized_header_doesnt_work() {
|
||||
run_test(|| {
|
||||
let mut storage = PalletStorage::<TestRuntime>::new();
|
||||
let genesis = TestHeader::new_from_number(0);
|
||||
let genesis = test_header(0);
|
||||
|
||||
let genesis = ImportedHeader {
|
||||
header: genesis,
|
||||
@@ -627,14 +702,23 @@ mod tests {
|
||||
let headers = vec![(1, false, false)];
|
||||
let imported_headers = write_headers(&mut storage, headers);
|
||||
|
||||
// Set up our initial authority set
|
||||
let set_id = 1;
|
||||
let authorities = authority_list();
|
||||
let initial_authority_set = AuthoritySet::new(authorities.clone(), set_id);
|
||||
storage.update_current_authority_set(initial_authority_set);
|
||||
|
||||
// This is header N
|
||||
let mut header = TestHeader::new_from_number(2);
|
||||
header.parent_hash = imported_headers[1].hash();
|
||||
let header = test_header(2);
|
||||
|
||||
// Since we want to finalize N we need a justification for it
|
||||
let grandpa_round = 1;
|
||||
let justification = make_justification_for_header(&header, grandpa_round, set_id, &authorities).encode();
|
||||
|
||||
// Schedule a change at height N
|
||||
let set_id = 1;
|
||||
let set_id = 2;
|
||||
let height = *header.number();
|
||||
let authorities = vec![(1, 1)];
|
||||
let authorities = vec![alice()];
|
||||
let change = schedule_next_change(authorities, set_id, height);
|
||||
storage.schedule_next_set_change(change.clone());
|
||||
|
||||
@@ -651,17 +735,17 @@ mod tests {
|
||||
);
|
||||
|
||||
// Now we want to import some headers which are past N
|
||||
let mut child = TestHeader::new_from_number(*header.number() + 1);
|
||||
child.parent_hash = header.hash();
|
||||
let child = test_header(*header.number() + 1);
|
||||
assert!(verifier.import_header(child.clone()).is_ok());
|
||||
|
||||
let mut grandchild = TestHeader::new_from_number(*child.number() + 1);
|
||||
grandchild.parent_hash = child.hash();
|
||||
let grandchild = test_header(*child.number() + 1);
|
||||
assert!(verifier.import_header(grandchild).is_ok());
|
||||
|
||||
// Even though we're a few headers ahead we should still be able to import
|
||||
// a justification for header N
|
||||
assert!(verifier.import_finality_proof(header.hash(), vec![4, 2].into()).is_ok());
|
||||
assert!(verifier
|
||||
.import_finality_proof(header.hash(), justification.into())
|
||||
.is_ok());
|
||||
|
||||
// Some checks to make sure that our header has been correctly finalized
|
||||
let finalized_header = storage.header_by_hash(header.hash()).unwrap();
|
||||
|
||||
Reference in New Issue
Block a user